Files
wasmeld/crates/wasmeld-console/web/src/components/ui/service-table.tsx
T

196 lines
7.9 KiB
TypeScript
Raw Normal View History

import {
Box,
Braces,
CircleStop,
Play,
RadioTower,
RefreshCw,
RotateCcw,
Search,
SquareTerminal,
} from "lucide-solid";
import { For, Show } from "solid-js";
import { serviceKey, type Service } from "../../lib/model";
import type { HostSharedState, PageCommand } from "../../sdk/protocol";
import { EmptyState } from "./empty-state";
import { StatusBadge } from "./status-badge";
export function ServiceTable(props: {
services: Service[];
state: HostSharedState;
command: (command: PageCommand) => void;
compact?: boolean;
}) {
return (
<Show
when={props.services.length > 0}
fallback={
<EmptyState icon={Search} title="没有匹配的服务" detail="调整搜索关键词或状态筛选。" />
}
>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>服务</th>
<th>状态</th>
<Show when={!props.compact}>
<th>制品</th>
<th>Host 能力</th>
</Show>
<th>调用</th>
<th>延迟</th>
<th>
<span class="sr-only">操作</span>
</th>
</tr>
</thead>
<tbody>
<For each={props.services}>
{(service) => {
const key = serviceKey(service);
return (
<tr>
<td>
<div class="flex min-w-48 items-center gap-3">
<span class="flex size-8 shrink-0 items-center justify-center rounded-md bg-brand-soft text-brand">
<Box size={15} />
</span>
<span class="min-w-0">
<span class="flex items-center gap-2">
<strong class="truncate">{service.id}</strong>
<Show when={service.active}>
<span class="inline-flex items-center gap-1 rounded-sm bg-cyan-soft px-1.5 py-0.5 text-[10px] font-semibold text-cyan-strong">
<RadioTower size={10} />
对外
</span>
</Show>
</span>
<small class="mt-0.5 block text-xs text-muted">{service.revision}</small>
</span>
</div>
</td>
<td>
<StatusBadge status={service.status} />
</td>
<Show when={!props.compact}>
<td class="code max-w-48 truncate text-muted" title={service.artifact}>
{service.artifact}
</td>
<td>
<Show
when={service.capabilities.length > 0}
fallback={<span class="text-xs text-muted"></span>}
>
<span
class="inline-flex max-w-56 items-center gap-1.5 text-xs text-muted"
title={service.capabilities
.map((capability) => capability.interface)
.join("\n")}
>
<Braces size={12} />
<span class="truncate">
{service.capabilities[0]?.package}/{service.capabilities[0]?.name}
</span>
<Show when={service.capabilities.length > 1}>
<small>+{service.capabilities.length - 1}</small>
</Show>
</span>
</Show>
</td>
</Show>
<td class="font-mono text-xs">{service.calls.toLocaleString("zh-CN")}</td>
<td class="font-mono text-xs">
{service.latencyMs === null ? "—" : `${service.latencyMs} ms`}
</td>
<td>
<div class="row-actions">
<button
type="button"
aria-label="设为对外版本"
title={service.active ? "当前对外版本" : "设为对外版本"}
disabled={
service.active ||
props.state.snapshot?.runtime.status !== "running" ||
props.state.deploymentAction !== null
}
onClick={() => props.command({ type: "open-activate", serviceKey: key })}
>
{props.state.deploymentAction === key ? (
<RefreshCw class="spin" size={15} />
) : (
<RadioTower size={15} />
)}
</button>
<Show
when={service.status === "running"}
fallback={
<button
type="button"
aria-label={`启动 ${service.id}`}
title="启动"
disabled={props.state.serviceAction !== null}
onClick={() =>
props.command({
type: "service-action",
serviceKey: key,
action: "start",
})
}
>
<Play size={15} />
</button>
}
>
<button
type="button"
aria-label={`调用 ${service.id}`}
title="测试调用"
onClick={() => props.command({ type: "open-invoke", serviceKey: key })}
>
<SquareTerminal size={15} />
</button>
<button
type="button"
aria-label={`重启 ${service.id}`}
title="重启"
disabled={props.state.serviceAction !== null}
onClick={() =>
props.command({
type: "service-action",
serviceKey: key,
action: "restart",
})
}
>
<RotateCcw size={15} />
</button>
<button
type="button"
aria-label={`停止 ${service.id}`}
title="停止"
disabled={props.state.serviceAction !== null}
onClick={() =>
props.command({
type: "service-action",
serviceKey: key,
action: "stop",
})
}
>
<CircleStop size={15} />
</button>
</Show>
</div>
</td>
</tr>
);
}}
</For>
</tbody>
</table>
</div>
</Show>
);
}