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
@@ -1,47 +1,30 @@
|
||||
// 数据源管理页面 — Prometheus + Zabbix 双 Tab // Logan.Li
|
||||
import { useState } from 'react';
|
||||
import { Database } from 'lucide-react';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { PageShell } from '@/components/layout/PageShell';
|
||||
import PrometheusDatasourceManager from '@/components/monitor/datasource/PrometheusDatasourceManager';
|
||||
import ZabbixDatasourceManager from '@/components/monitor/datasource/ZabbixDatasourceManager';
|
||||
|
||||
export default function DatasourcePage() {
|
||||
const [activeTab, setActiveTab] = useState('prometheus');
|
||||
|
||||
const pageTabs = [
|
||||
{ id: 'prometheus', label: 'Prometheus' },
|
||||
{ id: 'zabbix', label: 'Zabbix' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="h-5 w-5 text-blue-400" />
|
||||
<h1 className="text-xl font-bold text-[var(--dg-text-primary)]">数据源管理</h1>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<TabsTrigger value="prometheus" className="data-[state=active]:bg-orange-500/10 data-[state=active]:text-orange-400">
|
||||
<img
|
||||
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/prometheus.png"
|
||||
className="w-4 h-4 rounded mr-1.5"
|
||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||
/>
|
||||
Prometheus
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="zabbix" className="data-[state=active]:bg-orange-500/10 data-[state=active]:text-orange-400">
|
||||
<img
|
||||
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/zabbix.png"
|
||||
className="w-4 h-4 rounded mr-1.5"
|
||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||
/>
|
||||
Zabbix
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="prometheus" className="mt-4">
|
||||
<PrometheusDatasourceManager />
|
||||
</TabsContent>
|
||||
<TabsContent value="zabbix" className="mt-4">
|
||||
<ZabbixDatasourceManager />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<PageShell
|
||||
variant="list"
|
||||
title="数据源管理"
|
||||
subtitle="管理 Prometheus 与 Zabbix 监控数据源"
|
||||
icon={<Database className="h-5 w-5" />}
|
||||
tabs={pageTabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
>
|
||||
{activeTab === 'prometheus' && <PrometheusDatasourceManager />}
|
||||
{activeTab === 'zabbix' && <ZabbixDatasourceManager />}
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -224,12 +224,12 @@ export default function ZabbixDatasourceManager() {
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<div className={`w-2 h-2 rounded-full ${ds.status === 1 ? 'bg-emerald-400' : 'bg-slate-500'}`} />
|
||||
<div className={`w-2 h-2 rounded-full ${ds.status === 1 ? 'bg-emerald-400' : 'bg-[var(--dg-bg-active)]'}`} />
|
||||
<span className="font-medium text-[var(--dg-text-primary)]">{ds.name}</span>
|
||||
{ds.is_default && <Badge className="bg-amber-500/20 text-amber-400 text-[10px]"><Star className="w-3 h-3 mr-0.5" />默认</Badge>}
|
||||
{ds.is_default && <Badge className="bg-amber-500/20 dg-text-yellow text-[10px]"><Star className="w-3 h-3 mr-0.5" />默认</Badge>}
|
||||
{ds.status === 1
|
||||
? <Badge className="bg-emerald-500/20 text-emerald-400 text-[10px]">启用</Badge>
|
||||
: <Badge className="bg-slate-500/20 text-slate-400 text-[10px]">禁用</Badge>}
|
||||
? <Badge className="bg-emerald-500/20 dg-text-green text-[10px]">启用</Badge>
|
||||
: <Badge className="bg-[var(--dg-bg-active)]/20 dg-text-secondary text-[10px]">禁用</Badge>}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--dg-text-muted)] space-x-3">
|
||||
<code className="text-orange-300">{ds.url}</code>
|
||||
@@ -239,14 +239,14 @@ export default function ZabbixDatasourceManager() {
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{!ds.is_default && (
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs text-amber-400" onClick={() => handleSetDefault(ds)}>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs dg-text-yellow" onClick={() => handleSetDefault(ds)}>
|
||||
<Star className="w-3 h-3 mr-0.5" />默认
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => openEdit(ds)}>
|
||||
<Edit className="w-3 h-3 mr-0.5" />编辑
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs text-red-400" onClick={() => handleDelete(ds)}>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs dg-text-red" onClick={() => handleDelete(ds)}>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -262,7 +262,7 @@ export default function ZabbixDatasourceManager() {
|
||||
<DialogContent className="max-w-xl max-h-[85vh] overflow-y-auto bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Database className="h-5 w-5 text-orange-400" />
|
||||
<Database className="h-5 w-5 dg-text-orange" />
|
||||
{editingId ? '编辑 Zabbix 数据源' : '新建 Zabbix 数据源'}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -390,7 +390,7 @@ export default function ZabbixDatasourceManager() {
|
||||
{/* 测试结果 */}
|
||||
{testResult && (
|
||||
<div className={`flex items-center gap-2 p-2.5 rounded text-sm ${
|
||||
testResult.success ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400'
|
||||
testResult.success ? 'bg-emerald-500/10 dg-text-green' : 'bg-red-500/10 dg-text-red'
|
||||
}`}>
|
||||
{testResult.success ? <CheckCircle2 className="h-4 w-4" /> : <XCircle className="h-4 w-4" />}
|
||||
{testResult.message}
|
||||
|
||||
@@ -148,7 +148,7 @@ export default function PromDsFormDialog({
|
||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-lg">
|
||||
<Database className="w-5 h-5 text-orange-400" />
|
||||
<Database className="w-5 h-5 dg-text-orange" />
|
||||
{editingId ? '编辑数据源' : '添加数据源'}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -160,7 +160,7 @@ export default function PromDsFormDialog({
|
||||
{/* === Name & Default === */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ds-name" className="text-sm">Name <span className="text-red-400">*</span></Label>
|
||||
<Label htmlFor="ds-name" className="text-sm">Name <span className="dg-text-red">*</span></Label>
|
||||
<Input
|
||||
id="ds-name"
|
||||
placeholder="Prometheus 数据源名称"
|
||||
@@ -187,7 +187,7 @@ export default function PromDsFormDialog({
|
||||
<Globe className="w-4 h-4" /> HTTP
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ds-url" className="text-sm">URL <span className="text-red-400">*</span></Label>
|
||||
<Label htmlFor="ds-url" className="text-sm">URL <span className="dg-text-red">*</span></Label>
|
||||
<div className="flex gap-2">
|
||||
<Select value={form.access} onValueChange={v => updateForm('access', v)}>
|
||||
<SelectTrigger className="w-32 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm">
|
||||
@@ -340,7 +340,7 @@ export default function PromDsFormDialog({
|
||||
className="flex-1 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm h-8"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost" size="sm" className="h-8 w-8 p-0 text-red-400 hover:text-red-300"
|
||||
variant="ghost" size="sm" className="h-8 w-8 p-0 dg-text-red hover:dg-text-red"
|
||||
onClick={() => removeHeader(idx)}
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
@@ -482,8 +482,8 @@ export default function PromDsFormDialog({
|
||||
{testResult && (
|
||||
<div className={`rounded-lg p-3 flex items-center gap-2 text-sm ${
|
||||
testResult.success
|
||||
? 'bg-emerald-500/10 border border-emerald-500/30 text-emerald-400'
|
||||
: 'bg-red-500/10 border border-red-500/30 text-red-400'
|
||||
? 'bg-emerald-500/10 border border-emerald-500/30 dg-text-green'
|
||||
: 'bg-red-500/10 border border-red-500/30 dg-text-red'
|
||||
}`}>
|
||||
{testResult.success
|
||||
? <CheckCircle className="w-4 h-4 shrink-0" />
|
||||
|
||||
Reference in new issue
Block a user