diff --git a/panel/src/pages/servers/ServersPage.test.tsx b/panel/src/pages/servers/ServersPage.test.tsx
index 51c1eba..37b6dd0 100644
--- a/panel/src/pages/servers/ServersPage.test.tsx
+++ b/panel/src/pages/servers/ServersPage.test.tsx
@@ -111,6 +111,36 @@ describe("ServersPage fleet ownership", () => {
expect(skyblock.getByRole("button", { name: /Claim/ })).toBeTruthy();
});
+ it("offers a player only the claim on a server nobody owns, and an admin its wake and console too", async () => {
+ tier.isAdmin = false;
+ calls.myServers.mockResolvedValue([
+ { name: "skyblock", subdomain: "skyblock", owned: false, claimable: true, phase: "Stopped", playersOnline: 0, playersMax: 20 },
+ ] satisfies MyServerView[]);
+ const { unmount } = render(
+
+
+ ,
+ );
+ let skyblock = await tableRow("skyblock");
+ const names = skyblock.getAllByRole("button").map((b) => b.getAttribute("aria-label") ?? b.textContent?.trim());
+ expect(names).toEqual(["Copy address skyblock.example.test:25570", "Claim"]);
+ expect(skyblock.queryByRole("link")).toBeNull();
+ unmount();
+
+ tier.isAdmin = true;
+ calls.fleet.mockResolvedValue([row("skyblock", { claimable: true })]);
+ render(
+
+
+ ,
+ );
+ skyblock = await tableRow("skyblock");
+ expect(skyblock.getByRole("button", { name: /Claim/ })).toBeTruthy();
+ expect(skyblock.getByRole("button", { name: /Wake/ })).toHaveProperty("disabled", false);
+ expect(skyblock.getByRole("link").getAttribute("href")).toBe("/servers/skyblock");
+ expect(within(skyblock.getByRole("link")).getByRole("button", { name: /Console/ })).toHaveProperty("disabled", false);
+ });
+
it("says the owner is unknown and offers no claim when the lookup failed", async () => {
calls.fleet.mockResolvedValue([row("survival", { ownerUnknown: true })]);
render(
diff --git a/panel/src/pages/servers/ServersPage.tsx b/panel/src/pages/servers/ServersPage.tsx
index fe990ae..a5a7aae 100644
--- a/panel/src/pages/servers/ServersPage.tsx
+++ b/panel/src/pages/servers/ServersPage.tsx
@@ -525,10 +525,15 @@ function ServerActions({
);
}
+ // An unclaimed server is the player's to claim and nothing more: waking it and
+ // its console belong to its owner, and it has none yet. An admin runs any server
+ // (the wake and console routes admit them), so theirs keep both beside the claim.
+ const claimable = server.claimable && !server.owned;
+
return (
- {server.claimable && !server.owned ? (
+ {claimable && (
<>
-
>
- ) : (
- <>
-
- {(server.owned || isAdmin) && (
-
-
-
- )}
- >
+ )}
+ {(!claimable || isAdmin) && (
+
+ )}
+ {(server.owned || isAdmin) && (
+
+
+
)}
{error && (