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
@@ -12,9 +12,11 @@ import { StatStrip } from '@/components/ui/stat-strip';
|
||||
import {
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { toast } from 'sonner';
|
||||
import { PageShell } from '@/components/layout/PageShell';
|
||||
import { PageEmpty } from '@/components/layout/PageEmpty';
|
||||
import { PageLoading } from '@/components/layout/PageLoading';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { alertCenterApi, alertApi } from '@/api';
|
||||
import { useAlerts, useAgentAlertRules, useAlertMutations } from '@/hooks/api';
|
||||
@@ -178,30 +180,16 @@ const AlertCenter: React.FC = () => {
|
||||
setPage(1); // 过滤条件变化时重置到第一页
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{/* 统计 — 紧凑条 */}
|
||||
<StatStrip
|
||||
items={[
|
||||
{ label: t('alert.stat.firing'), value: stats.firing, color: stats.firing > 0 ? 'red' : 'muted' },
|
||||
{ label: t('alert.stat.critical'), value: stats.critical, color: 'red' },
|
||||
{ label: t('alert.stat.warning'), value: stats.warning, color: 'amber' },
|
||||
{ label: t('alert.stat.resolved'), value: stats.resolved, color: 'emerald' },
|
||||
]}
|
||||
/>
|
||||
const pageTabs = [
|
||||
{ id: 'alerts', label: t('alert.tab.alerts') },
|
||||
{ id: 'rules', label: t('alert.tab.rules') },
|
||||
{ id: 'silences', label: t('alert.tab.silences') },
|
||||
{ id: 'contacts', label: t('alert.tab.contacts') },
|
||||
{ id: 'groups', label: t('alert.tab.groups') },
|
||||
];
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="grid w-full grid-cols-5 lg:w-[600px] h-9">
|
||||
<TabsTrigger value="alerts" className="text-xs">{t('alert.tab.alerts')}</TabsTrigger>
|
||||
<TabsTrigger value="rules" className="text-xs">{t('alert.tab.rules')}</TabsTrigger>
|
||||
<TabsTrigger value="silences" className="text-xs">{t('alert.tab.silences')}</TabsTrigger>
|
||||
<TabsTrigger value="contacts" className="text-xs">{t('alert.tab.contacts')}</TabsTrigger>
|
||||
<TabsTrigger value="groups" className="text-xs">{t('alert.tab.groups')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 告警列表 Tab */}
|
||||
<TabsContent value="alerts" className="space-y-3">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
const alertsToolbar = activeTab === 'alerts' ? (
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 w-full">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-[280px]">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" />
|
||||
@@ -235,16 +223,65 @@ const AlertCenter: React.FC = () => {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<span>{t('alert.totalItems', { count: total })}</span>
|
||||
<Button variant="outline" size="sm" onClick={() => refetchAlerts()} className="h-7 text-xs px-2">
|
||||
<RefreshCw className="w-3 h-3 mr-1" />{t('common.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-xs dg-text-muted">
|
||||
<span>{t('alert.totalItems', { count: total })}</span>
|
||||
<Button variant="outline" size="sm" onClick={() => refetchAlerts()} className="h-7 text-xs px-2 dg-btn-secondary">
|
||||
<RefreshCw className="w-3 h-3 mr-1" />{t('common.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : undefined;
|
||||
|
||||
{/* 告警表格 — 紧凑 */}
|
||||
<Card>
|
||||
return (
|
||||
<PageShell
|
||||
variant="list"
|
||||
title="告警中心"
|
||||
subtitle="告警事件处理、规则与通知配置"
|
||||
icon={<AlertTriangle className="h-5 w-5" />}
|
||||
tabs={pageTabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
toolbar={alertsToolbar}
|
||||
metrics={(
|
||||
<StatStrip
|
||||
items={[
|
||||
{ label: t('alert.stat.firing'), value: stats.firing, color: stats.firing > 0 ? 'red' : 'muted' },
|
||||
{ label: t('alert.stat.critical'), value: stats.critical, color: 'red' },
|
||||
{ label: t('alert.stat.warning'), value: stats.warning, color: 'amber' },
|
||||
{ label: t('alert.stat.resolved'), value: stats.resolved, color: 'emerald' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
actions={activeTab === 'rules' ? (
|
||||
<>
|
||||
<Button className="dg-btn-primary" onClick={handleOpenAddRule}>
|
||||
<Plus className="w-4 h-4 mr-1" />{t('alert.addRule')}
|
||||
</Button>
|
||||
<Button variant="secondary" className="dg-btn-secondary" onClick={handleQuickSetup} disabled={quickSetupMutation.isPending}>
|
||||
<Rocket className="w-4 h-4 mr-1" />
|
||||
{quickSetupMutation.isPending ? t('alert.quickSetupCreating') : t('alert.quickSetup')}
|
||||
</Button>
|
||||
<Button variant="outline" className="dg-btn-secondary" onClick={() => refetchRules()}>
|
||||
<RefreshCw className="w-4 h-4 mr-2" />{t('common.refresh')}
|
||||
</Button>
|
||||
</>
|
||||
) : activeTab === 'contacts' ? (
|
||||
<Button size="sm" className="dg-btn-primary" onClick={() => { setEditingContact(null); setContactDialogOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-1" />添加联系人
|
||||
</Button>
|
||||
) : activeTab === 'groups' ? (
|
||||
<>
|
||||
<Button variant="outline" className="dg-btn-secondary" onClick={fetchContactGroups}>
|
||||
<RefreshCw className="w-4 h-4 mr-2" />{t('common.refresh')}
|
||||
</Button>
|
||||
<Button className="dg-btn-primary" onClick={() => { setEditingGroup(null); setGroupDialogOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-2" />添加联系组
|
||||
</Button>
|
||||
</>
|
||||
) : undefined}
|
||||
>
|
||||
{activeTab === 'alerts' && (
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
@@ -260,9 +297,9 @@ const AlertCenter: React.FC = () => {
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{loading ? (
|
||||
<tr><td colSpan={6} className="px-3 py-6 text-center text-xs text-muted-foreground">{t('alert.loading')}</td></tr>
|
||||
<tr><td colSpan={6} className="px-3 py-2"><PageLoading label={t('alert.loading')} /></td></tr>
|
||||
) : alerts.length === 0 ? (
|
||||
<tr><td colSpan={6} className="px-3 py-6 text-center text-xs text-muted-foreground">{t('alert.noAlerts')}</td></tr>
|
||||
<tr><td colSpan={6} className="px-3 py-2"><PageEmpty title={t('alert.noAlerts')} className="py-6" /></td></tr>
|
||||
) : (
|
||||
alerts.filter(alert => {
|
||||
// 客户端也保留关键词搜索过滤
|
||||
@@ -302,7 +339,7 @@ const AlertCenter: React.FC = () => {
|
||||
{alert.status === 'firing' && (
|
||||
<Button variant="ghost" size="sm" className="h-7 w-7 p-0"
|
||||
onClick={() => handleAcknowledge(alert.id)}>
|
||||
<Check className="w-3.5 h-3.5 text-blue-600" />
|
||||
<Check className="w-3.5 h-3.5 dg-text-blue" />
|
||||
</Button>
|
||||
)}
|
||||
{(alert.status === 'firing' || alert.status === 'acknowledged') && (
|
||||
@@ -353,31 +390,15 @@ const AlertCenter: React.FC = () => {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{/* 告警规则 Tab */}
|
||||
<TabsContent value="rules" className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button onClick={handleOpenAddRule}>
|
||||
<Plus className="w-4 h-4 mr-1" />{t('alert.addRule')}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleQuickSetup} disabled={quickSetupMutation.isPending}>
|
||||
<Rocket className="w-4 h-4 mr-1" />
|
||||
{quickSetupMutation.isPending ? t('alert.quickSetupCreating') : t('alert.quickSetup')}
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="outline" onClick={() => refetchRules()}>
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
{rules.length === 0 ? (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<AlertTriangle className="w-10 h-10 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">{t('alert.noRules')}</p>
|
||||
<p className="text-xs mt-1">{t('alert.noRulesHint')}</p>
|
||||
</div>
|
||||
{activeTab === 'rules' && (
|
||||
rules.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<AlertTriangle className="w-10 h-10" />}
|
||||
title={t('alert.noRules')}
|
||||
description={t('alert.noRulesHint')}
|
||||
/>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{rules.map((rule) => (
|
||||
@@ -417,7 +438,7 @@ const AlertCenter: React.FC = () => {
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleEditRule(rule)}>{t('common.edit')}</Button>
|
||||
<Button variant="ghost" size="sm" className="text-red-400" onClick={() => handleDeleteRule(rule)}>{t('common.delete')}</Button>
|
||||
<Button variant="ghost" size="sm" className="dg-text-red" onClick={() => handleDeleteRule(rule)}>{t('common.delete')}</Button>
|
||||
<Switch
|
||||
checked={rule.enabled}
|
||||
onCheckedChange={(checked) => toggleRule(String(rule.id), checked)}
|
||||
@@ -428,27 +449,14 @@ const AlertCenter: React.FC = () => {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* 静默规则 Tab */}
|
||||
<TabsContent value="silences">
|
||||
<SilenceRuleTab />
|
||||
</TabsContent>
|
||||
{activeTab === 'silences' && <SilenceRuleTab />}
|
||||
|
||||
{/* 报警联系人 Tab */}
|
||||
<TabsContent value="contacts" className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-sm font-medium">{t('alert.contactsTitle')}</h3>
|
||||
<Button size="sm" onClick={() => { setEditingContact(null); setContactDialogOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
添加联系人
|
||||
</Button>
|
||||
</div>
|
||||
{contacts.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground border border-dashed rounded-lg text-xs">
|
||||
暂无报警联系人,点击「添加联系人」创建
|
||||
</div>
|
||||
{activeTab === 'contacts' && (
|
||||
contacts.length === 0 ? (
|
||||
<PageEmpty title="暂无报警联系人" description="点击「添加联系人」创建" />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{contacts.map((c) => (
|
||||
@@ -492,25 +500,12 @@ const AlertCenter: React.FC = () => {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* 报警联系组 Tab */}
|
||||
<TabsContent value="groups" className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<Button variant="outline" onClick={fetchContactGroups}>
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
刷新
|
||||
</Button>
|
||||
<Button onClick={() => { setEditingGroup(null); setGroupDialogOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
添加联系组
|
||||
</Button>
|
||||
</div>
|
||||
{contactGroups.length === 0 ? (
|
||||
<div className="text-center py-16 text-muted-foreground border border-dashed rounded-lg">
|
||||
暂无报警联系组,点击「添加联系组」创建
|
||||
</div>
|
||||
{activeTab === 'groups' && (
|
||||
contactGroups.length === 0 ? (
|
||||
<PageEmpty title="暂无报警联系组" description="点击「添加联系组」创建" />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{contactGroups.map((g) => {
|
||||
@@ -554,9 +549,8 @@ const AlertCenter: React.FC = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
)}
|
||||
|
||||
<AlertDetailDialog
|
||||
alert={selectedAlert}
|
||||
@@ -590,7 +584,7 @@ const AlertCenter: React.FC = () => {
|
||||
contacts={contacts}
|
||||
onSaved={() => { fetchContactGroups(); }}
|
||||
/>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user