Fix declared server config file reads

This commit is contained in:
npc0-hue
2026-09-21 10:05:50 +08:00
parent b481b1b798
commit 20008b4043
16 changed files with 166 additions and 33 deletions
+15 -7
View File
@@ -28,6 +28,7 @@ export function ServerConfigEditor({ instance, operations, requester, onClose }:
const [message, setMessage] = useState("");
const [error, setError] = useState("");
const readInFlightRef = useRef("");
const readAttemptRef = useRef(new Map<string, number>());
const operationsRef = useRef(operations);
operationsRef.current = operations;
@@ -35,7 +36,7 @@ export function ServerConfigEditor({ instance, operations, requester, onClose }:
const selectedFile = configFiles.find((file) => file.key === selectedKey) ?? configFiles[0];
const fields = useMemo(() => (workspace?.configFields ?? []).filter((field) => field.fileKey === selectedFile?.key), [selectedFile?.key, workspace?.configFields]);
const readSelectedFile = useCallback(async (file: PluginLogicalFileResponse) => {
const readSelectedFile = useCallback(async (file: PluginLogicalFileResponse, forceRetry = false) => {
const readKey = `${instance.id}:${file.key}`;
if (readInFlightRef.current === readKey) return;
readInFlightRef.current = readKey;
@@ -44,16 +45,23 @@ export function ServerConfigEditor({ instance, operations, requester, onClose }:
setMessage("正在通过 Run 读取配置文件…");
try {
let next = await platformApiClient.getServerFileReadSnapshot(instance.id, file.key);
if (next.state !== "ready") {
const failedState = next.state === "failed" || next.state === "cancelled";
let dispatched = false;
if (next.state === "not-read" || (forceRetry && failedState)) {
const readAttempt = (readAttemptRef.current.get(readKey) ?? 0) + 1;
readAttemptRef.current.set(readKey, readAttempt);
const operationId = operationsRef.current.begin({ intent: "读取服务器配置", targetKind: "config", targetId: instance.id, requester });
const dispatch = await platformApiClient.readServerFile(instance.id, { key: file.key, idempotencyKey: configOperationKey("read", instance.id, file.key) });
const dispatch = await platformApiClient.readServerFile(instance.id, { key: file.key, idempotencyKey: configOperationKey("read", instance.id, file.key, readAttempt) });
operationsRef.current.succeed(operationId, `配置读取任务 ${dispatch.job.id} 已派发`, dispatch.job);
for (let attempt = 0; attempt < 60 && next.state !== "ready"; attempt += 1) {
dispatched = true;
}
if (dispatched || next.state === "pending") {
for (let pollAttempt = 0; pollAttempt < 60 && next.state !== "ready"; pollAttempt += 1) {
await new Promise((resolve) => window.setTimeout(resolve, 500));
next = await platformApiClient.getServerFileReadSnapshot(instance.id, file.key);
}
}
if (next.state !== "ready" || next.content === undefined) throw new Error(next.reason ?? "Run 未返回配置文件内容。");
if (next.state !== "ready" || next.content === undefined) throw new Error(next.reason ?? (next.state === "pending" ? "Run 仍在读取配置文件,请稍后重试。" : "Run 未返回配置文件内容。"));
setSnapshot(next);
setRaw(next.content);
setValues(Object.fromEntries(fields.map((field) => [field.key, iniValue(next.content ?? "", field.configKey)])));
@@ -120,7 +128,7 @@ export function ServerConfigEditor({ instance, operations, requester, onClose }:
<div className="panel-header"><div><h3>{selectedFile?.label}</h3><span className="page-status"> Run </span></div></div>
<div className="file-workbench-mode"><span>{mode === "friendly" ? "人类可读模式:只修改插件声明的配置项" : "源码模式:编辑完整原始文件"}</span><button type="button" className={mode === "friendly" ? "file-workbench-mode-active" : undefined} onClick={() => setMode("friendly")}><FileCog size={13} /> </button><button type="button" className={mode === "source" ? "file-workbench-mode-active" : undefined} onClick={() => setMode("source")}><Code2 size={13} /> </button></div>
{message && <ResultBadge status={error ? "failed" : "pending"} label={message} />}
{error && workspace && <ErrorState title="配置操作失败" reason={error} diagnosticId={`server-config:${instance.id}:${selectedFile?.key ?? "unknown"}`} compact />}
{error && workspace && <ErrorState title="配置操作失败" reason={error} diagnosticId={`server-config:${instance.id}:${selectedFile?.key ?? "unknown"}`} onRetry={selectedFile ? () => void readSelectedFile(selectedFile, true) : undefined} compact />}
{loading && <LoadingState label="正在读取配置文件…" compact />}
{!loading && mode === "friendly" && <div className="file-workbench-fields">{fields.length === 0 && <span className="provider-id"></span>}{configFieldGroups(fields).map((group) => <div key={group.label} className="file-workbench-group"><h4>{group.label}</h4>{group.fields.map((field) => <ConfigField key={field.key} field={field} value={values[field.key] ?? ""} onChange={(value) => setValues((current) => ({ ...current, [field.key]: value }))} />)}</div>)}</div>}
{!loading && mode === "source" && <div className="file-workbench-raw"><textarea className="file-workbench-raw-editor" value={raw} spellCheck={false} onChange={(event) => setRaw(event.target.value)} aria-label={`${selectedFile?.label ?? "配置文件"}源码`} /><small> Run </small></div>}
@@ -147,5 +155,5 @@ function configFieldGroups(fields: PluginConfigFieldResponse[]): Array<{ label:
return [...groups.entries()].map(([label, groupFields]) => ({ label, fields: groupFields }));
}
function configOperationKey(operation: string, serverId: string, fileKey: string): string { return `web:server-config:${operation}:${serverId}:${fileKey}`; }
function configOperationKey(operation: string, serverId: string, fileKey: string, attempt = 1): string { return `web:server-config:${operation}:${serverId}:${fileKey}:${attempt}`; }
function formatReadTime(value: string): string { const date = new Date(value); return Number.isNaN(date.getTime()) ? value : date.toLocaleString("zh-CN"); }