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

feat: adds preview for settings (#2669)

This commit is contained in:
Amir Raminfar
2024-01-08 12:21:37 -08:00
committed by GitHub
parent 27fe0663cf
commit 72a580574a
10 changed files with 164 additions and 87 deletions

View File

@@ -26,6 +26,7 @@ declare module 'vue' {
'Cil:xCircle': typeof import('~icons/cil/x-circle')['default']
ComplexLogItem: typeof import('./components/LogViewer/ComplexLogItem.vue')['default']
ContainerHealth: typeof import('./components/LogViewer/ContainerHealth.vue')['default']
ContainerLogViewer: typeof import('./components/LogViewer/ContainerLogViewer.vue')['default']
ContainerPopup: typeof import('./components/LogViewer/ContainerPopup.vue')['default']
ContainerStat: typeof import('./components/LogViewer/ContainerStat.vue')['default']
ContainerTable: typeof import('./components/ContainerTable.vue')['default']
@@ -40,6 +41,7 @@ declare module 'vue' {
'Ic:sharpKeyboardReturn': typeof import('~icons/ic/sharp-keyboard-return')['default']
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
KeyShortcut: typeof import('./components/common/KeyShortcut.vue')['default']
LabeledInput: typeof import('./components/common/LabeledInput.vue')['default']
Links: typeof import('./components/Links.vue')['default']
LogActionsToolbar: typeof import('./components/LogViewer/LogActionsToolbar.vue')['default']
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']

View File

@@ -0,0 +1,39 @@
<template>
<log-viewer :messages="filtered" :last-selected-item="lastSelectedItem" :visible-keys="visibleKeys" />
</template>
<script lang="ts" setup>
import { useRouteHash } from "@vueuse/router";
import { type JSONObject, LogEntry } from "@/models/LogEntry";
const props = defineProps<{
messages: LogEntry<string | JSONObject>[];
}>();
const { container } = useContainerContext();
const visibleKeys = persistentVisibleKeys(container);
const { filteredPayload } = useVisibleFilter(visibleKeys);
const { filteredMessages } = useSearchFilter();
const { messages } = toRefs(props);
const visible = filteredPayload(messages);
const filtered = filteredMessages(visible);
const { lastSelectedItem } = useLogSearchContext() as {
lastSelectedItem: Ref<LogEntry<string | JSONObject> | undefined>;
};
const routeHash = useRouteHash();
watch(
routeHash,
(hash) => {
if (hash) {
document.querySelector(`[data-key="${hash.substring(1)}"]`)?.scrollIntoView({ block: "center" });
}
},
{ immediate: true, flush: "post" },
);
</script>
<style scoped lang="postcss"></style>

View File

@@ -10,7 +10,7 @@ import { computed, nextTick } from "vue";
import { createI18n } from "vue-i18n";
import { createRouter, createWebHistory } from "vue-router";
import LogEventSource from "./LogEventSource.vue";
import LogViewer from "./LogViewer.vue";
import ContainerLogViewer from "./ContainerLogViewer.vue";
vi.mock("@/stores/config", () => ({
__esModule: true,
@@ -71,7 +71,7 @@ describe("<LogEventSource />", () => {
global: {
plugins: [router, createTestingPinia({ createSpy: vi.fn }), createI18n({})],
components: {
LogViewer,
ContainerLogViewer,
},
provide: {
[containerContext as symbol]: {
@@ -83,7 +83,7 @@ describe("<LogEventSource />", () => {
},
slots: {
default: `
<template #scoped="params"><log-viewer :messages="params.messages"></log-viewer></template>
<template #scoped="params"><container-log-viewer :messages="params.messages" /></template>
`,
},
props: {},

View File

@@ -1,7 +1,7 @@
<template>
<ul class="events group py-4" :class="{ 'disable-wrap': !softWrap, [size]: true }">
<li
v-for="item in filtered"
v-for="item in messages"
:key="item.id"
:data-key="item.id"
:class="{ 'border border-secondary': toRaw(item) === toRaw(lastSelectedItem) }"
@@ -14,36 +14,14 @@
<script lang="ts" setup>
import { toRaw } from "vue";
import { useRouteHash } from "@vueuse/router";
import { type JSONObject, LogEntry } from "@/models/LogEntry";
const props = defineProps<{
defineProps<{
messages: LogEntry<string | JSONObject>[];
visibleKeys: string[][];
lastSelectedItem: LogEntry<string | JSONObject> | undefined;
}>();
const { container } = useContainerContext();
const visibleKeys = persistentVisibleKeys(container);
const { filteredPayload } = useVisibleFilter(visibleKeys);
const { filteredMessages } = useSearchFilter();
const { messages } = toRefs(props);
const visible = filteredPayload(messages);
const filtered = filteredMessages(visible);
const { lastSelectedItem } = useLogSearchContext();
const routeHash = useRouteHash();
watch(
routeHash,
(hash) => {
if (hash) {
document.querySelector(`[data-key="${hash.substring(1)}"]`)?.scrollIntoView({ block: "center" });
}
},
{ immediate: true, flush: "post" },
);
</script>
<style scoped lang="postcss">
.events {

View File

@@ -1,6 +1,6 @@
<template>
<log-event-source ref="source" #default="{ messages }" @loading-more="loadingMore($event)">
<log-viewer :messages="messages"></log-viewer>
<container-log-viewer :messages="messages" />
</log-event-source>
</template>

View File

@@ -0,0 +1,10 @@
<template>
<label class="label cursor-pointer gap-4">
<div class="label-text"><slot name="label" /></div>
<slot name="input" />
</label>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@@ -1,8 +1,12 @@
<template>
<label class="label inline-flex cursor-pointer gap-4 font-normal">
<input type="checkbox" class="toggle toggle-primary" v-model="modelValue" />
<slot />
</label>
<labeled-input>
<template #label>
<slot />
</template>
<template #input>
<input type="checkbox" class="toggle toggle-primary" v-model="modelValue" />
</template>
</labeled-input>
</template>
<script lang="ts" setup>

View File

@@ -9,11 +9,12 @@ 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 type Level = "debug" | "info" | "warn" | "error" | "fatal" | "trace" | "unknown";
export interface LogEvent {
readonly m: string | JSONObject;
readonly ts: number;
readonly id: number;
readonly l: string;
readonly l: Level;
readonly p: Position;
readonly s: "stdout" | "stderr" | "unknown";
}
@@ -25,7 +26,7 @@ export abstract class LogEntry<T extends string | JSONObject> {
public readonly id: number,
public readonly date: Date,
public readonly std: Std,
public readonly level?: string,
public readonly level?: Level,
) {
this._message = message;
}
@@ -42,7 +43,7 @@ export class SimpleLogEntry extends LogEntry<string> {
message: string,
id: number,
date: Date,
public readonly level: string,
public readonly level: Level,
public readonly position: Position,
public readonly std: Std,
) {
@@ -60,7 +61,7 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
message: JSONObject,
id: number,
date: Date,
public readonly level: string,
public readonly level: Level,
public readonly std: Std,
visibleKeys?: Ref<string[][]>,
) {

View File

@@ -14,66 +14,85 @@
</div>
</section>
<section class="flex flex-col gap-4">
<section class="flex flex-col">
<div class="has-underline">
<h2>{{ $t("settings.display") }}</h2>
</div>
<div>
<toggle v-model="smallerScrollbars"> {{ $t("settings.small-scrollbars") }} </toggle>
</div>
<div>
<toggle v-model="showTimestamp">{{ $t("settings.show-timesamps") }}</toggle>
</div>
<div>
<toggle v-model="showStd">{{ $t("settings.show-std") }}</toggle>
</div>
<section class="grid-cols-2 gap-4 md:grid">
<div class="flex flex-col gap-2 text-balance md:pr-8">
<toggle v-model="smallerScrollbars"> {{ $t("settings.small-scrollbars") }} </toggle>
<div>
<toggle v-model="softWrap">{{ $t("settings.soft-wrap") }}</toggle>
</div>
<toggle v-model="showTimestamp">{{ $t("settings.show-timesamps") }}</toggle>
<div class="flex items-center gap-6">
<dropdown-menu
v-model="hourStyle"
:options="[
{ label: 'Auto', value: 'auto' },
{ label: '12', value: '12' },
{ label: '24', value: '24' },
]"
<toggle v-model="showStd">{{ $t("settings.show-std") }}</toggle>
<toggle v-model="softWrap">{{ $t("settings.soft-wrap") }}</toggle>
<labeled-input>
<template #label>
{{ $t("settings.12-24-format") }}
</template>
<template #input>
<dropdown-menu
v-model="hourStyle"
:options="[
{ label: 'Auto', value: 'auto' },
{ label: '12', value: '12' },
{ label: '24', value: '24' },
]"
/>
</template>
</labeled-input>
<labeled-input>
<template #label>
{{ $t("settings.font-size") }}
</template>
<template #input>
<dropdown-menu
v-model="size"
:options="[
{ label: 'Small', value: 'small' },
{ label: 'Medium', value: 'medium' },
{ label: 'Large', value: 'large' },
]"
/>
</template>
</labeled-input>
<labeled-input>
<template #label>
{{ $t("settings.color-scheme") }}
</template>
<template #input>
<dropdown-menu
v-model="lightTheme"
:options="[
{ label: 'Auto', value: 'auto' },
{ label: 'Dark', value: 'dark' },
{ label: 'Light', value: 'light' },
]"
/>
</template>
</labeled-input>
</div>
<log-viewer
:messages="fakeMessages"
:visible-keys="keys"
:last-selected-item="undefined"
class="mobile-hidden overflow-hidden rounded-lg border border-base-content/50 shadow"
/>
{{ $t("settings.12-24-format") }}
</div>
<div class="flex items-center gap-6">
<dropdown-menu
v-model="size"
:options="[
{ label: 'Small', value: 'small' },
{ label: 'Medium', value: 'medium' },
{ label: 'Large', value: 'large' },
]"
/>
{{ $t("settings.font-size") }}
</div>
<div class="flex items-center gap-6">
<dropdown-menu
v-model="lightTheme"
:options="[
{ label: 'Auto', value: 'auto' },
{ label: 'Dark', value: 'dark' },
{ label: 'Light', value: 'light' },
]"
/>
{{ $t("settings.color-scheme") }}
</div>
</section>
</section>
<section class="flex flex-col gap-2">
<div class="has-underline">
<h2>{{ $t("settings.options") }}</h2>
</div>
<div>
<toggle v-model="search">
<div>{{ $t("settings.search") }} <key-shortcut char="f" class="align-top"></key-shortcut></div>
{{ $t("settings.search") }} <key-shortcut char="f" class="align-top"></key-shortcut>
</toggle>
</div>
@@ -89,6 +108,8 @@
</template>
<script lang="ts" setup>
import { ComplexLogEntry, SimpleLogEntry } from "@/models/LogEntry";
import {
automaticRedirect,
hourStyle,
@@ -106,6 +127,30 @@ const { t } = useI18n();
setTitle(t("title.settings"));
const { latest, hasUpdate } = useReleases();
const keys = ref<string[][]>([]);
const fakeMessages = [
new SimpleLogEntry("This is a preview of the logs", 1, new Date(), "info", undefined, "stdout"),
new SimpleLogEntry("A warning log looks like this", 2, new Date(), "warn", undefined, "stdout"),
new SimpleLogEntry("This is a multi line error message", 3, new Date(), "error", "start", "stderr"),
new SimpleLogEntry("with a second line", 4, new Date(), "error", "middle", "stderr"),
new SimpleLogEntry("and finally third line.", 5, new Date(), "error", "end", "stderr"),
new ComplexLogEntry(
{
message: "This is a complex log entry",
context: {
key: "value",
key2: "value2",
},
},
6,
new Date(),
"info",
"stdout",
keys,
),
];
</script>
<style lang="postcss" scoped>
.has-underline {

View File

@@ -68,9 +68,7 @@ settings:
small-scrollbars: Use smaller scrollbars
show-timesamps: Show timestamps
soft-wrap: Soft wrap lines
12-24-format: >-
By default, Dozzle will use your browser's locale to format time. You can
force to 12 or 24 hour style.
12-24-format: Time format
font-size: Font size to use for logs
color-scheme: Color scheme
options: Options