feat: 完整游戏运维功能
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
endpointLabel,
|
||||
pendingJobsForServer,
|
||||
pluginLabel,
|
||||
runtimeBindingFields,
|
||||
type ServerCreateFormState
|
||||
} from "../contracts/serverManagement";
|
||||
import { filterServerCards, serverIsOnline, type ServerCardView, type ServerStatusFilter } from "../contracts/workspace";
|
||||
@@ -75,13 +76,21 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
|
||||
setEndpoints(endpointResponse.items);
|
||||
setInstances(instanceResponse.items);
|
||||
setJobs(jobResponse.items);
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
pluginId: pluginResponse.items.some((plugin) => plugin.id === current.pluginId) ? current.pluginId : pluginResponse.items[0]?.id || "",
|
||||
runEndpointId: endpointResponse.items.some((endpoint) => endpoint.id === current.runEndpointId)
|
||||
? current.runEndpointId
|
||||
: endpointResponse.items[0]?.id || ""
|
||||
}));
|
||||
setForm((current) => {
|
||||
const plugin = pluginResponse.items.find((item) => item.id === current.pluginId) ?? pluginResponse.items[0];
|
||||
const profileKey = plugin?.runtimeProfiles?.lifecycleProfiles?.some((profile) => profile.key === current.profileKey)
|
||||
? current.profileKey
|
||||
: plugin?.runtimeProfiles?.lifecycleProfiles?.[0]?.key ?? "";
|
||||
return {
|
||||
...current,
|
||||
pluginId: plugin?.id ?? "",
|
||||
profileKey,
|
||||
bindings: plugin?.id === current.pluginId && profileKey === current.profileKey ? current.bindings : {},
|
||||
runEndpointId: endpointResponse.items.some((endpoint) => endpoint.id === current.runEndpointId)
|
||||
? current.runEndpointId
|
||||
: endpointResponse.items[0]?.id || ""
|
||||
};
|
||||
});
|
||||
setListState("ready");
|
||||
setListError("");
|
||||
} catch (error) {
|
||||
@@ -115,10 +124,25 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
|
||||
|
||||
const visibleCards = useMemo(() => filterServerCards(cards, keyword, statusFilter), [cards, keyword, statusFilter]);
|
||||
const createPending = operations.isPending("platform", "创建服务器");
|
||||
const selectedCreatePlugin = plugins.find((plugin) => plugin.id === form.pluginId);
|
||||
const createBindingFields = runtimeBindingFields(selectedCreatePlugin, form.profileKey);
|
||||
|
||||
function updateForm(event: ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
|
||||
const { name, value } = event.target;
|
||||
setForm((current) => ({ ...current, [name]: value }));
|
||||
setForm((current) => {
|
||||
if (name === "pluginId") {
|
||||
const plugin = plugins.find((item) => item.id === value);
|
||||
return { ...current, pluginId: value, profileKey: plugin?.runtimeProfiles?.lifecycleProfiles?.[0]?.key ?? "", bindings: {} };
|
||||
}
|
||||
if (name === "profileKey") {
|
||||
return { ...current, profileKey: value, bindings: {} };
|
||||
}
|
||||
return { ...current, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
function updateBinding(key: string, value: string) {
|
||||
setForm((current) => ({ ...current, bindings: { ...current.bindings, [key]: value } }));
|
||||
}
|
||||
|
||||
async function handleCreate(event: FormEvent<HTMLFormElement>) {
|
||||
@@ -203,7 +227,11 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
|
||||
return `依赖检查任务已排队,job ${job.id}`;
|
||||
}
|
||||
if (action === "dependencies-install") {
|
||||
const job = await platformApiClient.installDependencies(instance.id, dependencyJobRequest(instance.id, defaults.probeKey, defaults.installPlanKey));
|
||||
const catalog = await platformApiClient.getDependencyCatalog(instance.id);
|
||||
const plan = catalog.plans.find((candidate) => candidate.key === defaults.installPlanKey);
|
||||
const probe = catalog.probes.find((candidate) => candidate.key === defaults.probeKey);
|
||||
if (!plan || probe?.installPlanKey !== plan.key) throw new Error("Platform 未返回与当前 probe 匹配的审核安装计划");
|
||||
const job = await platformApiClient.installDependencies(instance.id, dependencyJobRequest(instance.id, probe.key, plan.key, plan.digest));
|
||||
return `依赖安装任务已排队,job ${job.id}`;
|
||||
}
|
||||
if (action === "live-logs") {
|
||||
@@ -384,8 +412,31 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
运行配置
|
||||
<select name="profileKey" value={form.profileKey} onChange={updateForm} required>
|
||||
{(selectedCreatePlugin?.runtimeProfiles?.lifecycleProfiles ?? []).map((profile) => (
|
||||
<option key={profile.key} value={profile.key}>
|
||||
{profile.key} · {profile.mode}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
{createBindingFields.map((field) => (
|
||||
<label key={field.key}>
|
||||
{field.key}{field.required ? "(必填)" : ""}
|
||||
<input
|
||||
type={field.sensitive ? "password" : "text"}
|
||||
autoComplete="off"
|
||||
value={form.bindings[field.key] ?? ""}
|
||||
onChange={(event) => updateBinding(field.key, event.target.value)}
|
||||
placeholder={field.sensitive ? "托管凭据引用" : "安全逻辑值"}
|
||||
required={field.required}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<button type="submit" className="primary-command" disabled={createPending} title="创建服务器">
|
||||
<button type="submit" className="primary-command" disabled={createPending || !form.profileKey} title="创建服务器">
|
||||
<Sparkles size={16} />
|
||||
<span>{createPending ? "创建中…" : "创建并安装"}</span>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user