1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 21:33:18 +01:00

Trims top events depending on scroll status (#1860)

* Trims top events depending on scroll status

* Cleans up models and adds better OOP around logging events

* Adds log entries being skipped component

* Fixes type errors

* Fixes tets

* Styles skipping logs
This commit is contained in:
Amir Raminfar
2022-09-13 14:28:53 -07:00
committed by GitHub
parent 2b82a0816c
commit 8de492d16b
21 changed files with 388 additions and 252 deletions

View File

@@ -10,18 +10,22 @@ declare module '@vue/runtime-core' {
CarbonCaretDown: typeof import('~icons/carbon/caret-down')['default']
CilColumns: typeof import('~icons/cil/columns')['default']
CilFindInPage: typeof import('~icons/cil/find-in-page')['default']
ContainerStat: typeof import('./components/ContainerStat.vue')['default']
ContainerTitle: typeof import('./components/ContainerTitle.vue')['default']
ComplexLogItem: typeof import('./components/LogViewer/ComplexLogItem.vue')['default']
ComplexPayload: typeof import('./components/LogViewer/ComplexPayload.vue')['default']
ContainerStat: typeof import('./components/LogViewer/ContainerStat.vue')['default']
ContainerTitle: typeof import('./components/LogViewer/ContainerTitle.vue')['default']
copy: typeof import('./components/LogViewer/DockerEventLogItem copy.vue')['default']
DockerEventLogItem: typeof import('./components/LogViewer/DockerEventLogItem.vue')['default']
DropdownMenu: typeof import('./components/DropdownMenu.vue')['default']
FieldList: typeof import('./components/FieldList.vue')['default']
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
JSONPayload: typeof import('./components/JSONPayload.vue')['default']
JSONPayload: typeof import('./components/LogViewer/JSONPayload.vue')['default']
LogActionsToolbar: typeof import('./components/LogActionsToolbar.vue')['default']
LogContainer: typeof import('./components/LogContainer.vue')['default']
LogEventSource: typeof import('./components/LogEventSource.vue')['default']
LogViewer: typeof import('./components/LogViewer.vue')['default']
LogViewerWithSource: typeof import('./components/LogViewerWithSource.vue')['default']
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']
LogEventSource: typeof import('./components/LogViewer/LogEventSource.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiLightChevronDoubleDown: typeof import('~icons/mdi-light/chevron-double-down')['default']
MdiLightChevronLeft: typeof import('~icons/mdi-light/chevron-left')['default']
@@ -40,5 +44,8 @@ declare module '@vue/runtime-core' {
ScrollProgress: typeof import('./components/ScrollProgress.vue')['default']
Search: typeof import('./components/Search.vue')['default']
SideMenu: typeof import('./components/SideMenu.vue')['default']
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default']
StringPayload: typeof import('./components/LogViewer/StringPayload.vue')['default']
}
}

View File

@@ -7,27 +7,19 @@
</template>
</li>
</ul>
<field-list :fields="logEntry.unfilteredPayload" :expanded="expanded" :visible-keys="visibleKeys"></field-list>
<field-list :fields="logEntry.unfilteredMessage" :expanded="expanded" :visible-keys="visibleKeys"></field-list>
</template>
<script lang="ts" setup>
import { type VisibleLogEntry } from "@/types/VisibleLogEntry";
import { PropType, ref } from "vue";
import { type ComplexLogEntry } from "@/models/LogEntry";
const { markSearch } = useSearchFilter();
defineProps({
logEntry: {
type: Object as PropType<VisibleLogEntry>,
required: true,
},
visibleKeys: {
type: Array as PropType<string[][]>,
default: [],
},
});
defineProps<{
logEntry: ComplexLogEntry;
visibleKeys: string[][];
}>();
const expanded = ref(false);
let expanded = $ref(false);
</script>
<style lang="scss" scoped>

View File

@@ -0,0 +1,27 @@
<template>
<span class="text" :data-event="logEntry.event" v-html="logEntry.message"></span>
</template>
<script lang="ts" setup>
import { DockerEventLogEntry } from "@/models/LogEntry";
defineProps<{
logEntry: DockerEventLogEntry;
}>();
</script>
<style lang="scss" scoped>
span {
&[data-event="container-stopped"] {
color: #f14668;
}
&[data-event="container-started"] {
color: hsl(141, 53%, 53%);
}
&.text {
white-space: pre-wrap;
&::before {
content: " ";
}
}
}
</style>

View File

@@ -4,7 +4,7 @@ import { createTestingPinia } from "@pinia/testing";
import EventSource, { sources } from "eventsourcemock";
import LogEventSource from "./LogEventSource.vue";
import LogViewer from "./LogViewer.vue";
import { settings } from "../composables/settings";
import { settings } from "../../composables/settings";
import { useSearchFilter } from "@/composables/search";
import { vi, describe, expect, beforeEach, test, beforeAll, afterAll, afterEach } from "vitest";
import { computed, nextTick } from "vue";
@@ -68,6 +68,7 @@ describe("<LogEventSource />", () => {
},
provide: {
container: computed(() => ({ id: "abc", image: "test:v123" })),
scrollingPaused: computed(() => false),
},
},
slots: {

View File

@@ -7,7 +7,10 @@
import { type Container } from "@/types/Container";
import { type ComputedRef } from "vue";
const emit = defineEmits(["loading-more"]);
const emit = defineEmits<{
(e: "loading-more", value: boolean): void;
}>();
const container = inject("container") as ComputedRef<Container>;
const { connect, messages, loadOlderLogs } = useLogStream(container);

View File

@@ -4,7 +4,6 @@
v-for="(item, index) in filtered"
:key="item.id"
:data-key="item.id"
:data-event="item.event"
:class="{ selected: toRaw(item) === toRaw(lastSelectedItem) }"
>
<div class="line-options" v-show="isSearching()">
@@ -25,45 +24,35 @@
</div>
<div class="line">
<span class="date" v-if="showTimestamp"> <relative-time :date="item.date"></relative-time></span>
<JSONPayload :log-entry="item" :visible-keys="visibleKeys.value" v-if="item.isComplex()"></JSONPayload>
<span class="text" v-html="colorize(item.message)" v-if="item.isSimple()"></span>
<component :is="item.getComponent()" :log-entry="item" :visible-keys="visibleKeys.value"></component>
</div>
</li>
</ul>
</template>
<script lang="ts" setup>
import { type ComputedRef, type PropType, toRaw } from "vue";
import { type ComputedRef, toRaw } from "vue";
import { useRouteHash } from "@vueuse/router";
import { size, showTimestamp, softWrap } from "@/composables/settings";
import { type VisibleLogEntry } from "@/types/VisibleLogEntry";
import { type LogEntry } from "@/types/LogEntry";
import { type Container } from "@/types/Container";
import AnsiConvertor from "ansi-to-html";
import { type JSONObject, type LogEntry } from "@/models/LogEntry";
const props = defineProps({
messages: {
type: Array as PropType<LogEntry[]>,
required: true,
},
});
const ansiConvertor = new AnsiConvertor({ escapeXML: true });
const colorize = (value: string) => markSearch(ansiConvertor.toHtml(value));
const props = defineProps<{
messages: LogEntry<string | JSONObject>[];
}>();
const { messages } = toRefs(props);
let visibleKeys = persistentVisibleKeys(inject("container") as ComputedRef<Container>);
const { filteredPayload } = useVisibleFilter(visibleKeys);
const { filteredMessages, resetSearch, markSearch, isSearching } = useSearchFilter();
const { filteredMessages, resetSearch, isSearching } = useSearchFilter();
const visible = filteredPayload(messages);
const filtered = filteredMessages(visible);
const events = ref<HTMLElement>();
let lastSelectedItem = ref<VisibleLogEntry>();
let lastSelectedItem = ref<LogEntry<string | JSONObject>>();
function handleJumpLineSelected(e: Event, item: VisibleLogEntry) {
function handleJumpLineSelected(e: Event, item: LogEntry<string | JSONObject>) {
lastSelectedItem.value = item;
resetSearch();
}
@@ -83,19 +72,11 @@ watch(
font-family: SFMono-Regular, Consolas, Liberation Mono, monaco, Menlo, monospace;
&.disable-wrap {
.line,
.text {
.line {
white-space: nowrap;
}
}
.text {
white-space: pre-wrap;
&::before {
content: " ";
}
}
& > li {
display: flex;
word-wrap: break-word;
@@ -107,12 +88,7 @@ watch(
&:nth-child(odd) {
background-color: rgba(125, 125, 125, 0.08);
}
&[data-event="container-stopped"] {
color: #f14668;
}
&[data-event="container-started"] {
color: hsl(141, 53%, 53%);
}
&.selected .date {
background-color: var(--menu-item-active-background-color);
@@ -124,6 +100,7 @@ watch(
& > .line {
margin: auto 0;
width: 100%;
display: flex;
}
& > .line-options {
display: flex;

View File

@@ -7,7 +7,9 @@
<script lang="ts" setup>
import LogEventSource from "./LogEventSource.vue";
const emit = defineEmits(["loading-more"]);
const emit = defineEmits<{
(e: "loading-more", value: boolean): void;
}>();
const source = $ref<InstanceType<typeof LogEventSource>>();
function clear() {

View File

@@ -0,0 +1,30 @@
<template>
<span class="text" v-html="colorize(logEntry.message)"></span>
</template>
<script lang="ts" setup>
import { SimpleLogEntry } from "@/models/LogEntry";
import AnsiConvertor from "ansi-to-html";
const ansiConvertor = new AnsiConvertor({ escapeXML: true });
defineProps<{
logEntry: SimpleLogEntry;
}>();
const { markSearch } = useSearchFilter();
const colorize = (value: string) => markSearch(ansiConvertor.toHtml(value));
</script>
<style lang="scss" scoped>
.disable-wrap {
.text {
white-space: nowrap;
}
}
.text {
white-space: pre-wrap;
&::before {
content: " ";
}
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
<span class="text">{{ logEntry.message }}</span>
</template>
<script lang="ts" setup>
import { DockerEventLogEntry } from "@/models/LogEntry";
defineProps<{
logEntry: DockerEventLogEntry;
}>();
</script>
<style lang="scss" scoped>
span {
&.text {
flex-grow: 1;
text-align: center;
font-weight: bold;
white-space: pre-wrap;
&::before {
content: " ";
}
}
}
</style>

View File

@@ -1,181 +1,169 @@
// Vitest Snapshot v1
exports[`<LogEventSource /> > render html correctly > should render dates with 12 hour style 1`] = `
"<ul class=\\"events medium\\" data-v-cce5b553=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-cce5b553=\\"\\">
<div class=\\"line-options\\" data-v-cce5b553=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-cce5b553=\\"\\">
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-cce5b553=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-cce5b553=\\"\\">
<div class=\\"level-left\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-cce5b553=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\">Jump to Context</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"line\\" data-v-cce5b553=\\"\\"><span class=\\"date\\" data-v-cce5b553=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-cce5b553=\\"\\">06/12/2019 10:55:42 AM</time></span>
<!--v-if--><span class=\\"text\\" data-v-cce5b553=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span></div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render dates with 24 hour style 1`] = `
"<ul class=\\"events medium\\" data-v-cce5b553=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-cce5b553=\\"\\">
<div class=\\"line-options\\" data-v-cce5b553=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-cce5b553=\\"\\">
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-cce5b553=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-cce5b553=\\"\\">
<div class=\\"level-left\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-cce5b553=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\">Jump to Context</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"line\\" data-v-cce5b553=\\"\\"><span class=\\"date\\" data-v-cce5b553=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-cce5b553=\\"\\">06/12/2019 10:55:42</time></span>
<!--v-if--><span class=\\"text\\" data-v-cce5b553=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span></div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render messages 1`] = `
"<ul class=\\"events medium\\" data-v-cce5b553=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-cce5b553=\\"\\">
<div class=\\"line-options\\" data-v-cce5b553=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-cce5b553=\\"\\">
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-cce5b553=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-cce5b553=\\"\\">
<div class=\\"level-left\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-cce5b553=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\">Jump to Context</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"line\\" data-v-cce5b553=\\"\\"><span class=\\"date\\" data-v-cce5b553=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-cce5b553=\\"\\">06/12/2019 10:55:42 AM</time></span>
<!--v-if--><span class=\\"text\\" data-v-cce5b553=\\"\\">This is a message.</span>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">This is a message.</span></div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render messages with color 1`] = `
"<ul class=\\"events medium\\" data-v-cce5b553=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-cce5b553=\\"\\">
<div class=\\"line-options\\" data-v-cce5b553=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-cce5b553=\\"\\">
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-cce5b553=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-cce5b553=\\"\\">
<div class=\\"level-left\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-cce5b553=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\">Jump to Context</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"line\\" data-v-cce5b553=\\"\\"><span class=\\"date\\" data-v-cce5b553=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-cce5b553=\\"\\">06/12/2019 10:55:42 AM</time></span>
<!--v-if--><span class=\\"text\\" data-v-cce5b553=\\"\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></span>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></span></div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render messages with filter 1`] = `
"<ul class=\\"events medium\\" data-v-cce5b553=\\"\\">
<li data-key=\\"2\\" class=\\"\\" data-v-cce5b553=\\"\\">
<div class=\\"line-options\\" data-v-cce5b553=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-cce5b553=\\"\\">
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"2\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#2\\" data-v-cce5b553=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-cce5b553=\\"\\">
<div class=\\"level-left\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-cce5b553=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#2\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\">Jump to Context</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"line\\" data-v-cce5b553=\\"\\"><span class=\\"date\\" data-v-cce5b553=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-cce5b553=\\"\\">06/12/2019 10:55:42 AM</time></span>
<!--v-if--><span class=\\"text\\" data-v-cce5b553=\\"\\"><mark>test</mark> bar</span>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\"><mark>test</mark> bar</span></div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render messages with html entities 1`] = `
"<ul class=\\"events medium\\" data-v-cce5b553=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-cce5b553=\\"\\">
<div class=\\"line-options\\" data-v-cce5b553=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-cce5b553=\\"\\">
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-cce5b553=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-cce5b553=\\"\\">
<div class=\\"level-left\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-cce5b553=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-cce5b553=\\"\\">
<div class=\\"level-item\\" data-v-cce5b553=\\"\\">Jump to Context</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"line\\" data-v-cce5b553=\\"\\"><span class=\\"date\\" data-v-cce5b553=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-cce5b553=\\"\\">06/12/2019 10:55:42 AM</time></span>
<!--v-if--><span class=\\"text\\" data-v-cce5b553=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span></div>
</li>
</ul>"
`;
@@ -188,13 +176,13 @@ exports[`<LogEventSource /> > renders correctly 1`] = `
<div class=\\"bounce3\\" data-v-1cd63c6e=\\"\\"></div>
</div>
</div>
<ul class=\\"events medium\\" data-v-cce5b553=\\"\\"></ul>"
<ul class=\\"events medium\\" data-v-2e92daca=\\"\\"></ul>"
`;
exports[`<LogEventSource /> > should parse messages 1`] = `
{
SimpleLogEntry {
"_message": "This is a message.",
"date": 2019-06-12T10:55:42.459Z,
"id": 1,
"message": "This is a message.",
}
`;

View File

@@ -40,6 +40,8 @@ const loading = ref(false);
const scrollObserver = ref<HTMLElement>();
const scrollableContent = ref<HTMLElement>();
provide("scrollingPaused", paused);
const mutationObserver = new MutationObserver((e) => {
if (!paused.value) {
scrollToBottom();

View File

@@ -1,23 +1,48 @@
import { onUnmounted, ComputedRef } from "vue";
import { type ComputedRef, type Ref } from "vue";
import debounce from "lodash.debounce";
import type { LogEntry, LogEvent } from "@/types/LogEntry";
import {
type LogEvent,
type JSONObject,
LogEntry,
asLogEntry,
DockerEventLogEntry,
SkippedLogsEntry,
} from "@/models/LogEntry";
import { type Container } from "@/types/Container";
function parseMessage(data: string): LogEntry {
function parseMessage(data: string): LogEntry<string | JSONObject> {
const e = JSON.parse(data) as LogEvent;
const id = e.id;
const date = new Date(e.ts);
return { id, date, message: e.m };
return asLogEntry(e);
}
export function useLogStream(container: ComputedRef<Container>) {
const messages = ref<LogEntry[]>([]);
const buffer = ref<LogEntry[]>([]);
let messages = $ref<LogEntry<string | JSONObject>[]>([]);
let buffer = $ref<LogEntry<string | JSONObject>[]>([]);
const scrollingPaused = $ref(inject("scrollingPaused") as Ref<boolean>);
function flushNow() {
messages.value.push(...buffer.value);
buffer.value = [];
if (messages.length > config.maxLogs) {
if (scrollingPaused) {
console.log("Skipping ", buffer.length, " log items");
if (messages.at(-1) instanceof SkippedLogsEntry) {
const lastEvent = messages.at(-1) as SkippedLogsEntry;
const lastItem = buffer.at(-1) as LogEntry<string | JSONObject>;
lastEvent.addSkippedEntries(buffer.length, lastItem);
} else {
const firstItem = buffer.at(0) as LogEntry<string | JSONObject>;
const lastItem = buffer.at(-1) as LogEntry<string | JSONObject>;
messages.push(new SkippedLogsEntry(new Date(), buffer.length, firstItem, lastItem));
}
buffer = [];
} else {
messages.push(...buffer);
buffer = [];
messages.splice(0, messages.length - config.maxLogs);
}
} else {
messages.push(...buffer);
buffer = [];
}
}
const flushBuffer = debounce(flushNow, 250, { maxWait: 1000 });
let es: EventSource | null = null;
@@ -28,8 +53,8 @@ export function useLogStream(container: ComputedRef<Container>) {
if (clear) {
flushBuffer.cancel();
messages.value = [];
buffer.value = [];
messages = [];
buffer = [];
lastEventId = "";
}
@@ -37,12 +62,8 @@ export function useLogStream(container: ComputedRef<Container>) {
es.addEventListener("container-stopped", () => {
es?.close();
es = null;
buffer.value.push({
event: "container-stopped",
message: "Container stopped",
date: new Date(),
id: new Date().getTime(),
});
buffer.push(new DockerEventLogEntry("Container stopped", new Date(), "container-stopped"));
flushBuffer();
flushBuffer.flush();
});
@@ -50,18 +71,18 @@ export function useLogStream(container: ComputedRef<Container>) {
es.onmessage = (e) => {
lastEventId = e.lastEventId;
if (e.data) {
buffer.value.push(parseMessage(e.data));
buffer.push(parseMessage(e.data));
flushBuffer();
}
};
}
async function loadOlderLogs({ beforeLoading, afterLoading } = { beforeLoading: () => {}, afterLoading: () => {} }) {
if (messages.value.length < 300) return;
if (messages.length < 300) return;
beforeLoading();
const to = messages.value[0].date;
const last = messages.value[299].date;
const to = messages[0].date;
const last = messages[299].date;
const delta = to.getTime() - last.getTime();
const from = new Date(to.getTime() + delta);
const logs = await (
@@ -72,7 +93,7 @@ export function useLogStream(container: ComputedRef<Container>) {
.trim()
.split("\n")
.map((line) => parseMessage(line));
messages.value.unshift(...newMessages);
messages.unshift(...newMessages);
}
afterLoading();
}
@@ -82,12 +103,7 @@ export function useLogStream(container: ComputedRef<Container>) {
(newValue, oldValue) => {
console.log("LogEventSource: container changed", newValue, oldValue);
if (newValue == "running" && newValue != oldValue) {
buffer.value.push({
event: "container-started",
message: "Container started",
date: new Date(),
id: new Date().getTime(),
});
buffer.push(new DockerEventLogEntry("Container started", new Date(), "container-started"));
connect({ clear: false });
}
}
@@ -104,5 +120,5 @@ export function useLogStream(container: ComputedRef<Container>) {
() => connect()
);
return { connect, messages, loadOlderLogs };
return $$({ connect, messages, loadOlderLogs });
}

View File

@@ -1,5 +1,5 @@
import { type Ref } from "vue";
import { type VisibleLogEntry } from "@/types/VisibleLogEntry";
import { type LogEntry, type JSONObject, SimpleLogEntry, ComplexLogEntry } from "@/models/LogEntry";
const searchFilter = ref<string>("");
const debouncedSearchFilter = useDebounce(searchFilter);
@@ -24,14 +24,14 @@ export function useSearchFilter() {
return isSmartCase ? new RegExp(debouncedSearchFilter.value, "i") : new RegExp(debouncedSearchFilter.value);
});
function filteredMessages(messages: Ref<VisibleLogEntry[]>) {
function filteredMessages(messages: Ref<LogEntry<string | JSONObject>[]>) {
return computed(() => {
if (debouncedSearchFilter.value) {
try {
return messages.value.filter((d) => {
if (d.isSimple()) {
if (d instanceof SimpleLogEntry) {
return regex.value.test(d.message);
} else if (d.isComplex()) {
} else if (d instanceof ComplexLogEntry) {
return matchRecord(d.message, regex.value);
}
throw new Error("Unknown message type");

View File

@@ -1,11 +1,16 @@
import { type LogEntry } from "@/types/LogEntry";
import { VisibleLogEntry } from "@/types/VisibleLogEntry";
import { ComplexLogEntry, type JSONObject, type LogEntry } from "@/models/LogEntry";
import type { ComputedRef, Ref } from "vue";
export function useVisibleFilter(visibleKeys: ComputedRef<Ref<string[][]>>) {
function filteredPayload(messages: Ref<LogEntry[]>) {
function filteredPayload(messages: Ref<LogEntry<string | JSONObject>[]>) {
return computed(() => {
return messages.value.map((d) => new VisibleLogEntry(d, visibleKeys.value));
return messages.value.map((d) => {
if (d instanceof ComplexLogEntry) {
return ComplexLogEntry.fromLogEvent(d, visibleKeys.value);
} else {
return d;
}
});
});
}

117
assets/models/LogEntry.ts Normal file
View File

@@ -0,0 +1,117 @@
import { Component, ComputedRef, Ref } from "vue";
import { flattenJSON, getDeep } from "@/utils";
import ComplexLogItem from "@/components/LogViewer/ComplexLogItem.vue";
import SimpleLogItem from "@/components/LogViewer/SimpleLogItem.vue";
import DockerEventLogItem from "@/components/LogViewer/DockerEventLogItem.vue";
import SkippedEntriesLogItem from "@/components/LogViewer/SkippedEntriesLogItem.vue";
export interface HasComponent {
getComponent(): Component;
}
export type JSONValue = string | number | boolean | JSONObject | Array<JSONValue>;
export type JSONObject = { [x: string]: JSONValue };
export interface LogEvent {
readonly m: string | JSONObject;
readonly ts: number;
readonly id: 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) {
this._message = message;
}
public get message(): T {
return this._message;
}
abstract getComponent(): Component;
}
export class SimpleLogEntry extends LogEntry<string> {
getComponent(): Component {
return SimpleLogItem;
}
}
export class ComplexLogEntry extends LogEntry<JSONObject> {
private readonly filteredMessage: ComputedRef<JSONObject>;
constructor(message: JSONObject, id: number, date: Date, visibleKeys?: Ref<string[][]>) {
super(message, id, date);
if (visibleKeys) {
this.filteredMessage = computed(() => {
if (!visibleKeys.value.length) {
return flattenJSON(message);
} else {
return visibleKeys.value.reduce((acc, attr) => ({ ...acc, [attr.join(".")]: getDeep(message, attr) }), {});
}
});
} else {
this.filteredMessage = computed(() => flattenJSON(message));
}
}
getComponent(): Component {
return ComplexLogItem;
}
public get message(): JSONObject {
return this.filteredMessage.value;
}
public get unfilteredMessage(): JSONObject {
return this._message;
}
static fromLogEvent(event: ComplexLogEntry, visibleKeys: Ref<string[][]>): ComplexLogEntry {
return new ComplexLogEntry(event._message, event.id, event.date, visibleKeys);
}
}
export class DockerEventLogEntry extends LogEntry<string> {
constructor(message: string, date: Date, public readonly event: string) {
super(message, date.getTime(), date);
}
getComponent(): Component {
return DockerEventLogItem;
}
}
export class SkippedLogsEntry extends LogEntry<string> {
private totalSkipped = 0;
private lastSkipped: LogEntry<string | JSONObject>;
constructor(
date: Date,
totalSkipped: number,
public readonly firstSkipped: LogEntry<string | JSONObject>,
lastSkipped: LogEntry<string | JSONObject>
) {
super("", date.getTime(), date);
this.totalSkipped = totalSkipped;
this.lastSkipped = lastSkipped;
}
getComponent(): Component {
return SkippedEntriesLogItem;
}
public get message(): string {
return `Skipped ${this.totalSkipped} entries`;
}
public addSkippedEntries(totalSkipped: number, lastItem: LogEntry<string | JSONObject>) {
this.totalSkipped += totalSkipped;
this.lastSkipped = lastItem;
}
}
export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
if (typeof event.m === "string") {
return new SimpleLogEntry(event.m, event.id, new Date(event.ts));
} else {
return new ComplexLogEntry(event.m, event.id, new Date(event.ts));
}
}

View File

@@ -1,6 +1,20 @@
const text = document.querySelector("script#config__json")?.textContent || "{}";
const config = JSON.parse(text);
interface Config {
version: string;
base: string;
authorizationNeeded: boolean | "false" | "true";
secured: boolean | "false" | "true";
maxLogs: number;
}
const pageConfig = JSON.parse(text);
const config: Config = {
maxLogs: 600,
...pageConfig,
};
if (config.version == "{{ .Version }}") {
config.version = "master";
config.base = "";
@@ -11,4 +25,5 @@ if (config.version == "{{ .Version }}") {
config.authorizationNeeded = config.authorizationNeeded === "true";
config.secured = config.secured === "true";
}
export default config;
export default config as Config;

View File

@@ -1,16 +0,0 @@
export interface LogEntry {
readonly date: Date;
readonly message: string | JSONObject;
readonly id: number;
event?: string;
selected?: boolean;
}
export interface LogEvent {
readonly m: string | JSONObject;
readonly ts: number;
readonly id: number;
}
export type JSONValue = string | number | boolean | JSONObject | Array<JSONValue>;
export type JSONObject = { [x: string]: JSONValue };

View File

@@ -1,54 +0,0 @@
import { computed, ComputedRef, Ref } from "vue";
import { flattenJSON, getDeep } from "@/utils";
import type { JSONObject, LogEntry } from "./LogEntry";
export class VisibleLogEntry implements LogEntry {
private readonly entry: LogEntry;
filteredMessage: undefined | ComputedRef<Record<string, any>>;
constructor(entry: LogEntry, visibleKeys: Ref<string[][]>) {
this.entry = entry;
this.filteredMessage = undefined;
if (this.isComplex()) {
const message = this.message;
this.filteredMessage = computed(() => {
if (!visibleKeys.value.length) {
return flattenJSON(message);
} else {
return visibleKeys.value.reduce((acc, attr) => ({ ...acc, [attr.join(".")]: getDeep(message, attr) }), {});
}
});
}
}
public isComplex(): this is { message: JSONObject } {
return typeof this.entry.message === "object";
}
public isSimple(): this is { message: string } {
return !this.isComplex();
}
public get unfilteredPayload(): JSONObject {
if (typeof this.entry.message === "string") {
throw new Error("Cannot get unfiltered payload of a simple message");
}
return this.entry.message;
}
public get date(): Date {
return this.entry.date;
}
public get message(): string | JSONObject {
return this.filteredMessage?.value ?? this.entry.message;
}
public get id(): number {
return this.entry.id;
}
public get event(): string | undefined {
return this.entry.event;
}
}