feat: 全站 PageShell 设计系统与专业 UI 统一

- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架
- index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩)
- 全站路由页接入 PageShell,云账单三 Tab 为标杆
- 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化
- 登录页/侧栏/顶栏去渐变光球,企业克制风
- 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
Logan committed 2026-07-01 12:45:38 +08:00
1 parent 1acb0c49af
commit e363ded445
213 files changed
+7148 -6818

No files matched your search

@@ -13,7 +13,7 @@ import {
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { toast } from 'sonner';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { usePromDatasources, usePromDatasourceMutations } from '@/hooks/api';
@@ -21,6 +21,9 @@ import { usePromDatasources, usePromDatasourceMutations } from '@/hooks/api';
import type { PromDS } from './prometheus-ds/types';
import { handleTestQuick } from './prometheus-ds/helpers';
import PromDsFormDialog from './prometheus-ds/PromDsFormDialog';
import { PageLoading } from '@/components/layout/PageLoading';
import { PageEmpty } from '@/components/layout/PageEmpty';
import { ContentPanel } from '@/components/layout/ContentPanel';
export default function PrometheusDatasourceManager() {
const _confirm = useConfirm();
@@ -69,145 +72,132 @@ export default function PrometheusDatasourceManager() {
};
if (isLoading) {
return (
<div className="flex items-center justify-center h-96">
<RefreshCw className="animate-spin w-8 h-8 text-[var(--dg-text-secondary)]" />
</div>
);
return <PageLoading />;
}
return (
<div className="p-6 space-y-6 max-w-7xl mx-auto">
{/* 页头 */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-[var(--dg-text-primary)]">数据源管理</h1>
<p className="text-sm text-[var(--dg-text-secondary)] mt-1">
Prometheus 数据源配置,用于指标监控与监控大屏
</p>
</div>
<Button onClick={openCreate} className="gap-2">
<Plus className="w-4 h-4" />
添加数据源
</Button>
</div>
const toolbar = (
<div className="flex items-center justify-between w-full">
<span className="text-sm dg-text-secondary flex items-center gap-2">
<Database className="w-4 h-4 dg-text-blue" />
Prometheus 数据源
<Badge variant="secondary" className="dg-badge-muted">{sources.length}</Badge>
</span>
<Button onClick={openCreate} size="sm" className="dg-btn-primary">
<Plus className="w-4 h-4 mr-1" />
添加数据源
</Button>
</div>
);
{/* 数据源列表 */}
<Card className="border-[var(--dg-border)] bg-[var(--dg-bg-secondary)]">
<CardHeader className="pb-2">
<CardTitle className="text-base flex items-center gap-2">
<Database className="w-4 h-4 text-blue-400" />
已配置的数据源
<Badge variant="secondary" className="ml-2">{sources.length}</Badge>
</CardTitle>
</CardHeader>
<CardContent className="p-0">
{sources.length === 0 ? (
<div className="py-12 text-center text-[var(--dg-text-secondary)]">
<Database className="w-12 h-12 mx-auto mb-3 opacity-30" />
<p className="text-sm">暂无数据源</p>
<p className="text-xs mt-1">点击「添加数据源」开始配置</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead className="border-b border-[var(--dg-border)]">
<tr className="text-xs text-[var(--dg-text-secondary)]">
<th className="text-left py-3 px-4 font-medium">名称</th>
<th className="text-left py-3 px-4 font-medium">URL</th>
<th className="text-left py-3 px-4 font-medium w-20">默认</th>
<th className="text-left py-3 px-4 font-medium w-16">状态</th>
<th className="text-right py-3 px-4 font-medium w-36">操作</th>
return (
<div className="space-y-4">
<ContentPanel toolbar={toolbar} noPadding>
{sources.length === 0 ? (
<PageEmpty
icon={<Database className="h-12 w-12" />}
title="暂无 Prometheus 数据源"
description="添加数据源后可用于指标监控与监控大屏"
action={(
<Button onClick={openCreate} size="sm" className="dg-btn-primary">
<Plus className="w-4 h-4 mr-1" />添加数据源
</Button>
)}
/>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead className="border-b border-[var(--border-default)]">
<tr className="text-xs dg-text-secondary">
<th className="text-left py-3 px-4 font-medium">名称</th>
<th className="text-left py-3 px-4 font-medium">URL</th>
<th className="text-left py-3 px-4 font-medium w-20">默认</th>
<th className="text-left py-3 px-4 font-medium w-16">状态</th>
<th className="text-right py-3 px-4 font-medium w-36">操作</th>
</tr>
</thead>
<tbody>
{sources.map(ds => (
<tr
key={ds.id}
className="dg-table-row border-b border-[var(--border-subtle)]"
>
<td className="py-3 px-4">
<div className="flex items-center gap-2">
<img
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/prometheus.png"
alt="Prometheus"
className="w-5 h-5 rounded"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/>
<div>
<span className="text-sm font-medium text-[var(--text-primary)]">{ds.name}</span>
{ds.basic_auth && <Key className="w-3 h-3 inline ml-1.5 dg-text-yellow" />}
</div>
</div>
</td>
<td className="py-3 px-4">
<code className="text-xs dg-text-blue bg-[var(--surface-inset)] px-2 py-0.5 rounded max-w-[320px] block truncate"
title={ds.url}>
{ds.url}
</code>
</td>
<td className="py-3 px-4">
{ds.is_default ? (
<span className="dg-badge-yellow"><Star className="w-3 h-3 mr-1 inline" />默认</span>
) : (
<span className="text-xs dg-text-muted">--</span>
)}
</td>
<td className="py-3 px-4">
{ds.status === 1 ? (
<span className="dg-status-running">启用</span>
) : (
<span className="dg-badge-muted">禁用</span>
)}
</td>
<td className="py-3 px-4">
<div className="flex items-center justify-end gap-1">
<Button
variant="ghost" size="sm"
className="h-8 px-2"
onClick={() => handleTestQuick(ds)}
title="测试连接"
>
<Plug className="w-3.5 h-3.5" />
</Button>
{!ds.is_default && (
<Button
variant="ghost" size="sm"
className="h-8 px-2 dg-text-yellow"
onClick={() => handleSetDefault(ds)}
title="设为默认"
>
<Star className="w-3.5 h-3.5" />
</Button>
)}
<Button
variant="ghost" size="sm"
className="h-8 px-2"
onClick={() => openEdit(ds)}
>
<Edit className="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost" size="sm"
className="h-8 px-2 dg-text-red hover:dg-text-red"
onClick={() => handleDelete(ds)}
>
<Trash2 className="w-3.5 h-3.5" />
</Button>
</div>
</td>
</tr>
</thead>
<tbody>
{sources.map(ds => (
<tr
key={ds.id}
className="border-b border-[var(--dg-border)]/50 hover:bg-[var(--dg-bg-tertiary)]/50 transition-colors"
>
<td className="py-3 px-4">
<div className="flex items-center gap-2">
<img
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/prometheus.png"
alt="Prometheus"
className="w-5 h-5 rounded"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/>
<div>
<span className="text-sm font-medium text-[var(--dg-text-primary)]">{ds.name}</span>
{ds.basic_auth && <Key className="w-3 h-3 inline ml-1.5 text-amber-400" />}
</div>
</div>
</td>
<td className="py-3 px-4">
<code className="text-xs text-blue-300 bg-slate-800/50 px-2 py-0.5 rounded max-w-[320px] block truncate"
title={ds.url}>
{ds.url}
</code>
</td>
<td className="py-3 px-4">
{ds.is_default ? (
<Badge variant="default" className="bg-amber-500/15 text-amber-400 border-amber-500/30 text-xs">
<Star className="w-3 h-3 mr-1" /> 默认
</Badge>
) : (
<span className="text-xs text-[var(--dg-text-secondary)]">--</span>
)}
</td>
<td className="py-3 px-4">
{ds.status === 1 ? (
<Badge variant="default" className="bg-emerald-500/15 text-emerald-400 border-emerald-500/30 text-xs">
启用
</Badge>
) : (
<Badge variant="secondary" className="text-xs">禁用</Badge>
)}
</td>
<td className="py-3 px-4">
<div className="flex items-center justify-end gap-1">
<Button
variant="ghost" size="sm"
className="h-8 px-2"
onClick={() => handleTestQuick(ds)}
title="测试连接"
>
<Plug className="w-3.5 h-3.5" />
</Button>
{!ds.is_default && (
<Button
variant="ghost" size="sm"
className="h-8 px-2 text-amber-400 hover:text-amber-300"
onClick={() => handleSetDefault(ds)}
title="设为默认"
>
<Star className="w-3.5 h-3.5" />
</Button>
)}
<Button
variant="ghost" size="sm"
className="h-8 px-2"
onClick={() => openEdit(ds)}
>
<Edit className="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost" size="sm"
className="h-8 px-2 text-red-400 hover:text-red-300"
onClick={() => handleDelete(ds)}
>
<Trash2 className="w-3.5 h-3.5" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
))}
</tbody>
</table>
</div>
)}
</ContentPanel>
<PromDsFormDialog
open={dialogOpen}