mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 13:23:07 +01:00
feat: adds support for different std out and err streams (#2229)
* feat: adds support for different std out and err streams * feat: adds std to json * fixes tests * fixes deprecated code * fixes download * adds defineEmit as an option * chore: updates modules * adds ui elements * fixes tests * updates languages
This commit is contained in:
9
assets/auto-imports.d.ts
vendored
9
assets/auto-imports.d.ts
vendored
@@ -93,7 +93,7 @@ declare global {
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const persistentVisibleKeys: typeof import('./utils/index')['persistentVisibleKeys']
|
||||
const persistentVisibleKeys: typeof import('./composables/storage')['persistentVisibleKeys']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
@@ -121,6 +121,7 @@ declare global {
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const showAllContainers: typeof import('./composables/settings')['showAllContainers']
|
||||
const showStd: typeof import('./composables/settings')['showStd']
|
||||
const showTimestamp: typeof import('./composables/settings')['showTimestamp']
|
||||
const size: typeof import('./composables/settings')['size']
|
||||
const smallerScrollbars: typeof import('./composables/settings')['smallerScrollbars']
|
||||
@@ -428,7 +429,7 @@ declare module 'vue' {
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly persistentVisibleKeys: UnwrapRef<typeof import('./utils/index')['persistentVisibleKeys']>
|
||||
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composables/storage')['persistentVisibleKeys']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
@@ -456,6 +457,7 @@ declare module 'vue' {
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||
readonly showAllContainers: UnwrapRef<typeof import('./composables/settings')['showAllContainers']>
|
||||
readonly showStd: UnwrapRef<typeof import('./composables/settings')['showStd']>
|
||||
readonly showTimestamp: UnwrapRef<typeof import('./composables/settings')['showTimestamp']>
|
||||
readonly size: UnwrapRef<typeof import('./composables/settings')['size']>
|
||||
readonly smallerScrollbars: UnwrapRef<typeof import('./composables/settings')['smallerScrollbars']>
|
||||
@@ -757,7 +759,7 @@ declare module '@vue/runtime-core' {
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly persistentVisibleKeys: UnwrapRef<typeof import('./utils/index')['persistentVisibleKeys']>
|
||||
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composables/storage')['persistentVisibleKeys']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
@@ -785,6 +787,7 @@ declare module '@vue/runtime-core' {
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||
readonly showAllContainers: UnwrapRef<typeof import('./composables/settings')['showAllContainers']>
|
||||
readonly showStd: UnwrapRef<typeof import('./composables/settings')['showStd']>
|
||||
readonly showTimestamp: UnwrapRef<typeof import('./composables/settings')['showTimestamp']>
|
||||
readonly size: UnwrapRef<typeof import('./composables/settings')['size']>
|
||||
readonly smallerScrollbars: UnwrapRef<typeof import('./composables/settings')['smallerScrollbars']>
|
||||
|
||||
3
assets/components.d.ts
vendored
3
assets/components.d.ts
vendored
@@ -32,8 +32,10 @@ declare module '@vue/runtime-core' {
|
||||
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
|
||||
LogEventSource: typeof import('./components/LogViewer/LogEventSource.vue')['default']
|
||||
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
|
||||
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
|
||||
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
|
||||
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
|
||||
'Mdi:check': typeof import('~icons/mdi/check')['default']
|
||||
'Mdi:dotsVertical': typeof import('~icons/mdi/dots-vertical')['default']
|
||||
'Mdi:lightChevronDoubleDown': typeof import('~icons/mdi-light/chevron-double-down')['default']
|
||||
'Mdi:lightChevronLeft': typeof import('~icons/mdi-light/chevron-left')['default']
|
||||
@@ -56,6 +58,7 @@ declare module '@vue/runtime-core' {
|
||||
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default']
|
||||
StatMonitor: typeof import('./components/LogViewer/StatMonitor.vue')['default']
|
||||
StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default']
|
||||
Tag: typeof import('./components/Tag.vue')['default']
|
||||
ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="columns is-1 is-variable is-mobile">
|
||||
<div class="column is-narrow" v-if="showStd">
|
||||
<log-std :std="logEntry.std"></log-std>
|
||||
</div>
|
||||
<div class="column is-narrow" v-if="showTimestamp">
|
||||
<log-date :date="logEntry.date"></log-date>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<div class="column is-ellipsis">
|
||||
{{ container.name }}<span v-if="container.isSwarm">{{ container.swarmId }}</span>
|
||||
<span class="tag is-dark is-hidden-mobile">{{ container.image.replace(/@sha.*/, "") }}</span>
|
||||
<tag class="is-hidden-mobile">{{ container.image.replace(/@sha.*/, "") }}</tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<dropdown-menu class="is-right">
|
||||
<a class="dropdown-item" @click="onClearClicked">
|
||||
<a class="dropdown-item" @click="clear()">
|
||||
<div class="level is-justify-content-start">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
@@ -37,6 +37,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<hr class="dropdown-divider" />
|
||||
<a class="dropdown-item" @click="streamConfig.stdout = !streamConfig.stdout">
|
||||
<div class="level is-justify-content-start">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<mdi:check class="mr-4 is-blue" v-if="streamConfig.stdout" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
{{ $t(streamConfig.stdout ? "toolbar.hide" : "toolbar.show", { std: "STDOUT" }) }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item" @click="streamConfig.stderr = !streamConfig.stderr">
|
||||
<div class="level is-justify-content-start">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<mdi:check class="mr-4 is-red" v-if="streamConfig.stderr" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
{{ $t(streamConfig.stderr ? "toolbar.hide" : "toolbar.show", { std: "STDERR" }) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</dropdown-menu>
|
||||
</template>
|
||||
|
||||
@@ -47,23 +75,14 @@ import { Container } from "@/models/Container";
|
||||
const { showSearch } = useSearchFilter();
|
||||
const { base } = config;
|
||||
|
||||
const { onClearClicked = (e: Event) => {} } = defineProps<{
|
||||
onClearClicked: (e: Event) => void;
|
||||
}>();
|
||||
const clear = defineEmit();
|
||||
|
||||
const container = inject("container") as ComputedRef<Container>;
|
||||
const streamConfig = inject("stream-config") as { stdout: boolean; stderr: boolean };
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#download.button,
|
||||
#clear.button {
|
||||
.icon {
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
.level-left .level-item {
|
||||
width: 2.2em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mr-2 column is-narrow is-paddingless is-hidden-mobile">
|
||||
<log-actions-toolbar :onClearClicked="onClearClicked" />
|
||||
<log-actions-toolbar @clear="onClearClicked()" />
|
||||
</div>
|
||||
<div class="mr-2 column is-narrow is-paddingless" v-if="closable">
|
||||
<button class="delete is-medium" @click="emit('close')"></button>
|
||||
@@ -45,8 +45,10 @@ const emit = defineEmits<{
|
||||
const store = useContainerStore();
|
||||
|
||||
const container = store.currentContainer($$(id));
|
||||
const config = reactive({ stdout: true, stderr: true });
|
||||
|
||||
provide("container", container);
|
||||
provide("stream-config", config);
|
||||
|
||||
const viewer = ref<InstanceType<typeof LogViewerWithSource>>();
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ describe("<LogEventSource />", () => {
|
||||
},
|
||||
provide: {
|
||||
container: computed(() => ({ id: "abc", image: "test:v123" })),
|
||||
"stream-config": reactive({ stdout: true, stderr: true }),
|
||||
scrollingPaused: computed(() => false),
|
||||
},
|
||||
},
|
||||
@@ -84,6 +85,8 @@ describe("<LogEventSource />", () => {
|
||||
});
|
||||
}
|
||||
|
||||
const sourceUrl = "/api/logs/stream?id=abc&lastEventId=&host=localhost&stdout=1&stderr=1";
|
||||
|
||||
test("renders correctly", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
@@ -91,22 +94,22 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
test("should connect to EventSource", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
expect(sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].readyState).toBe(1);
|
||||
sources[sourceUrl].emitOpen();
|
||||
expect(sources[sourceUrl].readyState).toBe(1);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("should close EventSource", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
sources[sourceUrl].emitOpen();
|
||||
wrapper.unmount();
|
||||
expect(sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].readyState).toBe(2);
|
||||
expect(sources[sourceUrl].readyState).toBe(2);
|
||||
});
|
||||
|
||||
test("should parse messages", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitMessage({
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"This is a message.", "id":1}`,
|
||||
});
|
||||
|
||||
@@ -121,8 +124,8 @@ describe("<LogEventSource />", () => {
|
||||
describe("render html correctly", () => {
|
||||
test("should render messages", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitMessage({
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"This is a message.", "id":1}`,
|
||||
});
|
||||
|
||||
@@ -134,8 +137,8 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
test("should render messages with color", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitMessage({
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: '{"ts":1560336942459,"m":"\\u001b[30mblack\\u001b[37mwhite", "id":1}',
|
||||
});
|
||||
|
||||
@@ -147,8 +150,8 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
test("should render messages with html entities", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitMessage({
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"<test>foo bar</test>", "id":1}`,
|
||||
});
|
||||
|
||||
@@ -160,8 +163,8 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
test("should render dates with 12 hour style", async () => {
|
||||
const wrapper = createLogEventSource({ hourStyle: "12" });
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitMessage({
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"<test>foo bar</test>", "id":1}`,
|
||||
});
|
||||
|
||||
@@ -173,8 +176,8 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
test("should render dates with 24 hour style", async () => {
|
||||
const wrapper = createLogEventSource({ hourStyle: "24" });
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitMessage({
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"<test>foo bar</test>", "id":1}`,
|
||||
});
|
||||
|
||||
@@ -186,11 +189,11 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
test("should render messages with filter", async () => {
|
||||
const wrapper = createLogEventSource({ searchFilter: "test" });
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitMessage({
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"foo bar", "id":1}`,
|
||||
});
|
||||
sources["/api/logs/stream?id=abc&lastEventId=&host=localhost"].emitMessage({
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"test bar", "id":2}`,
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const container = inject("container") as ComputedRef<Container>;
|
||||
const { messages, loadOlderLogs } = useLogStream(container);
|
||||
const config = inject("stream-config") as { stdout: boolean; stderr: boolean };
|
||||
const { messages, loadOlderLogs } = useLogStream(container, config);
|
||||
|
||||
const beforeLoading = () => emit("loading-more", true);
|
||||
const afterLoading = () => emit("loading-more", false);
|
||||
|
||||
25
assets/components/LogViewer/LogStd.vue
Normal file
25
assets/components/LogViewer/LogStd.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<tag size="small" :std="std">
|
||||
{{ std }}
|
||||
</tag>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Std } from "@/models/LogEntry";
|
||||
|
||||
defineProps<{
|
||||
std: Std;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tag {
|
||||
&[std="stdout"] {
|
||||
color: var(--blue-color);
|
||||
}
|
||||
|
||||
&[std="stderr"] {
|
||||
color: var(--red-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="columns is-1 is-variable is-mobile">
|
||||
<div class="column is-narrow" v-if="showStd">
|
||||
<log-std :std="logEntry.std"></log-std>
|
||||
</div>
|
||||
<div class="column is-narrow" v-if="showTimestamp">
|
||||
<log-date :date="logEntry.date"></log-date>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 1
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
|
||||
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
</div>
|
||||
@@ -60,6 +61,7 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 2
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
|
||||
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42</time></div>
|
||||
</div>
|
||||
@@ -96,6 +98,7 @@ exports[`<LogEventSource /> > render html correctly > should render messages 1`]
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
|
||||
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
</div>
|
||||
@@ -132,6 +135,7 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
|
||||
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
</div>
|
||||
@@ -168,6 +172,7 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
|
||||
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
</div>
|
||||
@@ -204,6 +209,7 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
|
||||
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
</div>
|
||||
@@ -235,5 +241,6 @@ SimpleLogEntry {
|
||||
"id": 1,
|
||||
"level": undefined,
|
||||
"position": undefined,
|
||||
"std": "stderr",
|
||||
}
|
||||
`;
|
||||
|
||||
19
assets/components/Tag.vue
Normal file
19
assets/components/Tag.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="tag" :size="size">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { size = undefined } = defineProps<{ size?: "small" | undefined }>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tag {
|
||||
background-color: var(--scheme-main-ter);
|
||||
border: 1px solid var(--border-color);
|
||||
&[size="small"] {
|
||||
font-size: 0.61rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -21,7 +21,12 @@ function parseMessage(data: string): LogEntry<string | JSONObject> {
|
||||
return asLogEntry(e);
|
||||
}
|
||||
|
||||
export function useLogStream(container: ComputedRef<Container>) {
|
||||
type LogStreamConfig = {
|
||||
stdout: boolean;
|
||||
stderr: boolean;
|
||||
};
|
||||
|
||||
export function useLogStream(container: ComputedRef<Container>, streamConfig: LogStreamConfig) {
|
||||
let messages: LogEntry<string | JSONObject>[] = $ref([]);
|
||||
let buffer: LogEntry<string | JSONObject>[] = $ref([]);
|
||||
const scrollingPaused = $ref(inject("scrollingPaused") as Ref<boolean>);
|
||||
@@ -64,9 +69,20 @@ export function useLogStream(container: ComputedRef<Container>) {
|
||||
lastEventId = "";
|
||||
}
|
||||
|
||||
es = new EventSource(
|
||||
`${config.base}/api/logs/stream?id=${container.value.id}&lastEventId=${lastEventId}&host=${sessionHost.value}`
|
||||
);
|
||||
const params = {
|
||||
id: container.value.id,
|
||||
lastEventId,
|
||||
host: sessionHost.value,
|
||||
} as { id: string; lastEventId: string; host: string; stdout?: string; stderr?: string };
|
||||
|
||||
if (streamConfig.stdout) {
|
||||
params.stdout = "1";
|
||||
}
|
||||
if (streamConfig.stderr) {
|
||||
params.stderr = "1";
|
||||
}
|
||||
|
||||
es = new EventSource(`${config.base}/api/logs/stream?${new URLSearchParams(params).toString()}`);
|
||||
es.addEventListener("container-stopped", () => {
|
||||
es?.close();
|
||||
es = null;
|
||||
@@ -93,9 +109,22 @@ export function useLogStream(container: ComputedRef<Container>) {
|
||||
const last = messages[299].date;
|
||||
const delta = to.getTime() - last.getTime();
|
||||
const from = new Date(to.getTime() + delta);
|
||||
const logs = await (
|
||||
await fetch(`${config.base}/api/logs?id=${container.value.id}&from=${from.toISOString()}&to=${to.toISOString()}`)
|
||||
).text();
|
||||
|
||||
const params = {
|
||||
id: container.value.id,
|
||||
from: from.toISOString(),
|
||||
to: to.toISOString(),
|
||||
host: sessionHost.value,
|
||||
} as { id: string; from: string; to: string; host: string; stdout?: string; stderr?: string };
|
||||
|
||||
if (streamConfig.stdout) {
|
||||
params.stdout = "1";
|
||||
}
|
||||
if (streamConfig.stderr) {
|
||||
params.stderr = "1";
|
||||
}
|
||||
|
||||
const logs = await (await fetch(`${config.base}/api/logs?${new URLSearchParams(params).toString()}`)).text();
|
||||
if (logs) {
|
||||
const newMessages = logs
|
||||
.trim()
|
||||
@@ -129,5 +158,7 @@ export function useLogStream(container: ComputedRef<Container>) {
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(streamConfig, () => connect());
|
||||
|
||||
return { ...$$({ messages }), loadOlderLogs };
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export const DEFAULT_SETTINGS: {
|
||||
menuWidth: number;
|
||||
smallerScrollbars: boolean;
|
||||
showTimestamp: boolean;
|
||||
showStd: boolean;
|
||||
showAllContainers: boolean;
|
||||
lightTheme: "auto" | "dark" | "light";
|
||||
hourStyle: "auto" | "24" | "12";
|
||||
@@ -17,6 +18,7 @@ export const DEFAULT_SETTINGS: {
|
||||
menuWidth: 15,
|
||||
smallerScrollbars: false,
|
||||
showTimestamp: true,
|
||||
showStd: false,
|
||||
showAllContainers: false,
|
||||
lightTheme: "auto",
|
||||
hourStyle: "auto",
|
||||
@@ -51,6 +53,11 @@ const showTimestamp = computed({
|
||||
set: (value) => (settings.value.showTimestamp = value),
|
||||
});
|
||||
|
||||
const showStd = computed({
|
||||
get: () => settings.value.showStd,
|
||||
set: (value) => (settings.value.showStd = value),
|
||||
});
|
||||
|
||||
const showAllContainers = computed({
|
||||
get: () => settings.value.showAllContainers,
|
||||
set: (value) => (settings.value.showAllContainers = value),
|
||||
@@ -83,6 +90,7 @@ export {
|
||||
lightTheme,
|
||||
showAllContainers,
|
||||
showTimestamp,
|
||||
showStd,
|
||||
smallerScrollbars,
|
||||
menuWidth,
|
||||
size,
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { Container } from "@/models/Container";
|
||||
|
||||
const sessionHost = useSessionStorage("host", "localhost");
|
||||
|
||||
export { sessionHost };
|
||||
function persistentVisibleKeys(container: ComputedRef<Container>) {
|
||||
return computed(() => useStorage(stripVersion(container.value.image) + ":" + container.value.command, []));
|
||||
}
|
||||
|
||||
export { sessionHost, persistentVisibleKeys };
|
||||
|
||||
@@ -12,17 +12,25 @@ export interface HasComponent {
|
||||
export type JSONValue = string | number | boolean | JSONObject | Array<JSONValue>;
|
||||
export type JSONObject = { [x: string]: JSONValue };
|
||||
export type Position = "start" | "end" | "middle" | undefined;
|
||||
export type Std = "stdout" | "stderr";
|
||||
export interface LogEvent {
|
||||
readonly m: string | JSONObject;
|
||||
readonly ts: number;
|
||||
readonly id: number;
|
||||
readonly l: string;
|
||||
readonly p: Position;
|
||||
readonly s: number;
|
||||
}
|
||||
|
||||
export abstract class LogEntry<T extends string | JSONObject> implements HasComponent {
|
||||
protected readonly _message: T;
|
||||
constructor(message: T, public readonly id: number, public readonly date: Date, public readonly level?: string) {
|
||||
constructor(
|
||||
message: T,
|
||||
public readonly id: number,
|
||||
public readonly date: Date,
|
||||
public readonly std: Std,
|
||||
public readonly level?: string
|
||||
) {
|
||||
this._message = message;
|
||||
}
|
||||
|
||||
@@ -39,9 +47,10 @@ export class SimpleLogEntry extends LogEntry<string> {
|
||||
id: number,
|
||||
date: Date,
|
||||
public readonly level: string,
|
||||
public readonly position: Position
|
||||
public readonly position: Position,
|
||||
public readonly std: Std
|
||||
) {
|
||||
super(message, id, date, level);
|
||||
super(message, id, date, std, level);
|
||||
}
|
||||
getComponent(): Component {
|
||||
return SimpleLogItem;
|
||||
@@ -56,9 +65,10 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
|
||||
id: number,
|
||||
date: Date,
|
||||
public readonly level: string,
|
||||
public readonly std: Std,
|
||||
visibleKeys?: Ref<string[][]>
|
||||
) {
|
||||
super(message, id, date, level);
|
||||
super(message, id, date, std, level);
|
||||
if (visibleKeys) {
|
||||
this.filteredMessage = computed(() => {
|
||||
if (!visibleKeys.value.length) {
|
||||
@@ -84,13 +94,13 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
|
||||
}
|
||||
|
||||
static fromLogEvent(event: ComplexLogEntry, visibleKeys: Ref<string[][]>): ComplexLogEntry {
|
||||
return new ComplexLogEntry(event._message, event.id, event.date, event.level, visibleKeys);
|
||||
return new ComplexLogEntry(event._message, event.id, event.date, event.level, event.std, visibleKeys);
|
||||
}
|
||||
}
|
||||
|
||||
export class DockerEventLogEntry extends LogEntry<string> {
|
||||
constructor(message: string, date: Date, public readonly event: string) {
|
||||
super(message, date.getTime(), date, "info");
|
||||
super(message, date.getTime(), date, "stderr", "info");
|
||||
}
|
||||
getComponent(): Component {
|
||||
return DockerEventLogItem;
|
||||
@@ -107,7 +117,7 @@ export class SkippedLogsEntry extends LogEntry<string> {
|
||||
public readonly firstSkipped: LogEntry<string | JSONObject>,
|
||||
lastSkipped: LogEntry<string | JSONObject>
|
||||
) {
|
||||
super("", date.getTime(), date, "info");
|
||||
super("", date.getTime(), date, "stderr", "info");
|
||||
this._totalSkipped = totalSkipped;
|
||||
this.lastSkipped = lastSkipped;
|
||||
}
|
||||
@@ -135,8 +145,15 @@ export class SkippedLogsEntry extends LogEntry<string> {
|
||||
|
||||
export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
|
||||
if (typeof event.m === "string") {
|
||||
return new SimpleLogEntry(event.m, event.id, new Date(event.ts), event.l, event.p);
|
||||
return new SimpleLogEntry(
|
||||
event.m,
|
||||
event.id,
|
||||
new Date(event.ts),
|
||||
event.l,
|
||||
event.p,
|
||||
event.s === 1 ? "stdout" : "stderr"
|
||||
);
|
||||
} else {
|
||||
return new ComplexLogEntry(event.m, event.id, new Date(event.ts), event.l);
|
||||
return new ComplexLogEntry(event.m, event.id, new Date(event.ts), event.l, event.s === 1 ? "stdout" : "stderr");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
<div class="item">
|
||||
<o-switch v-model="showTimestamp"> {{ $t("settings.show-timesamps") }} </o-switch>
|
||||
</div>
|
||||
<div class="item">
|
||||
<o-switch v-model="showStd"> {{ $t("settings.show-std") }} </o-switch>
|
||||
</div>
|
||||
|
||||
<div class="item">
|
||||
<o-switch v-model="softWrap"> {{ $t("settings.soft-wrap") }}</o-switch>
|
||||
@@ -136,6 +139,7 @@ import {
|
||||
lightTheme,
|
||||
smallerScrollbars,
|
||||
showTimestamp,
|
||||
showStd,
|
||||
hourStyle,
|
||||
showAllContainers,
|
||||
size,
|
||||
@@ -155,7 +159,8 @@ async function fetchNextRelease() {
|
||||
const response = await fetch("https://api.github.com/repos/amir20/dozzle/releases/latest");
|
||||
if (response.ok) {
|
||||
const release = await response.json();
|
||||
hasUpdate = release.tag_name.slice(1).localeCompare(currentVersion, undefined, { numeric: true, sensitivity: 'base' }) > 0;
|
||||
hasUpdate =
|
||||
release.tag_name.slice(1).localeCompare(currentVersion, undefined, { numeric: true, sensitivity: "base" }) > 0;
|
||||
nextRelease = release;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -96,13 +96,6 @@ $light-toolbar-color: rgba($grey-darker, 0.7);
|
||||
--text-light-color: #{$grey};
|
||||
}
|
||||
|
||||
:root {
|
||||
--green-color: #00b5ad;
|
||||
--red-color: #f44336;
|
||||
--purple-color: #9c27b0;
|
||||
--orange-color: #ff9800;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
@include dark;
|
||||
}
|
||||
@@ -123,6 +116,34 @@ $light-toolbar-color: rgba($grey-darker, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--green-color: #00b5ad;
|
||||
--red-color: #f44336;
|
||||
--purple-color: #9c27b0;
|
||||
--orange-color: #ff9800;
|
||||
--blue-color: #2196f3;
|
||||
}
|
||||
|
||||
.is-red {
|
||||
color: var(--red-color);
|
||||
}
|
||||
|
||||
.is-green {
|
||||
color: var(--green-color);
|
||||
}
|
||||
|
||||
.is-purple {
|
||||
color: var(--purple-color);
|
||||
}
|
||||
|
||||
.is-orange {
|
||||
color: var(--orange-color);
|
||||
}
|
||||
|
||||
.is-blue {
|
||||
color: var(--blue-color);
|
||||
}
|
||||
|
||||
html {
|
||||
overflow-x: unset;
|
||||
overflow-y: unset;
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { Container } from "@/models/Container";
|
||||
import { useStorage } from "@vueuse/core";
|
||||
import { computed, ComputedRef } from "vue";
|
||||
|
||||
export function formatBytes(bytes: number, decimals = 2) {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024;
|
||||
@@ -37,10 +33,6 @@ export function arrayEquals(a: string[], b: string[]): boolean {
|
||||
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, index) => val === b[index]);
|
||||
}
|
||||
|
||||
export function persistentVisibleKeys(container: ComputedRef<Container>) {
|
||||
return computed(() => useStorage(stripVersion(container.value.image) + ":" + container.value.command, []));
|
||||
}
|
||||
|
||||
export function stripVersion(label: string) {
|
||||
const [name, _] = label.split(":");
|
||||
return name;
|
||||
|
||||
@@ -27,6 +27,27 @@ type dockerClient struct {
|
||||
filters filters.Args
|
||||
}
|
||||
|
||||
type StdType int
|
||||
|
||||
const (
|
||||
STDOUT StdType = 1 << iota
|
||||
STDERR
|
||||
)
|
||||
const STDALL = STDOUT | STDERR
|
||||
|
||||
func (s StdType) String() string {
|
||||
switch s {
|
||||
case STDOUT:
|
||||
return "out"
|
||||
case STDERR:
|
||||
return "err"
|
||||
case STDALL:
|
||||
return "all"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type dockerProxy interface {
|
||||
ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error)
|
||||
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||
@@ -40,9 +61,10 @@ type dockerProxy interface {
|
||||
type Client interface {
|
||||
ListContainers() ([]Container, error)
|
||||
FindContainer(string) (Container, error)
|
||||
ContainerLogs(context.Context, string, string) (io.ReadCloser, error)
|
||||
ContainerLogs(context.Context, string, string, StdType) (io.ReadCloser, error)
|
||||
ContainerLogReader(context.Context, string) (io.ReadCloser, error)
|
||||
Events(context.Context) (<-chan ContainerEvent, <-chan error)
|
||||
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time) (io.ReadCloser, error)
|
||||
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time, StdType) (io.ReadCloser, error)
|
||||
ContainerStats(context.Context, string, chan<- ContainerStat) error
|
||||
Ping(context.Context) (types.Ping, error)
|
||||
}
|
||||
@@ -227,8 +249,8 @@ func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, since string) (io.ReadCloser, error) {
|
||||
log.WithField("id", id).WithField("since", since).Debug("streaming logs for container")
|
||||
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, since string, stdType StdType) (io.ReadCloser, error) {
|
||||
log.WithField("id", id).WithField("since", since).WithField("stdType", stdType).Debug("streaming logs for container")
|
||||
|
||||
if since != "" {
|
||||
if millis, err := strconv.ParseInt(since, 10, 64); err == nil {
|
||||
@@ -239,8 +261,8 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, since strin
|
||||
}
|
||||
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
ShowStdout: stdType&STDOUT != 0,
|
||||
ShowStderr: stdType&STDERR != 0,
|
||||
Follow: true,
|
||||
Tail: "300",
|
||||
Timestamps: true,
|
||||
@@ -258,7 +280,7 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, since strin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newLogReader(reader, containerJSON.Config.Tty), nil
|
||||
return newLogReader(reader, containerJSON.Config.Tty, true), nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) Events(ctx context.Context) (<-chan ContainerEvent, <-chan error) {
|
||||
@@ -290,11 +312,35 @@ func (d *dockerClient) Events(ctx context.Context) (<-chan ContainerEvent, <-cha
|
||||
return messages, errors
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time) (io.ReadCloser, error) {
|
||||
func (d *dockerClient) ContainerLogReader(ctx context.Context, id string) (io.ReadCloser, error) {
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Timestamps: true,
|
||||
Since: time.Unix(0, 0).Format(time.RFC3339),
|
||||
Until: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
reader, err := d.cli.ContainerLogs(ctx, id, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containerJSON, err := d.cli.ContainerInspect(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newLogReader(reader, containerJSON.Config.Tty, false), nil
|
||||
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) {
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: stdType&STDOUT != 0,
|
||||
ShowStderr: stdType&STDERR != 0,
|
||||
Timestamps: true,
|
||||
Since: from.Format(time.RFC3339),
|
||||
Until: to.Format(time.RFC3339),
|
||||
}
|
||||
@@ -312,7 +358,7 @@ func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newLogReader(reader, containerJSON.Config.Tty), nil
|
||||
return newLogReader(reader, containerJSON.Config.Tty, true), nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) Ping(ctx context.Context) (types.Ping, error) {
|
||||
|
||||
@@ -133,7 +133,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
|
||||
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
|
||||
|
||||
client := &dockerClient{proxy, filters.NewArgs()}
|
||||
logReader, _ := client.ContainerLogs(context.Background(), id, "since")
|
||||
logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL)
|
||||
|
||||
actual, _ := io.ReadAll(logReader)
|
||||
assert.Equal(t, expected, string(actual), "message doesn't match expected")
|
||||
@@ -154,7 +154,7 @@ func Test_dockerClient_ContainerLogs_happy_with_tty(t *testing.T) {
|
||||
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
|
||||
|
||||
client := &dockerClient{proxy, filters.NewArgs()}
|
||||
logReader, _ := client.ContainerLogs(context.Background(), id, "")
|
||||
logReader, _ := client.ContainerLogs(context.Background(), id, "", STDALL)
|
||||
|
||||
actual, _ := io.ReadAll(logReader)
|
||||
assert.Equal(t, expected, string(actual), "message doesn't match expected")
|
||||
@@ -170,7 +170,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
|
||||
|
||||
client := &dockerClient{proxy, filters.NewArgs()}
|
||||
|
||||
reader, err := client.ContainerLogs(context.Background(), id, "")
|
||||
reader, err := client.ContainerLogs(context.Background(), id, "", STDALL)
|
||||
|
||||
assert.Nil(t, reader, "reader should be nil")
|
||||
assert.Error(t, err, "error should have been returned")
|
||||
|
||||
@@ -93,8 +93,20 @@ func (g *eventGenerator) consume() {
|
||||
if message != "" {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(message))
|
||||
std := message[:3]
|
||||
var stdType StdType
|
||||
switch std {
|
||||
case "OUT":
|
||||
stdType = STDOUT
|
||||
case "ERR":
|
||||
stdType = STDERR
|
||||
default:
|
||||
log.Panicf("unknown std type [%s] with message [%s]", std, message)
|
||||
}
|
||||
|
||||
logEvent := &LogEvent{Id: h.Sum32(), Message: message}
|
||||
message = message[3:]
|
||||
|
||||
logEvent := &LogEvent{Id: h.Sum32(), Message: message, StdType: stdType}
|
||||
|
||||
if index := strings.IndexAny(message, " "); index != -1 {
|
||||
logId := message[:index]
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func TestNewEventIterator(t *testing.T) {
|
||||
input := "example input"
|
||||
reader := bufio.NewReader(strings.NewReader(input))
|
||||
reader := bufio.NewReader(strings.NewReader("OUT" + input))
|
||||
|
||||
generator := NewEventIterator(reader)
|
||||
require.NotNil(t, generator, "Expected generator to not be nil, but got nil")
|
||||
@@ -20,7 +20,7 @@ func TestNewEventIterator(t *testing.T) {
|
||||
|
||||
func TestEventGenerator_Next(t *testing.T) {
|
||||
input := "example input"
|
||||
reader := bufio.NewReader(strings.NewReader(input))
|
||||
reader := bufio.NewReader(strings.NewReader("OUT" + input))
|
||||
|
||||
generator := NewEventIterator(reader)
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestEventGenerator_Next(t *testing.T) {
|
||||
|
||||
func TestEventGenerator_LastError(t *testing.T) {
|
||||
input := "example input"
|
||||
reader := bufio.NewReader(strings.NewReader(input))
|
||||
reader := bufio.NewReader(strings.NewReader("OUT" + input))
|
||||
|
||||
generator := NewEventIterator(reader)
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestEventGenerator_LastError(t *testing.T) {
|
||||
|
||||
func TestEventGenerator_Peek(t *testing.T) {
|
||||
input := "example input"
|
||||
reader := bufio.NewReader(strings.NewReader(input))
|
||||
reader := bufio.NewReader(strings.NewReader("OUT" + input))
|
||||
|
||||
generator := NewEventIterator(reader)
|
||||
|
||||
|
||||
@@ -11,14 +11,16 @@ type logReader struct {
|
||||
tty bool
|
||||
lastHeader []byte
|
||||
buffer bytes.Buffer
|
||||
label bool
|
||||
}
|
||||
|
||||
func newLogReader(reader io.ReadCloser, tty bool) io.ReadCloser {
|
||||
func newLogReader(reader io.ReadCloser, tty bool, labelStd bool) io.ReadCloser {
|
||||
return &logReader{
|
||||
reader,
|
||||
tty,
|
||||
make([]byte, 8),
|
||||
bytes.Buffer{},
|
||||
labelStd,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +36,16 @@ func (r *logReader) Read(p []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if r.label {
|
||||
std := r.lastHeader[0] // https://github.com/rancher/docker/blob/master/pkg/stdcopy/stdcopy.go#L94
|
||||
|
||||
if std == 1 {
|
||||
r.buffer.WriteString("OUT")
|
||||
}
|
||||
if std == 2 {
|
||||
r.buffer.WriteString("ERR")
|
||||
}
|
||||
}
|
||||
count := binary.BigEndian.Uint32(r.lastHeader[4:])
|
||||
_, err = io.CopyN(&r.buffer, r.readerCloser, int64(count))
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package docker
|
||||
|
||||
import "math"
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Container represents an internal representation of docker containers
|
||||
type Container struct {
|
||||
@@ -44,6 +46,7 @@ type LogEvent struct {
|
||||
Id uint32 `json:"id,omitempty"`
|
||||
Level string `json:"l,omitempty"`
|
||||
Position LogPosition `json:"p,omitempty"`
|
||||
StdType StdType `json:"s,omitempty"`
|
||||
}
|
||||
|
||||
func (l *LogEvent) HasLevel() bool {
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@netlify/functions": "^1.6.0",
|
||||
"@unocss/preset-typography": "^0.52.3",
|
||||
"@unocss/reset": "^0.52.3",
|
||||
"@unocss/transformer-directives": "^0.52.3",
|
||||
"@unocss/preset-typography": "^0.52.7",
|
||||
"@unocss/reset": "^0.52.7",
|
||||
"@unocss/transformer-directives": "^0.52.7",
|
||||
"dozzle": "workspace:*",
|
||||
"sitemap": "^7.1.1",
|
||||
"unocss": "^0.52.3"
|
||||
"unocss": "^0.52.7"
|
||||
}
|
||||
}
|
||||
|
||||
248
docs/pnpm-lock.yaml
generated
248
docs/pnpm-lock.yaml
generated
@@ -1,18 +1,22 @@
|
||||
lockfileVersion: '6.0'
|
||||
lockfileVersion: '6.1'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
devDependencies:
|
||||
'@netlify/functions':
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
'@unocss/preset-typography':
|
||||
specifier: ^0.52.3
|
||||
version: 0.52.3
|
||||
specifier: ^0.52.7
|
||||
version: 0.52.7
|
||||
'@unocss/reset':
|
||||
specifier: ^0.52.3
|
||||
version: 0.52.3
|
||||
specifier: ^0.52.7
|
||||
version: 0.52.7
|
||||
'@unocss/transformer-directives':
|
||||
specifier: ^0.52.3
|
||||
version: 0.52.3
|
||||
specifier: ^0.52.7
|
||||
version: 0.52.7
|
||||
dozzle:
|
||||
specifier: workspace:*
|
||||
version: link:..
|
||||
@@ -20,8 +24,8 @@ devDependencies:
|
||||
specifier: ^7.1.1
|
||||
version: 7.1.1
|
||||
unocss:
|
||||
specifier: ^0.52.3
|
||||
version: 0.52.3(postcss@8.4.23)(vite@4.3.8)
|
||||
specifier: ^0.52.7
|
||||
version: 0.52.7(postcss@8.4.24)(vite@4.3.9)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -40,8 +44,8 @@ packages:
|
||||
find-up: 5.0.0
|
||||
dev: true
|
||||
|
||||
/@antfu/utils@0.7.2:
|
||||
resolution: {integrity: sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g==}
|
||||
/@antfu/utils@0.7.4:
|
||||
resolution: {integrity: sha512-qe8Nmh9rYI/HIspLSTwtbMFPj6dISG6+dJnOguTlPNXtCvS2uezdxscVBb7/3DrmNbQK49TDqpkSQ1chbRGdpQ==}
|
||||
dev: true
|
||||
|
||||
/@esbuild/android-arm64@0.17.19:
|
||||
@@ -250,7 +254,7 @@ packages:
|
||||
resolution: {integrity: sha512-6MvDI+I6QMvXn5rK9KQGdpEE4mmLTcuQdLZEiX5N+uZB+vc4Yw9K1OtnOgkl8mp4d9X0UrILREyZgF1NUwUt+Q==}
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 0.1.1
|
||||
'@antfu/utils': 0.7.2
|
||||
'@antfu/utils': 0.7.4
|
||||
'@iconify/types': 2.0.0
|
||||
debug: 4.3.4
|
||||
kolorist: 1.8.0
|
||||
@@ -357,27 +361,27 @@ packages:
|
||||
'@types/node': 18.14.2
|
||||
dev: true
|
||||
|
||||
/@unocss/astro@0.52.3(vite@4.3.8):
|
||||
resolution: {integrity: sha512-S9Rb1TROB0Q1c4qgLBwLWqccaYq+Q+ZJaUvpgNjvDeKdam1pcGCELJos0HIK5oxOXpALSVmlMkGEh7OOZzDhCQ==}
|
||||
/@unocss/astro@0.52.7(vite@4.3.9):
|
||||
resolution: {integrity: sha512-jGm3sVB6AU3A1vXJskCdG2kUw1aRdg2fV60nILCBiRmj7SIlbMTXEHrz864AaleGVnxTiV7oGL4P1DfDJ3tQSA==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/reset': 0.52.3
|
||||
'@unocss/vite': 0.52.3(vite@4.3.8)
|
||||
'@unocss/core': 0.52.7
|
||||
'@unocss/reset': 0.52.7
|
||||
'@unocss/vite': 0.52.7(vite@4.3.9)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- vite
|
||||
dev: true
|
||||
|
||||
/@unocss/cli@0.52.3:
|
||||
resolution: {integrity: sha512-bVR9cwltNvYi35gWR7XYdtrgwU+saYxeBRWt7vlargaIPmQ0s9EgfcHYC7mlD82SZPnRj1KQhyFVTFtyrQCiVg==}
|
||||
/@unocss/cli@0.52.7:
|
||||
resolution: {integrity: sha512-WC82yIMH6RH8W/0Gb26WEjNf/E8Rb1m6qywhtpuzwEYWmA8z6+uDvIaoXu8lhSpVeggQwjdzOXFe0++GRTcQ3Q==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.2.1
|
||||
'@rollup/pluginutils': 5.0.2
|
||||
'@unocss/config': 0.52.3
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/preset-uno': 0.52.3
|
||||
'@unocss/config': 0.52.7
|
||||
'@unocss/core': 0.52.7
|
||||
'@unocss/preset-uno': 0.52.7
|
||||
cac: 6.7.14
|
||||
chokidar: 3.5.3
|
||||
colorette: 2.0.20
|
||||
@@ -390,158 +394,158 @@ packages:
|
||||
- rollup
|
||||
dev: true
|
||||
|
||||
/@unocss/config@0.52.3:
|
||||
resolution: {integrity: sha512-T/OLuf8twR6/b6zcRgdL3iVmz8jEv2CSy08kUQlpjVDJhV2MZcdlTNi+pQcLK1NTRkHiBVodZwTFPNje2eUIxA==}
|
||||
/@unocss/config@0.52.7:
|
||||
resolution: {integrity: sha512-VKj4VnJR88EK0ikJnQbfslZfMCqdGu6LhnErs3x0HjQPVQU1oFsB1IM4ySGLaGhM4WcfZf05gzMzIav3kFyopg==}
|
||||
engines: {node: '>=14'}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
unconfig: 0.3.9
|
||||
dev: true
|
||||
|
||||
/@unocss/core@0.52.3:
|
||||
resolution: {integrity: sha512-AdpksuSj1+jAjF7Ek1Ubtt+pE/bi4EmVqz/sx7PTgp9RUyBX1457kDlSWJPFOvEEkKL8VLtwXB46hD2oPAp36Q==}
|
||||
/@unocss/core@0.52.7:
|
||||
resolution: {integrity: sha512-dZonrlfu33SkUMsZXlsyYSM79tr2nLer/hBEU2ZaemRik2KchxIUNlZV6kX1f1k3m+gEtVQOyx1MImpgLS8PWg==}
|
||||
dev: true
|
||||
|
||||
/@unocss/extractor-arbitrary-variants@0.52.3:
|
||||
resolution: {integrity: sha512-dEDQ9mfwlS/aC420iRO6wUT1p0z2WBH5nupTdVgrU9Wjtff+NmLaas78skN+GPE5FCPXgKTJJsaDX6+etc/hrw==}
|
||||
/@unocss/extractor-arbitrary-variants@0.52.7:
|
||||
resolution: {integrity: sha512-nJ4iE7nIRpoOIQfD8S58yG4qJd6AhVPEfEOf7ksX1u8xLf71rrBIojwraRXvv7aPqNdZiWvXdh/znpA/QC5b9w==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/inspector@0.52.3:
|
||||
resolution: {integrity: sha512-VXbglsSzwpXGo51IAnmQWsjqrROMz+DbGujMW8xksmDqUcJArV1KgLRpZHaeyhs5o2D6UTstgpSpqWgvlcvLNA==}
|
||||
/@unocss/inspector@0.52.7:
|
||||
resolution: {integrity: sha512-XuxoCerVpIw9XR1iO8PEPrCj+KLwEGLAziHedObnXkS5ANbHdd+eWXIPpsG8DbICdLGUDnalL7wfxB19X1S9AQ==}
|
||||
dependencies:
|
||||
gzip-size: 6.0.0
|
||||
sirv: 2.0.3
|
||||
dev: true
|
||||
|
||||
/@unocss/postcss@0.52.3(postcss@8.4.23):
|
||||
resolution: {integrity: sha512-n3SdpSsn0MpWB9Pf6JjzR7U2rsA6jkD5QPJttIL9yxrK9i4KXTwGNio/4iM2Rs4x+qAzLtNjIBJ1xdxtIFA3kA==}
|
||||
/@unocss/postcss@0.52.7(postcss@8.4.24):
|
||||
resolution: {integrity: sha512-0yG7K8ie9gky7Y/oD29Jzpe4l92IgRPB2Fo9a7g2f4dGlKOuih5S+NsH3EO4WODrawntISyxVXMHsIydze2vAw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4.21
|
||||
dependencies:
|
||||
'@unocss/config': 0.52.3
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/config': 0.52.7
|
||||
'@unocss/core': 0.52.7
|
||||
css-tree: 2.3.1
|
||||
fast-glob: 3.2.12
|
||||
magic-string: 0.30.0
|
||||
postcss: 8.4.23
|
||||
postcss: 8.4.24
|
||||
dev: true
|
||||
|
||||
/@unocss/preset-attributify@0.52.3:
|
||||
resolution: {integrity: sha512-2+1i1iMnTv+Mh+KHmNm7kDtAfTD/rJn134PjIgTJq06WmS62RF9lDsj7ng0NA09vXLHQKtwXGeRk7Ca3P7/Jwg==}
|
||||
/@unocss/preset-attributify@0.52.7:
|
||||
resolution: {integrity: sha512-rq3ntPbuwGTZO7ebQhsuaZjKCmkDPBNP7sX+lXhaOsIsIGM4JGmLTBNSZ03YUx6QVgYVbjO1MKv734AHNYG4/Q==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/preset-icons@0.52.3:
|
||||
resolution: {integrity: sha512-OBy9AeLE8li8R2ActigLBC/GEq3SrcCA4SVUVvz4pM17RoXhxSyg6sxa97UgcJ0QTbJQL6YzgS9lB857Bv0fjA==}
|
||||
/@unocss/preset-icons@0.52.7:
|
||||
resolution: {integrity: sha512-4M8V7dhNxA+XGRqz+mlmEtqHOnyXYuqFpc+3biqjhlJb4zirNgJ9ujEty0OWwrKhC8QKfxifVlTtHInfjQQkDA==}
|
||||
dependencies:
|
||||
'@iconify/utils': 2.1.5
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
ofetch: 1.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@unocss/preset-mini@0.52.3:
|
||||
resolution: {integrity: sha512-9KJMlO3YF6UZRgua3js7pTh8lImMFLbtTpGWrrRNojJH2MvsmQNd4OlWLDobs3jUJG+4tlYiSH175Y3bdEHVXQ==}
|
||||
/@unocss/preset-mini@0.52.7:
|
||||
resolution: {integrity: sha512-c5VRzPwyAmIBWwz2ufEboYwHGiheG+V9SCmJJLHlu/gcW5KndFsxoeJPE6nOfXVmbx4AGq/rkzV35ZXtH8Iecw==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/extractor-arbitrary-variants': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
'@unocss/extractor-arbitrary-variants': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/preset-tagify@0.52.3:
|
||||
resolution: {integrity: sha512-zdBHZRYRAbtRQu7kzg18lMa8ZxtmAt93eUjQa8qEv180roL3+ycx2G05wkLn+dRx9n3Nn/wEL++FN/y5Fu/3Zg==}
|
||||
/@unocss/preset-tagify@0.52.7:
|
||||
resolution: {integrity: sha512-Zoard/LvUT03buLkDAnFAsgUUDfqIrVXADQFqRN7uDkf5lXocqjp56IzHng1Py2EJY4RpqHx+Mixn0fBH45E4g==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/preset-typography@0.52.3:
|
||||
resolution: {integrity: sha512-BfgBrLDjIS7Mbjie8eZWRh8VDLAT3o5EoW9OLbOpJfeyy2wfgtj2e10TK7xk8sNqaxSud5wTovQJi0tr4+Fc7w==}
|
||||
/@unocss/preset-typography@0.52.7:
|
||||
resolution: {integrity: sha512-mx7NQm6ZEo1UTQX9ZIzhZePjIBb2PEw7VDg6rWAPzdMRYQ1PnetjVbGFK5IafKmgVD1PP43UUwqDo8P0bD/aOg==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/preset-mini': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
'@unocss/preset-mini': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/preset-uno@0.52.3:
|
||||
resolution: {integrity: sha512-6rNjthD517yUBST3efxE5dsiErYf198RNh6fV8Fxhw0JwI+X1B9e5lzhviuyXbJj+qvJTpZFYcebyVxlzyT1lQ==}
|
||||
/@unocss/preset-uno@0.52.7:
|
||||
resolution: {integrity: sha512-J5royXxvaPvwRplZ2zwEcB1jJETp3dTA3sIezf9ydSNr4px3h6Ul6TxFDuJpBUWlx/cxP7aRWM0p9+e2ivdRkA==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/preset-mini': 0.52.3
|
||||
'@unocss/preset-wind': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
'@unocss/preset-mini': 0.52.7
|
||||
'@unocss/preset-wind': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/preset-web-fonts@0.52.3:
|
||||
resolution: {integrity: sha512-beILgZF707CjzoBy7AYAgdoX+oX6ZHUfSFEqVbenkargZv2w4M3Tgae/mJxwaQfHB8lMyq2IRTnn1fOj8J814g==}
|
||||
/@unocss/preset-web-fonts@0.52.7:
|
||||
resolution: {integrity: sha512-KnWpYPqRVqD1wu8pJMQVy+sMgrJKSqr5R0C1xMMT4u4TZk4fc2YWXox6UNw5WWWzdc1KzJ/k36wSPnq+jSjfDA==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
ofetch: 1.0.1
|
||||
dev: true
|
||||
|
||||
/@unocss/preset-wind@0.52.3:
|
||||
resolution: {integrity: sha512-YBfn1goa509Xxet2+mJimUkVO9t1rsTcqv5ytDpA9kUMNMdR8hrHh6hyM6WPB5Pg8/B7yQ739iZ6dkfbr/UFgQ==}
|
||||
/@unocss/preset-wind@0.52.7:
|
||||
resolution: {integrity: sha512-IT36cDftK7B+zDUElL4qdZZEj6iwknIpetXwuVvW/X8ljS/ocY/qfyjSX7C8k163FLAw7nTARFjW3xL066NsLw==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/preset-mini': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
'@unocss/preset-mini': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/reset@0.52.3:
|
||||
resolution: {integrity: sha512-2vp4egIZC+d48IwX9e4jv8x04aPdKy0mP5VZSE+n4wczlh2ctLE5b9z6hnv0mM9BwHgA1nIX/7iNkdd+2pkJ6g==}
|
||||
/@unocss/reset@0.52.7:
|
||||
resolution: {integrity: sha512-TJW2BaGGQoh0OSDd22Ti8bZ/Ds3YMGT8aBxNPkcyhesH4fCJeWK+rwsAc5g8CS/wp9OdLS8P4Jy9k2Yg/GfrVQ==}
|
||||
dev: true
|
||||
|
||||
/@unocss/scope@0.52.3:
|
||||
resolution: {integrity: sha512-TYpb7ICvIK4KNsj2Uq8Fa4RBeABG+7zoauo9RK9c9NoVUiDJhm/lCba1Q6V7ArEAsEKldG4JA4F08k9Hr0rcRQ==}
|
||||
/@unocss/scope@0.52.7:
|
||||
resolution: {integrity: sha512-J8QMwfbm+lCt3Lpt52NllnXbuICvH8+Njl/L65wN9TfE6gHk0StA5nrEOlOB79R1aOhnRaoqG4MkAvFXK/1dcQ==}
|
||||
dev: true
|
||||
|
||||
/@unocss/transformer-attributify-jsx-babel@0.52.3:
|
||||
resolution: {integrity: sha512-KO0c+uCGstKulHAlTtoWb7RS8uq/MkjADhxtvGsyj73vQT6CiicZ4dgzPvN+XP9cEs02H0Hl5OJ1171dbvtKgw==}
|
||||
/@unocss/transformer-attributify-jsx-babel@0.52.7:
|
||||
resolution: {integrity: sha512-6O2wSmALwaY0gmo/6quIEEiB6mpE3HFRJU2FmDQny5PVBrDhKps72h1zeNkDA8wjxz8XizNBhPbH/Uzc1lnAVg==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/transformer-attributify-jsx@0.52.3:
|
||||
resolution: {integrity: sha512-1qYNY3qGLBu2Fsoq2j1LGVyATkIe1BtLogK7o+Zpk3tAGR3GvJl8HTzirIaI1FaBfYScsPEFS4uFtLawNVvSww==}
|
||||
/@unocss/transformer-attributify-jsx@0.52.7:
|
||||
resolution: {integrity: sha512-5Wz4KCUB+ZnXKwvtyASoN0yH61GPMRyNfLP3tz/uel9H2lyfgIPSKFthPVY8dsUCEixT7oGiIvQCLqk6f3po3A==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/transformer-compile-class@0.52.3:
|
||||
resolution: {integrity: sha512-dQKxPuCWOahLJueu6mup+nJFas3pqosj4/jiJEok9uFFXbeq2Y9z3XxI1MWGTI/JSPtD6yLxH6Vwe0eOk2OJOw==}
|
||||
/@unocss/transformer-compile-class@0.52.7:
|
||||
resolution: {integrity: sha512-4gHqzeLq+9Ehl+yxYtGNUWrYACxnNfeiHBXfix7VmRHsBWIRol0/81Shqplxm9JRhkQcbXzadogynOav5LQcBg==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/transformer-directives@0.52.3:
|
||||
resolution: {integrity: sha512-19ECVhIOzllR8iTA9oTupsMdVs9F1+5ooLmfeRtvl9hJP+3YhSP0nPHau5x172rbx2lrt4MsomjWBlcQV+twUw==}
|
||||
/@unocss/transformer-directives@0.52.7:
|
||||
resolution: {integrity: sha512-v68nQjeU/8I8aOIQC6prIk5GJi8SpkaFsdh9p1UPSkJPL3rYv0bBLIkYrwBcmaqKUOvzL5joN0Cueolq/+GtUw==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
css-tree: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@unocss/transformer-variant-group@0.52.3:
|
||||
resolution: {integrity: sha512-tr4ZfwvBGQBXkjiM+Jroe7T9AlryFzt5F1pkvqdx3cDy9BeQpzC6+ZrLjH1xPLDv1wposHXbURLfMe/9dXka7w==}
|
||||
/@unocss/transformer-variant-group@0.52.7:
|
||||
resolution: {integrity: sha512-pGqTfT1hax3F+yjs6n6r5loSIP/Dsm/NuEA5nwazTu4gmubiIBi11UjoK/pE/cFg9Z3yp6n9Lspo71yALJbpVg==}
|
||||
dependencies:
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/core': 0.52.7
|
||||
dev: true
|
||||
|
||||
/@unocss/vite@0.52.3(vite@4.3.8):
|
||||
resolution: {integrity: sha512-N/e2zbRGrn8mmllVAiCeCoB3AQ96+l1XTTTN5mvOTj2VMzfsaYE4z28X4jUQ35JppfppfDKwESaDD+b/DZyJqA==}
|
||||
/@unocss/vite@0.52.7(vite@4.3.9):
|
||||
resolution: {integrity: sha512-Hn1u6/uPP2q0s5gfwA7KQFtclviEUrEKnEa3l1kFJA3S/tHXYjwQkzbDQObQzolVAXyzIhf1cQ8e1tEMyHm1qg==}
|
||||
peerDependencies:
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.2.1
|
||||
'@rollup/pluginutils': 5.0.2
|
||||
'@unocss/config': 0.52.3
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/inspector': 0.52.3
|
||||
'@unocss/scope': 0.52.3
|
||||
'@unocss/transformer-directives': 0.52.3
|
||||
'@unocss/config': 0.52.7
|
||||
'@unocss/core': 0.52.7
|
||||
'@unocss/inspector': 0.52.7
|
||||
'@unocss/scope': 0.52.7
|
||||
'@unocss/transformer-directives': 0.52.7
|
||||
chokidar: 3.5.3
|
||||
fast-glob: 3.2.12
|
||||
magic-string: 0.30.0
|
||||
vite: 4.3.8
|
||||
vite: 4.3.9
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: true
|
||||
@@ -930,8 +934,8 @@ packages:
|
||||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/postcss@8.4.23:
|
||||
resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==}
|
||||
/postcss@8.4.24:
|
||||
resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.6
|
||||
@@ -1038,40 +1042,40 @@ packages:
|
||||
/unconfig@0.3.9:
|
||||
resolution: {integrity: sha512-8yhetFd48M641mxrkWA+C/lZU4N0rCOdlo3dFsyFPnBHBjMJfjT/3eAZBRT2RxCRqeBMAKBVgikejdS6yeBjMw==}
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.2
|
||||
'@antfu/utils': 0.7.4
|
||||
defu: 6.1.2
|
||||
jiti: 1.18.2
|
||||
dev: true
|
||||
|
||||
/unocss@0.52.3(postcss@8.4.23)(vite@4.3.8):
|
||||
resolution: {integrity: sha512-BgL3kbxwt839t0ojo/j+i8xU4qu+fyV34SJOMQuFhLu6xkPNepvr6uPeipzNDajR7EZP3Q+jXJT9AWLKLLg1jw==}
|
||||
/unocss@0.52.7(postcss@8.4.24)(vite@4.3.9):
|
||||
resolution: {integrity: sha512-c35lqmzWqnQH0hW2IE1owac2qfGOvNAhrIrLV2+pNmc2MDWq8WMjIEuWo8G+OS5JqFQY3ZBlE61q2x/tHPlujQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@unocss/webpack': 0.52.3
|
||||
'@unocss/webpack': 0.52.7
|
||||
peerDependenciesMeta:
|
||||
'@unocss/webpack':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@unocss/astro': 0.52.3(vite@4.3.8)
|
||||
'@unocss/cli': 0.52.3
|
||||
'@unocss/core': 0.52.3
|
||||
'@unocss/extractor-arbitrary-variants': 0.52.3
|
||||
'@unocss/postcss': 0.52.3(postcss@8.4.23)
|
||||
'@unocss/preset-attributify': 0.52.3
|
||||
'@unocss/preset-icons': 0.52.3
|
||||
'@unocss/preset-mini': 0.52.3
|
||||
'@unocss/preset-tagify': 0.52.3
|
||||
'@unocss/preset-typography': 0.52.3
|
||||
'@unocss/preset-uno': 0.52.3
|
||||
'@unocss/preset-web-fonts': 0.52.3
|
||||
'@unocss/preset-wind': 0.52.3
|
||||
'@unocss/reset': 0.52.3
|
||||
'@unocss/transformer-attributify-jsx': 0.52.3
|
||||
'@unocss/transformer-attributify-jsx-babel': 0.52.3
|
||||
'@unocss/transformer-compile-class': 0.52.3
|
||||
'@unocss/transformer-directives': 0.52.3
|
||||
'@unocss/transformer-variant-group': 0.52.3
|
||||
'@unocss/vite': 0.52.3(vite@4.3.8)
|
||||
'@unocss/astro': 0.52.7(vite@4.3.9)
|
||||
'@unocss/cli': 0.52.7
|
||||
'@unocss/core': 0.52.7
|
||||
'@unocss/extractor-arbitrary-variants': 0.52.7
|
||||
'@unocss/postcss': 0.52.7(postcss@8.4.24)
|
||||
'@unocss/preset-attributify': 0.52.7
|
||||
'@unocss/preset-icons': 0.52.7
|
||||
'@unocss/preset-mini': 0.52.7
|
||||
'@unocss/preset-tagify': 0.52.7
|
||||
'@unocss/preset-typography': 0.52.7
|
||||
'@unocss/preset-uno': 0.52.7
|
||||
'@unocss/preset-web-fonts': 0.52.7
|
||||
'@unocss/preset-wind': 0.52.7
|
||||
'@unocss/reset': 0.52.7
|
||||
'@unocss/transformer-attributify-jsx': 0.52.7
|
||||
'@unocss/transformer-attributify-jsx-babel': 0.52.7
|
||||
'@unocss/transformer-compile-class': 0.52.7
|
||||
'@unocss/transformer-directives': 0.52.7
|
||||
'@unocss/transformer-variant-group': 0.52.7
|
||||
'@unocss/vite': 0.52.7(vite@4.3.9)
|
||||
transitivePeerDependencies:
|
||||
- postcss
|
||||
- rollup
|
||||
@@ -1079,8 +1083,8 @@ packages:
|
||||
- vite
|
||||
dev: true
|
||||
|
||||
/vite@4.3.8:
|
||||
resolution: {integrity: sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==}
|
||||
/vite@4.3.9:
|
||||
resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -1105,7 +1109,7 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
esbuild: 0.17.19
|
||||
postcss: 8.4.23
|
||||
postcss: 8.4.24
|
||||
rollup: 3.23.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
@@ -2,6 +2,8 @@ toolbar:
|
||||
clear: Clear
|
||||
download: Download
|
||||
search: Search
|
||||
show: Show {std}
|
||||
hide: Hide {std}
|
||||
label:
|
||||
containers: Containers
|
||||
total-containers: Total Containers
|
||||
@@ -49,3 +51,4 @@ settings:
|
||||
update-available: >-
|
||||
New version is available! Update to <a :href="{href}" class="next-release"
|
||||
target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Show stdout and stderr labels
|
||||
|
||||
@@ -2,6 +2,7 @@ toolbar:
|
||||
clear: Limpiar
|
||||
download: Descargar
|
||||
search: Buscar
|
||||
show: Mostrar {std}
|
||||
label:
|
||||
containers: Contenedores
|
||||
total-containers: Contenedores Totales
|
||||
@@ -49,3 +50,4 @@ settings:
|
||||
update-available: >-
|
||||
¡La nueva versión está disponible! Actualizar a la
|
||||
<a :href="{href}" class="next-release" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Mostrar etiquetas de salida estándar y salida de error estándar
|
||||
|
||||
@@ -2,6 +2,8 @@ toolbar:
|
||||
clear: Limpar
|
||||
download: Descarregar
|
||||
search: Pesquisa
|
||||
show: Mostrar {std}
|
||||
hide: Ocultar {std}
|
||||
label:
|
||||
containers: Contentores
|
||||
total-containers: Contentores Totais
|
||||
@@ -49,3 +51,4 @@ settings:
|
||||
update-available: >-
|
||||
Está disponível uma nova versão! Actualização para
|
||||
<a :href="{href}" class="next-release" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Mostrar etiquetas de saída padrão e saída de erro padrão
|
||||
|
||||
@@ -2,6 +2,8 @@ toolbar:
|
||||
clear: Очистить
|
||||
download: Скачать
|
||||
search: Поиск
|
||||
show: Показать {std}
|
||||
hide: Скрыть {std}
|
||||
label:
|
||||
containers: Контейнеры
|
||||
total-containers: Всего Контейнеров
|
||||
@@ -48,3 +50,4 @@ settings:
|
||||
update-available: >-
|
||||
Доступна новая версия! Обновить до <a :href="{href}" class="next-release"
|
||||
target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Показывать метки stdout и stderr
|
||||
|
||||
22
package.json
22
package.json
@@ -26,7 +26,7 @@
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-json/carbon": "^1.1.16",
|
||||
"@iconify-json/carbon": "^1.1.17",
|
||||
"@iconify-json/cil": "^1.1.4",
|
||||
"@iconify-json/mdi": "^1.1.52",
|
||||
"@iconify-json/mdi-light": "^1.1.6",
|
||||
@@ -39,7 +39,7 @@
|
||||
"@vueuse/router": "^10.1.2",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"bulma": "^0.9.4",
|
||||
"d3-array": "^3.2.3",
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-ease": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-selection": "^3.0.0",
|
||||
@@ -53,30 +53,29 @@
|
||||
"splitpanes": "^3.1.5",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.2.1"
|
||||
"vue-router": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pinia/testing": "^0.1.2",
|
||||
"@playwright/test": "^1.34.2",
|
||||
"@types/d3-array": "^3.0.4",
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@types/d3-array": "^3.0.5",
|
||||
"@types/d3-ease": "^3.0.0",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-selection": "^3.0.5",
|
||||
"@types/d3-shape": "^3.1.1",
|
||||
"@types/d3-transition": "^3.0.3",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/node": "^20.2.3",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/semver": "^7.5.0",
|
||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"@vue-macros/reactivity-transform": "^0.3.8",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"bumpp": "^9.1.0",
|
||||
"c8": "^7.13.0",
|
||||
"c8": "^7.14.0",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"jest-serializer-vue": "^3.1.0",
|
||||
"jsdom": "^22.0.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^13.2.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.8.8",
|
||||
@@ -84,14 +83,15 @@
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4",
|
||||
"unplugin-auto-import": "^0.16.2",
|
||||
"unplugin-auto-import": "^0.16.4",
|
||||
"unplugin-icons": "^0.16.1",
|
||||
"unplugin-vue-components": "^0.25.0",
|
||||
"unplugin-vue-macros": "^2.2.1",
|
||||
"vite": "4.3.9",
|
||||
"vite-plugin-pages": "^0.30.1",
|
||||
"vite-plugin-vue-layouts": "^0.8.0",
|
||||
"vitepress": "1.0.0-beta.1",
|
||||
"vitest": "^0.31.1",
|
||||
"vitest": "^0.31.3",
|
||||
"vue-tsc": "^1.6.5"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
586
pnpm-lock.yaml
generated
586
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
||||
"vue/ref-macros",
|
||||
"vite-plugin-pages/client",
|
||||
"vite-plugin-vue-layouts/client",
|
||||
"@vue-macros/reactivity-transform/macros-global"
|
||||
"unplugin-vue-macros/macros-global"
|
||||
]
|
||||
},
|
||||
"include": ["assets/**/*.ts", "assets/**/*.d.ts", "assets/**/*.vue"],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import ReactivityTransform from "@vue-macros/reactivity-transform/vite";
|
||||
import Vue from "@vitejs/plugin-vue";
|
||||
import VueMacros from "unplugin-vue-macros/vite";
|
||||
import Icons from "unplugin-icons/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
@@ -26,12 +26,15 @@ export default defineConfig(() => ({
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
ReactivityTransform(),
|
||||
vue({
|
||||
template: {
|
||||
compilerOptions: {
|
||||
whitespace: "preserve",
|
||||
},
|
||||
VueMacros({
|
||||
plugins: {
|
||||
vue: Vue({
|
||||
template: {
|
||||
compilerOptions: {
|
||||
whitespace: "preserve",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
Icons({
|
||||
|
||||
@@ -76,8 +76,8 @@ HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Content-Type: application/ld+json; charset=UTF-8
|
||||
|
||||
{"m":"INFO Testing logs...","ts":1589396137772,"id":2908612274,"l":"info"}
|
||||
{"m":"INFO Testing logs...","ts":1589396137772,"id":2908612274,"l":"info"}
|
||||
{"m":"INFO Testing logs...","ts":1589396137772,"id":1122614848,"l":"info","s":1}
|
||||
{"m":"INFO Testing logs...","ts":1589396137772,"id":1543246723,"l":"info","s":2}
|
||||
|
||||
/* snapshot: Test_handler_streamEvents_error */
|
||||
HTTP/1.1 200 OK
|
||||
@@ -143,6 +143,14 @@ X-Content-Type-Options: nosniff
|
||||
|
||||
test error
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_error_std */
|
||||
HTTP/1.1 400 Bad Request
|
||||
Connection: close
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
X-Content-Type-Options: nosniff
|
||||
|
||||
stdout or stderr is required
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_happy */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
@@ -152,7 +160,7 @@ Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
data: {"m":"INFO Testing logs...","ts":0,"id":4256192898,"l":"info"}
|
||||
data: {"m":"INFO Testing logs...","ts":0,"id":852638900,"l":"info","s":1}
|
||||
|
||||
event: container-stopped
|
||||
data: end of stream
|
||||
@@ -178,7 +186,7 @@ Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
data: {"m":"INFO Testing logs...","ts":1589396137772,"id":1469707724,"l":"info"}
|
||||
data: {"m":"INFO Testing logs...","ts":1589396137772,"id":3373215946,"l":"info","s":1}
|
||||
id: 1589396137772
|
||||
|
||||
event: container-stopped
|
||||
|
||||
33
web/logs.go
33
web/logs.go
@@ -28,7 +28,6 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
from := time.Unix(container.Created, 0)
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s-%s.log.gz", container.Name, now.Format("2006-01-02T15-04-05")))
|
||||
w.Header().Set("Content-Type", "application/gzip")
|
||||
@@ -38,7 +37,7 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
|
||||
zw.Comment = "Logs generated by Dozzle"
|
||||
zw.ModTime = now
|
||||
|
||||
reader, err := h.clientFromRequest(r).ContainerLogsBetweenDates(r.Context(), container.ID, from, now)
|
||||
reader, err := h.clientFromRequest(r).ContainerLogReader(r.Context(), container.ID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -53,7 +52,20 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
to, _ := time.Parse(time.RFC3339, r.URL.Query().Get("to"))
|
||||
id := r.URL.Query().Get("id")
|
||||
|
||||
reader, err := h.clientFromRequest(r).ContainerLogsBetweenDates(r.Context(), id, from, to)
|
||||
var stdTypes docker.StdType
|
||||
if r.URL.Query().Has("stdout") {
|
||||
stdTypes |= docker.STDOUT
|
||||
}
|
||||
if r.URL.Query().Has("stderr") {
|
||||
stdTypes |= docker.STDERR
|
||||
}
|
||||
|
||||
if stdTypes == 0 {
|
||||
http.Error(w, "stdout or stderr is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
reader, err := h.clientFromRequest(r).ContainerLogsBetweenDates(r.Context(), id, from, to, stdTypes)
|
||||
defer reader.Close()
|
||||
|
||||
if err != nil {
|
||||
@@ -83,6 +95,19 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var stdTypes docker.StdType
|
||||
if r.URL.Query().Has("stdout") {
|
||||
stdTypes |= docker.STDOUT
|
||||
}
|
||||
if r.URL.Query().Has("stderr") {
|
||||
stdTypes |= docker.STDERR
|
||||
}
|
||||
|
||||
if stdTypes == 0 {
|
||||
http.Error(w, "stdout or stderr is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
f, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||
@@ -106,7 +131,7 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
||||
lastEventId = r.URL.Query().Get("lastEventId")
|
||||
}
|
||||
|
||||
reader, err := h.clientFromRequest(r).ContainerLogs(r.Context(), container.ID, lastEventId)
|
||||
reader, err := h.clientFromRequest(r).ContainerLogs(r.Context(), container.ID, lastEventId, stdTypes)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
|
||||
|
||||
@@ -3,8 +3,9 @@ package web
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
@@ -118,7 +119,7 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(file)
|
||||
bytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"io"
|
||||
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -112,7 +111,7 @@ func Test_createRoutes_username_password(t *testing.T) {
|
||||
|
||||
func Test_createRoutes_username_password_invalid(t *testing.T) {
|
||||
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password"})
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream?id=123", nil)
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream?id=123&stdout=1&stderr=1", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
@@ -179,11 +178,11 @@ func Test_createRoutes_username_password_login_failed(t *testing.T) {
|
||||
func Test_createRoutes_username_password_valid_session(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
mockedClient.On("FindContainer", "123").Return(docker.Container{ID: "123"}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, "123", "").Return(ioutil.NopCloser(strings.NewReader("test data")), io.EOF)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, "123", "", docker.STDALL).Return(io.NopCloser(strings.NewReader("test data")), io.EOF)
|
||||
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password"})
|
||||
|
||||
// Get cookie first
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream?id=123", nil)
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream?id=123&stdout=1&stderr=1", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
session, _ := store.Get(req, sessionName)
|
||||
session.Values[authorityKey] = time.Now().Unix()
|
||||
@@ -192,7 +191,7 @@ func Test_createRoutes_username_password_valid_session(t *testing.T) {
|
||||
cookies := recorder.Result().Cookies()
|
||||
|
||||
// Test with cookie
|
||||
req, err = http.NewRequest("GET", "/api/logs/stream?id=123", nil)
|
||||
req, err = http.NewRequest("GET", "/api/logs/stream?id=123&stdout=1&stderr=1", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
req.AddCookie(cookies[0])
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -203,9 +202,9 @@ func Test_createRoutes_username_password_valid_session(t *testing.T) {
|
||||
func Test_createRoutes_username_password_invalid_session(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
mockedClient.On("FindContainer", "123").Return(docker.Container{ID: "123"}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, "since").Return(ioutil.NopCloser(strings.NewReader("test data")), io.EOF)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, "since", docker.STDALL).Return(io.NopCloser(strings.NewReader("test data")), io.EOF)
|
||||
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password"})
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream?id=123", nil)
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream?id=123&stdout=1&stderr=1", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
req.AddCookie(&http.Cookie{Name: "session", Value: "baddata"})
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -23,13 +22,15 @@ func Test_handler_streamLogs_happy(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
q.Add("stdout", "true")
|
||||
q.Add("stderr", "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
reader := ioutil.NopCloser(strings.NewReader("INFO Testing logs..."))
|
||||
reader := io.NopCloser(strings.NewReader("OUTINFO Testing logs..."))
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, "").Return(reader, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, "", docker.STDALL).Return(reader, nil)
|
||||
|
||||
clients := map[string]docker.Client{
|
||||
"localhost": mockedClient,
|
||||
@@ -47,13 +48,15 @@ func Test_handler_streamLogs_happy_with_id(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
q.Add("stdout", "true")
|
||||
q.Add("stderr", "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
reader := ioutil.NopCloser(strings.NewReader("2020-05-13T18:55:37.772853839Z INFO Testing logs..."))
|
||||
reader := io.NopCloser(strings.NewReader("OUT2020-05-13T18:55:37.772853839Z INFO Testing logs..."))
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, "").Return(reader, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, "", docker.STDALL).Return(reader, nil)
|
||||
|
||||
clients := map[string]docker.Client{
|
||||
"localhost": mockedClient,
|
||||
@@ -71,12 +74,14 @@ func Test_handler_streamLogs_happy_container_stopped(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
q.Add("stdout", "true")
|
||||
q.Add("stderr", "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, id, "").Return(ioutil.NopCloser(strings.NewReader("")), io.EOF)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, id, "", docker.STDALL).Return(io.NopCloser(strings.NewReader("")), io.EOF)
|
||||
|
||||
clients := map[string]docker.Client{
|
||||
"localhost": mockedClient,
|
||||
@@ -94,6 +99,8 @@ func Test_handler_streamLogs_error_finding_container(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
q.Add("stdout", "true")
|
||||
q.Add("stderr", "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
@@ -116,12 +123,35 @@ func Test_handler_streamLogs_error_reading(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
q.Add("stdout", "true")
|
||||
q.Add("stderr", "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, id, "").Return(ioutil.NopCloser(strings.NewReader("")), errors.New("test error"))
|
||||
mockedClient.On("ContainerLogs", mock.Anything, id, "", docker.STDALL).Return(io.NopCloser(strings.NewReader("")), errors.New("test error"))
|
||||
|
||||
clients := map[string]docker.Client{
|
||||
"localhost": mockedClient,
|
||||
}
|
||||
h := handler{clients: clients, config: &Config{}}
|
||||
handler := http.HandlerFunc(h.streamLogs)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_streamLogs_error_std(t *testing.T) {
|
||||
id := "123456"
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
|
||||
clients := map[string]docker.Client{
|
||||
"localhost": mockedClient,
|
||||
@@ -232,11 +262,13 @@ func Test_handler_between_dates(t *testing.T) {
|
||||
q.Add("from", from.Format(time.RFC3339))
|
||||
q.Add("to", to.Format(time.RFC3339))
|
||||
q.Add("id", "123456")
|
||||
q.Add("stdout", "true")
|
||||
q.Add("stderr", "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
reader := ioutil.NopCloser(strings.NewReader("2020-05-13T18:55:37.772853839Z INFO Testing logs...\n2020-05-13T18:55:37.772853839Z INFO Testing logs...\n"))
|
||||
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, "123456", from, to).Return(reader, nil)
|
||||
reader := io.NopCloser(strings.NewReader("OUT2020-05-13T18:55:37.772853839Z INFO Testing logs...\nERR2020-05-13T18:55:37.772853839Z INFO Testing logs...\n"))
|
||||
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, "123456", from, to, docker.STDALL).Return(reader, nil)
|
||||
|
||||
clients := map[string]docker.Client{
|
||||
"localhost": mockedClient,
|
||||
|
||||
@@ -31,8 +31,8 @@ func (m *MockedClient) ListContainers() ([]docker.Container, error) {
|
||||
return args.Get(0).([]docker.Container), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since string) (io.ReadCloser, error) {
|
||||
args := m.Called(ctx, id, since)
|
||||
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since string, stdType docker.StdType) (io.ReadCloser, error) {
|
||||
args := m.Called(ctx, id, since, stdType)
|
||||
return args.Get(0).(io.ReadCloser), args.Error(1)
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ func (m *MockedClient) ContainerStats(context.Context, string, chan<- docker.Con
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time) (io.ReadCloser, error) {
|
||||
args := m.Called(ctx, id, from, to)
|
||||
func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType docker.StdType) (io.ReadCloser, error) {
|
||||
args := m.Called(ctx, id, from, to, stdType)
|
||||
return args.Get(0).(io.ReadCloser), args.Error(1)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user