fix(web): show unsupported resources (vm) disabled; Overview uses new-model framing
1) Deploy Sub Agent: stop hiding resource types AM doesn't support yet (vm) — show them but disabled with a "暂不支持" badge + tooltip, so users see their bindings instead of wondering where the vm went. 2) Overview (概述/cockpit): replaced the old task-model status cards (Running/Completed/Failed/裁决 + "code delivery runs") with template-agent semantics — Agent 总数 / 运行中 / 启动中 / 异常, "最近部署 · 我的 Agent", all Chinese. Dropped unused t()/useTranslation/PlayCircle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vendored
+1
-1
@@ -20,7 +20,7 @@
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
<meta name="theme-color" content="#7B6BE3" />
|
||||
<link rel="icon" href="/favicon.ico"><script defer src="/static/js/vendor-radix.829c7e3fad.js"></script><script defer src="/static/js/vendor-tanstack.632dbe8908.js"></script><script defer src="/static/js/lib-react.5c8909c28c.js"></script><script defer src="/static/js/6374.bc21d8b214.js"></script><script defer src="/static/js/index.0ac7693b4a.js"></script><link href="/static/css/index.08eae7aeba.css" rel="stylesheet"></head>
|
||||
<link rel="icon" href="/favicon.ico"><script defer src="/static/js/vendor-radix.829c7e3fad.js"></script><script defer src="/static/js/vendor-tanstack.632dbe8908.js"></script><script defer src="/static/js/lib-react.5c8909c28c.js"></script><script defer src="/static/js/6374.bc21d8b214.js"></script><script defer src="/static/js/index.8097a36f3b.js"></script><link href="/static/css/index.e9e9dfbb24.css" rel="stylesheet"></head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowUpRight,
|
||||
Building2,
|
||||
CheckCircle2,
|
||||
CircleDashed,
|
||||
PlayCircle,
|
||||
Rocket,
|
||||
ScrollText,
|
||||
} from 'lucide-react'
|
||||
@@ -159,8 +157,6 @@ function formatRelative(value: string | number | undefined): string {
|
||||
}
|
||||
|
||||
export function CockpitView() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const deploymentsQuery = useQuery({
|
||||
queryKey: ['cockpit', 'agents'],
|
||||
queryFn: listMyAgents,
|
||||
@@ -177,24 +173,17 @@ export function CockpitView() {
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const list: AgentItem[] = deploymentsQuery.data ?? []
|
||||
const counters: Record<StatusKey, number> = {
|
||||
running: 0,
|
||||
success: 0,
|
||||
failed: 0,
|
||||
pending: 0,
|
||||
}
|
||||
list.forEach((d) => {
|
||||
counters[classifyStatus(d.status || '')]++
|
||||
})
|
||||
const total = list.length || 1
|
||||
const successRate = Math.round(
|
||||
((counters.success + counters.running) / total) * 100
|
||||
)
|
||||
return {
|
||||
list,
|
||||
counters,
|
||||
successRate: Number.isFinite(successRate) ? successRate : 0,
|
||||
let running = 0
|
||||
let starting = 0
|
||||
let abnormal = 0
|
||||
for (const a of list) {
|
||||
const v = (a.status || '').toLowerCase()
|
||||
if (['running', 'active', 'ready'].includes(v)) running++
|
||||
else if (['pending', 'starting', 'provisioning'].includes(v)) starting++
|
||||
else if (['failed', 'error', 'unhealthy', 'crashed', 'stopped'].includes(v))
|
||||
abnormal++
|
||||
}
|
||||
return { list, running, starting, abnormal, total: list.length }
|
||||
}, [deploymentsQuery.data])
|
||||
|
||||
const recentDeployments = useMemo(
|
||||
@@ -211,43 +200,43 @@ export function CockpitView() {
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
{/* Tier 1: Status */}
|
||||
{/* Tier 1: Status (template-agent model) */}
|
||||
<div className='grid grid-cols-2 gap-3 lg:grid-cols-4'>
|
||||
<StatCard
|
||||
label={t('Running agents')}
|
||||
value={String(stats.counters.running)}
|
||||
hint={t('Agent runs in progress')}
|
||||
icon={PlayCircle}
|
||||
tone='primary'
|
||||
label='Agent 总数'
|
||||
value={String(stats.total)}
|
||||
hint='我部署的 Agent'
|
||||
icon={Rocket}
|
||||
tone='muted'
|
||||
/>
|
||||
<StatCard
|
||||
label={t('Completed')}
|
||||
value={String(stats.counters.success)}
|
||||
hint={t('Finished with a deliverable')}
|
||||
label='运行中'
|
||||
value={String(stats.running)}
|
||||
hint='正常对外服务'
|
||||
icon={CheckCircle2}
|
||||
tone='success'
|
||||
/>
|
||||
<StatCard
|
||||
label={t('Failed')}
|
||||
value={String(stats.counters.failed)}
|
||||
hint={t('Failed or no deliverable')}
|
||||
icon={AlertTriangle}
|
||||
tone='danger'
|
||||
/>
|
||||
<StatCard
|
||||
label={t('Pending')}
|
||||
value={String(stats.counters.pending)}
|
||||
hint={t('Queued / waiting / needs codegen')}
|
||||
label='启动中'
|
||||
value={String(stats.starting)}
|
||||
hint='正在拉起'
|
||||
icon={CircleDashed}
|
||||
tone='warn'
|
||||
/>
|
||||
<StatCard
|
||||
label='异常'
|
||||
value={String(stats.abnormal)}
|
||||
hint='已停止 / 失败 / 不健康'
|
||||
icon={AlertTriangle}
|
||||
tone='danger'
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Usage & cost — the CURRENT user's OWN lifetime usage (from /api/user/self),
|
||||
not the admin-global aggregate. */}
|
||||
<div>
|
||||
<p className='text-muted-foreground mb-2 text-[11px] font-semibold tracking-[0.16em] uppercase'>
|
||||
{t('Usage & cost')}
|
||||
费用与消耗
|
||||
</p>
|
||||
<WalletStatsCard
|
||||
user={selfQuery.data ?? null}
|
||||
@@ -261,15 +250,13 @@ export function CockpitView() {
|
||||
<header className='mb-4 flex items-center justify-between'>
|
||||
<div>
|
||||
<p className='text-[11px] font-semibold tracking-[0.16em] text-muted-foreground uppercase'>
|
||||
{t('Recent deployments')}
|
||||
最近部署
|
||||
</p>
|
||||
<h3 className='mt-1 text-lg font-semibold'>
|
||||
{t('Live code delivery runs')}
|
||||
</h3>
|
||||
<h3 className='mt-1 text-lg font-semibold'>我的 Agent</h3>
|
||||
</div>
|
||||
<Button asChild size='sm' variant='ghost' className='gap-1'>
|
||||
<Link to='/deploy-agent'>
|
||||
{t('Open Deployments')}
|
||||
去部署
|
||||
<ArrowUpRight className='h-3.5 w-3.5' />
|
||||
</Link>
|
||||
</Button>
|
||||
@@ -286,7 +273,7 @@ export function CockpitView() {
|
||||
</div>
|
||||
) : recentDeployments.length === 0 ? (
|
||||
<div className='rounded-xl border border-dashed border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] p-8 text-center text-sm text-muted-foreground'>
|
||||
{t('No deployments yet. Trigger a run to populate the cockpit.')}
|
||||
还没有部署 Agent。去「部署 Sub Agent」部署一个。
|
||||
</div>
|
||||
) : (
|
||||
<ul className='space-y-2'>
|
||||
@@ -317,7 +304,7 @@ export function CockpitView() {
|
||||
>
|
||||
<Link to='/deploy-agent'>
|
||||
<ScrollText className='mr-1 h-3.5 w-3.5' />
|
||||
{t('Inspect')}
|
||||
详情
|
||||
</Link>
|
||||
</Button>
|
||||
</li>
|
||||
|
||||
@@ -138,12 +138,11 @@ export function DeploySubAgentPage() {
|
||||
s.includes(id) ? s.filter((x) => x !== id) : [...s, id]
|
||||
)
|
||||
|
||||
// Only resource types the agent runtime currently supports (git / mysql /
|
||||
// postgres / azure-blob). vm & others are hidden until AM adds them.
|
||||
// Resource types the agent runtime currently supports (git / mysql / postgres
|
||||
// / azure-blob). Others (e.g. vm) are SHOWN but disabled, with an "暂不支持"
|
||||
// hint, until AM adds support — don't hide the user's bindings.
|
||||
const SUPPORTED_TYPES = new Set(['git', 'database', 'blob'])
|
||||
const resources = (resourcesQ.data ?? []).filter((r) =>
|
||||
SUPPORTED_TYPES.has(r.resource_type)
|
||||
)
|
||||
const resources = resourcesQ.data ?? []
|
||||
const templates = templatesQ.data ?? []
|
||||
const canDeploy = Boolean(selectedTemplate) && !deploy.isPending
|
||||
|
||||
@@ -182,17 +181,22 @@ export function DeploySubAgentPage() {
|
||||
<div className='mt-4 grid gap-3 sm:grid-cols-2'>
|
||||
{resources.map((r) => {
|
||||
const Icon = RESOURCE_ICON[r.resource_type] ?? Database
|
||||
const on = selectedBindings.includes(r.id)
|
||||
const supported = SUPPORTED_TYPES.has(r.resource_type)
|
||||
const on = supported && selectedBindings.includes(r.id)
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
key={r.id}
|
||||
onClick={() => toggleBinding(r.id)}
|
||||
disabled={!supported}
|
||||
title={supported ? undefined : '当前 Agent 暂不支持此类资源'}
|
||||
onClick={() => supported && toggleBinding(r.id)}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-xl border p-3 text-left transition',
|
||||
on
|
||||
? 'border-primary/60 bg-[color-mix(in_oklch,var(--primary)_12%,transparent)]'
|
||||
: 'border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] hover:border-primary/40'
|
||||
!supported
|
||||
? 'cursor-not-allowed border-[color-mix(in_oklch,var(--border)_60%,transparent)] opacity-50'
|
||||
: on
|
||||
? 'border-primary/60 bg-[color-mix(in_oklch,var(--primary)_12%,transparent)]'
|
||||
: 'border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] hover:border-primary/40'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
@@ -209,7 +213,13 @@ export function DeploySubAgentPage() {
|
||||
{resourceTypeLabel(r.resource_type)} · {r.provider}
|
||||
</span>
|
||||
</span>
|
||||
{on && <CheckCircle2 className='text-primary h-4 w-4 shrink-0' />}
|
||||
{!supported ? (
|
||||
<span className='shrink-0 rounded-full bg-muted/50 px-2 py-0.5 text-[10px] text-muted-foreground'>
|
||||
暂不支持
|
||||
</span>
|
||||
) : on ? (
|
||||
<CheckCircle2 className='text-primary h-4 w-4 shrink-0' />
|
||||
) : null}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user