Files
browser/platform_web/components/SourceRCONCommandPanel.tsx
T

94 lines
4.6 KiB
TypeScript

import { type FormEvent, useState } from "react";
import { platformApiClient } from "../api/client";
import { scumAnnouncementCommand, scumManagementRCONCommandRequest } from "../schemas/scumManagementRcon";
import { ResultBadge } from "./StateViews";
interface SourceRCONCommandPanelProps {
serverId: string;
pluginId: string;
}
type DispatchState = { status: "pending" | "succeeded" | "failed"; label: string } | null;
export function SourceRCONCommandPanel({ serverId, pluginId }: SourceRCONCommandPanelProps) {
const [announcement, setAnnouncement] = useState("");
const [rawCommand, setRawCommand] = useState("");
const [pending, setPending] = useState<"announcement" | "command" | null>(null);
const [dispatch, setDispatch] = useState<DispatchState>(null);
if (pluginId !== "game.scum") {
return null;
}
async function sendAnnouncement(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setPending("announcement");
setDispatch({ status: "pending", label: "正在提交服务器公告" });
try {
const submitted = await platformApiClient.queueGameClientBridgeCommand(serverId, scumManagementRCONCommandRequest(serverId, scumAnnouncementCommand(announcement)));
setAnnouncement("");
setDispatch({ status: "succeeded", label: protectedRCONDispatchLabel(submitted.id, submitted.state) });
} catch (error) {
setDispatch({ status: "failed", label: error instanceof Error ? error.message : "服务器公告提交失败" });
} finally {
setPending(null);
}
}
async function sendRawCommand(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setPending("command");
setDispatch({ status: "pending", label: "正在提交原始管理员指令" });
try {
const submitted = await platformApiClient.queueGameClientBridgeCommand(serverId, scumManagementRCONCommandRequest(serverId, rawCommand));
setRawCommand("");
setDispatch({ status: "succeeded", label: protectedRCONDispatchLabel(submitted.id, submitted.state) });
} catch (error) {
setDispatch({ status: "failed", label: error instanceof Error ? error.message : "原始管理员指令提交失败" });
} finally {
setPending(null);
}
}
return (
<article className="console-panel" aria-label="SCUM protected RCON controls">
<div className="panel-header">
<div>
<h2>SCUM 公告与管理员指令</h2>
<p className="page-status">通过插件声明的 protected RCON 通道派发;只显示安全状态,不保留指令原文。</p>
</div>
{dispatch && <ResultBadge status={dispatch.status} label={dispatch.label} />}
</div>
<div className="operations-command-grid">
<section className="console-module" aria-label="SCUM announcement command">
<div className="panel-header"><h2>发送公告</h2></div>
<form className="provider-form" onSubmit={(event) => void sendAnnouncement(event)}>
<label>
公告内容
<textarea value={announcement} maxLength={1024} rows={3} onChange={(event) => setAnnouncement(event.target.value)} disabled={pending !== null} placeholder="输入单行公告内容" />
</label>
<p className="page-status">会生成 SCUM 管理命令:#Announce 公告内容。</p>
<div className="action-strip"><button type="submit" className="primary-command" disabled={pending !== null || !announcement.trim()}>{pending === "announcement" ? "提交中…" : "发送公告"}</button></div>
</form>
</section>
<section className="console-module" aria-label="SCUM raw administrator command">
<div className="panel-header"><h2>原始管理员指令</h2></div>
<form className="provider-form" onSubmit={(event) => void sendRawCommand(event)}>
<label>
指令
<textarea value={rawCommand} maxLength={8192} rows={5} onChange={(event) => setRawCommand(event.target.value)} disabled={pending !== null} placeholder="例如 #ListPlayers 或 #SetTime 12" />
</label>
<p className="page-status">裸命令会交给当前运行中的 SCUM;执行结果以 Run 日志为准。</p>
<div className="action-strip"><button type="submit" className="icon-command" disabled={pending !== null || !rawCommand.trim()}>{pending === "command" ? "提交中…" : "发送指令"}</button></div>
</form>
</section>
</div>
</article>
);
}
function protectedRCONDispatchLabel(commandId: string, state: string): string {
return `已${state === "pending" ? "排队" : "提交"} · 桥接命令 ${commandId}`;
}