1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 21:33:18 +01:00
Files
dozzle/assets/components/LogViewer.vue
Amir Raminfar c835f51cc4 Support for JSON logs (#1759)
* WIP for using json all the time

* Updates to render

* adds a new component for json

* Updates styles

* Adds nesting

* Adds field list

* Adds expanding

* Adds new composable for event source

* Creates an add button

* Removes unused code

* Adds and removes fields with defaults

* Fixes jumping when adding new fields

* Returns JSON correctly

* Fixes little bugs

* Fixes js tests

* Adds vscode

* Fixes json buffer error

* Fixes extra line

* Fixes tests

* Fixes tests and adds support for search

* Refactors visible payload keys to a composable

* Fixes typescript errors and refactors

* Fixes visible keys by ComputedRef<Ref>

* Fixes search bugs

* Updates tests

* Fixes go tests

* Fixes scroll view

* Fixes vue tsc errors

* Fixes EOF error

* Fixes build error

* Uses application/ld+json

* Fixes arrays and records

* Marks for json too
2022-08-16 13:53:31 -07:00

204 lines
4.9 KiB
Vue

<template>
<ul class="events" ref="events" :class="{ 'disable-wrap': !softWrap, [size]: true }">
<li
v-for="(item, index) in filtered"
:key="item.id"
:data-key="item.id"
:data-event="item.event"
:class="{ selected: toRaw(item) === toRaw(lastSelectedItem) }"
>
<div class="line-options" v-show="isSearching()">
<dropdown-menu :class="{ 'is-last': index === filtered.length - 1 }" class="is-top minimal">
<a class="dropdown-item" @click="handleJumpLineSelected($event, item)" :href="`#${item.id}`">
<div class="level is-justify-content-start">
<div class="level-left">
<div class="level-item">
<cil-find-in-page class="mr-4" />
</div>
</div>
<div class="level-right">
<div class="level-item">Jump to Context</div>
</div>
</div>
</a>
</dropdown-menu>
</div>
<div class="line">
<span class="date" v-if="showTimestamp"> <relative-time :date="item.date"></relative-time></span>
<JSONPayload :log-entry="item" :visible-keys="visibleKeys.value" v-if="item.hasPayload()"></JSONPayload>
<span class="text" v-html="colorize(item.message)" v-else-if="item.message"></span>
</div>
</li>
</ul>
</template>
<script lang="ts" setup>
import { ComputedRef, inject, PropType, ref, toRefs, watch, toRaw } from "vue";
import { useRouteHash } from "@vueuse/router";
import { size, showTimestamp, softWrap } from "@/composables/settings";
import { VisibleLogEntry } from "@/types/VisibleLogEntry";
import { LogEntry } from "@/types/LogEntry";
import { useSearchFilter } from "@/composables/search";
import { useVisibleFilter } from "@/composables/visible";
import { Container } from "@/types/Container";
import { persistentVisibleKeys } from "@/utils";
import RelativeTime from "./RelativeTime.vue";
import AnsiConvertor from "ansi-to-html";
import JSONPayload from "./JSONPayload.vue";
const props = defineProps({
messages: {
type: Array as PropType<LogEntry[]>,
required: true,
},
});
const ansiConvertor = new AnsiConvertor({ escapeXML: true });
const colorize = (value: string) => markSearch(ansiConvertor.toHtml(value));
const { messages } = toRefs(props);
let visibleKeys = persistentVisibleKeys(inject("container") as ComputedRef<Container>);
const { filteredPayload } = useVisibleFilter(visibleKeys);
const { filteredMessages, resetSearch, markSearch, isSearching } = useSearchFilter();
const visible = filteredPayload(messages);
const filtered = filteredMessages(visible);
const events = ref<HTMLElement>();
let lastSelectedItem = ref<VisibleLogEntry>();
function handleJumpLineSelected(e: Event, item: VisibleLogEntry) {
lastSelectedItem.value = item;
resetSearch();
}
const routeHash = useRouteHash();
watch(
routeHash,
(hash) => {
document.querySelector(`[data-key="${hash.substring(1)}"]`)?.scrollIntoView({ block: "center" });
},
{ immediate: true, flush: "post" }
);
</script>
<style scoped lang="scss">
.events {
padding: 1em 0;
font-family: SFMono-Regular, Consolas, Liberation Mono, monaco, Menlo, monospace;
&.disable-wrap {
.line,
.text {
white-space: nowrap;
}
}
.text {
white-space: pre-wrap;
&::before {
content: " ";
}
}
& > li {
display: flex;
word-wrap: break-word;
padding: 0.2em 1em;
&:last-child {
scroll-snap-align: end;
scroll-margin-block-end: 5rem;
}
&:nth-child(odd) {
background-color: rgba(125, 125, 125, 0.08);
}
&[data-event="container-stopped"] {
color: #f14668;
}
&[data-event="container-started"] {
color: hsl(141, 53%, 53%);
}
&.selected .date {
background-color: var(--menu-item-active-background-color);
color: var(--text-color);
}
&.selected > .date {
background-color: white;
}
& > .line {
margin: auto 0;
width: 100%;
}
& > .line-options {
display: flex;
flex-direction: row-reverse;
margin-right: 1em;
}
}
&.small {
font-size: 60%;
}
&.medium {
font-size: 80%;
}
&.large {
font-size: 120%;
}
}
@media (prefers-color-scheme: dark) {
.date {
background-color: #262626;
color: #258ccd;
}
}
[data-theme="dark"] {
.date {
background-color: #262626;
color: #258ccd;
}
}
@media (prefers-color-scheme: light) {
.date {
background-color: #f0f0f0;
color: #009900;
}
}
[data-theme="light"] {
.date {
background-color: #f0f0f0;
color: #009900;
}
}
.date {
padding-left: 5px;
padding-right: 5px;
border-radius: 3px;
}
:deep(mark) {
border-radius: 2px;
background-color: var(--secondary-color);
animation: pops 200ms ease-out;
display: inline-block;
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}
</style>