import { Component, ComputedRef, Ref } from "vue"; import { flattenJSON } from "@/utils"; import ComplexLogItem from "@/components/LogViewer/ComplexLogItem.vue"; import SimpleLogItem from "@/components/LogViewer/SimpleLogItem.vue"; import ContainerEventLogItem from "@/components/LogViewer/ContainerEventLogItem.vue"; import SkippedEntriesLogItem from "@/components/LogViewer/SkippedEntriesLogItem.vue"; import LoadMoreLogItem from "@/components/LogViewer/LoadMoreLogItem.vue"; export type JSONValue = string | number | boolean | JSONObject | Array; export type JSONObject = { [x: string]: JSONValue }; export type Position = "start" | "end" | "middle" | undefined; export type Std = "stdout" | "stderr"; export type Level = | "error" | "warn" | "warning" | "info" | "debug" | "trace" | "severe" | "critical" | "fatal" | "unknown"; export interface LogEvent { readonly m: string | JSONObject; readonly ts: number; readonly id: number; readonly l: Level; readonly p: Position; readonly s: "stdout" | "stderr" | "unknown"; readonly c: string; readonly rm: string; } export abstract class LogEntry { protected readonly _message: T; constructor( message: T, public readonly containerID: string, public readonly id: number, public readonly date: Date, public readonly std: Std, public readonly rawMessage: string, public readonly level?: Level, ) { this._message = message; } public get message(): T { return this._message; } abstract getComponent(): Component; } export class SimpleLogEntry extends LogEntry { constructor( message: string, containerID: string, id: number, date: Date, public readonly level: Level, public readonly position: Position, public readonly std: Std, public readonly rawMessage: string, ) { super(message, containerID, id, date, std, rawMessage, level); } getComponent(): Component { return SimpleLogItem; } } export class ComplexLogEntry extends LogEntry { private readonly filteredMessage: ComputedRef>; constructor( message: JSONObject, containerID: string, id: number, date: Date, public readonly level: Level, public readonly std: Std, public readonly rawMessage: string, visibleKeys?: Ref>, ) { super(message, containerID, id, date, std, rawMessage, level); if (visibleKeys) { this.filteredMessage = computed(() => { if (visibleKeys.value.size === 0) { return flattenJSON(message); } else { const flatJSON = flattenJSON(message); const filteredJSON: Record = {}; for (const [keys, enabled] of visibleKeys.value.entries()) { const key = keys.join("."); if (!enabled) { delete flatJSON[key]; continue; } filteredJSON[key] = flatJSON[key]; delete flatJSON[key]; } return { ...filteredJSON, ...flatJSON }; } }); } else { this.filteredMessage = computed(() => flattenJSON(message)); } } getComponent(): Component { return ComplexLogItem; } public get message(): Record { return unref(this.filteredMessage); } public get unfilteredMessage(): JSONObject { return this._message; } static fromLogEvent(event: ComplexLogEntry, visibleKeys: Ref>): ComplexLogEntry { return new ComplexLogEntry( event._message, event.containerID, event.id, event.date, event.level, event.std, event.rawMessage, visibleKeys, ); } } export class ContainerEventLogEntry extends LogEntry { constructor( message: string, containerID: string, date: Date, public readonly event: "container-stopped" | "container-started", ) { super(message, containerID, date.getTime(), date, "stderr", "unknown"); } getComponent(): Component { return ContainerEventLogItem; } } export class SkippedLogsEntry extends LogEntry { private _totalSkipped = ref(0); private lastSkipped: LogEntry; constructor( date: Date, totalSkipped: number, public readonly firstSkipped: LogEntry, lastSkipped: LogEntry, private readonly loader: (i: SkippedLogsEntry) => Promise, ) { super("", "", date.getTime(), date, "stderr", "info"); this._totalSkipped.value = totalSkipped; this.lastSkipped = lastSkipped; } getComponent(): Component { return SkippedEntriesLogItem; } public get message(): string { return `Skipped ${this._totalSkipped.value} entries`; } public addSkippedEntries(totalSkipped: number, lastItem: LogEntry) { this._totalSkipped.value += totalSkipped; this.lastSkipped = lastItem; } public get lastSkippedLog(): LogEntry { return this.lastSkipped; } public async loadSkippedEntries(): Promise { await this.loader(this); } public get totalSkipped(): number { return unref(this._totalSkipped); } } export class LoadMoreLogEntry extends LogEntry { constructor( date: Date, private readonly loader: (i: LoadMoreLogEntry) => Promise, public readonly rememberScrollPosition: boolean = true, ) { super("", "", date.getTime(), date, "stderr", "info"); } getComponent(): Component { return LoadMoreLogItem; } async loadMore(): Promise { await this.loader(this); } } export function asLogEntry(event: LogEvent): LogEntry { if (isObject(event.m)) { return new ComplexLogEntry( event.m, event.c, event.id, new Date(event.ts), event.l, event.s === "unknown" ? "stderr" : (event.s ?? "stderr"), event.rm, ); } else { return new SimpleLogEntry( event.m, event.c, event.id, new Date(event.ts), event.l, event.p, event.s === "unknown" ? "stderr" : (event.s ?? "stderr"), event.rm, ); } }