Add SCUM Source RCON transport
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import sourceRCONCommandPanelSource from "./SourceRCONCommandPanel.tsx?raw";
|
||||
|
||||
describe("SourceRCONCommandPanel", () => {
|
||||
it("uses the typed dispatch API without confirmation, transcript, or connection fields", () => {
|
||||
expect(sourceRCONCommandPanelSource).toContain("sendSourceRCONCommand");
|
||||
expect(sourceRCONCommandPanelSource).toContain("sourceRCONChatRequest");
|
||||
expect(sourceRCONCommandPanelSource).toContain("sourceRCONRawCommandRequest");
|
||||
expect(sourceRCONCommandPanelSource).not.toContain("ConfirmDialog");
|
||||
expect(sourceRCONCommandPanelSource).not.toContain("operations.");
|
||||
expect(sourceRCONCommandPanelSource).not.toContain("transcript");
|
||||
expect(sourceRCONCommandPanelSource).not.toContain("history");
|
||||
expect(sourceRCONCommandPanelSource).not.toContain("password");
|
||||
expect(sourceRCONCommandPanelSource).not.toContain("host");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import { type FormEvent, useState } from "react";
|
||||
|
||||
import { platformApiClient } from "../api/client";
|
||||
import { sourceRCONChatRequest, sourceRCONRawCommandRequest } from "../schemas/sourceRcon";
|
||||
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 [chatType, setChatType] = useState(4);
|
||||
const [chatMessage, setChatMessage] = useState("");
|
||||
const [targetSteamId, setTargetSteamId] = useState("");
|
||||
const [rawCommand, setRawCommand] = useState("");
|
||||
const [pending, setPending] = useState<"chat" | "command" | null>(null);
|
||||
const [dispatch, setDispatch] = useState<DispatchState>(null);
|
||||
|
||||
if (pluginId !== "game.scum") {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function sendChat(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
setPending("chat");
|
||||
setDispatch({ status: "pending", label: "正在提交聊天消息" });
|
||||
try {
|
||||
const submitted = await platformApiClient.sendSourceRCONCommand(serverId, sourceRCONChatRequest(serverId, { chatType, message: chatMessage, targetSteamId }));
|
||||
setChatMessage("");
|
||||
setTargetSteamId("");
|
||||
setDispatch({ status: "succeeded", label: sourceRCONDispatchLabel(submitted.jobId, submitted.status) });
|
||||
} 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.sendSourceRCONCommand(serverId, sourceRCONRawCommandRequest(serverId, rawCommand));
|
||||
setRawCommand("");
|
||||
setDispatch({ status: "succeeded", label: sourceRCONDispatchLabel(submitted.jobId, submitted.status) });
|
||||
} catch (error) {
|
||||
setDispatch({ status: "failed", label: error instanceof Error ? error.message : "原始管理员指令提交失败" });
|
||||
} finally {
|
||||
setPending(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<article className="console-panel" aria-label="SCUM Source RCON controls">
|
||||
<div className="panel-header">
|
||||
<div>
|
||||
<h2>SCUM 聊天与管理员指令</h2>
|
||||
<p className="page-status">立即派发一次性任务;只显示安全状态,不保留聊天或指令记录。</p>
|
||||
</div>
|
||||
{dispatch && <ResultBadge status={dispatch.status} label={dispatch.label} />}
|
||||
</div>
|
||||
<div className="operations-command-grid">
|
||||
<section className="console-module" aria-label="SCUM chat command">
|
||||
<div className="panel-header"><h2>发送聊天</h2></div>
|
||||
<form className="provider-form" onSubmit={(event) => void sendChat(event)}>
|
||||
<div className="form-grid">
|
||||
<label>
|
||||
聊天类型
|
||||
<select value={chatType} onChange={(event) => setChatType(Number(event.target.value))} disabled={pending !== null}>
|
||||
{[0, 1, 2, 3, 4, 5, 6, 7].map((value) => <option key={value} value={value}>类型 {value}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
目标 SteamID64(可选)
|
||||
<input value={targetSteamId} inputMode="numeric" maxLength={17} onChange={(event) => setTargetSteamId(event.target.value)} disabled={pending !== null} placeholder="留空为广播" />
|
||||
</label>
|
||||
</div>
|
||||
<label>
|
||||
聊天内容
|
||||
<textarea value={chatMessage} maxLength={1024} rows={3} onChange={(event) => setChatMessage(event.target.value)} disabled={pending !== null} placeholder="输入单行聊天内容" />
|
||||
</label>
|
||||
<div className="action-strip"><button type="submit" className="primary-command" disabled={pending !== null || !chatMessage.trim()}>{pending === "chat" ? "提交中…" : "发送聊天"}</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={4000} rows={5} onChange={(event) => setRawCommand(event.target.value)} disabled={pending !== null} placeholder="例如 SetTime 12" />
|
||||
</label>
|
||||
<p className="page-status">指令会直接交给当前运行中的 SCUM,不会显示执行回包。</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 sourceRCONDispatchLabel(jobId: string, status: string): string {
|
||||
return `已${status === "queued" ? "排队" : "提交"} · 任务 ${jobId}`;
|
||||
}
|
||||
Reference in New Issue
Block a user