fix: 平台构建
This commit is contained in:
@@ -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} /> 部分指标源不可用,点击重试
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
编辑
|
||||
|
||||
Reference in New Issue
Block a user