85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback } from "react";
|
|
|
|
import { useAppStore, useAppStoreApi } from "@/components/state-provider";
|
|
import {
|
|
getAgentUpdateJob,
|
|
getInstallJob,
|
|
previewAgentUpdate,
|
|
previewAgentUpdateUseDefault,
|
|
updateAgent,
|
|
updateAgentUseDefault,
|
|
type AgentUpdateJob,
|
|
} from "@/lib/api";
|
|
import { ApiError } from "@/lib/api/client";
|
|
// Thrown from a callback rather than rendered as a literal, so this uses the
|
|
// module-level `t`, which resolves at call time. The message surfaces in the
|
|
// runtime-update dialog through `agents:unableToStartUpdate`.
|
|
import { t } from "@/lib/i18n";
|
|
|
|
type MaintenanceConflict = {
|
|
active_job_id: string;
|
|
active_kind: "install" | "update";
|
|
};
|
|
|
|
function maintenanceConflict(error: unknown): MaintenanceConflict | null {
|
|
if (!(error instanceof ApiError) || error.status !== 409) return null;
|
|
if (!error.body || typeof error.body !== "object") return null;
|
|
const body = error.body as Record<string, unknown>;
|
|
if (typeof body.active_job_id !== "string") return null;
|
|
if (body.active_kind !== "install" && body.active_kind !== "update") return null;
|
|
return {
|
|
active_job_id: body.active_job_id,
|
|
active_kind: body.active_kind,
|
|
};
|
|
}
|
|
|
|
export function useAgentRuntimeUpdates() {
|
|
const store = useAppStoreApi();
|
|
const updateJobs = useAppStore((state) => state.updateJobs.byAgent);
|
|
|
|
const hydrateConflict = useCallback(
|
|
async (agentName: string, conflict: MaintenanceConflict): Promise<AgentUpdateJob> => {
|
|
if (conflict.active_kind === "update") {
|
|
const job = await getAgentUpdateJob(conflict.active_job_id, { cache: "no-store" });
|
|
store.getState().upsertAgentUpdateJob(job);
|
|
return job;
|
|
}
|
|
const job = await getInstallJob(conflict.active_job_id, { cache: "no-store" });
|
|
store.getState().upsertInstallJob(job.agent_name ? job : { ...job, agent_name: agentName });
|
|
throw new Error(t("agents:agentInstallAlreadyInProgress"));
|
|
},
|
|
[store],
|
|
);
|
|
|
|
const startUpdate = useCallback(
|
|
async (agentName: string, targetVersion: string, useDefault = false) => {
|
|
try {
|
|
const job = useDefault
|
|
? await updateAgentUseDefault(agentName)
|
|
: await updateAgent(agentName, targetVersion);
|
|
store.getState().upsertAgentUpdateJob(job);
|
|
return job;
|
|
} catch (error) {
|
|
const conflict = maintenanceConflict(error);
|
|
if (conflict) {
|
|
return hydrateConflict(agentName, conflict);
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
[hydrateConflict, store],
|
|
);
|
|
|
|
const previewUpdate = useCallback(
|
|
(agentName: string, targetVersion?: string, useDefault = false) =>
|
|
useDefault
|
|
? previewAgentUpdateUseDefault(agentName, { cache: "no-store" })
|
|
: previewAgentUpdate(agentName, targetVersion, { cache: "no-store" }),
|
|
[],
|
|
);
|
|
|
|
return { updateJobs, previewUpdate, startUpdate };
|
|
}
|