1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-24 06:28:42 +01:00

Cleans up log viewer and adds zigzag line for skipped logs (#1885)

* Cleans up log viewer and adds zigzag line for skipped logs

* Updates components

* Cleans up css

* Cleans up more css

* Fixes tests

* Fixes typing

* Fixes typescript errors

* Fixes selected color
This commit is contained in:
Amir Raminfar
2022-09-20 13:14:31 -07:00
committed by GitHub
parent 1a1dd74142
commit 69c647336e
20 changed files with 162 additions and 131 deletions

View File

@@ -11,18 +11,16 @@ declare module '@vue/runtime-core' {
CilColumns: typeof import('~icons/cil/columns')['default']
CilFindInPage: typeof import('~icons/cil/find-in-page')['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/LogViewer/FieldList.vue')['default']
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
JSONPayload: typeof import('./components/LogViewer/JSONPayload.vue')['default']
LogActionsToolbar: typeof import('./components/LogViewer/LogActionsToolbar.vue')['default']
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']
LogDate: typeof import('./components/LogViewer/LogDate.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']
@@ -46,6 +44,6 @@ declare module '@vue/runtime-core' {
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']
ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default']
}
}

View File

@@ -1,14 +1,19 @@
<template>
<div>
<ul class="fields" @click="expanded = !expanded">
<li v-for="(value, name) in logEntry.message">
<template v-if="value">
<span class="has-text-grey">{{ name }}=</span>
<span class="has-text-weight-bold" v-html="markSearch(value)"></span>
</template>
</li>
</ul>
<field-list :fields="logEntry.unfilteredMessage" :expanded="expanded" :visible-keys="visibleKeys"></field-list>
<div class="columns is-1 is-variable">
<div class="column is-narrow" v-if="showTimestamp">
<log-date :date="logEntry.date"></log-date>
</div>
<div class="column">
<ul class="fields" @click="expanded = !expanded">
<li v-for="(value, name) in logEntry.message">
<template v-if="value">
<span class="has-text-grey">{{ name }}=</span>
<span class="has-text-weight-bold" v-html="markSearch(value)"></span>
</template>
</li>
</ul>
<field-list :fields="logEntry.unfilteredMessage" :expanded="expanded" :visible-keys="visibleKeys"></field-list>
</div>
</div>
</template>
<script lang="ts" setup>
@@ -42,7 +47,6 @@ let expanded = $ref(false);
li {
display: inline-block;
margin-left: 1em;
}
}
</style>

View File

@@ -19,9 +19,6 @@ span {
}
&.text {
white-space: pre-wrap;
&::before {
content: " ";
}
}
}
</style>

View File

@@ -0,0 +1,45 @@
<template>
<relative-time :date="date" class="date"></relative-time>
</template>
<script lang="ts" setup>
defineProps<{
date: Date;
}>();
</script>
<style lang="scss" scoped>
.date {
padding-left: 5px;
padding-right: 5px;
border-radius: 3px;
white-space: nowrap;
}
@media (prefers-color-scheme: dark) {
.date {
background-color: #262626;
color: #258ccd;
}
}
[data-theme="dark"] {
.date {
background-color: #262626;
color: #258ccd;
}
}
@media (prefers-color-scheme: light) {
.date {
background-color: #f0f0f0;
color: #009900;
}
}
[data-theme="light"] {
.date {
background-color: #f0f0f0;
color: #009900;
}
}
</style>

View File

