181 lines
7.6 KiB
TypeScript
181 lines
7.6 KiB
TypeScript
import { runCommand } from "./command";
|
|
import { type UniDeskConfig, repoRoot } from "./config";
|
|
|
|
export const dispatchCommands = ["docker.ps", "provider.upgrade", "host.ssh", "microservice.http", "echo"] as const;
|
|
export type DebugDispatchCommand = typeof dispatchCommands[number];
|
|
|
|
export function isDebugDispatchCommand(value: unknown): value is DebugDispatchCommand {
|
|
return dispatchCommands.includes(value as DebugDispatchCommand);
|
|
}
|
|
|
|
async function readJson(url: string, init?: RequestInit): Promise<unknown> {
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), 5000);
|
|
try {
|
|
const res = await fetch(url, { ...init, signal: controller.signal });
|
|
const text = await res.text();
|
|
return { ok: res.ok, status: res.status, body: text.length > 0 ? JSON.parse(text) : null };
|
|
} catch (error) {
|
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
}
|
|
|
|
function coreInternalFetch(path: string, init?: { method?: string; body?: unknown }): unknown {
|
|
const code = `
|
|
const res = await fetch(${JSON.stringify(`http://127.0.0.1:8080${path}`)}, ${JSON.stringify({
|
|
method: init?.method ?? "GET",
|
|
headers: init?.body === undefined ? undefined : { "content-type": "application/json" },
|
|
body: init?.body === undefined ? undefined : JSON.stringify(init.body),
|
|
})});
|
|
const text = await res.text();
|
|
let body = null;
|
|
try { body = text ? JSON.parse(text) : null; } catch { body = { text }; }
|
|
console.log(JSON.stringify({ ok: res.ok, status: res.status, body }));
|
|
`;
|
|
const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot);
|
|
if (result.exitCode !== 0) {
|
|
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
|
}
|
|
try {
|
|
return JSON.parse(result.stdout.trim()) as unknown;
|
|
} catch {
|
|
return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
|
}
|
|
}
|
|
|
|
function coreDockerStatusSummary(): unknown {
|
|
const code = `
|
|
const res = await fetch('http://127.0.0.1:8080/api/nodes/docker-status');
|
|
const text = await res.text();
|
|
let body = null;
|
|
try { body = text ? JSON.parse(text) : null; } catch { body = { text }; }
|
|
const dockerStatuses = (body?.dockerStatuses || []).map((item) => {
|
|
const status = item.dockerStatus || {};
|
|
return {
|
|
providerId: item.providerId,
|
|
name: item.name,
|
|
nodeStatus: item.nodeStatus,
|
|
updatedAt: item.updatedAt,
|
|
dockerStatus: {
|
|
ok: status.ok,
|
|
socketPresent: status.socketPresent,
|
|
collectedAt: status.collectedAt,
|
|
counts: status.counts,
|
|
daemon: status.daemon,
|
|
containersPreview: (status.containers || []).slice(0, 8).map((container) => ({
|
|
id: container.id,
|
|
name: container.name,
|
|
image: container.image,
|
|
state: container.state,
|
|
status: container.status,
|
|
ports: container.ports,
|
|
})),
|
|
},
|
|
};
|
|
});
|
|
console.log(JSON.stringify({ ok: res.ok, status: res.status, body: { ok: body?.ok === true, dockerStatuses } }));
|
|
`;
|
|
const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot);
|
|
if (result.exitCode !== 0) {
|
|
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
|
}
|
|
try {
|
|
return JSON.parse(result.stdout.trim()) as unknown;
|
|
} catch {
|
|
return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
|
}
|
|
}
|
|
|
|
function coreSystemStatusSummary(): unknown {
|
|
const code = `
|
|
const res = await fetch('http://127.0.0.1:8080/api/nodes/system-status?limit=24');
|
|
const text = await res.text();
|
|
let body = null;
|
|
try { body = text ? JSON.parse(text) : null; } catch { body = { text }; }
|
|
const systemStatuses = (body?.systemStatuses || []).map((item) => {
|
|
const current = item.current || {};
|
|
return {
|
|
providerId: item.providerId,
|
|
name: item.name,
|
|
nodeStatus: item.nodeStatus,
|
|
updatedAt: item.updatedAt,
|
|
current: item.current ? {
|
|
ok: current.ok,
|
|
collectedAt: current.collectedAt,
|
|
cpu: current.cpu,
|
|
memory: current.memory,
|
|
disk: current.disk,
|
|
} : null,
|
|
historyPreview: (item.history || []).slice(-8),
|
|
historyCount: (item.history || []).length,
|
|
};
|
|
});
|
|
console.log(JSON.stringify({ ok: res.ok, status: res.status, body: { ok: body?.ok === true, systemStatuses } }));
|
|
`;
|
|
const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot);
|
|
if (result.exitCode !== 0) {
|
|
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
|
}
|
|
try {
|
|
return JSON.parse(result.stdout.trim()) as unknown;
|
|
} catch {
|
|
return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
|
}
|
|
}
|
|
|
|
export async function debugHealth(config: UniDeskConfig): Promise<unknown> {
|
|
return {
|
|
coreInternal: await coreInternalFetch("/health"),
|
|
overviewInternal: await coreInternalFetch("/api/overview"),
|
|
nodesInternal: await coreInternalFetch("/api/nodes"),
|
|
systemStatusInternal: coreSystemStatusSummary(),
|
|
dockerStatusInternal: coreDockerStatusSummary(),
|
|
frontendPublic: await readJson(`http://127.0.0.1:${config.network.frontend.port}/health`),
|
|
providerIngressPublic: await readJson(`http://127.0.0.1:${config.network.providerIngress.port}/health`),
|
|
publicExposureBoundary: {
|
|
coreHostPort: { port: config.network.core.port, expected: "not-exposed" },
|
|
databaseHostPort: { port: config.network.database.port, expected: "restricted-to-code-queue-provider" },
|
|
oaEventFlowHostPort: { port: 4255, expected: "restricted-to-code-queue-provider" },
|
|
},
|
|
};
|
|
}
|
|
|
|
async function waitForTask(taskId: string, timeoutMs: number): Promise<unknown> {
|
|
const started = Date.now();
|
|
let latest: unknown = null;
|
|
while (Date.now() - started < timeoutMs) {
|
|
latest = coreInternalFetch("/api/tasks?limit=100");
|
|
const tasks = (latest as { body?: { tasks?: Array<{ id?: string; status?: string; result?: unknown }> } }).body?.tasks ?? [];
|
|
const task = tasks.find((item) => item.id === taskId);
|
|
if (task?.status === "succeeded" || task?.status === "failed") return { ok: true, task };
|
|
await Bun.sleep(500);
|
|
}
|
|
return { ok: false, timeoutMs, latest };
|
|
}
|
|
|
|
export async function debugTask(_config: UniDeskConfig, taskId: string): Promise<unknown> {
|
|
const tasksResponse = coreInternalFetch("/api/tasks?limit=100");
|
|
const tasks = (tasksResponse as { body?: { tasks?: Array<{ id?: string }> } }).body?.tasks ?? [];
|
|
const task = taskId === "latest" ? tasks[0] : tasks.find((item) => item.id === taskId);
|
|
return { tasksResponse, taskId, task: task ?? null };
|
|
}
|
|
|
|
export async function debugDispatch(
|
|
config: UniDeskConfig,
|
|
providerId: string,
|
|
command: DebugDispatchCommand,
|
|
payload?: Record<string, unknown>,
|
|
waitMs = 0,
|
|
): Promise<unknown> {
|
|
const dispatchPayload = payload ?? (command === "provider.upgrade" ? { source: "cli-debug", mode: "plan" } : { source: "cli-debug" });
|
|
const dispatch = coreInternalFetch("/api/dispatch", {
|
|
method: "POST",
|
|
body: { providerId, command, payload: dispatchPayload },
|
|
});
|
|
const taskId = (dispatch as { body?: { taskId?: string } }).body?.taskId ?? "";
|
|
const wait = waitMs > 0 && taskId.length > 0 ? await waitForTask(taskId, waitMs) : null;
|
|
return { dispatch, wait };
|
|
}
|