feat: 全站 PageShell 设计系统与专业 UI 统一
- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架 - index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩) - 全站路由页接入 PageShell,云账单三 Tab 为标杆 - 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化 - 登录页/侧栏/顶栏去渐变光球,企业克制风 - 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
1 parent
1acb0c49af
commit
e363ded445
213 files changed
+7148
-6818
No files matched your search
@@ -104,12 +104,12 @@ export function KubernetesWorkloads() {
|
||||
|
||||
const getPodStatusBadge = (status: string) => {
|
||||
const map: Record<string, string> = {
|
||||
Running: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30',
|
||||
Pending: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
|
||||
Failed: 'bg-red-500/20 text-red-400 border-red-500/30',
|
||||
Succeeded: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
|
||||
Running: 'bg-emerald-500/20 dg-text-green border-emerald-500/30',
|
||||
Pending: 'bg-yellow-500/20 dg-text-yellow border-yellow-500/30',
|
||||
Failed: 'bg-red-500/20 dg-text-red border-red-500/30',
|
||||
Succeeded: 'bg-blue-500/20 dg-text-blue border-blue-500/30',
|
||||
};
|
||||
return <Badge className={map[status] || 'bg-slate-500/20 text-slate-400 border-slate-500/30'}>{status || 'Unknown'}</Badge>;
|
||||
return <Badge className={map[status] || 'bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)]'}>{status || 'Unknown'}</Badge>;
|
||||
};
|
||||
|
||||
const handleQuickScale = async (dep: JsonData, delta: number) => {
|
||||
@@ -144,32 +144,32 @@ export function KubernetesWorkloads() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-100">工作负载</h1>
|
||||
<p className="text-sm text-slate-500 mt-1">Deployment 与 Pod 运行状态</p>
|
||||
<h1 className="text-2xl font-bold text-[var(--dg-text-primary)]">工作负载</h1>
|
||||
<p className="text-sm dg-text-muted mt-1">Deployment 与 Pod 运行状态</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select value={selectedCluster} onValueChange={(v) => { setSelectedCluster(v); setSelectedNamespace('all'); }}>
|
||||
<SelectTrigger className="w-48 h-9 bg-slate-800/50 border-slate-700 text-slate-200">
|
||||
<SelectTrigger className="w-48 h-9 dg-input">
|
||||
<SelectValue placeholder="选择集群" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-slate-800 border-slate-700">
|
||||
<SelectContent className="dg-input">
|
||||
{clusters.map((c) => (
|
||||
<SelectItem key={c.id} value={c.code || String(c.id)}>{c.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={selectedNamespace} onValueChange={setSelectedNamespace}>
|
||||
<SelectTrigger className="w-40 h-9 bg-slate-800/50 border-slate-700 text-slate-200">
|
||||
<SelectTrigger className="w-40 h-9 dg-input">
|
||||
<SelectValue placeholder="命名空间" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-slate-800 border-slate-700 max-h-[300px]">
|
||||
<SelectContent className="dg-input max-h-[300px]">
|
||||
<SelectItem value="all">全部命名空间</SelectItem>
|
||||
{namespaces.map((ns) => (
|
||||
<SelectItem key={ns} value={ns}>{ns}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" size="icon" className="h-9 w-9 border-slate-700 text-slate-400" onClick={loadData} disabled={loading}>
|
||||
<Button variant="outline" size="icon" className="h-9 w-9 border-[var(--dg-border)] dg-text-secondary" onClick={loadData} disabled={loading}>
|
||||
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -178,15 +178,15 @@ export function KubernetesWorkloads() {
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: 'Deployment', value: deployments.length, icon: <Boxes className="h-5 w-5" />, color: 'text-rose-400' },
|
||||
{ label: 'Pod 总数', value: pods.length, icon: <Container className="h-5 w-5" />, color: 'text-blue-400' },
|
||||
{ label: 'Running', value: pods.filter((p) => p.status === 'Running').length, icon: <Container className="h-5 w-5" />, color: 'text-emerald-400' },
|
||||
{ label: '异常', value: pods.filter((p) => ['Failed', 'Pending'].includes(p.status)).length, icon: <Container className="h-5 w-5" />, color: 'text-red-400' },
|
||||
{ label: 'Pod 总数', value: pods.length, icon: <Container className="h-5 w-5" />, color: 'dg-text-blue' },
|
||||
{ label: 'Running', value: pods.filter((p) => p.status === 'Running').length, icon: <Container className="h-5 w-5" />, color: 'dg-text-green' },
|
||||
{ label: '异常', value: pods.filter((p) => ['Failed', 'Pending'].includes(p.status)).length, icon: <Container className="h-5 w-5" />, color: 'dg-text-red' },
|
||||
].map((s) => (
|
||||
<Card key={s.label} className="bg-slate-900 border-slate-800">
|
||||
<Card key={s.label} className="dg-card">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-slate-500">{s.label}</p>
|
||||
<p className="text-sm dg-text-muted">{s.label}</p>
|
||||
<p className={`text-2xl font-bold ${s.color}`}>{s.value}</p>
|
||||
</div>
|
||||
<div className="opacity-60">{s.icon}</div>
|
||||
@@ -197,40 +197,40 @@ export function KubernetesWorkloads() {
|
||||
</div>
|
||||
|
||||
<div className="relative w-56">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-500" />
|
||||
<Input placeholder="搜索..." value={search} onChange={(e) => setSearch(e.target.value)} className="pl-9 h-9 bg-slate-800/50 border-slate-700 text-slate-200" />
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" />
|
||||
<Input placeholder="搜索..." value={search} onChange={(e) => setSearch(e.target.value)} className="pl-9 h-9 dg-input" />
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="bg-slate-800 border border-slate-700">
|
||||
<TabsTrigger value="deployments" className="data-[state=active]:bg-slate-700">Deployment ({filteredDeps.length})</TabsTrigger>
|
||||
<TabsTrigger value="pods" className="data-[state=active]:bg-slate-700">Pod ({filteredPods.length})</TabsTrigger>
|
||||
<TabsList className="bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)]">
|
||||
<TabsTrigger value="deployments" className="data-[state=active]:bg-[var(--dg-bg-active)]">Deployment ({filteredDeps.length})</TabsTrigger>
|
||||
<TabsTrigger value="pods" className="data-[state=active]:bg-[var(--dg-bg-active)]">Pod ({filteredPods.length})</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="deployments">
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-slate-800 hover:bg-transparent">
|
||||
<TableHead className="text-slate-400">名称</TableHead>
|
||||
<TableHead className="text-slate-400">命名空间</TableHead>
|
||||
<TableHead className="text-slate-400">副本</TableHead>
|
||||
<TableHead className="text-slate-400">镜像</TableHead>
|
||||
<TableHead className="text-slate-400 w-[140px]">操作</TableHead>
|
||||
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
|
||||
<TableHead className="dg-text-secondary">名称</TableHead>
|
||||
<TableHead className="dg-text-secondary">命名空间</TableHead>
|
||||
<TableHead className="dg-text-secondary">副本</TableHead>
|
||||
<TableHead className="dg-text-secondary">镜像</TableHead>
|
||||
<TableHead className="dg-text-secondary w-[140px]">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow><TableCell colSpan={5} className="text-center text-slate-500 py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中...</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={5} className="text-center dg-text-muted py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中...</TableCell></TableRow>
|
||||
) : filteredDeps.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={5} className="text-center text-slate-500 py-12"><Boxes className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无 Deployment</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={5} className="text-center dg-text-muted py-12"><Boxes className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无 Deployment</TableCell></TableRow>
|
||||
) : (
|
||||
filteredDeps.map((d) => (
|
||||
<TableRow key={d.id || d.name} className="border-slate-800 hover:bg-slate-800/30 cursor-pointer"
|
||||
<TableRow key={d.id || d.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)] cursor-pointer"
|
||||
onClick={() => setSelectedDeployment({ name: d.name, namespace: d.namespace })}>
|
||||
<TableCell className="font-medium text-slate-200">{d.name}</TableCell>
|
||||
<TableCell><Badge variant="outline" className="border-slate-700 text-slate-400">{d.namespace}</Badge></TableCell>
|
||||
<TableCell className="font-medium text-[var(--dg-text-primary)]">{d.name}</TableCell>
|
||||
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary">{d.namespace}</Badge></TableCell>
|
||||
<TableCell>
|
||||
{(() => {
|
||||
const ready = d.ready_replicas || 0;
|
||||
@@ -239,27 +239,27 @@ export function KubernetesWorkloads() {
|
||||
const done = ready === total;
|
||||
return (
|
||||
<div className="flex items-center gap-2 min-w-[100px]">
|
||||
<div className="flex-1 h-1.5 rounded-full bg-slate-700/50 overflow-hidden">
|
||||
<div className="flex-1 h-1.5 rounded-full bg-[var(--dg-bg-active)]/50 overflow-hidden">
|
||||
<div className={`h-full rounded-full ${done ? 'bg-emerald-500' : 'bg-yellow-500'}`} style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<span className={`text-xs ${done ? 'text-emerald-400' : 'text-yellow-400'}`}>{ready}/{total}</span>
|
||||
<span className={`text-xs ${done ? 'dg-text-green' : 'dg-text-yellow'}`}>{ready}/{total}</span>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-slate-500 max-w-[200px] truncate">{d.image || '-'}</TableCell>
|
||||
<TableCell className="text-xs dg-text-muted max-w-[200px] truncate">{d.image || '-'}</TableCell>
|
||||
<TableCell onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-slate-200"
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-secondary hover:text-[var(--dg-text-primary)]"
|
||||
title="减少副本" disabled={(d.replicas || 0) <= 0}
|
||||
onClick={() => handleQuickScale(d, -1)}>
|
||||
<Minus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-slate-200"
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-secondary hover:text-[var(--dg-text-primary)]"
|
||||
title="增加副本" onClick={() => handleQuickScale(d, 1)}>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-blue-400"
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-secondary hover:dg-text-blue"
|
||||
title="查看详情" onClick={() => setSelectedDeployment({ name: d.name, namespace: d.namespace })}>
|
||||
<Eye className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
@@ -275,37 +275,37 @@ export function KubernetesWorkloads() {
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="pods">
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-slate-800 hover:bg-transparent">
|
||||
<TableHead className="text-slate-400">名称</TableHead>
|
||||
<TableHead className="text-slate-400">命名空间</TableHead>
|
||||
<TableHead className="text-slate-400">状态</TableHead>
|
||||
<TableHead className="text-slate-400">节点</TableHead>
|
||||
<TableHead className="text-slate-400">CPU</TableHead>
|
||||
<TableHead className="text-slate-400">内存</TableHead>
|
||||
<TableHead className="text-slate-400">重启</TableHead>
|
||||
<TableHead className="text-slate-400">镜像</TableHead>
|
||||
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
|
||||
<TableHead className="dg-text-secondary">名称</TableHead>
|
||||
<TableHead className="dg-text-secondary">命名空间</TableHead>
|
||||
<TableHead className="dg-text-secondary">状态</TableHead>
|
||||
<TableHead className="dg-text-secondary">节点</TableHead>
|
||||
<TableHead className="dg-text-secondary">CPU</TableHead>
|
||||
<TableHead className="dg-text-secondary">内存</TableHead>
|
||||
<TableHead className="dg-text-secondary">重启</TableHead>
|
||||
<TableHead className="dg-text-secondary">镜像</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow><TableCell colSpan={8} className="text-center text-slate-500 py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中...</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={8} className="text-center dg-text-muted py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中...</TableCell></TableRow>
|
||||
) : filteredPods.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={8} className="text-center text-slate-500 py-12"><Container className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无 Pod</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={8} className="text-center dg-text-muted py-12"><Container className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无 Pod</TableCell></TableRow>
|
||||
) : (
|
||||
filteredPods.map((p) => (
|
||||
<TableRow key={p.id} className="border-slate-800 hover:bg-slate-800/30">
|
||||
<TableCell className="font-medium text-slate-200">{p.name}</TableCell>
|
||||
<TableCell><Badge variant="outline" className="border-slate-700 text-slate-400">{p.namespace}</Badge></TableCell>
|
||||
<TableRow key={p.id} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]">
|
||||
<TableCell className="font-medium text-[var(--dg-text-primary)]">{p.name}</TableCell>
|
||||
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary">{p.namespace}</Badge></TableCell>
|
||||
<TableCell>{getPodStatusBadge(p.status)}</TableCell>
|
||||
<TableCell className="text-sm text-slate-400">{p.node_name || '-'}</TableCell>
|
||||
<TableCell className="text-sm text-cyan-400">{p.cpu_usage_m || '-'}</TableCell>
|
||||
<TableCell className="text-sm text-amber-400">{p.mem_usage_mi || '-'}</TableCell>
|
||||
<TableCell className={p.restart_count > 0 ? 'text-yellow-400' : 'text-slate-500'}>{p.restart_count || 0}</TableCell>
|
||||
<TableCell className="text-xs text-slate-500 max-w-[200px] truncate">{p.image || '-'}</TableCell>
|
||||
<TableCell className="text-sm dg-text-secondary">{p.node_name || '-'}</TableCell>
|
||||
<TableCell className="text-sm dg-text-cyan">{p.cpu_usage_m || '-'}</TableCell>
|
||||
<TableCell className="text-sm dg-text-yellow">{p.mem_usage_mi || '-'}</TableCell>
|
||||
<TableCell className={p.restart_count > 0 ? 'dg-text-yellow' : 'dg-text-muted'}>{p.restart_count || 0}</TableCell>
|
||||
<TableCell className="text-xs dg-text-muted max-w-[200px] truncate">{p.image || '-'}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user