From 75dd639ccad6b232f5bb033c68066bc1a9e4adfc Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Sun, 27 Sep 2026 06:02:13 +0800 Subject: [PATCH] =?UTF-8?q?fix(files):=20=E6=96=87=E4=BB=B6=E9=A1=B5?= =?UTF-8?q?=E5=81=9C=E6=9C=8D=E6=94=B9=E7=94=A8=E6=8E=A7=E5=88=B6=E5=8F=B0?= =?UTF-8?q?=E5=90=8C=E4=B8=80=E4=B8=AA=E6=8C=89=E9=92=AE=EF=BC=8C=E6=9C=89?= =?UTF-8?q?=E7=8E=A9=E5=AE=B6=E5=9C=A8=E7=BA=BF=E6=88=96=E4=BA=BA=E6=95=B0?= =?UTF-8?q?=E8=AF=BB=E4=B8=8D=E5=88=B0=E6=97=B6=E5=85=88=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- panel/src/components/PowerButton.test.tsx | 45 ++++++++++++++ panel/src/components/PowerButton.tsx | 13 +++- panel/src/i18n/resources/en-US/files.json | 3 +- panel/src/i18n/resources/zh-CN/files.json | 3 +- panel/src/pages/ServerFiles.test.tsx | 73 +++++++++++++++++++---- panel/src/pages/ServerFiles.tsx | 38 +++++------- 6 files changed, 135 insertions(+), 40 deletions(-) diff --git a/panel/src/components/PowerButton.test.tsx b/panel/src/components/PowerButton.test.tsx index 44d9fda..82e7052 100644 --- a/panel/src/components/PowerButton.test.tsx +++ b/panel/src/components/PowerButton.test.tsx @@ -273,3 +273,48 @@ describe("PowerButton on a server given up or being deleted", () => { expect(screen.getByText(t("servers:retiring_badge_delete"))).toBeTruthy(); }); }); + +describe("PowerButton on a page that needs the server down", () => { + const cases: { + phase: Phase; + desiredState?: "Running" | "Stopped"; + failed?: boolean; + shows: string; + disabled: boolean; + }[] = [ + { phase: "Running", desiredState: "Running", shows: "servers:stop", disabled: false }, + // A failed start gets Stop alone: a retry would start what the page waits to see down. + { phase: "Failed", desiredState: "Running", failed: true, shows: "servers:stop", disabled: false }, + // Woken while going down: Stop keeps it down. + { phase: "Stopping", desiredState: "Running", shows: "servers:stop", disabled: false }, + // A phase nobody can place offers Stop, never a wake. + { phase: "Unknown", desiredState: "Stopped", shows: "servers:stop", disabled: false }, + { phase: "Unknown", shows: "servers:stop", disabled: false }, + { phase: "Failed", desiredState: "Stopped", shows: "servers:stop", disabled: false }, + // Already on its way down: nothing to press. + { phase: "Stopping", desiredState: "Stopped", shows: "servers:stopping", disabled: true }, + { phase: "Running", desiredState: "Stopped", shows: "servers:stopping", disabled: true }, + ]; + + for (const c of cases) { + it(`${c.phase} asked ${c.desiredState ?? "(unknown)"}${c.failed ? " (failed)" : ""} offers ${c.shows}`, () => { + render( + , + ); + const buttons = screen.getAllByRole("button"); + expect(buttons).toHaveLength(1); + expect(buttons[0].textContent).toBe(t(c.shows)); + expect(buttons[0]).toHaveProperty("disabled", c.disabled); + }); + } + + it("still asks before disconnecting players", async () => { + stop.mockResolvedValue(undefined); + render(); + + await userEvent.click(screen.getByRole("button", { name: t("servers:stop") })); + + expect(stop).not.toHaveBeenCalled(); + expect(screen.getByText(t("servers:stop_confirm_players", { count: 2 }))).toBeTruthy(); + }); +}); diff --git a/panel/src/components/PowerButton.tsx b/panel/src/components/PowerButton.tsx index eb25ead..b0c0446 100644 --- a/panel/src/components/PowerButton.tsx +++ b/panel/src/components/PowerButton.tsx @@ -29,6 +29,10 @@ interface Props { /** A pending retirement: nothing starts the server until it is cancelled, so * the control gives way to a badge saying why. */ retiring?: RetireState; + /** Offer only ways down: a page that needs the server stopped (the file + * editor) never offers a wake or a retry, and a server it cannot place gets + * a Stop too. */ + stopOnly?: boolean; /** Called after the wake or stop was accepted, so the parent refetches. */ onChanged: () => void; size?: "sm" | "default"; @@ -54,6 +58,8 @@ function isUp(phase: Phase): boolean { // one asked to run with no pod yet offers Stop, which is the way out when it // never comes up. // A server given up or being deleted offers nothing to press, only why. +// The file editor passes stopOnly: it needs the server down, so it offers Stop +// alone, with the same question before disconnecting players. export function PowerButton({ name, phase, @@ -62,6 +68,7 @@ export function PowerButton({ playersOnline, playerCountUnknown, retiring, + stopOnly = false, onChanged, size = "sm", className, @@ -135,6 +142,8 @@ export function PowerButton({ control = ; } else if (submitted !== null) { control = inProgress(submitted); + } else if (failed && stopOnly) { + control = stopButton("outline", () => void run("stop")); } else if (failed) { control = (
@@ -147,10 +156,10 @@ export function PowerButton({ ); } else if (pending === "stop" || (phase === "Stopping" && pending === null)) { control = inProgress("stop"); - } else if (pending === "start" && phase === "Stopping") { + } else if (!stopOnly && pending === "start" && phase === "Stopping") { // Woken while stopping: the operator brings it back up once it is down. control = inProgress("wake"); - } else if (!on) { + } else if (!stopOnly && !on) { control = (
- + ) : listLoading && entries === null ? (