fix(panel): implement two-step confirmation for claiming a server to prevent accidental operations
This commit is contained in:
3 files changed
+42
-2
No files matched your search
@@ -19,6 +19,8 @@
|
|||||||
"online": "online",
|
"online": "online",
|
||||||
"console": "Console",
|
"console": "Console",
|
||||||
"claim": "Claim",
|
"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…",
|
"claiming": "Claiming…",
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
"stopping": "Stopping…",
|
"stopping": "Stopping…",
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
"online": "人在线",
|
"online": "人在线",
|
||||||
"console": "控制台",
|
"console": "控制台",
|
||||||
"claim": "认领",
|
"claim": "认领",
|
||||||
|
"claim_server_title": "认领服务器",
|
||||||
|
"claim_server_desc": "确认认领服务器「{{name}}」吗?认领后你将成为该服务器的所有者,获得控制台、白名单与备份管理权限。",
|
||||||
"claiming": "认领中…",
|
"claiming": "认领中…",
|
||||||
"stop": "停止",
|
"stop": "停止",
|
||||||
"stopping": "停止中…",
|
"stopping": "停止中…",
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
@@ -433,6 +441,7 @@ function ServerRow({
|
|||||||
const { t } = useTranslation("ops");
|
const { t } = useTranslation("ops");
|
||||||
const { t: ts } = useTranslation("servers");
|
const { t: ts } = useTranslation("servers");
|
||||||
const [busy, setBusy] = useState<null | "wake" | "stop" | "claim">(null);
|
const [busy, setBusy] = useState<null | "wake" | "stop" | "claim">(null);
|
||||||
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
async function act(kind: "wake" | "stop" | "claim", fn: () => Promise<unknown>) {
|
async function act(kind: "wake" | "stop" | "claim", fn: () => Promise<unknown>) {
|
||||||
@@ -523,14 +532,41 @@ function ServerRow({
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={busy !== null}
|
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"
|
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>
|
||||||
<Button size="sm" variant="outline" disabled>
|
<Button size="sm" variant="outline" disabled>
|
||||||
<Terminal /> {ts("console")}
|
<Terminal /> {ts("console")}
|
||||||
</Button>
|
</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>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in new issue
Block a user