mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-23 22:18:26 +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)) {
|
||||||
} else {
|
result.set(k, v);
|
||||||
result[newPath.join(".")] = value;
|
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
result.set(newPath, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ 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"`
|
||||||
}
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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