fix(panel): diagnose runtime failures and add owner recovery controls

This commit is contained in:
Lemon-miaow committed 2026-10-05 18:06:55 +08:00
1 parent d7ae1e66c1
commit a7d3ca4c9f
47 files changed
+1590 -102

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
import { test, expect, t, expectFitsScreen } from "./fixtures";
test("platform policy is an Owner entry and persists after reload", async ({ page, signIn }) => {
await signIn("owner");
await page.goto("/admin/platform");
await expect(page.getByRole("heading", { name: t("admin:platform_title"), exact: true })).toBeVisible();
const limit = page.getByLabel(t("admin:platform_limit"));
await expect(limit).toBeEnabled();
await limit.fill("3");
await page.getByLabel(t("admin:platform_cooldown")).fill("45");
await page.getByRole("button", { name: t("admin:platform_save") }).click();
await expect(page.getByText(t("admin:platform_saved"), { exact: true })).toBeVisible();
await page.reload();
await expect(limit).toHaveValue("3");
await expect(page.getByLabel(t("admin:platform_cooldown"))).toHaveValue("45");
await expectFitsScreen(page);
await signIn("linked");
await page.goto("/admin/platform");
await expect(page.getByRole("heading", { name: t("admin:platform_title"), exact: true })).toBeHidden();
expect((await page.request.get("/api/v1/settings/wake-policy")).status()).toBe(403);
});
test("source disclosure does not scale on press and long pages keep bottom breathing room", async ({ page, signIn }) => {
await signIn("owner");
for (const viewport of [{ width: 1440, height: 600 }, { width: 375, height: 812 }]) {
await page.setViewportSize(viewport);
await page.goto("/admin/auth-sources");
const disclosure = page.getByRole("button", { name: t("authSources:expand", { name: "littleskin" }) });
const box = await disclosure.boundingBox();
await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2);
await page.mouse.down();
await expect.poll(() => disclosure.evaluate((el) => getComputedStyle(el).transform)).toMatch(/^(none|matrix\(1, 0, 0, 1, 0, 0\))$/);
await page.mouse.up();
await expect(page.getByRole("button", { name: t("authSources:collapse", { name: "littleskin" }) })).toHaveAttribute("aria-expanded", "true");
const main = page.getByRole("main");
await main.evaluate((el) => { el.scrollTop = el.scrollHeight; });
const save = page.getByRole("button", { name: t("authSources:save"), exact: true });
await expect(save).toBeInViewport();
const room = await save.evaluate((el) => el.closest("main")!.getBoundingClientRect().bottom - el.parentElement!.parentElement!.getBoundingClientRect().bottom);
expect(room).toBeGreaterThanOrEqual(23);
await expectFitsScreen(page);
}
});
test("startup shows actual scheduling blockage, stale API state, then recovery", async ({ page, signIn }) => {
await signIn("owner");
let state: "blocked" | "outage" | "running" = "blocked";
await page.route("**/api/v1/servers/survival/status", async (route) => {
if (state === "outage") { await route.fulfill({ status: 503, json: { error: { code: "upstream_unavailable", message: "test API outage" } } }); return; }
const original = await route.fetch();
const data = await original.json();
await route.fulfill({ json: { ...data, phase: state === "running" ? "Running" : "Starting", desiredState: "Running", ready: state === "running", startup: state === "blocked" ? { stage: "scheduling", reason: "Unschedulable", message: "0/1 nodes: Insufficient memory", startedAt: new Date(Date.now() - 60000).toISOString(), logsAvailable: false } : undefined } });
});
await page.goto("/servers/survival");
await expect(page.getByText(t("servers:startup_scheduling"), { exact: true })).toBeVisible();
await expect(page.getByText(t("servers:startup_memory"), { exact: true })).toBeVisible();
await expect(page.getByText(t("servers:log_ended"), { exact: true })).toBeHidden();
await expect(page.getByRole("button", { name: t("servers:retire_delete"), exact: true })).toBeHidden();
state = "outage";
await expect(page.getByText(t("servers:status_stale"), { exact: true })).toBeVisible({ timeout: 7000 });
await expect(page.getByText(t("servers:startup_scheduling"), { exact: true })).toBeHidden();
await expect(page.getByRole("button", { name: t("servers:stop"), exact: true })).toBeHidden();
state = "running";
await expect(page.getByText(t("servers:status_stale"), { exact: true })).toBeHidden({ timeout: 7000 });
await page.getByRole("button", { name: t("servers:more_actions"), exact: true }).click();
await expect(page.getByRole("button", { name: t("servers:retire_delete"), exact: true })).toBeVisible();
});
test("Owner emergency stop stays secondary, confirms name, and distinguishes acceptance from shutdown", async ({ page, signIn }) => {
await signIn("owner");
await page.goto("/servers/survival");
await page.getByRole("button", { name: t("servers:more_actions"), exact: true }).click();
await page.getByRole("button", { name: t("servers:emergency_stop"), exact: true }).click();
const confirm = page.getByRole("button", { name: t("servers:emergency_stop"), exact: true });
await expect(confirm).toBeDisabled();
await page.getByLabel(t("servers:retire_confirm_label"), { exact: false }).fill("survival");
await confirm.click();
await expect(page.getByText(t("servers:emergency_stop_accepted"), { exact: true })).toBeVisible();
await expect(confirm).toBeDisabled();
await page.getByRole("dialog").getByRole("button", { name: t("common:close_sr"), exact: true }).last().click();
await expectFitsScreen(page);
await page.goto("/admin/platform");
const ownerNav = page.getByRole("navigation").getByRole("link");
const links = await ownerNav.evaluateAll((elements) => elements.map((el) => el.getAttribute("href")));
expect(links.at(-1)).toBe("/admin/platform");
});