Unverified Commit 83e57b4c authored by Lemon-miaow's avatar Lemon-miaow
Browse files

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

parent 439f19e2
Loading
Loading
Loading
Loading
+2 −0
Changes for panel/src/i18n/resources/en-US/servers.json: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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…",
+2 −0
Changes for panel/src/i18n/resources/zh-CN/servers.json: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@
  "online": "人在线",
  "console": "控制台",
  "claim": "认领",
  "claim_server_title": "认领服务器",
  "claim_server_desc": "确认认领服务器「{{name}}」吗?认领后你将成为该服务器的所有者,获得控制台、白名单与备份管理权限。",
  "claiming": "认领中…",
  "stop": "停止",
  "stopping": "停止中…",
+38 −2
Changes for panel/src/pages/servers/ServersPage.tsx: 38 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -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>
              </>
            ) : (
              <>