fix(panel): implement two-step confirmation for claiming a server to prevent accidental operations

This commit is contained in:
Lemon-miaow committed 2026-07-04 00:52:38 +08:00
1 parent 439f19e209
commit 83e57b4c45
3 files changed
+42 -2

No files matched your search

@@ -19,6 +19,8 @@
"online": "online",
"console": "Console",
"claim": "Claim",
"claim_server_title": "Claim Server",
"claim_server_desc": "Are you sure you want to claim the server \"{{name}}\"? Once claimed, you will have access to its console, player management, and backups.",
"claiming": "Claiming…",
"stop": "Stop",
"stopping": "Stopping…",
@@ -19,6 +19,8 @@
"online": "人在线",
"console": "控制台",
"claim": "认领",
"claim_server_title": "认领服务器",
"claim_server_desc": "确认认领服务器「{{name}}」吗?认领后你将成为该服务器的所有者,获得控制台、白名单与备份管理权限。",
"claiming": "认领中…",
"stop": "停止",
"stopping": "停止中…",
+38 -2
View File
@@ -18,6 +18,14 @@ import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Select,
SelectTrigger,
@@ -433,6 +441,7 @@ function ServerRow({
const { t } = useTranslation("ops");
const { t: ts } = useTranslation("servers");
const [busy, setBusy] = useState<null | "wake" | "stop" | "claim">(null);
const [confirmOpen, setConfirmOpen] = useState(false);
const [error, setError] = useState<string | null>(null);
async function act(kind: "wake" | "stop" | "claim", fn: () => Promise<unknown>) {
@@ -523,14 +532,41 @@ function ServerRow({
size="sm"
variant="outline"
disabled={busy !== null}
onClick={() => act("claim", () => api.claim(server.name))}
onClick={() => setConfirmOpen(true)}
className="text-primary hover:text-primary hover:bg-primary/5 border-primary/20"
>
<Hand /> {busy === "claim" ? ts("claiming") : ts("claim")}
<Hand /> {ts("claim")}
</Button>
<Button size="sm" variant="outline" disabled>
<Terminal /> {ts("console")}
</Button>
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{ts("claim_server_title")}</DialogTitle>
<DialogDescription>
{ts("claim_server_desc", { name: server.name })}
</DialogDescription>
</DialogHeader>
<DialogFooter className="gap-2 sm:gap-0">
<Button variant="ghost" onClick={() => setConfirmOpen(false)} disabled={busy !== null}>
{ts("access_cancel")}
</Button>
<Button
variant="default"
onClick={async () => {
await act("claim", () => api.claim(server.name));
setConfirmOpen(false);
}}
disabled={busy !== null}
>
{busy === "claim" && <RefreshCw className="h-4 w-4 animate-spin mr-1" />}
{ts("claim")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
) : (
<>