refactor: use original dashboard UI primitives

This commit is contained in:
2026-09-20 17:14:13 +08:00
parent a25e84ac14
commit 59f170c191
32 changed files with 1981 additions and 2179 deletions
+67 -118
View File
@@ -1,16 +1,11 @@
import { useEffect, useState } from "react";
import { useDataProvider, useList } from "../../shared/hooks/dataHooks.js";
import {
Alert,
Button,
ConfirmDialog,
Field,
Input,
Modal,
PageHeader,
PageState,
Table,
} from "../../shared/ui/ui.jsx";
import { Alert } from "../../components/ui/alert";
import { Button } from "../../components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "../../components/ui/dialog";
import { Input } from "../../components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table";
import { ConfirmDialog, Field, PageHeader, PageState } from "../../shared/ui/ui.jsx";
import { useTitle } from "../../shared/hooks/hooks.js";
const namePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
@@ -22,26 +17,25 @@ function TokenCell({ token, name }) {
<code className="anywhere font-mono text-[13px]">
{visible ? token : "••••••••••••"}
</code>
<button
<Button
type="button"
variant="ghost"
size="icon-sm"
aria-label={visible ? `隐藏 ${name} 的令牌` : `显示 ${name} 的令牌`}
onClick={() => setVisible((current) => !current)}
className="rounded p-1 text-muted hover:text-primary"
>
<i
className={visible ? "ri-eye-off-line" : "ri-eye-line"}
aria-hidden="true"
/>
</button>
<i className={visible ? "ri-eye-off-line" : "ri-eye-line"} aria-hidden="true" />
</Button>
{visible ? (
<button
<Button
type="button"
variant="ghost"
size="icon-sm"
aria-label={`复制 ${token}`}
onClick={() => navigator.clipboard?.writeText(token)}
className="rounded p-1 text-muted hover:text-primary"
>
<i className="ri-file-copy-line" aria-hidden="true" />
</button>
</Button>
) : null}
</div>
);
@@ -87,30 +81,10 @@ function GatewayFormModal({ open, onClose, onSubmit, busy, error, initial }) {
}
return (
<Modal
open={open}
onClose={onClose}
title={editing ? "编辑网关" : "注册网关"}
labelledBy={`gateway-${mode}-title`}
footer={
<>
<Button onClick={onClose} disabled={busy}>
取消
</Button>
<Button
variant="primary"
type="submit"
form={`gateway-${mode}-form`}
busy={busy}
busyText={editing ? "保存中…" : "注册中…"}
disabled={!valid}
>
{editing ? "保存" : "注册网关"}
</Button>
</>
}
>
<form id={`gateway-${mode}-form`} onSubmit={submit} noValidate>
<Dialog open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onClose() }}>
<DialogContent>
<DialogHeader><DialogTitle>{editing ? "编辑网关" : "注册网关"}</DialogTitle></DialogHeader>
<form id={`gateway-${mode}-form`} onSubmit={submit} noValidate>
{error ? (
<Alert variant="destructive" className="mb-4">
{error.message}
@@ -135,7 +109,7 @@ function GatewayFormModal({ open, onClose, onSubmit, busy, error, initial }) {
value={form.name}
onChange={(event) => update("name", event.target.value)}
placeholder="如 gw-main"
invalid={nameInvalid}
aria-invalid={nameInvalid || undefined}
/>
</Field>
<Field
@@ -151,7 +125,7 @@ function GatewayFormModal({ open, onClose, onSubmit, busy, error, initial }) {
value={form.endpoint}
onChange={(event) => update("endpoint", event.target.value)}
placeholder="http://127.0.0.1:8081"
invalid={endpointInvalid}
aria-invalid={endpointInvalid || undefined}
/>
</Field>
<Field
@@ -172,12 +146,19 @@ function GatewayFormModal({ open, onClose, onSubmit, busy, error, initial }) {
value={form.token}
onChange={(event) => update("token", event.target.value)}
placeholder={editing ? "留空保持现有令牌" : "留空由平台生成"}
invalid={tokenInvalid}
aria-invalid={tokenInvalid || undefined}
/>
</Field>
</div>
</form>
</Modal>
<DialogFooter>
<Button type="button" variant="outline" onClick={onClose} disabled={busy}>取消</Button>
<Button variant="default" type="submit" form={`gateway-${mode}-form`} disabled={!valid || busy}>
{busy ? (editing ? "保存中…" : "注册中…") : editing ? "保存" : "注册网关"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}
@@ -255,27 +236,25 @@ export function GatewayList() {
title="网关管理"
description="注册宿主机 native browser gateway;令牌须与 gateway 进程的 GATEWAY_TOKEN 一致"
>
<Button
variant="primary"
icon="ri-add-line"
onClick={() => setCreateOpen(true)}
>
<Button variant="default" onClick={() => setCreateOpen(true)}>
<i className="ri-add-line" aria-hidden="true" />
注册网关
</Button>
</PageHeader>
{created ? (
<Alert variant="success" className="mb-4">
<Alert variant="default" className="mb-4">
网关 {created.name} 已注册。
<span className="ml-1 inline-flex items-center gap-1">
令牌 <code className="font-mono">{created.token}</code>
<button
<Button
type="button"
variant="ghost"
size="icon-sm"
aria-label={`复制 ${created.token}`}
onClick={() => navigator.clipboard?.writeText(created.token)}
className="rounded p-1 text-muted hover:text-primary"
>
<i className="ri-file-copy-line" aria-hidden="true" />
</button>
</Button>
</span>
<Button size="sm" onClick={() => setCreated(null)}>
知道了
@@ -293,65 +272,35 @@ export function GatewayList() {
empty={gateways.length === 0}
emptyText="暂无网关;注册后环境即可调度到该网关。"
>
<Table
columns={[
{
header: "名称",
width: "20%",
render: (gateway) => (
<span className="font-semibold">{gateway.name}</span>
),
},
{
header: "Endpoint",
width: "34%",
render: (gateway) => (
<code className="anywhere font-mono text-[13px]">
{gateway.endpoint}
</code>
),
},
{
header: "令牌",
width: "30%",
render: (gateway) => (
<TokenCell token={gateway.token} name={gateway.name} />
),
},
{
header: "操作",
width: "16%",
align: "right",
render: (gateway) => (
<div className="flex items-center justify-end gap-1">
<button
type="button"
aria-label={`编辑 ${gateway.name}`}
disabled={busy === gateway.name || busy === "edit"}
onClick={() => {
setEditing(gateway);
setEditError(null);
}}
className="rounded p-1.5 text-muted hover:bg-[#f5f7fa] hover:text-primary disabled:opacity-50"
>
<i className="ri-edit-line" aria-hidden="true" />
</button>
<button
type="button"
aria-label={`删除 ${gateway.name}`}
disabled={busy === gateway.name || busy === "edit"}
onClick={() => setDeleting(gateway)}
className="rounded p-1.5 text-danger hover:bg-[#fdf1f0] disabled:opacity-50"
>
<i className="ri-delete-bin-line" aria-hidden="true" />
</button>
</div>
),
},
]}
rows={gateways}
rowKey={(gateway) => gateway.name}
/>
<Table>
<TableHeader>
<TableRow>
<TableHead>名称</TableHead>
<TableHead>Endpoint</TableHead>
<TableHead>令牌</TableHead>
<TableHead>操作</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{gateways.map((gateway) => (
<TableRow key={gateway.name}>
<TableCell><span className="font-semibold">{gateway.name}</span></TableCell>
<TableCell><code className="anywhere font-mono text-[13px]">{gateway.endpoint}</code></TableCell>
<TableCell><TokenCell token={gateway.token} name={gateway.name} /></TableCell>
<TableCell>
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon-sm" aria-label={`编辑 ${gateway.name}`} disabled={busy === gateway.name || busy === "edit"} onClick={() => { setEditing(gateway); setEditError(null); }}>
<i className="ri-edit-line" aria-hidden="true" />
</Button>
<Button variant="destructive" size="icon-sm" aria-label={`删除 ${gateway.name}`} disabled={busy === gateway.name || busy === "edit"} onClick={() => setDeleting(gateway)}>
<i className="ri-delete-bin-line" aria-hidden="true" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</PageState>
<GatewayFormModal
open={createOpen}