import {
Activity,
AlertTriangle,
Archive,
ArrowRight,
Box,
Braces,
Check,
CircleStop,
CloudUpload,
Cpu,
Download,
FileCode2,
Package,
Play,
RadioTower,
RefreshCw,
RotateCcw,
Search,
Server,
SquareTerminal,
X,
Zap,
} from "lucide-react";
import { ReactNode, useState } from "react";
import {
BackendCapability,
BackendRuntime,
BackendWitPackage,
witPackageDownloadUrl,
} from "../api";
import {
EventTone,
RuntimeEvent,
Service,
ServiceStatus,
STATUS_META,
formatDuration,
serviceKey,
} from "../console-model";
function PageHeading({
eyebrow,
title,
description,
actions,
}: {
eyebrow: string;
title: string;
description: string;
actions?: ReactNode;
}) {
return (
{eyebrow}
{title}
{description}
{actions &&
{actions}
}
);
}
export function Overview({
services,
filteredServices,
events,
running,
faulted,
totalCalls,
deploymentCount,
runtime,
selectedService,
onSelect,
onDeploy,
onInvoke,
onStatus,
onActivate,
onViewAll,
onRefresh,
onRuntimeAction,
runtimeAction,
deploymentAction,
}: {
services: Service[];
filteredServices: Service[];
events: RuntimeEvent[];
running: number;
faulted: number;
totalCalls: number;
deploymentCount: number;
runtime: BackendRuntime | null;
selectedService: Service | null;
onSelect: (service: Service) => void;
onDeploy: () => void;
onInvoke: (service: Service) => void;
onStatus: (service: Service, status: ServiceStatus) => void;
onActivate: (service: Service) => void;
onViewAll: () => void;
onRefresh: () => void;
onRuntimeAction: (action: "start" | "stop" | "restart") => void;
runtimeAction: "start" | "stop" | "restart" | null;
deploymentAction: string | null;
}) {
const errorCount = services.reduce((sum, service) => sum + service.errors, 0);
const latencies = services
.map((service) => service.latencyMs)
.filter((latency): latency is number => latency !== null);
const latestLatency = latencies.length ? Math.max(...latencies) : null;
return (
<>
>
}
/>
}
label="服务版本"
value={services.length.toString()}
note={`${deploymentCount} 个对外服务`}
tone="cyan"
/>
}
label="运行实例"
value={running.toString()}
note={`${services.length - running} 个未运行`}
tone="green"
/>
}
label="累计调用"
value={totalCalls.toLocaleString("zh-CN")}
note="持久化统计"
tone="amber"
/>
}
label="异常"
value={(faulted + errorCount).toString()}
note={faulted + errorCount ? "需要检查" : "无异常"}
tone="coral"
/>
Wasmeld Runtime
{runtime?.status === "running"
? "Engine 已启用 fuel 与 epoch interruption"
: "Engine 与所有 Actor 已释放"}
{runtime?.status === "stopped" ? (
) : (
<>
>
)}
{selectedService && (
当前选中
{selectedService.id}
@{selectedService.revision}
{!selectedService.active && (
)}
)}
>
);
}
function Metric({
icon,
label,
value,
note,
tone,
}: {
icon: ReactNode;
label: string;
value: string;
note: string;
tone: string;
}) {
return (
{icon}
{label}
{value}
{note}
);
}
function RuntimeStat({ label, value, detail }: { label: string; value: string; detail: string }) {
return (
{label}
{value}
{detail}
);
}
function DetailValue({ label, value }: { label: string; value: string }) {
return (
{label}
{value}
);
}
function StatusBadge({ status }: { status: ServiceStatus }) {
const meta = STATUS_META[status];
return (
{meta.label}
);
}
function CapabilitySummary({ capabilities }: { capabilities: BackendCapability[] }) {
if (capabilities.length === 0) {
return 无;
}
const [first, ...remaining] = capabilities;
return (
capability.interface).join("\n")}
>
{first.package}/{first.name} @{first.version}
{remaining.length > 0 && +{remaining.length}}
);
}
function ServiceTable({
services,
selectedKey,
onSelect,
onInvoke,
onStatus,
onActivate,
canActivate,
deploymentAction,
compact = false,
}: {
services: Service[];
selectedKey?: string;
onSelect: (service: Service) => void;
onInvoke: (service: Service) => void;
onStatus: (service: Service, status: ServiceStatus) => void;
onActivate: (service: Service) => void;
canActivate: boolean;
deploymentAction: string | null;
compact?: boolean;
}) {
if (services.length === 0) {
return (
没有匹配的服务
调整搜索关键词或状态筛选。
);
}
return (
| 服务 |
状态 |
{!compact && 制品 | }
{!compact && Host 能力 | }
调用 |
延迟 |
操作
|
{services.map((service) => (
|
|
|
{!compact && {service.artifact} | }
{!compact && (
|
)}
{service.calls.toLocaleString("zh-CN")} |
{service.latencyMs ? `${service.latencyMs} ms` : "—"}
|
{service.status === "running" ? (
<>
>
) : (
)}
|
))}
);
}
function EventList({ events }: { events: RuntimeEvent[] }) {
const icons: Record = {
success: ,
warning: ,
danger: ,
neutral: ,
};
return (
{events.map((event) => (
{icons[event.tone]}
{event.title}
{event.detail}
))}
);
}
export function ServicesView({
services,
statusFilter,
onFilter,
onDeploy,
onInvoke,
onSelect,
onStatus,
onActivate,
canActivate,
deploymentAction,
}: {
services: Service[];
statusFilter: "all" | ServiceStatus;
onFilter: (status: "all" | ServiceStatus) => void;
onDeploy: () => void;
onInvoke: (service: Service) => void;
onSelect: (service: Service) => void;
onStatus: (service: Service, status: ServiceStatus) => void;
onActivate: (service: Service) => void;
canActivate: boolean;
deploymentAction: string | null;
}) {
return (
<>
注册组件
}
/>
{[
["all", "全部"],
["running", "运行中"],
["stopped", "已停止"],
["faulted", "故障"],
].map(([value, label]) => (
))}
{services.length} 个版本
>
);
}
export function WitPackagesView({
apiBase,
packages,
onPublish,
}: {
apiBase: string;
packages: BackendWitPackage[];
onPublish: () => void;
}) {
function downloadPackage(packageMetadata: BackendWitPackage) {
const anchor = document.createElement("a");
anchor.href = witPackageDownloadUrl(apiBase, packageMetadata);
anchor.download = `${packageMetadata.name.replace(":", "-")}-${packageMetadata.version}.wasm`;
document.body.append(anchor);
anchor.click();
anchor.remove();
}
return (
<>
发布 WIT 包
}
/>
发布后不可覆盖;包名、版本与依赖由制品自动解析。
{packages.length} 个包版本
{packages.length === 0 ? (
没有匹配的 WIT 包
发布二进制 WIT Package,或调整搜索关键词。
) : (
| Package |
依赖 |
SHA-256 |
操作
|
{packages.map((packageMetadata) => {
const dependencies = packageMetadata.dependencies
.map((dependency) => `${dependency.name}@${dependency.version}`)
.join(", ");
return (
|
{packageMetadata.name}
{packageMetadata.version}
|
{dependencies || "无直接依赖"}
|
{packageMetadata.sha256}
|
|
);
})}
)}
>
);
}
export function InstancesView({
services,
onInvoke,
onStatus,
onRefresh,
}: {
services: Service[];
onInvoke: (service: Service) => void;
onStatus: (service: Service, status: ServiceStatus) => void;
onRefresh: () => void;
}) {
const instances = services.filter(
(service) => service.status === "running" || service.status === "faulted",
);
return (
<>
}
/>
{instances.length === 0 ? (
) : (
instances.map((service) => (
{service.id}@{service.revision}
actor/{service.id}-01
{service.updatedAt}
))
)}
>
);
}
export function ActivityView({
events,
services,
}: {
events: RuntimeEvent[];
services: Service[];
}) {
const calls = services.reduce((sum, service) => sum + service.calls, 0);
const errors = services.reduce((sum, service) => sum + service.errors, 0);
const latencies = services
.map((service) => service.latencyMs)
.filter((latency): latency is number => latency !== null);
const averageLatency = latencies.length
? latencies.reduce((sum, latency) => sum + latency, 0) / latencies.length
: null;
function exportEvents() {
const blob = new Blob([JSON.stringify(events, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = "wasmeld-events.json";
anchor.click();
URL.revokeObjectURL(url);
}
return (
<>
导出记录
}
/>
时间范围
当前进程
成功率
{calls ? `${(((calls - errors) / calls) * 100).toFixed(2)}%` : "—"}
平均延迟
{averageLatency === null ? "—" : `${averageLatency.toFixed(1)} ms`}
记录数
{events.length}
>
);
}
export function SettingsView({
endpoint,
onSaveEndpoint,
}: {
endpoint: string;
onSaveEndpoint: (value: string) => void;
}) {
const [value, setValue] = useState(endpoint);
return (
<>
>
);
}