@@ -6,7 +6,7 @@ import LogEventSource from "./LogEventSource.vue";
import LogViewer from "./LogViewer.vue";
import { settings } from "../../composables/settings";
import { useSearchFilter } from "@/composables/search";
import { vi, describe, expect, beforeEach, test, beforeAll, afterAll, afterEach } from "vitest";
import { vi, describe, expect, beforeEach, test, afterEach } from "vitest";
import { computed, nextTick } from "vue";
import { createRouter, createWebHistory } from "vue-router";
@@ -23,6 +23,7 @@ describe("<LogEventSource />", () => {
beforeEach(() => {
global.EventSource = EventSource;
// @ts-ignore
window.scrollTo = vi.fn();
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
@@ -112,6 +113,7 @@ describe("<LogEventSource />", () => {
vi.runAllTimers();
await nextTick();
// @ts-ignore
const [message, _] = wrapper.vm.messages;
expect(message).toMatchSnapshot();
});

View File

@@ -12,7 +12,7 @@ const emit = defineEmits<{
}>();
const container = inject("container") as ComputedRef<Container>;
const { connect, messages, loadOlderLogs } = useLogStream(container);
const { messages, loadOlderLogs } = useLogStream(container);
const beforeLoading = () => emit("loading-more", true);
const afterLoading = () => emit("loading-more", false);
@@ -22,6 +22,4 @@ defineExpose({
});
const fetchMore = () => loadOlderLogs({ beforeLoading, afterLoading });
connect();
</script>

View File

@@ -22,10 +22,7 @@
</a>
</dropdown-menu>
</div>
<div class="line">
<span class="date" v-if="showTimestamp"> <relative-time :date="item.date"></relative-time></span>
<component :is="item.getComponent()" :log-entry="item" :visible-keys="visibleKeys.value"></component>
</div>
<component :is="item.getComponent()" :log-entry="item" :visible-keys="visibleKeys.value"></component>
</li>
</ul>
</template>
@@ -34,7 +31,7 @@
import { type ComputedRef, toRaw } from "vue";
import { useRouteHash } from "@vueuse/router";
import { type Container } from "@/types/Container";
import { type JSONObject, type LogEntry } from "@/models/LogEntry";
import { type JSONObject, LogEntry } from "@/models/LogEntry";
const props = defineProps<{
messages: LogEntry<string | JSONObject>[];
@@ -50,10 +47,10 @@ const visible = filteredPayload(messages);
const filtered = filteredMessages(visible);
const events = ref<HTMLElement>();
let lastSelectedItem = ref<LogEntry<string | JSONObject>>();
let lastSelectedItem: LogEntry<string | JSONObject> | undefined = $ref(undefined);
function handleJumpLineSelected(e: Event, item: LogEntry<string | JSONObject>) {
lastSelectedItem.value = item;
lastSelectedItem = item;
resetSearch();
}
@@ -71,12 +68,6 @@ watch(
padding: 1em 0;
font-family: SFMono-Regular, Consolas, Liberation Mono, monaco, Menlo, monospace;
&.disable-wrap {
.line {
white-space: nowrap;
}
}
& > li {
display: flex;
word-wrap: break-word;
@@ -89,19 +80,10 @@ watch(
background-color: rgba(125, 125, 125, 0.08);
}
&.selected .date {
background-color: var(--menu-item-active-background-color);
&.selected {
border: 1px var(--secondary-color) solid;
}
color: var(--text-color);
}
&.selected > .date {
background-color: white;
}
& > .line {
margin: auto 0;
width: 100%;
display: flex;
}
& > .line-options {
display: flex;
flex-direction: row-reverse;
@@ -121,55 +103,4 @@ watch(
font-size: 120%;
}
}
@media (prefers-color-scheme: dark) {
.date {
background-color: #262626;
color: #258ccd;
}
}
[data-theme="dark"] {
.date {
background-color: #262626;
color: #258ccd;
}
}
@media (prefers-color-scheme: light) {
.date {
background-color: #f0f0f0;
color: #009900;
}
}
[data-theme="light"] {
.date {
background-color: #f0f0f0;
color: #009900;
}
}
.date {
padding-left: 5px;
padding-right: 5px;
border-radius: 3px;
white-space: nowrap;
}
:deep(mark) {
border-radius: 2px;
background-color: var(--secondary-color);
animation: pops 200ms ease-out;
display: inline-block;
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<span class="text" v-html="colorize(logEntry.message)"></span>
<div class="columns is-1 is-variable">
<div class="column is-narrow" v-if="showTimestamp">
<log-date :date="logEntry.date"></log-date>
</div>
<div class="text column" v-html="colorize(logEntry.message)"></div>
</div>
</template>
<script lang="ts" setup>
import { SimpleLogEntry } from "@/models/LogEntry";
@@ -23,8 +28,5 @@ const colorize = (value: string) => markSearch(ansiConvertor.toHtml(value));
.text {
white-space: pre-wrap;
&::before {
content: " ";
}
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<span class="text">{{ $t("error.logs-skipped", { total: logEntry.totalSkipped }) }}</span>
<div class="is-flex-grow-1 has-text-centered my-4">
<div class="is-relative">
<zig-zag class="is-overlay mt-2"></zig-zag>
<span class="text is-relative py-2 px-4">{{ $t("error.logs-skipped", { total: logEntry.totalSkipped }) }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { SkippedLogsEntry } from "@/models/LogEntry";
@@ -10,15 +15,9 @@ defineProps<{
</script>
<style lang="scss" scoped>
span {
&.text {
flex-grow: 1;
text-align: center;
font-weight: bold;
white-space: pre-wrap;
&::before {
content: " ";
}
}
.text {
font-weight: bold;
white-space: pre-wrap;
background-color: var(--body-background-color);
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<svg width="100%" height="8" class="zigzag">
<defs>
<pattern id="zigzag" x="0" y="0" width="30" height="8" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="15" y2="8" class="line" />
<line x1="15" y1="8" x2="30" y2="0" class="line" />
</pattern>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="url(#zigzag)"></rect>
</svg>
</template>
<style lang="scss" scoped>
.line {
stroke: var(--primary-color);
stroke-width: 1;
stroke-linecap: round;
}
</style>

View File

@@ -23,7 +23,10 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 1
</div>
</div>
</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>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;
@@ -51,7 +54,10 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 2
</div>
</div>
</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>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;
@@ -79,7 +85,10 @@ exports[`<LogEventSource /> > render html correctly > should render messages 1`]
</div>
</div>
</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>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">This is a message.</div>
</div>
</li>
</ul>"
`;
@@ -107,7 +116,10 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</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>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></div>
</div>
</li>
</ul>"
`;
@@ -135,7 +147,10 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</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>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\"><mark>test</mark> bar</div>
</div>
</li>
</ul>"
`;
@@ -163,7 +178,10 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</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>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;

View File

@@ -46,10 +46,10 @@ const store = useContainerStore();
const route = useRoute();
const { visibleContainers, allContainersById } = storeToRefs(store);
const showNav = ref(false);
let showNav = $ref(false);
watch(route, () => {
showNav.value = false;
showNav = false;
});
</script>
<style scoped lang="scss">

View File

@@ -16,8 +16,8 @@ function parseMessage(data: string): LogEntry<string | JSONObject> {
}
export function useLogStream(container: ComputedRef<Container>) {
let messages = $ref<LogEntry<string | JSONObject>[]>([]);
let buffer = $ref<LogEntry<string | JSONObject>[]>([]);
let messages: LogEntry<string | JSONObject>[] = $ref([]);
let buffer: LogEntry<string | JSONObject>[] = $ref([]);
const scrollingPaused = $ref(inject("scrollingPaused") as Ref<boolean>);
function flushNow() {
@@ -117,8 +117,9 @@ export function useLogStream(container: ComputedRef<Container>) {
watch(
() => container.value.id,
() => connect()
() => connect(),
{ immediate: true }
);
return $$({ connect, messages, loadOlderLogs });
return { ...$$({ messages }), loadOlderLogs };
}

View File

@@ -49,9 +49,9 @@ export function useSearchFilter() {
});
}
function markSearch(log: string): string;
function markSearch(log: { toString(): string }): string;
function markSearch(log: string[]): string[];
function markSearch(log: string | string[]) {
function markSearch(log: { toString(): string } | string[]) {
if (!debouncedSearchFilter.value) {
return log;
}

View File

@@ -42,6 +42,7 @@
</template>
<script lang="ts" setup>
// @ts-ignore - splitpanes types are not available
import { Splitpanes, Pane } from "splitpanes";
import { useProgrammatic } from "@oruga-ui/oruga-next";
import FuzzySearchModal from "@/components/FuzzySearchModal.vue";

View File

@@ -55,7 +55,7 @@
type="text"
:placeholder="$t('placeholder.search-containers')"
v-model="search"
@keyup.esc="search = null"
@keyup.esc="search.value = null"
@keyup.enter="onEnter()"
/>
<span class="icon is-left">

View File

@@ -57,7 +57,7 @@ setTitle(t("title.login"));
let error = $ref(false);
let username = $ref("");
let password = $ref("");
let form = $ref();
let form: HTMLFormElement = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {

View File

@@ -55,7 +55,7 @@
type="text"
:placeholder="$t('placeholder.search-containers')"
v-model="search"
@keyup.esc="search = null"
@keyup.esc="search.value = null"
@keyup.enter="onEnter()"
/>
<span class="icon is-left">

View File

@@ -57,7 +57,7 @@ setTitle(t("title.login"));
let error = $ref(false);
let username = $ref("");
let password = $ref("");
let form = $ref();
let form: HTMLFormElement = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {

View File

@@ -176,3 +176,19 @@ html.has-custom-scrollbars {
.button .button-wrapper > span {
display: contents;
}
mark {
border-radius: 2px;
background-color: var(--secondary-color);
animation: pops 200ms ease-out;
display: inline-block;
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}