mirror of
https://github.com/amir20/dozzle.git
synced 2026-06-22 20:00:11 +00:00
refactor(ui): tighten TypeScript inference, drop redundant casts (#4779)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,9 +21,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { Container } from "@/models/Container";
|
||||
import type { Host } from "@/stores/hosts";
|
||||
// @ts-ignore
|
||||
import PhCpu from "~icons/ph/cpu";
|
||||
// @ts-ignore
|
||||
import PhMemory from "~icons/ph/memory";
|
||||
|
||||
const {
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Container } from "@/models/Container";
|
||||
// @ts-ignore
|
||||
import PhWarning from "~icons/ph/warning-fill";
|
||||
|
||||
const WARN = 0.85;
|
||||
|
||||
@@ -65,9 +65,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { Host } from "@/stores/hosts";
|
||||
import { Container } from "@/models/Container";
|
||||
// @ts-ignore
|
||||
import PhCpu from "~icons/ph/cpu";
|
||||
// @ts-ignore
|
||||
import PhMemory from "~icons/ph/memory";
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -189,11 +189,8 @@ import { Container } from "@/models/Container";
|
||||
import { sessionHost } from "@/composable/storage";
|
||||
import { showAllContainers, groupContainers } from "@/stores/settings";
|
||||
|
||||
// @ts-ignore
|
||||
import Pin from "~icons/ph/map-pin-simple";
|
||||
// @ts-ignore
|
||||
import Stack from "~icons/ph/stack";
|
||||
// @ts-ignore
|
||||
import Containers from "~icons/octicon/container-24";
|
||||
|
||||
const containerStore = useContainerStore();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<span class="key">{{ name }}=</span>
|
||||
<span class="value" v-if="value === null"><null></span>
|
||||
<ReuseTemplate :data="value" v-else-if="isObject(value) || Array.isArray(value)" />
|
||||
<span v-else class="value" :class="typeof value" v-html="stripAnsi(value.toString())"></span>
|
||||
<span v-else class="value" :class="typeof value" v-html="stripAnsi(String(value))"></span>
|
||||
</li>
|
||||
<li v-else-if="Array.isArray(data)">
|
||||
<ul class="array inline-flex flex-wrap space-x-1">
|
||||
|
||||
@@ -18,13 +18,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// @ts-ignore
|
||||
import PhNetwork from "~icons/ph/network";
|
||||
// @ts-ignore
|
||||
import PhHardDrives from "~icons/ph/hard-drives";
|
||||
// @ts-ignore
|
||||
import PhArrowUp from "~icons/ph/arrow-up";
|
||||
// @ts-ignore
|
||||
import PhArrowDown from "~icons/ph/arrow-down";
|
||||
|
||||
const { networkRx, networkTx, diskRead, diskWrite } = defineProps<{
|
||||
|
||||
@@ -66,9 +66,7 @@ import { Container, Stat, emptyStat } from "@/models/Container";
|
||||
import StatCard from "@/components/LogViewer/StatCard.vue";
|
||||
import IOCard from "@/components/LogViewer/IOCard.vue";
|
||||
import BarChart from "@/components/BarChart.vue";
|
||||
// @ts-ignore
|
||||
import PhCpu from "~icons/ph/cpu";
|
||||
// @ts-ignore
|
||||
import PhMemory from "~icons/ph/memory";
|
||||
|
||||
const { containers } = defineProps<{
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Service, Stack } from "@/models/Stack";
|
||||
import { Container, GroupedContainers } from "@/models/Container";
|
||||
import { parseMessage } from "@/composable/loadBetween";
|
||||
import { useLogLoader } from "@/composable/logLoader";
|
||||
import { parseEventData } from "@/utils/events";
|
||||
|
||||
const { isSearching, debouncedSearchFilter, inverseFilter } = useSearchFilter();
|
||||
|
||||
@@ -170,11 +171,11 @@ function useLogStream(url: Ref<string>, container?: Ref<Container>) {
|
||||
searchStatus.value = { active: isSearching.value, done: false, matches: 0 };
|
||||
es = new EventSource(urlWithParams.value);
|
||||
es.addEventListener("container-event", (e) => {
|
||||
const event = JSON.parse((e as MessageEvent).data) as {
|
||||
const event = parseEventData<{
|
||||
actorId: string;
|
||||
name: "container-stopped" | "container-started";
|
||||
time: string;
|
||||
};
|
||||
}>(e);
|
||||
const containerEvent = new ContainerEventLogEntry(
|
||||
event.name == "container-started" ? "Container started" : "Container stopped",
|
||||
event.actorId,
|
||||
@@ -188,18 +189,18 @@ function useLogStream(url: Ref<string>, container?: Ref<Container>) {
|
||||
});
|
||||
|
||||
es.addEventListener("logs-backfill", (e) => {
|
||||
const data = JSON.parse((e as MessageEvent).data) as LogEvent[];
|
||||
const data = parseEventData<LogEvent[]>(e);
|
||||
const logs = data.map((e) => asLogEntry(e));
|
||||
messages.value = [...logs, ...messages.value];
|
||||
});
|
||||
|
||||
es.addEventListener("search-status", (e) => {
|
||||
const data = JSON.parse((e as MessageEvent).data) as {
|
||||
const data = parseEventData<{
|
||||
scannedTo: string;
|
||||
matches: number;
|
||||
done: boolean;
|
||||
reason?: "capped" | "exhausted";
|
||||
};
|
||||
}>(e);
|
||||
searchStatus.value = {
|
||||
active: !data.done,
|
||||
done: data.done,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Completion } from "@codemirror/autocomplete";
|
||||
import type { Completion, CompletionContext, CompletionSource } from "@codemirror/autocomplete";
|
||||
|
||||
export interface ExprEditorOptions {
|
||||
parent: HTMLElement;
|
||||
@@ -44,9 +44,9 @@ export function createContainerHints(
|
||||
{ label: '"healthy"', detail: "health value", type: "string" },
|
||||
{ label: '"unhealthy"', detail: "health value", type: "string" },
|
||||
{ label: '"none"', detail: "health value", type: "string" },
|
||||
...containerNames.map((name) => ({ label: `"${name}"`, detail: "container name", type: "string" }) as Completion),
|
||||
...imageNames.map((image) => ({ label: `"${image}"`, detail: "image name", type: "string" }) as Completion),
|
||||
...hostNames.map((host) => ({ label: `"${host}"`, detail: "host name", type: "string" }) as Completion),
|
||||
...containerNames.map((name): Completion => ({ label: `"${name}"`, detail: "container name", type: "string" })),
|
||||
...imageNames.map((image): Completion => ({ label: `"${image}"`, detail: "image name", type: "string" })),
|
||||
...hostNames.map((host): Completion => ({ label: `"${host}"`, detail: "host name", type: "string" })),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export function createLogHints(messageKeys?: string[]): Completion[] {
|
||||
{ label: "timestamp", detail: "unix timestamp", type: "property" },
|
||||
{ label: "id", detail: "log entry ID", type: "property" },
|
||||
...(messageKeys ?? []).map(
|
||||
(key) => ({ label: `message.${key}`, detail: "message field", type: "property" }) as Completion,
|
||||
(key): Completion => ({ label: `message.${key}`, detail: "message field", type: "property" }),
|
||||
),
|
||||
...exprOperators,
|
||||
{ label: '"error"', detail: "level value", type: "string" },
|
||||
@@ -118,8 +118,8 @@ export function createEventHints(): Completion[] {
|
||||
];
|
||||
}
|
||||
|
||||
function createAutocomplete(getHints: () => Completion[]) {
|
||||
return (context: any) => {
|
||||
function createAutocomplete(getHints: () => Completion[]): CompletionSource {
|
||||
return (context: CompletionContext) => {
|
||||
const word = context.matchBefore(/[\w"=!&|]+/);
|
||||
if (!word && !context.explicit) return null;
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// @ts-ignore - splitpanes types are not available
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import { collapseNav } from "@/stores/settings";
|
||||
import SideDrawer from "@/components/common/SideDrawer.vue";
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Ref, UnwrapNestedRefs } from "vue";
|
||||
import type { ContainerHealth, ContainerJson, ContainerStat } from "@/types/Container";
|
||||
import { Container } from "@/models/Container";
|
||||
import i18n from "@/modules/i18n";
|
||||
import { parseEventData } from "@/utils/events";
|
||||
import { Host } from "./hosts";
|
||||
|
||||
const { showToast, removeToast } = useToast();
|
||||
@@ -54,11 +55,9 @@ export const useContainerStore = defineStore("container", () => {
|
||||
}
|
||||
});
|
||||
|
||||
es.addEventListener("containers-changed", (e: Event) =>
|
||||
updateContainers(JSON.parse((e as MessageEvent).data) as ContainerJson[]),
|
||||
);
|
||||
es.addEventListener("containers-changed", (e) => updateContainers(parseEventData<ContainerJson[]>(e)));
|
||||
es.addEventListener("container-stat", (e) => {
|
||||
const stat = JSON.parse((e as MessageEvent).data) as ContainerStat;
|
||||
const stat = parseEventData<ContainerStat>(e);
|
||||
const container = allContainersById.value[stat.id] as unknown as UnwrapNestedRefs<Container>;
|
||||
if (container) {
|
||||
const { id, ...rest } = stat;
|
||||
@@ -66,7 +65,7 @@ export const useContainerStore = defineStore("container", () => {
|
||||
}
|
||||
});
|
||||
es.addEventListener("container-event", (e) => {
|
||||
const event = JSON.parse((e as MessageEvent).data) as { actorId: string; name: string; time: string };
|
||||
const event = parseEventData<{ actorId: string; name: string; time: string }>(e);
|
||||
const container = allContainersById.value[event.actorId];
|
||||
if (container) {
|
||||
switch (event.name) {
|
||||
@@ -88,7 +87,7 @@ export const useContainerStore = defineStore("container", () => {
|
||||
});
|
||||
|
||||
es.addEventListener("container-updated", (e) => {
|
||||
const container = JSON.parse((e as MessageEvent).data) as ContainerJson;
|
||||
const container = parseEventData<ContainerJson>(e);
|
||||
const existing = allContainersById.value[container.id];
|
||||
if (existing) {
|
||||
existing.name = container.name;
|
||||
@@ -103,12 +102,12 @@ export const useContainerStore = defineStore("container", () => {
|
||||
});
|
||||
|
||||
es.addEventListener("update-host", (e) => {
|
||||
const host = JSON.parse((e as MessageEvent).data) as Host;
|
||||
const host = parseEventData<Host>(e);
|
||||
updateHost(host);
|
||||
});
|
||||
|
||||
es.addEventListener("container-health", (e) => {
|
||||
const event = JSON.parse((e as MessageEvent).data) as { actorId: string; health: ContainerHealth };
|
||||
const event = parseEventData<{ actorId: string; health: ContainerHealth }>(e);
|
||||
const container = allContainersById.value[event.actorId];
|
||||
if (container) {
|
||||
container.health = event.health;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Parses the JSON payload of an SSE `MessageEvent`. EventSource listeners
|
||||
* receive a `MessageEvent` whose `data` is an untyped string, so the parsed
|
||||
* shape is provided via the type parameter at each call site.
|
||||
*/
|
||||
export function parseEventData<T>(event: Event): T {
|
||||
return JSON.parse((event as MessageEvent).data) as T;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ export function getDeep(obj: Record<string, any>, path: string[]) {
|
||||
return path.reduce((acc, key) => acc?.[key], obj);
|
||||
}
|
||||
|
||||
export function isObject(value: any): value is Record<string, any> {
|
||||
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
|
||||
+9
-3
@@ -13,10 +13,16 @@
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {
|
||||
"@/*": ["./assets/*"],
|
||||
"@/*": ["./assets/*"]
|
||||
},
|
||||
"jsx": "preserve",
|
||||
"types": ["vitest", "vite/client", "unplugin-vue-macros/macros-global", "vite-plugin-vue-layouts/client"],
|
||||
"types": [
|
||||
"vitest",
|
||||
"vite/client",
|
||||
"unplugin-vue-macros/macros-global",
|
||||
"vite-plugin-vue-layouts/client",
|
||||
"unplugin-icons/types/vue"
|
||||
]
|
||||
},
|
||||
"exclude": ["dist", "node_modules", "e2e"],
|
||||
"exclude": ["dist", "node_modules", "e2e"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user