refactor(console-web): centralize page lifecycle and UI composition
Declare navigation, page metadata, lazy loaders, and keep-alive policy in one registry. Retain primary iframe pages until a continuous inactive TTL expires and expose typed show, hide, and unload events through the Page SDK. Move business-aware components out of the UI primitive directory, migrate views and Host dialogs to variant primitives and local Tailwind classes, and remove the global component style layer. Keep native dialogs inside the overlay flex context so they remain centered. Cover iframe expiration, lifecycle dispatch, UI dependency direction, and global style ownership with focused tests.
This commit is contained in:
@@ -11,8 +11,11 @@ import {
|
||||
Zap,
|
||||
} from "lucide-solid";
|
||||
import { createMemo, For, Show, type JSXElement } from "solid-js";
|
||||
import { PageHeading } from "../../components/ui/page-heading";
|
||||
import { ServiceTable } from "../../components/ui/service-table";
|
||||
import { Page } from "../../components/layout/page";
|
||||
import { PageHeading } from "../../components/layout/page-heading";
|
||||
import { ServiceTable } from "../../components/services/service-table";
|
||||
import { Button, IconButton } from "../../components/ui/button";
|
||||
import { Card, CardHeader } from "../../components/ui/card";
|
||||
import { eventsFrom, servicesFrom } from "../../lib/view-state";
|
||||
import { formatDuration } from "../../lib/model";
|
||||
import type { PageProps } from "../types";
|
||||
@@ -29,7 +32,7 @@ export default function OverviewPage(props: PageProps) {
|
||||
const errors = createMemo(() => services().reduce((total, service) => total + service.errors, 0));
|
||||
|
||||
return (
|
||||
<main class="page">
|
||||
<Page>
|
||||
<Show when={props.state()} fallback={<OverviewSkeleton />}>
|
||||
{(state) => (
|
||||
<>
|
||||
@@ -39,28 +42,28 @@ export default function OverviewPage(props: PageProps) {
|
||||
description={`${services().length} 个已注册版本,${running()} 个 Actor 正在运行。`}
|
||||
actions={
|
||||
<>
|
||||
<button
|
||||
class="icon-btn"
|
||||
type="button"
|
||||
<IconButton
|
||||
aria-label="刷新"
|
||||
title="刷新"
|
||||
onClick={() => props.command({ type: "refresh" })}
|
||||
>
|
||||
<RefreshCw size={16} />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
</IconButton>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => props.command({ type: "open-register" })}
|
||||
>
|
||||
<CloudUpload size={16} />
|
||||
注册组件
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<section class="metric-grid" aria-label="关键指标">
|
||||
<section
|
||||
class="mb-5 grid grid-cols-1 border-l border-t border-line sm:grid-cols-2 xl:grid-cols-4"
|
||||
aria-label="关键指标"
|
||||
>
|
||||
<Metric
|
||||
icon={<Package size={18} />}
|
||||
tone="bg-cyan-soft text-cyan-strong"
|
||||
@@ -91,7 +94,10 @@ export default function OverviewPage(props: PageProps) {
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="panel mb-5 grid lg:grid-cols-[minmax(280px,1.4fr)_repeat(3,minmax(130px,1fr))]">
|
||||
<Card
|
||||
as="section"
|
||||
class="mb-5 grid lg:grid-cols-[minmax(280px,1.4fr)_repeat(3,minmax(130px,1fr))]"
|
||||
>
|
||||
<div class="flex items-center gap-3 border-b border-line px-5 py-4 lg:border-b-0 lg:border-r">
|
||||
<span class="flex size-10 items-center justify-center rounded-md bg-brand-soft text-brand">
|
||||
<Cpu size={18} />
|
||||
@@ -108,32 +114,33 @@ export default function OverviewPage(props: PageProps) {
|
||||
<Show
|
||||
when={state().snapshot?.runtime.status === "running"}
|
||||
fallback={
|
||||
<button
|
||||
class="icon-btn"
|
||||
<IconButton
|
||||
aria-label="启动 Runtime"
|
||||
title="启动 Runtime"
|
||||
disabled={state().runtimeAction !== null}
|
||||
onClick={() => props.command({ type: "runtime-action", action: "start" })}
|
||||
>
|
||||
<Play size={15} />
|
||||
</button>
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<button
|
||||
class="icon-btn"
|
||||
<IconButton
|
||||
aria-label="重启 Runtime"
|
||||
title="重启 Runtime"
|
||||
disabled={state().runtimeAction !== null}
|
||||
onClick={() => props.command({ type: "runtime-action", action: "restart" })}
|
||||
>
|
||||
<RotateCcw size={15} />
|
||||
</button>
|
||||
<button
|
||||
class="icon-btn text-coral-strong"
|
||||
</IconButton>
|
||||
<IconButton
|
||||
tone="danger"
|
||||
aria-label="停止 Runtime"
|
||||
title="停止 Runtime"
|
||||
disabled={state().runtimeAction !== null}
|
||||
onClick={() => props.command({ type: "runtime-action", action: "stop" })}
|
||||
>
|
||||
<CircleStop size={15} />
|
||||
</button>
|
||||
</IconButton>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,32 +163,32 @@ export default function OverviewPage(props: PageProps) {
|
||||
}
|
||||
detail="当前 Runtime"
|
||||
/>
|
||||
</section>
|
||||
</Card>
|
||||
|
||||
<div class="grid gap-5 xl:grid-cols-[minmax(0,1.7fr)_minmax(300px,0.8fr)]">
|
||||
<section class="panel min-w-0">
|
||||
<div class="panel-header">
|
||||
<Card as="section" class="min-w-0">
|
||||
<CardHeader>
|
||||
<div>
|
||||
<h2>服务状态</h2>
|
||||
<p>已注册版本及当前 Actor 状态</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-secondary h-9"
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => props.command({ type: "navigate", view: "services" })}
|
||||
>
|
||||
查看全部
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<ServiceTable
|
||||
services={services().slice(0, 5)}
|
||||
state={state()}
|
||||
command={props.command}
|
||||
compact
|
||||
/>
|
||||
</section>
|
||||
</Card>
|
||||
|
||||
<aside class="panel">
|
||||
<div class="panel-header">
|
||||
<Card as="aside">
|
||||
<CardHeader>
|
||||
<div>
|
||||
<h2>最近事件</h2>
|
||||
<p>生命周期与调用结果</p>
|
||||
@@ -190,7 +197,7 @@ export default function OverviewPage(props: PageProps) {
|
||||
<span class="size-1.5 rounded-full bg-brand" />
|
||||
LIVE
|
||||
</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<div class="divide-y divide-line">
|
||||
<For
|
||||
each={events().slice(0, 6)}
|
||||
@@ -207,12 +214,12 @@ export default function OverviewPage(props: PageProps) {
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</aside>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
</main>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -224,13 +231,15 @@ function Metric(props: {
|
||||
note: string;
|
||||
}) {
|
||||
return (
|
||||
<article class="metric">
|
||||
<div class={`metric-icon ${props.tone}`}>{props.icon}</div>
|
||||
<div class="metric-copy">
|
||||
<span>{props.label}</span>
|
||||
<strong>{props.value}</strong>
|
||||
<article class="flex min-h-24 items-center gap-3 border-b border-r border-line bg-white px-4 py-3">
|
||||
<div class={`flex size-10 shrink-0 items-center justify-center rounded-md ${props.tone}`}>
|
||||
{props.icon}
|
||||
</div>
|
||||
<small>{props.note}</small>
|
||||
<div class="min-w-0 flex-1">
|
||||
<span class="block text-xs text-muted">{props.label}</span>
|
||||
<strong class="mt-1 block text-2xl font-bold">{props.value}</strong>
|
||||
</div>
|
||||
<small class="self-end whitespace-nowrap pb-1 text-[11px] text-muted">{props.note}</small>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -248,8 +257,8 @@ function RuntimeStat(props: { label: string; value: string; detail: string }) {
|
||||
function OverviewSkeleton() {
|
||||
return (
|
||||
<div class="space-y-5 py-5">
|
||||
<div class="skeleton-line w-36" />
|
||||
<div class="skeleton-line h-8 w-64" />
|
||||
<div class="h-3 w-36 animate-pulse rounded-sm bg-[#dfe6e4]" />
|
||||
<div class="h-8 w-64 animate-pulse rounded-sm bg-[#dfe6e4]" />
|
||||
<div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
||||
<For each={[1, 2, 3, 4]}>{() => <div class="h-24 animate-pulse bg-white" />}</For>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user