Fix declared server config file reads
This commit is contained in:
@@ -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"); }
|
||||
|
||||
Reference in New Issue
Block a user