Files
model-platform/frontend/app/features/schedules/state/logsSlice.ts
T

118 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Logs slice: per-node-run log fetch + display state.
// toggleLog owns the open/closed toggle and lazily loads content from the
// signed URL on first open.
import type { StateCreator } from "zustand";
import { requireApi } from "./helpers";
import type { LogState } from "./types";
import type { SchedulesStore } from "./useSchedulesStore";
// ---- State ----
export type LogsSliceState = {
openLogNodeRunIds: Set<string>;
logStates: Record<string, LogState>;
};
// ---- Actions ----
export type LogsSliceActions = {
// pure setters
setOpenLogNodeRunIds: (
value: Set<string> | ((current: Set<string>) => Set<string>),
) => void;
setLogStates: (
updater: (current: Record<string, LogState>) => Record<string, LogState>,
) => void;
// mutations
toggleLog: (runId: string, nodeRunId: string) => Promise<void>;
};
// `SchedulesStore` is the combined state+actions of all slices, so `get()`
// returns a fully typed snapshot — no `any` propagation.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const createLogsSlice: StateCreator<SchedulesStore, [], [], LogsSliceState & LogsSliceActions> = (set, get) => ({
// ---- initial state ----
openLogNodeRunIds: new Set(),
logStates: {},
// ---- pure setters ----
setOpenLogNodeRunIds: (value) =>
set((s: any) => ({
openLogNodeRunIds:
typeof value === "function"
? (value as (current: Set<string>) => Set<string>)(
s.openLogNodeRunIds,
)
: value,
})),
setLogStates: (updater) =>
set((s: any) => ({ logStates: updater(s.logStates) })),
// ---- mutations ----
toggleLog: async (runId, nodeRunId) => {
const api = requireApi();
const state = get();
const open = state.openLogNodeRunIds;
if (open.has(nodeRunId)) {
const next = new Set(open);
next.delete(nodeRunId);
set({ openLogNodeRunIds: next });
return;
}
const next = new Set(open);
next.add(nodeRunId);
set({ openLogNodeRunIds: next });
if (state.logStates[nodeRunId]?.content) return;
set((s: any) => ({
logStates: {
...s.logStates,
[nodeRunId]: {
loading: true,
content: null,
fileName: null,
error: null,
},
},
}));
try {
const artifacts = await api.getScheduleNodeRunArtifacts(runId, nodeRunId);
if (!artifacts.log) throw new Error("本次节点执行没有可用日志");
const response = await fetch(artifacts.log.url, {
credentials: "same-origin",
cache: "no-store",
});
if (!response.ok) {
throw new Error(`日志读取失败(HTTP ${response.status}`);
}
const content = await response.text();
set((s: any) => ({
logStates: {
...s.logStates,
[nodeRunId]: {
loading: false,
content,
fileName: artifacts.log?.file_name ?? null,
error: null,
},
},
}));
} catch (error) {
set((s: any) => ({
logStates: {
...s.logStates,
[nodeRunId]: {
loading: false,
content: null,
fileName: null,
error: error instanceof Error ? error.message : "日志读取失败",
},
},
}));
}
},
});