技术栈切换(按 AGENTS.md 批准栈):react-admin/MUI → Refine v5 + shadcn/ui
(Tailwind CSS v4),Vite 8 + React 19 + react-router v7 hash 路由。
- 全部 9 个页面重写:AccountsPage/BrowsersPage(BrowserList/CreatePage/Detail)/
BrowserImagesPage/GatewaysPage/NetworkExitsPage/TasksPage/TracePages/
DraftPage/AuditPage;自建 lib/ui.jsx 组件库与 Layout(侧导航 + Outlet)
- 简单创建/编辑改 modal(账号/网关/镜像/出口),环境创建走独立页
/browsers/new;列表按信息密度选表格或移动卡片双形态
- 图标统一 RemixIcon;领域动作保持显式动词(start/stop/upgrade/recycle
等,不伪装成 CRUD update)
- LoginPage:Basic 凭证存 localStorage;401 清凭证并跳 #/login;
AuthGate 改为渲染期读 localStorage(react-router v7 的 pushState 不触发
hashchange,事件订阅会在导航后拿到过期状态导致登录后卡死)
- dataProvider 适配 Refine v5 契约:所有方法收单个参数对象
({resource, pagination, filters, meta, variables}),create/update/
delete/deleteOne 语义对齐
- Modal 关闭时焦点还给触发元素;Select 保持原生 button/combobox 语义
- e2e 补登录前置;账号创建流程同步 modal 交互
验证:npm test 65/65 通过;覆盖率 71.5% stmts / 76.3% lines(门槛 65%);
vite build 成功;playwright e2e 9/9 通过。
42 lines
2.0 KiB
React
42 lines
2.0 KiB
React
import { Link, useParams } from 'react-router'
|
|
import { useOne } from '@refinedev/core'
|
|
import { Alert, Button, Card, CardContent, DetailList, PageHeader, PageState } from './lib/ui.jsx'
|
|
import { useTitle } from './lib/hooks.js'
|
|
|
|
function PendingOrError({ pending, error, children }) {
|
|
if (pending) return <PageState pending />
|
|
if (error) return <Alert variant="destructive">{error.message}</Alert>
|
|
return children
|
|
}
|
|
|
|
export function AttemptDetail() {
|
|
const { id } = useParams()
|
|
const { result, error, isPending } = useOne({ resource: 'attempts', id, queryOptions: { retry: false } })
|
|
const attempt = result
|
|
useTitle('CreatorHub · Attempt 详情')
|
|
return (
|
|
<PendingOrError pending={isPending} error={error}>
|
|
{attempt ? (
|
|
<div>
|
|
<PageHeader title="Attempt 详情" description={<span className="anywhere">{attempt.id}</span>} />
|
|
<Card>
|
|
<CardContent className="space-y-4">
|
|
<DetailList rows={[
|
|
['结果', attempt.outcome || '执行中'],
|
|
['开始', new Date(attempt.started_at).toLocaleString('zh-CN')],
|
|
['环境 / 出口 / 绑定版本', <span className="anywhere">{attempt.browser_env_alias || '未记录'} · {attempt.network_exit_id || '未记录'} · {attempt.binding_version || '未记录'}</span>],
|
|
]} />
|
|
<div className="flex flex-wrap gap-3">
|
|
<Button as={Link} to={`/tasks/${encodeURIComponent(attempt.task_id)}`}>返回任务</Button>
|
|
<Button as={Link} to={`/audit?attempt_id=${encodeURIComponent(attempt.id)}`}>查看审计</Button>
|
|
{attempt.browser_env_alias ? <Button as={Link} to={`/browsers/${encodeURIComponent(attempt.browser_env_alias)}`}>查看环境</Button> : null}
|
|
{attempt.network_exit_id ? <Button as={Link} to={`/network-exits/${encodeURIComponent(attempt.network_exit_id)}`}>查看出口</Button> : null}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
) : null}
|
|
</PendingOrError>
|
|
)
|
|
}
|