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:
Amir Raminfar
2026-06-04 08:25:31 -07:00
committed by GitHub
parent e283a3e361
commit de368a1d97
14 changed files with 39 additions and 40 deletions
-2
View File
@@ -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;
-2
View File
@@ -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<{
-3
View File
@@ -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">&lt;null&gt;</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">
-4
View File
@@ -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<{
+6 -5
View File
@@ -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,
+7 -7
View File
@@ -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;
-1
View File
@@ -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";
+7 -8
View File
@@ -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;
+8
View File
@@ -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;
}
+1 -1
View File
@@ -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
View File
@@ -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"]
}