mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 21:33:18 +01:00
feat: adds a side panel for complex logs that enables reordering and enabling fields (#3237)
This commit is contained in:
11
assets/auto-imports.d.ts
vendored
11
assets/auto-imports.d.ts
vendored
@@ -50,6 +50,7 @@ declare global {
|
|||||||
const effectScope: typeof import('vue')['effectScope']
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||||
const flattenJSON: typeof import('./utils/index')['flattenJSON']
|
const flattenJSON: typeof import('./utils/index')['flattenJSON']
|
||||||
|
const flattenJSONToMap: typeof import('./utils/index')['flattenJSONToMap']
|
||||||
const formatBytes: typeof import('./utils/index')['formatBytes']
|
const formatBytes: typeof import('./utils/index')['formatBytes']
|
||||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
@@ -104,6 +105,7 @@ declare global {
|
|||||||
const persistentVisibleKeysForContainer: typeof import('./composable/storage')['persistentVisibleKeysForContainer']
|
const persistentVisibleKeysForContainer: typeof import('./composable/storage')['persistentVisibleKeysForContainer']
|
||||||
const pinnedContainers: typeof import('./composable/storage')['pinnedContainers']
|
const pinnedContainers: typeof import('./composable/storage')['pinnedContainers']
|
||||||
const provide: typeof import('vue')['provide']
|
const provide: typeof import('vue')['provide']
|
||||||
|
const provideDetails: typeof import('./composable/showDetails')['provideDetails']
|
||||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||||
const provideLogDetails: typeof import('./composable/showLogDetails')['provideLogDetails']
|
const provideLogDetails: typeof import('./composable/showLogDetails')['provideLogDetails']
|
||||||
const provideLoggingContext: typeof import('./composable/logContext')['provideLoggingContext']
|
const provideLoggingContext: typeof import('./composable/logContext')['provideLoggingContext']
|
||||||
@@ -205,6 +207,7 @@ declare global {
|
|||||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||||
|
const useDetails: typeof import('./composable/showDetails')['useDetails']
|
||||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||||
@@ -413,6 +416,7 @@ declare module 'vue' {
|
|||||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||||
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
||||||
readonly flattenJSON: UnwrapRef<typeof import('./utils/index')['flattenJSON']>
|
readonly flattenJSON: UnwrapRef<typeof import('./utils/index')['flattenJSON']>
|
||||||
|
readonly flattenJSONToMap: UnwrapRef<typeof import('./utils/index')['flattenJSONToMap']>
|
||||||
readonly formatBytes: UnwrapRef<typeof import('./utils/index')['formatBytes']>
|
readonly formatBytes: UnwrapRef<typeof import('./utils/index')['formatBytes']>
|
||||||
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
@@ -468,6 +472,7 @@ declare module 'vue' {
|
|||||||
readonly pinnedContainers: UnwrapRef<typeof import('./composable/storage')['pinnedContainers']>
|
readonly pinnedContainers: UnwrapRef<typeof import('./composable/storage')['pinnedContainers']>
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
|
readonly provideLogDetails: UnwrapRef<typeof import('./composable/showLogDetails')['provideLogDetails']>
|
||||||
readonly provideLoggingContext: UnwrapRef<typeof import('./composable/logContext')['provideLoggingContext']>
|
readonly provideLoggingContext: UnwrapRef<typeof import('./composable/logContext')['provideLoggingContext']>
|
||||||
readonly provideScrollContext: UnwrapRef<typeof import('./composable/scrollContext')['provideScrollContext']>
|
readonly provideScrollContext: UnwrapRef<typeof import('./composable/scrollContext')['provideScrollContext']>
|
||||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||||
@@ -497,6 +502,7 @@ declare module 'vue' {
|
|||||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||||
readonly showAllContainers: UnwrapRef<typeof import('./stores/settings')['showAllContainers']>
|
readonly showAllContainers: UnwrapRef<typeof import('./stores/settings')['showAllContainers']>
|
||||||
|
readonly showLogDetails: UnwrapRef<typeof import('./composable/showLogDetails')['showLogDetails']>
|
||||||
readonly showStd: UnwrapRef<typeof import('./stores/settings')['showStd']>
|
readonly showStd: UnwrapRef<typeof import('./stores/settings')['showStd']>
|
||||||
readonly showTimestamp: UnwrapRef<typeof import('./stores/settings')['showTimestamp']>
|
readonly showTimestamp: UnwrapRef<typeof import('./stores/settings')['showTimestamp']>
|
||||||
readonly size: UnwrapRef<typeof import('./stores/settings')['size']>
|
readonly size: UnwrapRef<typeof import('./stores/settings')['size']>
|
||||||
@@ -607,6 +613,7 @@ declare module 'vue' {
|
|||||||
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
||||||
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
||||||
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
||||||
|
readonly useLogDetails: UnwrapRef<typeof import('./composable/showLogDetails')['useLogDetails']>
|
||||||
readonly useLogSearchContext: UnwrapRef<typeof import('./composable/logSearchContext')['useLogSearchContext']>
|
readonly useLogSearchContext: UnwrapRef<typeof import('./composable/logSearchContext')['useLogSearchContext']>
|
||||||
readonly useLoggingContext: UnwrapRef<typeof import('./composable/logContext')['useLoggingContext']>
|
readonly useLoggingContext: UnwrapRef<typeof import('./composable/logContext')['useLoggingContext']>
|
||||||
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
||||||
@@ -766,6 +773,7 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||||
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
||||||
readonly flattenJSON: UnwrapRef<typeof import('./utils/index')['flattenJSON']>
|
readonly flattenJSON: UnwrapRef<typeof import('./utils/index')['flattenJSON']>
|
||||||
|
readonly flattenJSONToMap: UnwrapRef<typeof import('./utils/index')['flattenJSONToMap']>
|
||||||
readonly formatBytes: UnwrapRef<typeof import('./utils/index')['formatBytes']>
|
readonly formatBytes: UnwrapRef<typeof import('./utils/index')['formatBytes']>
|
||||||
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
@@ -821,6 +829,7 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly pinnedContainers: UnwrapRef<typeof import('./composable/storage')['pinnedContainers']>
|
readonly pinnedContainers: UnwrapRef<typeof import('./composable/storage')['pinnedContainers']>
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
|
readonly provideLogDetails: UnwrapRef<typeof import('./composable/showLogDetails')['provideLogDetails']>
|
||||||
readonly provideLoggingContext: UnwrapRef<typeof import('./composable/logContext')['provideLoggingContext']>
|
readonly provideLoggingContext: UnwrapRef<typeof import('./composable/logContext')['provideLoggingContext']>
|
||||||
readonly provideScrollContext: UnwrapRef<typeof import('./composable/scrollContext')['provideScrollContext']>
|
readonly provideScrollContext: UnwrapRef<typeof import('./composable/scrollContext')['provideScrollContext']>
|
||||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||||
@@ -850,6 +859,7 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||||
readonly showAllContainers: UnwrapRef<typeof import('./stores/settings')['showAllContainers']>
|
readonly showAllContainers: UnwrapRef<typeof import('./stores/settings')['showAllContainers']>
|
||||||
|
readonly showLogDetails: UnwrapRef<typeof import('./composable/showLogDetails')['showLogDetails']>
|
||||||
readonly showStd: UnwrapRef<typeof import('./stores/settings')['showStd']>
|
readonly showStd: UnwrapRef<typeof import('./stores/settings')['showStd']>
|
||||||
readonly showTimestamp: UnwrapRef<typeof import('./stores/settings')['showTimestamp']>
|
readonly showTimestamp: UnwrapRef<typeof import('./stores/settings')['showTimestamp']>
|
||||||
readonly size: UnwrapRef<typeof import('./stores/settings')['size']>
|
readonly size: UnwrapRef<typeof import('./stores/settings')['size']>
|
||||||
@@ -960,6 +970,7 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
||||||
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
||||||
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
||||||
|
readonly useLogDetails: UnwrapRef<typeof import('./composable/showLogDetails')['useLogDetails']>
|
||||||
readonly useLogSearchContext: UnwrapRef<typeof import('./composable/logSearchContext')['useLogSearchContext']>
|
readonly useLogSearchContext: UnwrapRef<typeof import('./composable/logSearchContext')['useLogSearchContext']>
|
||||||
readonly useLoggingContext: UnwrapRef<typeof import('./composable/logContext')['useLoggingContext']>
|
readonly useLoggingContext: UnwrapRef<typeof import('./composable/logContext')['useLoggingContext']>
|
||||||
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
||||||
|
|||||||
3
assets/components.d.ts
vendored
3
assets/components.d.ts
vendored
@@ -49,6 +49,7 @@ declare module 'vue' {
|
|||||||
LabeledInput: typeof import('./components/common/LabeledInput.vue')['default']
|
LabeledInput: typeof import('./components/common/LabeledInput.vue')['default']
|
||||||
Links: typeof import('./components/Links.vue')['default']
|
Links: typeof import('./components/Links.vue')['default']
|
||||||
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
|
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
|
||||||
|
LogDetails: typeof import('./components/LogViewer/LogDetails.vue')['default']
|
||||||
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
|
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
|
||||||
LogList: typeof import('./components/LogViewer/LogList.vue')['default']
|
LogList: typeof import('./components/LogViewer/LogList.vue')['default']
|
||||||
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
|
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
|
||||||
@@ -69,6 +70,7 @@ declare module 'vue' {
|
|||||||
'Mdi:hexagonMultiple': typeof import('~icons/mdi/hexagon-multiple')['default']
|
'Mdi:hexagonMultiple': typeof import('~icons/mdi/hexagon-multiple')['default']
|
||||||
'Mdi:keyboardEsc': typeof import('~icons/mdi/keyboard-esc')['default']
|
'Mdi:keyboardEsc': typeof import('~icons/mdi/keyboard-esc')['default']
|
||||||
'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
|
'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
|
||||||
|
'Mdi:mdi:close': typeof import('~icons/mdi/mdi')['default']
|
||||||
'Mdi:satelliteVariant': typeof import('~icons/mdi/satellite-variant')['default']
|
'Mdi:satelliteVariant': typeof import('~icons/mdi/satellite-variant')['default']
|
||||||
MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
|
MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
|
||||||
MultiContainerActionToolbar: typeof import('./components/LogViewer/MultiContainerActionToolbar.vue')['default']
|
MultiContainerActionToolbar: typeof import('./components/LogViewer/MultiContainerActionToolbar.vue')['default']
|
||||||
@@ -97,6 +99,7 @@ declare module 'vue' {
|
|||||||
ScrollProgress: typeof import('./components/ScrollProgress.vue')['default']
|
ScrollProgress: typeof import('./components/ScrollProgress.vue')['default']
|
||||||
Search: typeof import('./components/Search.vue')['default']
|
Search: typeof import('./components/Search.vue')['default']
|
||||||
ServiceLog: typeof import('./components/ServiceViewer/ServiceLog.vue')['default']
|
ServiceLog: typeof import('./components/ServiceViewer/ServiceLog.vue')['default']
|
||||||
|
SideDrawer: typeof import('./components/common/SideDrawer.vue')['default']
|
||||||
SideMenu: typeof import('./components/SideMenu.vue')['default']
|
SideMenu: typeof import('./components/SideMenu.vue')['default']
|
||||||
SidePanel: typeof import('./components/SidePanel.vue')['default']
|
SidePanel: typeof import('./components/SidePanel.vue')['default']
|
||||||
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
|
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
|
||||||
|
|||||||
@@ -13,7 +13,12 @@
|
|||||||
v-model="query"
|
v-model="query"
|
||||||
:placeholder="$t('placeholder.search-containers')"
|
:placeholder="$t('placeholder.search-containers')"
|
||||||
/>
|
/>
|
||||||
<mdi:keyboard-esc class="flex" />
|
<form method="dialog">
|
||||||
|
<button class="swap swap-rotate hover:swap-active">
|
||||||
|
<mdi:keyboard-esc class="swap-off" />
|
||||||
|
<mdi:close class="swap-on" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="dropdown-content !relative mt-2 max-h-[calc(100dvh-20rem)] w-full overflow-y-scroll rounded-md border-y-8 border-transparent bg-base-lighter px-2"
|
class="dropdown-content !relative mt-2 max-h-[calc(100dvh-20rem)] w-full overflow-y-scroll rounded-md border-y-8 border-transparent bg-base-lighter px-2"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
ref="viewer"
|
ref="viewer"
|
||||||
:stream-source="useGroupedStream"
|
:stream-source="useGroupedStream"
|
||||||
:entity="group"
|
:entity="group"
|
||||||
:visible-keys="visibleKeys"
|
:visible-keys="new Map<string[], boolean>()"
|
||||||
:show-container-name="true"
|
:show-container-name="true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -43,6 +43,5 @@ const { customGroups } = storeToRefs(swarmStore);
|
|||||||
|
|
||||||
const group = computed(() => customGroups.value.find((g) => g.name === name) ?? new GroupedContainers("", []));
|
const group = computed(() => customGroups.value.find((g) => g.name === name) ?? new GroupedContainers("", []));
|
||||||
|
|
||||||
const visibleKeys = ref<string[][]>([]);
|
|
||||||
provideLoggingContext(toRef(() => group.value.containers));
|
provideLoggingContext(toRef(() => group.value.containers));
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="group/item relative flex w-full gap-x-2" @click="expandToggle()">
|
<div class="group/item relative flex w-full gap-x-2 hover:bg-secondary/10" @click="showLogDetails(logEntry)">
|
||||||
<label class="swap swap-rotate invisible absolute -left-4 top-0.5 size-4 group-hover/item:visible">
|
|
||||||
<input type="checkbox" v-model="expanded" @click="expandToggle()" />
|
|
||||||
<material-symbols:expand-all-rounded class="swap-off text-secondary" />
|
|
||||||
<material-symbols:collapse-all-rounded class="swap-on text-secondary" />
|
|
||||||
</label>
|
|
||||||
<div v-if="showContainerName">
|
<div v-if="showContainerName">
|
||||||
<ContainerName :id="logEntry.containerID" />
|
<ContainerName :id="logEntry.containerID" />
|
||||||
</div>
|
</div>
|
||||||
@@ -18,7 +13,7 @@
|
|||||||
<LogLevel :level="logEntry.level" />
|
<LogLevel :level="logEntry.level" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ul class="fields cursor-pointer space-x-4" :class="{ expanded }">
|
<ul class="fields cursor-pointer space-x-4">
|
||||||
<li v-for="(value, name) in validValues" :key="name">
|
<li v-for="(value, name) in validValues" :key="name">
|
||||||
<span class="text-light">{{ name }}=</span><span class="font-bold" v-if="value === null"><null></span>
|
<span class="text-light">{{ name }}=</span><span class="font-bold" v-if="value === null"><null></span>
|
||||||
<template v-else-if="Array.isArray(value)">
|
<template v-else-if="Array.isArray(value)">
|
||||||
@@ -28,7 +23,6 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="text-light" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
|
<li class="text-light" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
|
||||||
</ul>
|
</ul>
|
||||||
<FieldList :fields="logEntry.unfilteredMessage" :expanded="expanded" :visible-keys="visibleKeys" />
|
|
||||||
</div>
|
</div>
|
||||||
<LogMessageActions
|
<LogMessageActions
|
||||||
class="duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100"
|
class="duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100"
|
||||||
@@ -44,15 +38,14 @@ const { markSearch } = useSearchFilter();
|
|||||||
|
|
||||||
const { logEntry, showContainerName = false } = defineProps<{
|
const { logEntry, showContainerName = false } = defineProps<{
|
||||||
logEntry: ComplexLogEntry;
|
logEntry: ComplexLogEntry;
|
||||||
visibleKeys: string[][];
|
|
||||||
showContainerName?: boolean;
|
showContainerName?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const [expanded, expandToggle] = useToggle();
|
|
||||||
|
|
||||||
const validValues = computed(() => {
|
const validValues = computed(() => {
|
||||||
return Object.fromEntries(Object.entries(logEntry.message).filter(([_, value]) => value !== undefined));
|
return Object.fromEntries(Object.entries(logEntry.message).filter(([_, value]) => value !== undefined));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showLogDetails = useLogDetails();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
<style lang="postcss" scoped>
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ul v-if="expanded" ref="root" class="ml-8">
|
|
||||||
<li v-for="(value, name) in fields" :key="name">
|
|
||||||
<template v-if="isObject(value)">
|
|
||||||
<span class="text-light">{{ name }}=</span>
|
|
||||||
<FieldList :fields="value" :parent-key="parentKey.concat(name)" :visible-keys="visibleKeys" expanded />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="Array.isArray(value)">
|
|
||||||
<a @click.stop="toggleField(name)" class="link-primary mr-2 cursor-pointer">
|
|
||||||
{{ hasField(name) ? "remove" : "add" }}
|
|
||||||
</a>
|
|
||||||
<span class="text-light">{{ name }}=</span>[
|
|
||||||
<span class="font-bold" v-for="(item, index) in value">
|
|
||||||
<span v-html="JSON.stringify(item)"></span><span v-if="index !== value.length - 1">,</span>
|
|
||||||
</span>
|
|
||||||
]
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a @click.stop="toggleField(name)" class="link-primary mr-2 cursor-pointer">
|
|
||||||
{{ hasField(name) ? "remove" : "add" }}
|
|
||||||
</a>
|
|
||||||
<span class="text-light">{{ name }}=</span><span class="font-bold" v-html="value"></span>
|
|
||||||
</template>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { arrayEquals, isObject } from "@/utils";
|
|
||||||
|
|
||||||
const {
|
|
||||||
fields,
|
|
||||||
expanded = false,
|
|
||||||
parentKey = [],
|
|
||||||
visibleKeys = [],
|
|
||||||
} = defineProps<{
|
|
||||||
fields: Record<string, any>;
|
|
||||||
expanded?: boolean;
|
|
||||||
parentKey?: string[];
|
|
||||||
visibleKeys?: string[][];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const root = ref<HTMLElement>();
|
|
||||||
|
|
||||||
async function toggleField(field: string) {
|
|
||||||
const index = fieldIndex(field);
|
|
||||||
|
|
||||||
if (index > -1) {
|
|
||||||
visibleKeys.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
visibleKeys.push(parentKey.concat(field));
|
|
||||||
}
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
root.value?.scrollIntoView({
|
|
||||||
block: "center",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasField(field: string) {
|
|
||||||
return fieldIndex(field) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fieldIndex(field: string) {
|
|
||||||
const path = parentKey.concat(field);
|
|
||||||
return visibleKeys.findIndex((keys) => arrayEquals(toRaw(keys), toRaw(path)));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="postcss">
|
|
||||||
.text-light {
|
|
||||||
@apply text-base-content/70;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
119
assets/components/LogViewer/LogDetails.vue
Normal file
119
assets/components/LogViewer/LogDetails.vue
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<header class="flex items-center gap-4">
|
||||||
|
<Tag :data-level="entry.level" class="uppercase text-white">{{ entry.level }}</Tag>
|
||||||
|
<h1 class="text-lg">
|
||||||
|
<DateTime :date="entry.date" />
|
||||||
|
</h1>
|
||||||
|
<h2 class="text-sm"><DistanceTime :date="entry.date" /> on {{ entry.std }}</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="mt-8 flex flex-col gap-4">
|
||||||
|
<section class="grid grid-cols-3 gap-2">
|
||||||
|
<div>
|
||||||
|
<div class="font-thin">Container Name</div>
|
||||||
|
<div class="text-lg font-bold">{{ container.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-thin">Host</div>
|
||||||
|
<div class="text-lg font-bold">
|
||||||
|
{{ hosts[container.host].name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-thin">Image</div>
|
||||||
|
<div class="text-lg font-bold">{{ container.image }}</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<table class="table" v-if="entry instanceof ComplexLogEntry">
|
||||||
|
<thead class="text-lg">
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Value</th>
|
||||||
|
<th>Show</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody ref="list">
|
||||||
|
<tr v-for="{ key, value, enabled } in fields" :key="key.join('.')" class="hover">
|
||||||
|
<td class="cursor-move font-mono">
|
||||||
|
{{ key.join(".") }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<code v-html="JSON.stringify(value)"></code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" class="toggle toggle-primary" :checked="enabled" @change="toggleField(key)" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ComplexLogEntry } from "@/models/LogEntry";
|
||||||
|
import { useSortable } from "@vueuse/integrations/useSortable";
|
||||||
|
import DistanceTime from "../common/DistanceTime.vue";
|
||||||
|
|
||||||
|
const { entry } = defineProps<{ entry: ComplexLogEntry }>();
|
||||||
|
const { currentContainer } = useContainerStore();
|
||||||
|
const list = ref<HTMLElement>();
|
||||||
|
const container = currentContainer(toRef(() => entry.containerID));
|
||||||
|
const visibleKeys = persistentVisibleKeysForContainer(container);
|
||||||
|
const { hosts } = useHosts();
|
||||||
|
|
||||||
|
function toggleField(key: string[]) {
|
||||||
|
if (visibleKeys.value.size === 0) {
|
||||||
|
visibleKeys.value = new Map<string[], boolean>(fields.value.map(({ key }) => [key, true]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const enabled = visibleKeys.value.get(key);
|
||||||
|
visibleKeys.value.set(key, !enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = computed({
|
||||||
|
get() {
|
||||||
|
const fieldsWithValue: { key: string[]; value: any; enabled: boolean }[] = [];
|
||||||
|
const allFields = flattenJSONToMap(entry.unfilteredMessage);
|
||||||
|
if (visibleKeys.value.size === 0) {
|
||||||
|
for (const [key, value] of allFields) {
|
||||||
|
fieldsWithValue.push({ key, value, enabled: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const [key, enabled] of visibleKeys.value) {
|
||||||
|
const value = getDeep(entry.unfilteredMessage, key);
|
||||||
|
fieldsWithValue.push({ key, value, enabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of allFields) {
|
||||||
|
if ([...visibleKeys.value.keys()].findIndex((k) => arrayEquals(k, key)) === -1) {
|
||||||
|
fieldsWithValue.push({ key, value, enabled: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldsWithValue;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const map = new Map<string[], boolean>();
|
||||||
|
for (const { key, enabled } of value) {
|
||||||
|
map.set(key, enabled);
|
||||||
|
}
|
||||||
|
visibleKeys.value = map;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useSortable(list, fields);
|
||||||
|
</script>
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.font-mono {
|
||||||
|
font-family:
|
||||||
|
ui-monospace,
|
||||||
|
SFMono-Regular,
|
||||||
|
SF Mono,
|
||||||
|
Consolas,
|
||||||
|
Liberation Mono,
|
||||||
|
monaco,
|
||||||
|
Menlo,
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -32,25 +32,26 @@ defineProps<{
|
|||||||
margin-top: -0.4em;
|
margin-top: -0.4em;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="postcss">
|
||||||
[data-level="debug"],
|
[data-level="debug"],
|
||||||
[data-level="trace"] {
|
[data-level="trace"] {
|
||||||
@apply bg-purple;
|
@apply !bg-purple;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-level="info"] {
|
[data-level="info"] {
|
||||||
@apply bg-green;
|
@apply !bg-green;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-level="error"],
|
[data-level="error"],
|
||||||
[data-level="severe"],
|
[data-level="severe"],
|
||||||
[data-level="critical"],
|
[data-level="critical"],
|
||||||
[data-level="fatal"] {
|
[data-level="fatal"] {
|
||||||
@apply bg-red;
|
@apply !bg-red;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-level="warn"],
|
[data-level="warn"],
|
||||||
[data-level="warning"] {
|
[data-level="warning"] {
|
||||||
@apply bg-orange;
|
@apply !bg-orange;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,12 +13,7 @@
|
|||||||
:class="{ 'border border-secondary': toRaw(item) === toRaw(lastSelectedItem) }"
|
:class="{ 'border border-secondary': toRaw(item) === toRaw(lastSelectedItem) }"
|
||||||
class="group/entry"
|
class="group/entry"
|
||||||
>
|
>
|
||||||
<component
|
<component :is="item.getComponent()" :log-entry="item" :show-container-name="showContainerName" />
|
||||||
:is="item.getComponent()"
|
|
||||||
:log-entry="item"
|
|
||||||
:visible-keys="visibleKeys"
|
|
||||||
:show-container-name="showContainerName"
|
|
||||||
/>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
@@ -30,7 +25,6 @@ const { loading, progress, currentDate } = useScrollContext();
|
|||||||
|
|
||||||
const { messages } = defineProps<{
|
const { messages } = defineProps<{
|
||||||
messages: LogEntry<string | JSONObject>[];
|
messages: LogEntry<string | JSONObject>[];
|
||||||
visibleKeys: string[][];
|
|
||||||
lastSelectedItem: LogEntry<string | JSONObject> | undefined;
|
lastSelectedItem: LogEntry<string | JSONObject> | undefined;
|
||||||
showContainerName: boolean;
|
showContainerName: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="rounded bg-slate-800/60 px-1.5 py-1 text-primary hover:bg-slate-700"
|
class="rounded bg-slate-800/60 px-1.5 py-1 text-primary hover:bg-slate-700"
|
||||||
@click="copyLogMessageToClipBoard()"
|
@click.prevent="copyLogMessageToClipBoard()"
|
||||||
>
|
>
|
||||||
<carbon:copy-file />
|
<carbon:copy-file />
|
||||||
</span>
|
</span>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="rounded bg-slate-800/60 px-1.5 py-1 text-primary hover:bg-slate-700"
|
class="rounded bg-slate-800/60 px-1.5 py-1 text-primary hover:bg-slate-700"
|
||||||
@click="handleJumpLineSelected($event, logEntry)"
|
@click.prevent="handleJumpLineSelected($event, logEntry)"
|
||||||
:href="`#${logEntry.id}`"
|
:href="`#${logEntry.id}`"
|
||||||
>
|
>
|
||||||
<carbon:search-locate />
|
<carbon:search-locate />
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<LogList
|
<SideDrawer ref="drawer">
|
||||||
:messages="filtered"
|
<LogDetails :entry="entry" v-if="entry && entry instanceof ComplexLogEntry" />
|
||||||
:last-selected-item="lastSelectedItem"
|
</SideDrawer>
|
||||||
:visible-keys="visibleKeys"
|
<LogList :messages="filtered" :last-selected-item="lastSelectedItem" :show-container-name="showContainerName" />
|
||||||
:show-container-name="showContainerName"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouteHash } from "@vueuse/router";
|
import { useRouteHash } from "@vueuse/router";
|
||||||
|
import SideDrawer from "@/components/common/SideDrawer.vue";
|
||||||
import { type JSONObject, LogEntry } from "@/models/LogEntry";
|
import { ComplexLogEntry, type JSONObject, LogEntry } from "@/models/LogEntry";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
messages: LogEntry<string | JSONObject>[];
|
messages: LogEntry<string | JSONObject>[];
|
||||||
visibleKeys: string[][];
|
visibleKeys: Map<string[], boolean>;
|
||||||
showContainerName: boolean;
|
showContainerName: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -23,6 +21,10 @@ const { messages, visibleKeys } = toRefs(props);
|
|||||||
const { filteredPayload } = useVisibleFilter(visibleKeys);
|
const { filteredPayload } = useVisibleFilter(visibleKeys);
|
||||||
const { filteredMessages } = useSearchFilter();
|
const { filteredMessages } = useSearchFilter();
|
||||||
|
|
||||||
|
const drawer = ref<InstanceType<typeof SideDrawer>>() as Ref<InstanceType<typeof SideDrawer>>;
|
||||||
|
|
||||||
|
const { entry } = provideLogDetails(drawer);
|
||||||
|
|
||||||
const visible = filteredPayload(messages);
|
const visible = filteredPayload(messages);
|
||||||
const filtered = filteredMessages(visible);
|
const filtered = filteredMessages(visible);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { LogStreamSource } from "@/composable/eventStreams";
|
|||||||
|
|
||||||
const { streamSource, visibleKeys, showContainerName, entity } = defineProps<{
|
const { streamSource, visibleKeys, showContainerName, entity } = defineProps<{
|
||||||
streamSource: (t: Ref<T>) => LogStreamSource;
|
streamSource: (t: Ref<T>) => LogStreamSource;
|
||||||
visibleKeys: string[][];
|
visibleKeys: Map<string[], boolean>;
|
||||||
showContainerName: boolean;
|
showContainerName: boolean;
|
||||||
entity: T;
|
entity: T;
|
||||||
}>();
|
}>();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
exports[`<ContainerEventSource /> > render html correctly > should render dates with 12 hour style 1`] = `
|
exports[`<ContainerEventSource /> > render html correctly > should render dates with 12 hour style 1`] = `
|
||||||
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
||||||
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
||||||
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2">
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
||||||
@@ -23,7 +23,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
|||||||
exports[`<ContainerEventSource /> > render html correctly > should render dates with 24 hour style 1`] = `
|
exports[`<ContainerEventSource /> > render html correctly > should render dates with 24 hour style 1`] = `
|
||||||
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
||||||
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
||||||
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2">
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
||||||
@@ -43,7 +43,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
|||||||
exports[`<ContainerEventSource /> > render html correctly > should render messages 1`] = `
|
exports[`<ContainerEventSource /> > render html correctly > should render messages 1`] = `
|
||||||
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
||||||
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
||||||
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2">
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
||||||
@@ -63,7 +63,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
|||||||
exports[`<ContainerEventSource /> > render html correctly > should render messages with filter 1`] = `
|
exports[`<ContainerEventSource /> > render html correctly > should render messages with filter 1`] = `
|
||||||
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
||||||
<li data-v-cf9ff940="" data-key="2" data-time="1560336942459" class="group/entry">
|
<li data-v-cf9ff940="" data-key="2" data-time="1560336942459" class="group/entry">
|
||||||
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2">
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
||||||
@@ -85,7 +85,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
|||||||
exports[`<ContainerEventSource /> > render html correctly > should render messages with html entities 1`] = `
|
exports[`<ContainerEventSource /> > render html correctly > should render messages with html entities 1`] = `
|
||||||
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
|
||||||
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
||||||
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2">
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
<div data-v-961504e7="" data-v-a49e52d4="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
|
||||||
@@ -104,6 +104,16 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
|||||||
|
|
||||||
exports[`<ContainerEventSource /> > renders loading correctly 1`] = `
|
exports[`<ContainerEventSource /> > renders loading correctly 1`] = `
|
||||||
"<div class="flex min-h-[1px] justify-center"><span class="loading loading-bars loading-md mt-4 text-primary" style="display: none;"></span></div>
|
"<div class="flex min-h-[1px] justify-center"><span class="loading loading-bars loading-md mt-4 text-primary" style="display: none;"></span></div>
|
||||||
|
<dialog data-v-43493931="" class="modal-right modal items-start outline-none backdrop:bg-none">
|
||||||
|
<div data-v-43493931="" class="modal-box">
|
||||||
|
<form data-v-43493931="" method="dialog"><button data-v-43493931="" class="swap swap-rotate absolute right-4 top-4 hover:swap-active"><svg data-v-43493931="" viewBox="0 0 24 24" width="1.2em" height="1.2em" class="swap-off">
|
||||||
|
<path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1zm10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2"></path>
|
||||||
|
</svg><svg data-v-43493931="" viewBox="0 0 24 24" width="1.2em" height="1.2em" class="swap-on">
|
||||||
|
<path fill="currentColor" d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"></path>
|
||||||
|
</svg></button></form>
|
||||||
|
</div>
|
||||||
|
<form data-v-43493931="" method="dialog" class="modal-backdrop"><button data-v-43493931="">close</button></form>
|
||||||
|
</dialog>
|
||||||
<!--v-if-->"
|
<!--v-if-->"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
ref="viewer"
|
ref="viewer"
|
||||||
:stream-source="useMergedStream"
|
:stream-source="useMergedStream"
|
||||||
:entity="containers"
|
:entity="containers"
|
||||||
:visible-keys="visibleKeys"
|
:visible-keys="new Map<string[], boolean>()"
|
||||||
:show-container-name="true"
|
:show-container-name="true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,8 +35,5 @@ const containerStore = useContainerStore();
|
|||||||
const { allContainersById, ready } = storeToRefs(containerStore);
|
const { allContainersById, ready } = storeToRefs(containerStore);
|
||||||
|
|
||||||
const containers = computed(() => ids.map((id) => allContainersById.value[id]));
|
const containers = computed(() => ids.map((id) => allContainersById.value[id]));
|
||||||
|
|
||||||
const visibleKeys = ref<string[][]>([]);
|
|
||||||
|
|
||||||
provideLoggingContext(containers);
|
provideLoggingContext(containers);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="animate-background h-1 bg-gradient-to-br from-primary via-transparent to-primary"
|
class="animate-background h-1 bg-gradient-to-br from-primary via-primary/20 to-primary"
|
||||||
v-show="!scrollContext.paused && !scrollContext.loading"
|
v-show="!scrollContext.paused && !scrollContext.loading"
|
||||||
></div>
|
></div>
|
||||||
<div ref="scrollObserver" class="h-px"></div>
|
<div ref="scrollObserver" class="h-px"></div>
|
||||||
@@ -96,7 +96,7 @@ function scrollToBottom(behavior: "auto" | "smooth" = "auto") {
|
|||||||
|
|
||||||
.animate-background {
|
.animate-background {
|
||||||
background-size: 400% 400%;
|
background-size: 400% 400%;
|
||||||
animation: gradient-animation 4s ease infinite;
|
animation: gradient-animation 5s ease infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes gradient-animation {
|
@keyframes gradient-animation {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
ref="viewer"
|
ref="viewer"
|
||||||
:stream-source="useServiceStream"
|
:stream-source="useServiceStream"
|
||||||
:entity="service"
|
:entity="service"
|
||||||
:visible-keys="visibleKeys"
|
:visible-keys="new Map<string[], boolean>()"
|
||||||
:show-container-name="true"
|
:show-container-name="true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -36,7 +36,6 @@ const { name, scrollable = false } = defineProps<{
|
|||||||
name: string;
|
name: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const visibleKeys = ref<string[][]>([]);
|
|
||||||
const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
|
const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
|
||||||
const store = useSwarmStore();
|
const store = useSwarmStore();
|
||||||
const { services } = storeToRefs(store) as unknown as { services: Ref<Service[]> };
|
const { services } = storeToRefs(store) as unknown as { services: Ref<Service[]> };
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
ref="viewer"
|
ref="viewer"
|
||||||
:stream-source="useStackStream"
|
:stream-source="useStackStream"
|
||||||
:entity="stack"
|
:entity="stack"
|
||||||
:visible-keys="visibleKeys"
|
:visible-keys="new Map<string[], boolean>()"
|
||||||
:show-container-name="true"
|
:show-container-name="true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -38,7 +38,6 @@ const { name, scrollable = false } = defineProps<{
|
|||||||
name: string;
|
name: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const visibleKeys = ref<string[][]>([]);
|
|
||||||
const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
|
const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
|
||||||
const store = useSwarmStore();
|
const store = useSwarmStore();
|
||||||
const { stacks } = storeToRefs(store) as unknown as { stacks: Ref<Stack[]> };
|
const { stacks } = storeToRefs(store) as unknown as { stacks: Ref<Stack[]> };
|
||||||
|
|||||||
34
assets/components/common/SideDrawer.vue
Normal file
34
assets/components/common/SideDrawer.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<dialog ref="panel" class="modal-right modal items-start outline-none backdrop:bg-none">
|
||||||
|
<div class="modal-box">
|
||||||
|
<form method="dialog">
|
||||||
|
<button class="swap swap-rotate absolute right-4 top-4 hover:swap-active">
|
||||||
|
<mdi:keyboard-esc class="swap-off" />
|
||||||
|
<mdi:close class="swap-on" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const panel = ref<HTMLDialogElement>();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open: () => {
|
||||||
|
panel.value?.showModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="postcss">
|
||||||
|
.modal-right :where(.modal-box) {
|
||||||
|
@apply fixed right-0 h-lvh max-h-screen max-w-3xl translate-x-24 scale-100 rounded-none bg-base-lighter shadow-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-right[open] .modal-box {
|
||||||
|
@apply translate-x-0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,14 +1,34 @@
|
|||||||
import { Profile } from "@/stores/config";
|
import { Profile } from "@/stores/config";
|
||||||
|
|
||||||
export function useProfileStorage<K extends keyof Profile>(key: K, defaultValue: NonNullable<Profile[K]>) {
|
interface SerializerTransformer<T, U> {
|
||||||
|
from: (raw: U) => T;
|
||||||
|
to: (value: T) => U;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useProfileStorage<K extends keyof Profile>(
|
||||||
|
key: K,
|
||||||
|
defaultValue: NonNullable<Profile[K]>,
|
||||||
|
transformer?: SerializerTransformer<NonNullable<Profile[K]>, any>,
|
||||||
|
) {
|
||||||
const storageKey = "DOZZLE_" + key.toUpperCase();
|
const storageKey = "DOZZLE_" + key.toUpperCase();
|
||||||
const storage = useStorage<NonNullable<Profile[K]>>(storageKey, defaultValue, undefined, {
|
const storage = useStorage<NonNullable<Profile[K]>>(storageKey, defaultValue, undefined, {
|
||||||
writeDefaults: false,
|
writeDefaults: false,
|
||||||
mergeDefaults: true,
|
mergeDefaults: true,
|
||||||
|
serializer: transformer
|
||||||
|
? {
|
||||||
|
read: (raw) => transformer.from(JSON.parse(raw)),
|
||||||
|
write: (value) => JSON.stringify(transformer.to(value)),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
onError: (e) => {
|
||||||
|
console.error(`Failed to read ${storageKey} from storage`, e);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (config.profile?.[key]) {
|
if (config.profile?.[key]) {
|
||||||
if (storage.value instanceof Set && config.profile[key] instanceof Array) {
|
if (transformer) {
|
||||||
|
storage.value = transformer.from(config.profile[key]);
|
||||||
|
} else if (storage.value instanceof Set && config.profile[key] instanceof Array) {
|
||||||
storage.value = new Set([...(config.profile[key] as Iterable<any>)]) as unknown as NonNullable<Profile[K]>;
|
storage.value = new Set([...(config.profile[key] as Iterable<any>)]) as unknown as NonNullable<Profile[K]>;
|
||||||
} else if (config.profile[key] instanceof Array) {
|
} else if (config.profile[key] instanceof Array) {
|
||||||
storage.value = config.profile[key] as NonNullable<Profile[K]>;
|
storage.value = config.profile[key] as NonNullable<Profile[K]>;
|
||||||
@@ -23,9 +43,19 @@ export function useProfileStorage<K extends keyof Profile>(key: K, defaultValue:
|
|||||||
watch(
|
watch(
|
||||||
storage,
|
storage,
|
||||||
(value) => {
|
(value) => {
|
||||||
|
const transformedValue = transformer ? transformer.to(value) : value;
|
||||||
|
|
||||||
fetch(withBase("/api/profile"), {
|
fetch(withBase("/api/profile"), {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify({ [key]: value }, (_, value) => (value instanceof Set ? [...value] : value)),
|
body: JSON.stringify({ [key]: transformedValue }, (_, value) => {
|
||||||
|
if (value instanceof Set) {
|
||||||
|
return Array.from(value);
|
||||||
|
} else if (value instanceof Map) {
|
||||||
|
return Array.from(value.entries());
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
|
|||||||
22
assets/composable/showLogDetails.ts
Normal file
22
assets/composable/showLogDetails.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { JSONObject, LogEntry } from "@/models/LogEntry";
|
||||||
|
import SideDrawer from "@/components/common/SideDrawer.vue";
|
||||||
|
|
||||||
|
export const showLogDetails = Symbol("showLogDetails") as InjectionKey<
|
||||||
|
(logEntry: LogEntry<string | JSONObject>) => void
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const provideLogDetails = (drawer: Ref<InstanceType<typeof SideDrawer>>) => {
|
||||||
|
const entry = ref<LogEntry<string | JSONObject>>();
|
||||||
|
|
||||||
|
provide(showLogDetails, (logEntry: LogEntry<string | JSONObject>) => {
|
||||||
|
entry.value = logEntry;
|
||||||
|
drawer.value?.open();
|
||||||
|
});
|
||||||
|
|
||||||
|
return { entry };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLogDetails = () => {
|
||||||
|
const showDetails = inject(showLogDetails, () => {});
|
||||||
|
return showDetails;
|
||||||
|
};
|
||||||
@@ -7,17 +7,21 @@ if (config.hosts.length === 1 && !sessionHost.value) {
|
|||||||
sessionHost.value = config.hosts[0].id;
|
sessionHost.value = config.hosts[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function persistentVisibleKeysForContainer(container: Ref<Container>): Ref<string[][]> {
|
const storage = useProfileStorage("visibleKeys", new Map<string, Map<string[], boolean>>(), {
|
||||||
const storage = useProfileStorage("visibleKeys", {});
|
from(transformed: [string, [string[], boolean][]][]) {
|
||||||
return computed(() => {
|
return new Map(transformed.map(([key, value]) => [key, new Map(value)]));
|
||||||
if (!(container.value.storageKey in storage.value)) {
|
},
|
||||||
// Returning a temporary ref here to avoid writing an empty array to storage
|
to(value: Map<string, Map<string[], boolean>>) {
|
||||||
const visibleKeys = ref<string[][]>([]);
|
const outer = Array.from(value.entries());
|
||||||
watchOnce(visibleKeys, () => (storage.value[container.value.storageKey] = visibleKeys.value), { deep: true });
|
const inner = outer.map(([key, value]) => [key, Array.from(value.entries())]);
|
||||||
return visibleKeys.value;
|
return inner;
|
||||||
}
|
},
|
||||||
|
});
|
||||||
return storage.value[container.value.storageKey];
|
export function persistentVisibleKeysForContainer(container: Ref<Container>): Ref<Map<string[], boolean>> {
|
||||||
|
// Computed property to only store to storage when the value changes
|
||||||
|
return computed({
|
||||||
|
get: () => storage.value.get(container.value.storageKey) || new Map<string[], boolean>(),
|
||||||
|
set: (value: Map<string[], boolean>) => storage.value.set(container.value.storageKey, value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ComplexLogEntry, type JSONObject, type LogEntry } from "@/models/LogEntry";
|
import { ComplexLogEntry, type JSONObject, type LogEntry } from "@/models/LogEntry";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
|
|
||||||
export function useVisibleFilter(visibleKeys: Ref<string[][]>) {
|
export function useVisibleFilter(visibleKeys: Ref<Map<string[], boolean>>) {
|
||||||
function filteredPayload(messages: Ref<LogEntry<string | JSONObject>[]>) {
|
function filteredPayload(messages: Ref<LogEntry<string | JSONObject>[]>) {
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
return messages.value.map((d) => {
|
return messages.value.map((d) => {
|
||||||
|
|||||||
@@ -67,15 +67,18 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
|
|||||||
date: Date,
|
date: Date,
|
||||||
public readonly level: Level,
|
public readonly level: Level,
|
||||||
public readonly std: Std,
|
public readonly std: Std,
|
||||||
visibleKeys?: Ref<string[][]>,
|
visibleKeys?: Ref<Map<string[], boolean>>,
|
||||||
) {
|
) {
|
||||||
super(message, containerID, id, date, std, level);
|
super(message, containerID, id, date, std, level);
|
||||||
if (visibleKeys) {
|
if (visibleKeys) {
|
||||||
this.filteredMessage = computed(() => {
|
this.filteredMessage = computed(() => {
|
||||||
if (!visibleKeys.value.length) {
|
if (visibleKeys.value.size === 0) {
|
||||||
return flattenJSON(message);
|
return flattenJSON(message);
|
||||||
} else {
|
} else {
|
||||||
return visibleKeys.value.reduce((acc, attr) => ({ ...acc, [attr.join(".")]: getDeep(message, attr) }), {});
|
const keys = Array.from(visibleKeys.value.entries())
|
||||||
|
.filter(([, value]) => value)
|
||||||
|
.map(([key]) => key);
|
||||||
|
return keys.reduce((acc, attr) => ({ ...acc, [attr.join(".")]: getDeep(message, attr) }), {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -87,14 +90,14 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get message(): JSONObject {
|
public get message(): JSONObject {
|
||||||
return this.filteredMessage.value;
|
return unref(this.filteredMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get unfilteredMessage(): JSONObject {
|
public get unfilteredMessage(): JSONObject {
|
||||||
return this._message;
|
return this._message;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromLogEvent(event: ComplexLogEntry, visibleKeys: Ref<string[][]>): ComplexLogEntry {
|
static fromLogEvent(event: ComplexLogEntry, visibleKeys: Ref<Map<string[], boolean>>): ComplexLogEntry {
|
||||||
return new ComplexLogEntry(
|
return new ComplexLogEntry(
|
||||||
event._message,
|
event._message,
|
||||||
event.containerID,
|
event.containerID,
|
||||||
@@ -165,7 +168,7 @@ export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
|
|||||||
event.id,
|
event.id,
|
||||||
new Date(event.ts),
|
new Date(event.ts),
|
||||||
event.l,
|
event.l,
|
||||||
event.s === "unknown" ? "stderr" : event.s ?? "stderr",
|
event.s === "unknown" ? "stderr" : (event.s ?? "stderr"),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new SimpleLogEntry(
|
return new SimpleLogEntry(
|
||||||
@@ -175,7 +178,7 @@ export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
|
|||||||
new Date(event.ts),
|
new Date(event.ts),
|
||||||
event.l,
|
event.l,
|
||||||
event.p,
|
event.p,
|
||||||
event.s === "unknown" ? "stderr" : event.s ?? "stderr",
|
event.s === "unknown" ? "stderr" : (event.s ?? "stderr"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<LogList
|
<LogList
|
||||||
:messages="fakeMessages"
|
:messages="fakeMessages"
|
||||||
:visible-keys="keys"
|
|
||||||
:last-selected-item="undefined"
|
:last-selected-item="undefined"
|
||||||
:show-container-name="false"
|
:show-container-name="false"
|
||||||
class="hidden overflow-hidden rounded-lg border border-base-content/50 shadow @3xl:block"
|
class="hidden overflow-hidden rounded-lg border border-base-content/50 shadow @3xl:block"
|
||||||
@@ -163,7 +162,6 @@ const { t } = useI18n();
|
|||||||
setTitle(t("title.settings"));
|
setTitle(t("title.settings"));
|
||||||
const { latest, hasUpdate } = useReleases();
|
const { latest, hasUpdate } = useReleases();
|
||||||
|
|
||||||
const keys = ref<string[][]>([]);
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const hoursAgo = (hours: number) => {
|
const hoursAgo = (hours: number) => {
|
||||||
const date = new Date(now);
|
const date = new Date(now);
|
||||||
@@ -208,7 +206,6 @@ const fakeMessages = computedWithControl(
|
|||||||
new Date(),
|
new Date(),
|
||||||
"info",
|
"info",
|
||||||
"stdout",
|
"stdout",
|
||||||
keys,
|
|
||||||
),
|
),
|
||||||
new SimpleLogEntry(t("settings.log.simple"), "123", 7, new Date(), "debug", undefined, "stderr"),
|
new SimpleLogEntry(t("settings.log.simple"), "123", 7, new Date(), "debug", undefined, "stderr"),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export interface Config {
|
|||||||
export interface Profile {
|
export interface Profile {
|
||||||
settings?: Settings;
|
settings?: Settings;
|
||||||
pinned?: Set<string>;
|
pinned?: Set<string>;
|
||||||
visibleKeys?: { [key: string]: string[][] };
|
visibleKeys?: Map<string, Map<string[], boolean>>;
|
||||||
releaseSeen?: string;
|
releaseSeen?: string;
|
||||||
collapsedGroups?: Set<string>;
|
collapsedGroups?: Set<string>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,28 @@ export function isObject(value: any): value is Record<string, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function flattenJSON(obj: Record<string, any>, path: string[] = []) {
|
export function flattenJSON(obj: Record<string, any>, path: string[] = []) {
|
||||||
const result: Record<string, any> = {};
|
const map = flattenJSONToMap(obj);
|
||||||
Object.keys(obj).forEach((key) => {
|
const result = {} as Record<string, any>;
|
||||||
|
for (const [key, value] of map) {
|
||||||
|
result[key.join(".")] = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenJSONToMap(obj: Record<string, any>, path: string[] = []): Map<string[], any> {
|
||||||
|
const result = new Map<string[], any>();
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
const value = obj[key];
|
const value = obj[key];
|
||||||
const newPath = path.concat(key);
|
const newPath = path.concat(key);
|
||||||
if (isObject(value)) {
|
if (isObject(value)) {
|
||||||
Object.assign(result, flattenJSON(value, newPath));
|
for (const [k, v] of flattenJSONToMap(value, newPath)) {
|
||||||
|
result.set(k, v);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result[newPath.join(".")] = value;
|
result.set(newPath, value);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ type Settings struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
Settings *Settings `json:"settings,omitempty"`
|
Settings *Settings `json:"settings,omitempty"`
|
||||||
Pinned []string `json:"pinned"`
|
Pinned []string `json:"pinned"`
|
||||||
VisibleKeys map[string][][]string `json:"visibleKeys,omitempty"`
|
VisibleKeys []interface{} `json:"visibleKeys,omitempty"`
|
||||||
ReleaseSeen string `json:"releaseSeen,omitempty"`
|
ReleaseSeen string `json:"releaseSeen,omitempty"`
|
||||||
CollapsedGroups []string `json:"collapsedGroups"`
|
CollapsedGroups []string `json:"collapsedGroups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataPath string
|
var dataPath string
|
||||||
@@ -68,7 +68,7 @@ func UpdateFromReader(user auth.User, reader io.Reader) error {
|
|||||||
defer mux.Unlock()
|
defer mux.Unlock()
|
||||||
existingProfile, err := Load(user)
|
existingProfile, err := Load(user)
|
||||||
if err != nil && err != errMissingProfileErr {
|
if err != nil && err != errMissingProfileErr {
|
||||||
return err
|
log.Error().Err(err).Msg("Unable to load profile. Overwriting it.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(reader).Decode(&existingProfile); err != nil {
|
if err := json.NewDecoder(reader).Decode(&existingProfile); err != nil {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func Test_createRoutes_index(t *testing.T) {
|
|||||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func Test_createRoutes_redirect(t *testing.T) {
|
func Test_createRoutes_redirect(t *testing.T) {
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
|
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ error:
|
|||||||
events-stream:
|
events-stream:
|
||||||
title: Unexpected Error
|
title: Unexpected Error
|
||||||
message: >-
|
message: >-
|
||||||
Dozzle UI wasn't able to connect API. Please check your network settings.
|
Dozzle UI wasn't able to connect to API. Please check your network settings.
|
||||||
If you are using a reverse proxy, please make sure it is configured
|
If you are using a reverse proxy, please make sure it is configured
|
||||||
properly.
|
properly.
|
||||||
events-timeout:
|
events-timeout:
|
||||||
@@ -95,8 +95,8 @@ settings:
|
|||||||
preview: This is a preview of the logs
|
preview: This is a preview of the logs
|
||||||
warning: A warning log looks like this
|
warning: A warning log looks like this
|
||||||
complex: This is a complex log entry as json
|
complex: This is a complex log entry as json
|
||||||
simple: This is a very very long message which would wrap by default. Disabling soft wraps would disable this. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
simple: This is a very very long message which would wrap by default. Disabling soft wraps would disable this. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||||
multi-line-error:
|
multi-line-error:
|
||||||
start-line: This is a multi line error message
|
start-line: This is a multi line error message
|
||||||
middle-line: with a second line
|
middle-line: with a second line
|
||||||
end-line: and finally third line.
|
end-line: and finally third line.
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"pinia": "^2.2.2",
|
"pinia": "^2.2.2",
|
||||||
"postcss": "^8.4.41",
|
"postcss": "^8.4.41",
|
||||||
|
"sortablejs": "^1.15.2",
|
||||||
"splitpanes": "^3.1.5",
|
"splitpanes": "^3.1.5",
|
||||||
"strip-ansi": "^7.1.0",
|
"strip-ansi": "^7.1.0",
|
||||||
"tailwindcss": "^3.4.10",
|
"tailwindcss": "^3.4.10",
|
||||||
@@ -85,7 +86,7 @@
|
|||||||
"@types/d3-shape": "^3.1.6",
|
"@types/d3-shape": "^3.1.6",
|
||||||
"@types/d3-transition": "^3.0.8",
|
"@types/d3-transition": "^3.0.8",
|
||||||
"@types/lodash.debounce": "^4.0.9",
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
"@types/node": "^22.5.1",
|
"@types/node": "^22.5.0",
|
||||||
"@vitejs/plugin-vue": "5.1.2",
|
"@vitejs/plugin-vue": "5.1.2",
|
||||||
"@vue/compiler-sfc": "^3.4.38",
|
"@vue/compiler-sfc": "^3.4.38",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
|||||||
21
pnpm-lock.yaml
generated
21
pnpm-lock.yaml
generated
@@ -49,7 +49,7 @@ importers:
|
|||||||
version: 11.0.3(vue@3.4.38(typescript@5.5.4))
|
version: 11.0.3(vue@3.4.38(typescript@5.5.4))
|
||||||
'@vueuse/integrations':
|
'@vueuse/integrations':
|
||||||
specifier: ^11.0.3
|
specifier: ^11.0.3
|
||||||
version: 11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.4.38(typescript@5.5.4))
|
version: 11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))
|
||||||
'@vueuse/router':
|
'@vueuse/router':
|
||||||
specifier: ^11.0.3
|
specifier: ^11.0.3
|
||||||
version: 11.0.3(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4))
|
version: 11.0.3(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4))
|
||||||
@@ -98,6 +98,9 @@ importers:
|
|||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.4.41
|
specifier: ^8.4.41
|
||||||
version: 8.4.41
|
version: 8.4.41
|
||||||
|
sortablejs:
|
||||||
|
specifier: ^1.15.2
|
||||||
|
version: 1.15.2
|
||||||
splitpanes:
|
splitpanes:
|
||||||
specifier: ^3.1.5
|
specifier: ^3.1.5
|
||||||
version: 3.1.5
|
version: 3.1.5
|
||||||
@@ -133,7 +136,7 @@ importers:
|
|||||||
version: 0.11.0(vite@5.4.2(@types/node@22.5.1))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4))
|
version: 0.11.0(vite@5.4.2(@types/node@22.5.1))(vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4))
|
||||||
vitepress:
|
vitepress:
|
||||||
specifier: 1.3.4
|
specifier: 1.3.4
|
||||||
version: 1.3.4(@algolia/client-search@5.0.0)(@types/node@22.5.1)(fuse.js@7.0.0)(postcss@8.4.41)(search-insights@2.16.3)(typescript@5.5.4)
|
version: 1.3.4(@algolia/client-search@5.0.0)(@types/node@22.5.1)(fuse.js@7.0.0)(postcss@8.4.41)(search-insights@2.16.3)(sortablejs@1.15.2)(typescript@5.5.4)
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.4.38
|
specifier: ^3.4.38
|
||||||
version: 3.4.38(typescript@5.5.4)
|
version: 3.4.38(typescript@5.5.4)
|
||||||
@@ -172,7 +175,7 @@ importers:
|
|||||||
specifier: ^4.0.9
|
specifier: ^4.0.9
|
||||||
version: 4.0.9
|
version: 4.0.9
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.5.1
|
specifier: ^22.5.0
|
||||||
version: 22.5.1
|
version: 22.5.1
|
||||||
'@vitejs/plugin-vue':
|
'@vitejs/plugin-vue':
|
||||||
specifier: 5.1.2
|
specifier: 5.1.2
|
||||||
@@ -2661,6 +2664,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
|
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
sortablejs@1.15.2:
|
||||||
|
resolution: {integrity: sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==}
|
||||||
|
|
||||||
source-map-js@1.2.0:
|
source-map-js@1.2.0:
|
||||||
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -4305,7 +4311,7 @@ snapshots:
|
|||||||
- '@vue/composition-api'
|
- '@vue/composition-api'
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@vueuse/integrations@11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.4.38(typescript@5.5.4))':
|
'@vueuse/integrations@11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vueuse/core': 11.0.3(vue@3.4.38(typescript@5.5.4))
|
'@vueuse/core': 11.0.3(vue@3.4.38(typescript@5.5.4))
|
||||||
'@vueuse/shared': 11.0.3(vue@3.4.38(typescript@5.5.4))
|
'@vueuse/shared': 11.0.3(vue@3.4.38(typescript@5.5.4))
|
||||||
@@ -4313,6 +4319,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
focus-trap: 7.5.4
|
focus-trap: 7.5.4
|
||||||
fuse.js: 7.0.0
|
fuse.js: 7.0.0
|
||||||
|
sortablejs: 1.15.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@vue/composition-api'
|
- '@vue/composition-api'
|
||||||
- vue
|
- vue
|
||||||
@@ -5638,6 +5645,8 @@ snapshots:
|
|||||||
ansi-styles: 6.2.1
|
ansi-styles: 6.2.1
|
||||||
is-fullwidth-code-point: 5.0.0
|
is-fullwidth-code-point: 5.0.0
|
||||||
|
|
||||||
|
sortablejs@1.15.2: {}
|
||||||
|
|
||||||
source-map-js@1.2.0: {}
|
source-map-js@1.2.0: {}
|
||||||
|
|
||||||
source-map@0.6.1:
|
source-map@0.6.1:
|
||||||
@@ -6098,7 +6107,7 @@ snapshots:
|
|||||||
'@types/node': 22.5.1
|
'@types/node': 22.5.1
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
vitepress@1.3.4(@algolia/client-search@5.0.0)(@types/node@22.5.1)(fuse.js@7.0.0)(postcss@8.4.41)(search-insights@2.16.3)(typescript@5.5.4):
|
vitepress@1.3.4(@algolia/client-search@5.0.0)(@types/node@22.5.1)(fuse.js@7.0.0)(postcss@8.4.41)(search-insights@2.16.3)(sortablejs@1.15.2)(typescript@5.5.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@docsearch/css': 3.6.1
|
'@docsearch/css': 3.6.1
|
||||||
'@docsearch/js': 3.6.1(@algolia/client-search@5.0.0)(search-insights@2.16.3)
|
'@docsearch/js': 3.6.1(@algolia/client-search@5.0.0)(search-insights@2.16.3)
|
||||||
@@ -6109,7 +6118,7 @@ snapshots:
|
|||||||
'@vue/devtools-api': 7.3.8
|
'@vue/devtools-api': 7.3.8
|
||||||
'@vue/shared': 3.4.38
|
'@vue/shared': 3.4.38
|
||||||
'@vueuse/core': 11.0.3(vue@3.4.38(typescript@5.5.4))
|
'@vueuse/core': 11.0.3(vue@3.4.38(typescript@5.5.4))
|
||||||
'@vueuse/integrations': 11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.4.38(typescript@5.5.4))
|
'@vueuse/integrations': 11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))
|
||||||
focus-trap: 7.5.4
|
focus-trap: 7.5.4
|
||||||
mark.js: 8.11.1
|
mark.js: 8.11.1
|
||||||
minisearch: 7.1.0
|
minisearch: 7.1.0
|
||||||
|
|||||||
Reference in New Issue
Block a user