fix: 平台构建

This commit is contained in:
npc0-hue
2026-07-22 15:27:26 +08:00
parent 6c3eb6e45f
commit 8e34c8762a
12 changed files with 188 additions and 155 deletions
+11 -11
View File
@@ -224,7 +224,7 @@ export function HomePage({ session, onNavigate, initialState }: HomePageProps) {
)}
<section className="operations-command-grid" aria-label="平台运营模块">
<article className="console-panel operations-module" aria-label="任务脉冲">
<article className="console-panel console-module" aria-label="任务脉冲">
<div className="panel-header">
<h2><Workflow size={16} /> </h2>
<span className="page-status">{moduleFreshnessLabel(core.status === "ready" ? core.refreshedAt : core.refreshedAt)}</span>
@@ -233,20 +233,20 @@ export function HomePage({ session, onNavigate, initialState }: HomePageProps) {
{core.status === "error" && <ErrorState title="任务状态不可用" reason={core.reason} diagnosticId={core.diagnosticId} onRetry={() => void refreshCore()} compact />}
{core.status === "ready" && jobs && (
<>
<dl className="operations-pulse-strip">
<dl className="console-stat-strip">
<div><dt></dt><dd>{jobs.active.length}</dd></div>
<div><dt></dt><dd>{jobs.retrying.length}</dd></div>
<div><dt></dt><dd>{jobs.failed.length}</dd></div>
</dl>
{jobs.active.length === 0 && jobs.failed.length === 0 ? (
<p className="operations-module-empty"></p>
<p className="console-empty-note"></p>
) : (
<div className="operations-job-list">
<div className="console-row-list">
{[...jobs.active.slice(0, 3), ...jobs.failed.slice(0, 2)].map((job) => (
<button
key={job.id}
type="button"
className={cx("operations-job-row", job.state === "failed" && "operations-job-row-failed")}
className={cx("console-row console-row-button", job.state === "failed" && "console-row-danger")}
onClick={() => onNavigate(job.serverInstanceId ? "serverDetail" : "maintenance", job.serverInstanceId ? { serverId: job.serverInstanceId } : undefined)}
>
<span><strong>{job.capability}</strong><small>{job.serverInstanceId ? `服务器 ${job.serverInstanceId}` : "平台任务"}</small></span>
@@ -260,7 +260,7 @@ export function HomePage({ session, onNavigate, initialState }: HomePageProps) {
)}
</article>
<article className="console-panel operations-module" aria-label="运行节点状态">
<article className="console-panel console-module" aria-label="运行节点状态">
<div className="panel-header">
<h2><ServerCog size={16} /> </h2>
<button type="button" className="icon-command" onClick={() => onNavigate("maintenance")}><CircleGauge size={14} /> </button>
@@ -269,17 +269,17 @@ export function HomePage({ session, onNavigate, initialState }: HomePageProps) {
{core.status === "error" && <ErrorState title="节点状态不可用" reason={core.reason} diagnosticId={core.diagnosticId} onRetry={() => void refreshCore()} compact />}
{core.status === "ready" && endpointSummary && (
<>
<dl className="operations-pulse-strip">
<dl className="console-stat-strip">
<div><dt>线</dt><dd>{endpointSummary.online}</dd></div>
<div><dt></dt><dd>{endpointSummary.activeJobs}</dd></div>
<div><dt></dt><dd>{endpointSummary.queuedJobs}</dd></div>
</dl>
{core.data.endpoints.length === 0 ? (
<p className="operations-module-empty">Platform </p>
<p className="console-empty-note">Platform </p>
) : (
<div className="operations-endpoint-list">
<div className="console-row-list">
{core.data.endpoints.slice(0, 4).map((endpoint) => (
<div key={endpoint.id} className="operations-endpoint-row">
<div key={endpoint.id} className="console-row">
<span><strong>{endpoint.displayName}</strong><small>{endpoint.version}</small></span>
<span className={cx("status-pill", `status-${endpoint.status}`)}>{endpointStatusLabel(endpoint.status)}</span>
<span>{endpoint.capacity.runningJobs}/{endpoint.capacity.maxJobs} </span>
@@ -311,7 +311,7 @@ export function HomePage({ session, onNavigate, initialState }: HomePageProps) {
<UsageMeter label="内存" percent={metricAverages.memory} />
<UsageMeter label="磁盘" percent={metricAverages.disk} />
</div>
<span className="operations-module-freshness">{moduleFreshnessLabel(metricAverages.refreshedAt)}</span>
<span className="console-note">{moduleFreshnessLabel(metricAverages.refreshedAt)}</span>
{(usage.status === "error" || metrics.status === "error") && (
<button type="button" className="operations-inline-warning" onClick={() => { void refreshUsage(); void refreshMetrics(); }}>
<AlertTriangle size={14} />
+11 -11
View File
@@ -182,7 +182,7 @@ export function MaintenancePage({ session, operations, onNavigate }: PageCompone
<span> {endpoint.capabilities.slice(0, 4).join(" / ") || "未上报"}</span>
<span> {relatedServers.length}</span>
</details>
<div className="maintenance-actions">
<div className="console-row-actions">
<button type="button" className="theme-upload" onClick={() => void refreshEndpoints()}>
</button>
@@ -220,17 +220,17 @@ export function MaintenancePage({ session, operations, onNavigate }: PageCompone
<EmptyState title="暂无失败任务" description="最近任务没有失败记录;如果节点异常,请先查看运行节点心跳和容量。" actionLabel="刷新任务" onAction={() => void refreshJobs()} />
)}
{jobs.status === "ready" && failedJobs.length > 0 && (
<div className="operation-list">
<div className="console-record-list">
{failedJobs.map((job) => {
const server = job.serverInstanceId ? serverById.get(job.serverInstanceId) : undefined;
const endpoint = endpointById.get(job.runEndpointId);
return (
<div key={job.id} className="operation-item">
<div className="operation-item-head">
<div key={job.id} className="console-record">
<div className="console-record-head">
<strong>{job.capability}</strong>
<span className="status-pill status-error">{jobStateLabel(job.state)}</span>
</div>
<div className="operation-meta">
<div className="console-record-meta">
<span>
<code>{job.id}</code>
</span>
@@ -242,7 +242,7 @@ export function MaintenancePage({ session, operations, onNavigate }: PageCompone
</span>
<span>{formatTimestamp(job.updatedAt)}</span>
</div>
<div className="maintenance-actions">
<div className="console-row-actions">
<button type="button" className="theme-upload" onClick={() => void retryJob(job)}>
<RotateCcw size={13} />
@@ -273,14 +273,14 @@ export function MaintenancePage({ session, operations, onNavigate }: PageCompone
<EmptyState title="暂无审计事件" description="平台还没有记录任何审计事件。" actionLabel="刷新" onAction={() => void refreshEvents()} />
)}
{events.status === "ready" && events.data.length > 0 && (
<div className="operation-list">
<div className="console-record-list">
{events.data.slice(0, 30).map((event) => (
<div key={event.id} className="operation-item">
<div className="operation-item-head">
<div key={event.id} className="console-record">
<div className="console-record-head">
<strong>{event.summary || `${event.action} ${event.resourceKind}`}</strong>
<span className={cx("status-pill", auditResultClass(event))}>{event.result}</span>
</div>
<div className="operation-meta">
<div className="console-record-meta">
<span>
<code>{event.id}</code>
</span>
@@ -290,7 +290,7 @@ export function MaintenancePage({ session, operations, onNavigate }: PageCompone
</span>
<span>{formatTimestamp(event.createdAt)}</span>
</div>
<div className="maintenance-actions">
<div className="console-row-actions">
<button type="button" className="theme-upload" onClick={() => void refreshEvents()}>
</button>
+23 -23
View File
@@ -1783,7 +1783,7 @@ function PluginControlsSection({ serverId, instance, plugins, artifacts, session
}
return (
<div className="operation-list" aria-label="plugin controls">
<div className="console-record-list" aria-label="plugin controls">
{groups.length === 0 && (
<EmptyState title="该服务器没有可控制的插件" description="安装插件后,这里会按插件分组显示可用的日常操作。" />
)}
@@ -2206,17 +2206,17 @@ function HistorySection({ serverId, serverOperations, jobs, artifacts, metricHis
{serverOperations.length === 0 ? (
<EmptyState title="暂无操作记录" description="在本页发起启动、停止、插件控制或配置写入后,这里会显示完整的操作生命周期。" />
) : (
<div className="operation-list">
<div className="console-record-list">
{serverOperations.map((operation) => (
<div key={operation.id} className="operation-item">
<div className="operation-item-head">
<div key={operation.id} className="console-record">
<div className="console-record-head">
<strong>{operation.intent}</strong>
<ResultBadge
status={operation.status}
label={operation.status === "pending" ? "进行中" : operation.status === "succeeded" ? "成功" : "失败"}
/>
</div>
<div className="operation-meta">
<div className="console-record-meta">
<span>
<code>{operation.id}</code>
</span>
@@ -2249,16 +2249,16 @@ function HistorySection({ serverId, serverOperations, jobs, artifacts, metricHis
{jobs.length === 0 ? (
<EmptyState title="暂无任务" description="该服务器还没有平台侧任务记录。" />
) : (
<div className="operation-list">
<div className="console-record-list">
{jobs.slice(0, 20).map((job) => (
<div key={job.id} className="operation-item">
<div className="operation-item-head">
<div key={job.id} className="console-record">
<div className="console-record-head">
<strong>{job.capability}</strong>
<span className={cx("status-pill", job.state === "succeeded" ? "status-active" : job.state === "failed" ? "status-error" : "status-disabled")}>
{job.state}
</span>
</div>
<div className="operation-meta">
<div className="console-record-meta">
<span>
<code>{job.id}</code>
</span>
@@ -2291,18 +2291,18 @@ function HistorySection({ serverId, serverOperations, jobs, artifacts, metricHis
<div className="panel-header">
<h2></h2>
</div>
<div className="operation-list">
<div className="operation-item">
<div className="operation-item-head"><strong></strong><span className="status-pill status-active">{metricHistory.length} </span></div>
<div className="operation-meta"><span> {metricHistory.length > 0 ? new Date(metricHistory[metricHistory.length - 1].collectedAt).toLocaleString() : "暂无"}</span></div>
<div className="console-record-list">
<div className="console-record">
<div className="console-record-head"><strong></strong><span className="status-pill status-active">{metricHistory.length} </span></div>
<div className="console-record-meta"><span> {metricHistory.length > 0 ? new Date(metricHistory[metricHistory.length - 1].collectedAt).toLocaleString() : "暂无"}</span></div>
</div>
<div className="operation-item">
<div className="operation-item-head"><strong></strong><span className="status-pill status-active">{backups.length} </span></div>
<div className="operation-meta">{backups.slice(0, 4).map((backup) => <span key={backup.id}>{backup.id} · {backup.state} · {backup.checksum.slice(0, 18)}</span>)}</div>
<div className="console-record">
<div className="console-record-head"><strong></strong><span className="status-pill status-active">{backups.length} </span></div>
<div className="console-record-meta">{backups.slice(0, 4).map((backup) => <span key={backup.id}>{backup.id} · {backup.state} · {backup.checksum.slice(0, 18)}</span>)}</div>
</div>
<div className="operation-item">
<div className="operation-item-head"><strong></strong><span className="status-pill status-active">{remoteAdapters.length} </span></div>
<div className="operation-meta">{remoteAdapters.slice(0, 4).map((adapter) => <span key={adapter.key}>{adapter.key} · {adapter.kind} · {adapter.targetKeys.join(", ")}</span>)}</div>
<div className="console-record">
<div className="console-record-head"><strong></strong><span className="status-pill status-active">{remoteAdapters.length} </span></div>
<div className="console-record-meta">{remoteAdapters.slice(0, 4).map((adapter) => <span key={adapter.key}>{adapter.key} · {adapter.kind} · {adapter.targetKeys.join(", ")}</span>)}</div>
</div>
</div>
</article>
@@ -2347,16 +2347,16 @@ function ArtifactDownloadPanel({ serverId, artifacts }: ArtifactDownloadPanelPro
{artifacts.length === 0 ? (
<EmptyState title="暂无可下载制品" description="该服务器当前没有已完成的可用制品。" />
) : (
<div className="operation-list">
<div className="console-record-list">
{artifacts.slice(0, 12).map((artifact) => {
const itemResult = result[artifact.id];
return (
<div key={artifact.id} className="operation-item">
<div className="operation-item-head">
<div key={artifact.id} className="console-record">
<div className="console-record-head">
<strong>{artifact.id}</strong>
<ResultBadge status={itemResult?.status ?? "pending"} label={itemResult?.label ?? artifact.state} />
</div>
<div className="operation-meta">
<div className="console-record-meta">
<span> {serverId}</span>
<span>{formatBytes(artifact.sizeBytes)}</span>
<span>{artifact.checksum}</span>
+3 -3
View File
@@ -301,9 +301,9 @@ export function UsersPage({ session, operations, initialState }: UsersPageProps)
) : !loading && !loadError && filteredUsers.length === 0 ? (
<EmptyState title="没有匹配用户" description="调整名称、角色或状态筛选后再试。" actionLabel="清除筛选" onAction={() => { setKeyword(""); setRoleFilter(""); setStatusFilter("all"); }} />
) : (
<div className="resource-list user-management-list">
<div className="resource-list access-list">
{filteredUsers.map((user) => (
<article key={user.id} className="resource-list-item user-management-item">
<article key={user.id} className="resource-list-item access-list-row">
<div>
<strong>{user.displayName}</strong>
<span className="provider-id">{user.email ?? user.id}</span>
@@ -314,7 +314,7 @@ export function UsersPage({ session, operations, initialState }: UsersPageProps)
</span>
<span>{user.roles.map(roleLabel).join(" / ")}</span>
<span>{profileSummary(user)}</span>
<div className="user-actions" aria-label={`${user.displayName} 状态操作`}>
<div className="console-row-actions" aria-label={`${user.displayName} 状态操作`}>
<button type="button" className="theme-upload" disabled={mutationDisabled || operations.isPending(user.id, "编辑用户")} aria-label={`编辑 ${user.displayName}`} onClick={() => startEdit(user)}>
<UserPen size={13} />