From c1bf1a81a25135259a3f4da92a964cff8c6fb710 Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Sun, 27 Sep 2026 15:25:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(panel):=20=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=9A=84=E5=88=B7=E6=96=B0=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E5=8F=AA=E4=B8=BA=E8=87=AA=E5=B7=B1=E7=9A=84=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E8=BD=AC=E5=9C=88=E5=92=8C=E7=A6=81=E7=94=A8=EF=BC=8C=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E8=BD=AE=E8=AF=A2=E6=97=B6=E4=BF=9D=E6=8C=81=E5=8F=AF?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- panel/src/pages/servers/ServersPage.test.tsx | 38 ++++++++++++++++++++ panel/src/pages/servers/ServersPage.tsx | 16 +++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/panel/src/pages/servers/ServersPage.test.tsx b/panel/src/pages/servers/ServersPage.test.tsx index 37b6dd0..84fb625 100644 --- a/panel/src/pages/servers/ServersPage.test.tsx +++ b/panel/src/pages/servers/ServersPage.test.tsx @@ -428,3 +428,41 @@ describe("ServersPage with nothing matching", () => { expect(await tableOrder()).toEqual(names.slice(0, 20)); }); }); + +describe("ServersPage refresh button", () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it("stays still and clickable through the background reread, and spins for its own click", async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + calls.fleet.mockResolvedValue([row("survival", {})]); + const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); + render( + + + , + ); + await tableOrder(); + const button = screen.getByRole("button", { name: "Refresh now" }) as HTMLButtonElement; + const spinning = () => button.querySelector("svg")?.classList.contains("animate-spin"); + + // The background reread hangs; the button does not show it. + let finish: (v: FleetServer[]) => void = () => {}; + calls.fleet.mockReturnValueOnce(new Promise((r) => (finish = r))); + await act(() => vi.advanceTimersByTimeAsync(10_000)); + expect(calls.fleet).toHaveBeenCalledTimes(2); + expect(button.disabled).toBe(false); + expect(spinning()).toBe(false); + + calls.fleet.mockReturnValueOnce(new Promise((r) => (finish = r))); + await user.click(button); + expect(calls.fleet).toHaveBeenCalledTimes(3); + expect(button.disabled).toBe(true); + expect(spinning()).toBe(true); + + await act(async () => finish([row("survival", {})])); + expect(button.disabled).toBe(false); + expect(spinning()).toBe(false); + }); +}); diff --git a/panel/src/pages/servers/ServersPage.tsx b/panel/src/pages/servers/ServersPage.tsx index a5a7aae..a687602 100644 --- a/panel/src/pages/servers/ServersPage.tsx +++ b/panel/src/pages/servers/ServersPage.tsx @@ -148,6 +148,16 @@ export function ServersPage() { usePolling(reload, REFRESH_MS); + // The refresh button answers its own click alone. The background reread every + // few seconds leaves it still and clickable; a spin and a disabled button on + // each tick read as the page stalling. + const [refreshing, setRefreshing] = useState(false); + if (refreshing && !loading) setRefreshing(false); + const refresh = () => { + setRefreshing(true); + reload(); + }; + const servers = useMemo(() => { if (!data) return []; if (isAdmin) { @@ -253,12 +263,12 @@ export function ServersPage() { {isAdmin && cfg && }