功能修改

This commit is contained in:
npc0-hue
2026-07-20 16:42:33 +08:00
parent 48b8ad8d6c
commit a0e69417db
224 changed files with 22015 additions and 884 deletions
+61 -25
View File
@@ -1,4 +1,4 @@
import { CakeSlice, Candy, Search, Sparkles } from "lucide-react";
import { AlertTriangle, CakeSlice, Candy, Search, Sparkles } from "lucide-react";
import { type CSSProperties, type ChangeEvent, type FormEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
@@ -14,17 +14,17 @@ import {
runtimeUpdateStages,
useRuntimeTaskController
} from "../components/RuntimeTaskProgress";
import { UsageMeter } from "../components/OperationControls";
import { ManagementDialog, UsageMeter } from "../components/OperationControls";
import { EmptyState, ErrorState, LoadingState, ResultBadge } from "../components/StateViews";
import type { PageComponentProps } from "../contracts/page";
import {
defaultServerCreateForm,
endpointLabel,
pendingJobsForServer,
pluginLabel,
runtimeBindingFields,
type ServerCreateFormState
} from "../contracts/serverManagement";
import { summarizeServerOperations } from "../contracts/operationsConsole";
import { filterServerCards, serverIsOnline, type ServerCardView, type ServerStatusFilter } from "../contracts/workspace";
import {
clientManagerBuildRequest,
@@ -56,6 +56,7 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
const [jobs, setJobs] = useState<JobResponse[]>([]);
const [metrics, setMetrics] = useState<Map<string, ServerMetricsResponse>>(new Map());
const [metricsPending, setMetricsPending] = useState(true);
const [metricsError, setMetricsError] = useState("");
const [keyword, setKeyword] = useState("");
const [statusFilter, setStatusFilter] = useState<ServerStatusFilter>("all");
const [form, setForm] = useState<ServerCreateFormState>(() => defaultServerCreateForm([], []));
@@ -63,7 +64,7 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
const runtimeTask = useRuntimeTaskController();
const [runtimeTaskActions, setRuntimeTaskActions] = useState<RuntimeTaskDialogAction[]>([]);
const refresh = useCallback(async () => {
const refreshList = useCallback(async () => {
setListState("loading");
try {
const [pluginResponse, endpointResponse, instanceResponse, jobResponse] = await Promise.all([
@@ -97,33 +98,46 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
setListState("error");
setListError(error instanceof Error ? error.message : "加载失败");
}
}, []);
const refreshMetrics = useCallback(async () => {
setMetricsPending(true);
try {
const metricsResponse = await platformApiClient.listServerMetrics();
setMetrics(new Map(metricsResponse.items.map((item) => [item.serverInstanceId, item])));
} catch {
setMetricsError("");
} catch (error) {
setMetrics(new Map());
setMetricsError(error instanceof Error ? error.message : "服务器指标加载失败");
} finally {
setMetricsPending(false);
}
}, []);
const refresh = useCallback(async () => {
await Promise.all([refreshList(), refreshMetrics()]);
}, [refreshList, refreshMetrics]);
useEffect(() => {
void refresh();
}, [refresh]);
const cards = useMemo<ServerCardView[]>(
() =>
instances.map((instance) => ({
instance,
metrics: metrics.get(instance.id),
pendingJobs: pendingJobsForServer(jobs, instance.id).length
summarizeServerOperations(instances, metrics, jobs).map((summary) => ({
instance: summary.instance,
metrics: summary.metrics,
pendingJobs: summary.activeJobs,
activeJobs: summary.activeJobs,
failedJobs: summary.failedJobs,
latestJob: summary.latestJob
})),
[instances, jobs, metrics]
);
const visibleCards = useMemo(() => filterServerCards(cards, keyword, statusFilter), [cards, keyword, statusFilter]);
const createPending = operations.isPending("platform", "创建服务器");
const canManageServers = session.capabilities.includes("servers.manage");
const selectedCreatePlugin = plugins.find((plugin) => plugin.id === form.pluginId);
const createBindingFields = runtimeBindingFields(selectedCreatePlugin, form.profileKey);
@@ -356,13 +370,23 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
<Sparkles size={16} />
<span></span>
</button>
<button type="button" className="icon-command" title="创建服务器" onClick={() => setShowCreate((current) => !current)}>
<button type="button" className="icon-command" title={canManageServers ? "创建服务器" : "当前账号没有创建权限"} disabled={!canManageServers} onClick={() => setShowCreate((current) => !current)}>
<Candy size={16} />
<span></span>
</button>
</div>
</header>
{metricsError && (
<div className="operations-inline-warning" role="status">
<AlertTriangle size={14} />
<span>{metricsError}</span>
<button type="button" className="inline-link-command" onClick={() => void refreshMetrics()}>
</button>
</div>
)}
{latestCreate && (
<div className="inline-result-strip" aria-live="polite">
<ResultBadge
@@ -378,11 +402,14 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
</div>
)}
{showCreate && (
<form className="provider-form" onSubmit={(event) => void handleCreate(event)} aria-label="创建服务器">
<div className="form-header">
<h2></h2>
</div>
<ManagementDialog
open={showCreate && canManageServers}
title="创建服务器"
description="选择插件声明的运行配置和安全逻辑绑定。提交后以 Platform 返回的实例与安装任务为准。"
wide
onClose={() => { if (!createPending) setShowCreate(false); }}
>
<form className="provider-form dialog-form" onSubmit={(event) => void handleCreate(event)} aria-label="创建服务器">
<div className="form-grid">
<label>
ID
@@ -436,12 +463,15 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
</label>
))}
</div>
<button type="submit" className="primary-command" disabled={createPending || !form.profileKey} title="创建服务器">
<Sparkles size={16} />
<span>{createPending ? "创建中…" : "创建并安装"}</span>
</button>
<div className="confirm-actions">
<button type="button" disabled={createPending} onClick={() => setShowCreate(false)}></button>
<button type="submit" className="confirm-primary" disabled={createPending || !form.profileKey} title="创建服务器">
<Sparkles size={16} />
<span>{createPending ? "创建中…" : "创建并安装"}</span>
</button>
</div>
</form>
)}
</ManagementDialog>
<div className="server-toolbar" role="search">
<Search size={16} aria-hidden="true" />
@@ -465,7 +495,7 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
</div>
{listState === "loading" && <LoadingState label="正在加载服务器列表…" />}
{listState === "error" && <ErrorState title="服务器列表加载失败" reason={listError} diagnosticId="server-list" onRetry={() => void refresh()} />}
{listState === "error" && <ErrorState title="服务器列表加载失败" reason={listError} diagnosticId="server-list" onRetry={() => void refreshList()} />}
{listState === "ready" && cards.length === 0 && (
<EmptyState
icon={<CakeSlice size={26} />}
@@ -493,6 +523,8 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
key={card.instance.id}
card={card}
metricsPending={metricsPending}
metricsUnavailable={Boolean(metricsError)}
canManage={canManageServers}
onOpen={() => onNavigate("serverDetail", { serverId: card.instance.id })}
onQuickAction={(action) => void handleQuickRuntimeAction(card.instance, action)}
/>
@@ -517,12 +549,14 @@ type ServerQuickRuntimeAction =
interface ServerCardProps {
card: ServerCardView;
metricsPending: boolean;
metricsUnavailable: boolean;
canManage: boolean;
onOpen: () => void;
onQuickAction: (action: ServerQuickRuntimeAction) => void;
}
function ServerCard({ card, metricsPending, onOpen, onQuickAction }: ServerCardProps) {
const { instance, metrics, pendingJobs } = card;
function ServerCard({ card, metricsPending, metricsUnavailable, canManage, onOpen, onQuickAction }: ServerCardProps) {
const { instance, metrics, pendingJobs, failedJobs = 0 } = card;
const online = serverIsOnline(instance.state);
const menuButtonRef = useRef<HTMLButtonElement>(null);
const menuPanelRef = useRef<HTMLDivElement>(null);
@@ -622,7 +656,7 @@ function ServerCard({ card, metricsPending, onOpen, onQuickAction }: ServerCardP
</span>
<span className="server-card-stat">
<span></span>
<strong>{pendingJobs > 0 ? `${pendingJobs} 进行中` : online ? "空闲" : "--"}</strong>
<strong>{failedJobs > 0 ? `${failedJobs} 失败` : pendingJobs > 0 ? `${pendingJobs} 进行中` : online ? "空闲" : "--"}</strong>
</span>
</div>
<div className="server-card-meters">
@@ -630,12 +664,14 @@ function ServerCard({ card, metricsPending, onOpen, onQuickAction }: ServerCardP
<UsageMeter label="内存" percent={metrics?.memoryPercent} />
<UsageMeter label="磁盘" percent={metrics?.diskPercent} />
</div>
{metricsUnavailable && <span className="server-card-warning"><AlertTriangle size={13} /></span>}
{failedJobs > 0 && <span className="server-card-warning"><AlertTriangle size={13} /></span>}
<div className="action-strip" style={{ justifyContent: "space-between" }}>
<button type="button" className="icon-command" onClick={onOpen}>
<Sparkles size={14} />
<span></span>
</button>
<button ref={menuButtonRef} type="button" className="icon-command" aria-haspopup="menu" aria-expanded={menuOpen} onClick={toggleMenu}>
<button ref={menuButtonRef} type="button" className="icon-command" disabled={!canManage} title={canManage ? "运行操作" : "当前账号没有运行操作权限"} aria-haspopup="menu" aria-expanded={menuOpen} onClick={toggleMenu}>
<span></span>
</button>
</div>