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
@@ -112,19 +112,19 @@ export function AgentAlerts() {
|
||||
{/* 页头 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-slate-100">Agent 告警</h2>
|
||||
<p className="text-sm text-slate-400 mt-0.5">基于 Agent 上报指标的告警规则与事件</p>
|
||||
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 告警</h2>
|
||||
<p className="text-sm dg-text-secondary mt-0.5">基于 Agent 上报指标的告警规则与事件</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={loadData} disabled={loading} className="bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700">
|
||||
<Button variant="outline" size="sm" onClick={loadData} disabled={loading} className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]">
|
||||
<RefreshCw className={`h-4 w-4 mr-1.5 ${loading ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="bg-slate-800 border border-slate-700">
|
||||
<TabsTrigger value="events" className="data-[state=active]:bg-slate-700">告警事件</TabsTrigger>
|
||||
<TabsTrigger value="rules" className="data-[state=active]:bg-slate-700">告警规则</TabsTrigger>
|
||||
<TabsList className="bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)]">
|
||||
<TabsTrigger value="events" className="data-[state=active]:bg-[var(--dg-bg-active)]">告警事件</TabsTrigger>
|
||||
<TabsTrigger value="rules" className="data-[state=active]:bg-[var(--dg-bg-active)]">告警规则</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* ========== 告警事件 ========== */}
|
||||
@@ -134,7 +134,7 @@ export function AgentAlerts() {
|
||||
size="sm"
|
||||
variant={resolvedView ? 'outline' : 'default'}
|
||||
onClick={() => setResolvedView(false)}
|
||||
className={!resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-slate-800 border-slate-700 text-slate-300'}
|
||||
className={!resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'dg-input text-[var(--dg-text-secondary)]'}
|
||||
>
|
||||
未恢复
|
||||
</Button>
|
||||
@@ -142,53 +142,53 @@ export function AgentAlerts() {
|
||||
size="sm"
|
||||
variant={resolvedView ? 'default' : 'outline'}
|
||||
onClick={() => setResolvedView(true)}
|
||||
className={resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-slate-800 border-slate-700 text-slate-300'}
|
||||
className={resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'dg-input text-[var(--dg-text-secondary)]'}
|
||||
>
|
||||
已恢复
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="dg-card">
|
||||
<CardContent className="flex items-center justify-center py-16">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-slate-500" />
|
||||
<span className="ml-2 text-slate-400">加载中...</span>
|
||||
<Loader2 className="h-5 w-5 animate-spin dg-text-muted" />
|
||||
<span className="ml-2 dg-text-secondary">加载中...</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : alerts.length === 0 ? (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="dg-card">
|
||||
<CardContent className="flex flex-col items-center justify-center py-16">
|
||||
<CheckCircle className="h-10 w-10 text-emerald-500 mb-3" />
|
||||
<p className="text-slate-400">{resolvedView ? '暂无已恢复的告警' : '当前无活动告警,一切正常'}</p>
|
||||
<p className="dg-text-secondary">{resolvedView ? '暂无已恢复的告警' : '当前无活动告警,一切正常'}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{alerts.map((alert) => (
|
||||
<Card key={alert.id} className="bg-slate-900 border-slate-800 py-4">
|
||||
<Card key={alert.id} className="dg-card py-4">
|
||||
<CardContent>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-start gap-3 min-w-0 flex-1">
|
||||
<div className="w-9 h-9 rounded-lg bg-slate-800 flex items-center justify-center shrink-0">
|
||||
<AlertTriangle className={`h-4 w-4 ${(alert.severity || 'warning') === 'critical' ? 'text-red-400' : (alert.severity || 'warning') === 'warning' ? 'text-amber-400' : 'text-blue-400'}`} />
|
||||
<div className="w-9 h-9 rounded-lg bg-[var(--dg-bg-tertiary)] flex items-center justify-center shrink-0">
|
||||
<AlertTriangle className={`h-4 w-4 ${(alert.severity || 'warning') === 'critical' ? 'dg-text-red' : (alert.severity || 'warning') === 'warning' ? 'dg-text-yellow' : 'dg-text-blue'}`} />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{severityBadge(alert.severity)}
|
||||
<span className="font-medium text-slate-100">{alert.rule_name || alert.name || alert.title || '告警'}</span>
|
||||
<span className="font-medium text-[var(--dg-text-primary)]">{alert.rule_name || alert.name || alert.title || '告警'}</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-400 mt-1">{alert.message || alert.description || '触发告警阈值'}</p>
|
||||
<div className="flex items-center gap-4 mt-2 text-xs text-slate-500 flex-wrap">
|
||||
<p className="text-sm dg-text-secondary mt-1">{alert.message || alert.description || '触发告警阈值'}</p>
|
||||
<div className="flex items-center gap-4 mt-2 text-xs dg-text-muted flex-wrap">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Cpu className="h-3 w-3" />
|
||||
{alert.hostname || '--'}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<span className="text-slate-400">指标:</span> {getMetricLabel(alert.metric)}
|
||||
<span className="dg-text-secondary">指标:</span> {getMetricLabel(alert.metric)}
|
||||
</span>
|
||||
{alert.value != null && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<span className="text-slate-400">当前值:</span> {alert.value}
|
||||
<span className="dg-text-secondary">当前值:</span> {alert.value}
|
||||
</span>
|
||||
)}
|
||||
<span className="inline-flex items-center gap-1">
|
||||
@@ -203,7 +203,7 @@ export function AgentAlerts() {
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleResolve(alert.id)}
|
||||
className="shrink-0 h-8 bg-slate-800 border-slate-700 text-emerald-400 hover:bg-emerald-500/10 hover:text-emerald-400"
|
||||
className="shrink-0 h-8 dg-input dg-text-green hover:bg-emerald-500/10 hover:dg-text-green"
|
||||
>
|
||||
<CheckCircle className="h-3.5 w-3.5 mr-1" />
|
||||
恢复
|
||||
@@ -227,17 +227,17 @@ export function AgentAlerts() {
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="dg-card">
|
||||
<CardContent className="flex items-center justify-center py-16">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-slate-500" />
|
||||
<span className="ml-2 text-slate-400">加载中...</span>
|
||||
<Loader2 className="h-5 w-5 animate-spin dg-text-muted" />
|
||||
<span className="ml-2 dg-text-secondary">加载中...</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : rules.length === 0 ? (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="dg-card">
|
||||
<CardContent className="flex flex-col items-center justify-center py-16">
|
||||
<Bell className="h-10 w-10 text-slate-600 mb-3" />
|
||||
<p className="text-slate-400">暂无告警规则</p>
|
||||
<Bell className="h-10 w-10 dg-text-muted mb-3" />
|
||||
<p className="dg-text-secondary">暂无告警规则</p>
|
||||
<Button size="sm" onClick={openCreateRule} className="mt-4 bg-blue-600 hover:bg-blue-700 text-white">
|
||||
<Plus className="h-4 w-4 mr-1.5" />
|
||||
创建第一条规则
|
||||
@@ -249,42 +249,42 @@ export function AgentAlerts() {
|
||||
{rules.map((rule) => {
|
||||
const MetricIcon = METRIC_OPTIONS.find((m) => m.value === rule.metric)?.icon || Cpu;
|
||||
return (
|
||||
<Card key={rule.id} className={`bg-slate-900 ${rule.enabled === false ? 'border-slate-800 opacity-60' : 'border-slate-800'} py-4`}>
|
||||
<Card key={rule.id} className={`bg-[var(--dg-bg-secondary)] ${rule.enabled === false ? 'border-[var(--dg-border)] opacity-60' : 'border-[var(--dg-border)]'} py-4`}>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
<div className="w-9 h-9 rounded-lg bg-slate-800 flex items-center justify-center shrink-0">
|
||||
<MetricIcon className="h-4 w-4 text-blue-400" />
|
||||
<div className="w-9 h-9 rounded-lg bg-[var(--dg-bg-tertiary)] flex items-center justify-center shrink-0">
|
||||
<MetricIcon className="h-4 w-4 dg-text-blue" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-medium text-slate-100">{rule.name}</span>
|
||||
<span className="font-medium text-[var(--dg-text-primary)]">{rule.name}</span>
|
||||
{severityBadge(rule.severity)}
|
||||
{rule.enabled === false && (
|
||||
<Badge className="bg-slate-700 text-slate-400 border border-slate-600">已禁用</Badge>
|
||||
<Badge className="bg-[var(--dg-bg-active)] dg-text-secondary border border-[var(--dg-border-hover)]">已禁用</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-1.5 text-xs text-slate-500 flex-wrap">
|
||||
<div className="flex items-center gap-3 mt-1.5 text-xs dg-text-muted flex-wrap">
|
||||
<span>{getMetricLabel(rule.metric)}</span>
|
||||
{rule.metric !== 'offline' && (
|
||||
<span className="text-slate-400">
|
||||
<span className="dg-text-secondary">
|
||||
{rule.operator} {rule.threshold}{rule.metric === 'port_down' ? '' : '%'}
|
||||
</span>
|
||||
)}
|
||||
{rule.metric === 'port_down' && rule.port ? (
|
||||
<span className="text-slate-400">端口 {rule.port}</span>
|
||||
<span className="dg-text-secondary">端口 {rule.port}</span>
|
||||
) : null}
|
||||
{rule.duration ? <span className="text-slate-400">持续 {rule.duration}s</span> : null}
|
||||
{rule.duration ? <span className="dg-text-secondary">持续 {rule.duration}s</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<Switch checked={rule.enabled !== false} onCheckedChange={() => handleToggleRule(rule)} />
|
||||
<Button size="icon" variant="ghost" className="h-8 w-8 text-slate-400 hover:text-slate-100 hover:bg-slate-800" onClick={() => openEditRule(rule)}>
|
||||
<Button size="icon" variant="ghost" className="h-8 w-8 dg-text-secondary hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)]" onClick={() => openEditRule(rule)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="icon" variant="ghost" className="h-8 w-8 text-slate-400 hover:text-red-400 hover:bg-slate-800" onClick={() => handleDeleteRule(rule.id)}>
|
||||
<Button size="icon" variant="ghost" className="h-8 w-8 dg-text-secondary hover:dg-text-red hover:bg-[var(--dg-bg-hover)]" onClick={() => handleDeleteRule(rule.id)}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -129,9 +129,9 @@ export function AgentMonitor() {
|
||||
const onlineCount = hosts.filter((h) => isOnline(h.last_report || h.last_reported_at || h.updated_at)).length;
|
||||
|
||||
const stats = [
|
||||
{ label: '主机总数', value: hosts.length, icon: Server, color: 'text-blue-400' },
|
||||
{ label: '在线', value: onlineCount, icon: Wifi, color: 'text-emerald-400' },
|
||||
{ label: '离线', value: hosts.length - onlineCount, icon: WifiOff, color: 'text-red-400' },
|
||||
{ label: '主机总数', value: hosts.length, icon: Server, color: 'dg-text-blue' },
|
||||
{ label: '在线', value: onlineCount, icon: Wifi, color: 'dg-text-green' },
|
||||
{ label: '离线', value: hosts.length - onlineCount, icon: WifiOff, color: 'dg-text-red' },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -139,8 +139,8 @@ export function AgentMonitor() {
|
||||
{/* 页头 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-slate-100">Agent 监控</h2>
|
||||
<p className="text-sm text-slate-400 mt-0.5">实时监控 Agent 上报的主机指标</p>
|
||||
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 监控</h2>
|
||||
<p className="text-sm dg-text-secondary mt-0.5">实时监控 Agent 上报的主机指标</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
@@ -148,7 +148,7 @@ export function AgentMonitor() {
|
||||
size="sm"
|
||||
onClick={() => loadLatest()}
|
||||
disabled={refreshing}
|
||||
className="bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700 hover:text-slate-100"
|
||||
className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-1.5 ${refreshing ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
@@ -167,14 +167,14 @@ export function AgentMonitor() {
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{stats.map((s) => (
|
||||
<Card key={s.label} className="bg-slate-900 border-slate-800 py-4">
|
||||
<Card key={s.label} className="dg-card py-4">
|
||||
<CardContent className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-slate-800 flex items-center justify-center">
|
||||
<div className="w-10 h-10 rounded-lg bg-[var(--dg-bg-tertiary)] flex items-center justify-center">
|
||||
<s.icon className={`h-5 w-5 ${s.color}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-slate-100">{s.value}</div>
|
||||
<div className="text-xs text-slate-400">{s.label}</div>
|
||||
<div className="text-2xl font-bold text-[var(--dg-text-primary)]">{s.value}</div>
|
||||
<div className="text-xs dg-text-secondary">{s.label}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -183,17 +183,17 @@ export function AgentMonitor() {
|
||||
|
||||
{/* 主机列表 */}
|
||||
{loading ? (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="dg-card">
|
||||
<CardContent className="flex items-center justify-center py-16">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-slate-500" />
|
||||
<span className="ml-2 text-slate-400">加载中...</span>
|
||||
<Loader2 className="h-6 w-6 animate-spin dg-text-muted" />
|
||||
<span className="ml-2 dg-text-secondary">加载中...</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : hosts.length === 0 ? (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="dg-card">
|
||||
<CardContent className="flex flex-col items-center justify-center py-16">
|
||||
<Server className="h-10 w-10 text-slate-600 mb-3" />
|
||||
<p className="text-slate-400">暂无 Agent 上报数据</p>
|
||||
<Server className="h-10 w-10 dg-text-muted mb-3" />
|
||||
<p className="dg-text-secondary">暂无 Agent 上报数据</p>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setWizardOpen(true)}
|
||||
@@ -215,22 +215,22 @@ export function AgentMonitor() {
|
||||
return (
|
||||
<Card
|
||||
key={host.hostname}
|
||||
className="bg-slate-900 border-slate-800 hover:border-slate-700 transition-colors cursor-pointer py-4"
|
||||
className="dg-card hover:border-[var(--dg-border)] transition-colors cursor-pointer py-4"
|
||||
onClick={() => setSelectedHost(host)}
|
||||
>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Server className="h-4 w-4 text-slate-500 shrink-0" />
|
||||
<span className="font-medium text-slate-100 truncate">{host.hostname}</span>
|
||||
<Server className="h-4 w-4 dg-text-muted shrink-0" />
|
||||
<span className="font-medium text-[var(--dg-text-primary)] truncate">{host.hostname}</span>
|
||||
</div>
|
||||
{online ? (
|
||||
<Badge className="bg-emerald-500/15 text-emerald-400 border border-emerald-500/30">
|
||||
<Badge className="bg-emerald-500/15 dg-text-green border border-emerald-500/30">
|
||||
<Wifi className="h-3 w-3 mr-1" />
|
||||
在线
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="bg-red-500/15 text-red-400 border border-red-500/30">
|
||||
<Badge className="bg-red-500/15 dg-text-red border border-red-500/30">
|
||||
<WifiOff className="h-3 w-3 mr-1" />
|
||||
离线
|
||||
</Badge>
|
||||
@@ -245,7 +245,7 @@ export function AgentMonitor() {
|
||||
</div>
|
||||
|
||||
{/* 端口 & 时间 */}
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="flex items-center justify-between text-xs dg-text-secondary">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Network className="h-3.5 w-3.5" />
|
||||
{ports.length} 个端口
|
||||
@@ -261,7 +261,7 @@ export function AgentMonitor() {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex-1 h-8 bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700"
|
||||
className="flex-1 h-8 dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]"
|
||||
onClick={() => openCommandDialog(host.hostname)}
|
||||
>
|
||||
<Terminal className="h-3.5 w-3.5 mr-1" />
|
||||
@@ -270,7 +270,7 @@ export function AgentMonitor() {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex-1 h-8 bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700"
|
||||
className="flex-1 h-8 dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]"
|
||||
onClick={() => setSelectedHost(host)}
|
||||
>
|
||||
<Activity className="h-3.5 w-3.5 mr-1" />
|
||||
@@ -293,13 +293,13 @@ export function AgentMonitor() {
|
||||
|
||||
{/* 远程命令弹窗 */}
|
||||
<Dialog open={cmdOpen} onOpenChange={(v) => { if (!v) closeCommandDialog(); }}>
|
||||
<DialogContent className="max-w-2xl bg-slate-900 border-slate-800 text-slate-100">
|
||||
<DialogContent className="max-w-2xl dg-card text-[var(--dg-text-primary)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-slate-100 flex items-center gap-2">
|
||||
<Terminal className="h-5 w-5 text-blue-400" />
|
||||
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Terminal className="h-5 w-5 dg-text-blue" />
|
||||
远程命令
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-slate-400">
|
||||
<DialogDescription className="dg-text-secondary">
|
||||
在 {cmdHost} 上执行命令
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -307,34 +307,34 @@ export function AgentMonitor() {
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">目标主机</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">目标主机</Label>
|
||||
<Input
|
||||
value={cmdHost}
|
||||
onChange={(e) => setCmdHost(e.target.value)}
|
||||
readOnly
|
||||
className="h-9 bg-slate-800 border-slate-700 text-slate-200"
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">命令</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">命令</Label>
|
||||
<Textarea
|
||||
value={cmdCommand}
|
||||
onChange={(e) => setCmdCommand(e.target.value)}
|
||||
placeholder="例如:free -m && df -h"
|
||||
rows={3}
|
||||
className="bg-slate-950 border-slate-700 text-slate-200 font-mono text-sm resize-none"
|
||||
className="bg-[var(--dg-bg-primary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-sm resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-40 space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">超时(秒)</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">超时(秒)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={cmdTimeout}
|
||||
onChange={(e) => setCmdTimeout(Number(e.target.value) || 30)}
|
||||
className="h-9 bg-slate-800 border-slate-700 text-slate-200"
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -342,20 +342,20 @@ export function AgentMonitor() {
|
||||
{cmdResult && (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-slate-300 text-xs">执行结果</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">执行结果</Label>
|
||||
<Badge
|
||||
className={
|
||||
cmdResult.status === 'success' || cmdResult.status === 'done' || cmdResult.status === 'completed'
|
||||
? 'bg-emerald-500/15 text-emerald-400 border border-emerald-500/30'
|
||||
? 'bg-emerald-500/15 dg-text-green border border-emerald-500/30'
|
||||
: cmdResult.status === 'timeout'
|
||||
? 'bg-amber-500/15 text-amber-400 border border-amber-500/30'
|
||||
: 'bg-red-500/15 text-red-400 border border-red-500/30'
|
||||
? 'bg-amber-500/15 dg-text-yellow border border-amber-500/30'
|
||||
: 'bg-red-500/15 dg-text-red border border-red-500/30'
|
||||
}
|
||||
>
|
||||
{cmdResult.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<pre className="text-xs text-slate-300 font-mono whitespace-pre-wrap break-all bg-slate-950 border border-slate-800 rounded-lg p-3 max-h-48 overflow-auto">
|
||||
<pre className="text-xs text-[var(--dg-text-secondary)] font-mono whitespace-pre-wrap break-all bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-lg p-3 max-h-48 overflow-auto">
|
||||
{cmdResult.result || cmdResult.output || cmdResult.error || '无输出'}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -363,7 +363,7 @@ export function AgentMonitor() {
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={closeCommandDialog} className="border-slate-700 text-slate-300">
|
||||
<Button variant="outline" onClick={closeCommandDialog} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
|
||||
关闭
|
||||
</Button>
|
||||
<Button
|
||||
@@ -391,12 +391,12 @@ export function AgentMonitor() {
|
||||
function MetricBar({ icon: Icon, label, value }: { icon: ComponentType<{ className?: string }>; label: string; value: number }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className="h-3.5 w-3.5 text-slate-500 shrink-0" />
|
||||
<span className="text-xs text-slate-400 w-8 shrink-0">{label}</span>
|
||||
<Icon className="h-3.5 w-3.5 dg-text-muted shrink-0" />
|
||||
<span className="text-xs dg-text-secondary w-8 shrink-0">{label}</span>
|
||||
<div className="flex-1">
|
||||
<Progress value={value} className={`h-1.5 ${value >= 90 ? '[&>[data-slot=progress-indicator]]:bg-red-500' : value >= 75 ? '[&>[data-slot=progress-indicator]]:bg-amber-500' : '[&>[data-slot=progress-indicator]]:bg-emerald-500'}`} />
|
||||
</div>
|
||||
<span className={`text-xs font-medium w-10 text-right ${value >= 90 ? 'text-red-400' : value >= 75 ? 'text-amber-400' : 'text-slate-300'}`}>
|
||||
<span className={`text-xs font-medium w-10 text-right ${value >= 90 ? 'dg-text-red' : value >= 75 ? 'dg-text-yellow' : 'text-[var(--dg-text-secondary)]'}`}>
|
||||
{value.toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -104,31 +104,31 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(v) => { if (!v) onOpenChange(false); }}>
|
||||
<DialogContent className="max-w-md bg-slate-900 border-slate-800 text-slate-100">
|
||||
<DialogContent className="max-w-md dg-card text-[var(--dg-text-primary)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-slate-100">{editingRule ? '编辑规则' : '新建规则'}</DialogTitle>
|
||||
<DialogDescription className="text-slate-400">配置 Agent 指标告警规则</DialogDescription>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">{editingRule ? '编辑规则' : '新建规则'}</DialogTitle>
|
||||
<DialogDescription className="dg-text-secondary">配置 Agent 指标告警规则</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">规则名称</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">规则名称</Label>
|
||||
<Input
|
||||
value={ruleForm.name}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })}
|
||||
placeholder="例如:CPU 使用率告警"
|
||||
className="h-9 bg-slate-800 border-slate-700 text-slate-200"
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">监控指标</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">监控指标</Label>
|
||||
<Select value={ruleForm.metric} onValueChange={(v) => setRuleForm({ ...ruleForm, metric: v })}>
|
||||
<SelectTrigger className="h-9 bg-slate-800 border-slate-700 text-slate-200">
|
||||
<SelectTrigger className="h-9 dg-input">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-slate-800 border-slate-700 text-slate-200">
|
||||
<SelectContent className="dg-input">
|
||||
{METRIC_OPTIONS.map((m) => (
|
||||
<SelectItem key={m.value} value={m.value}>{m.label}</SelectItem>
|
||||
))}
|
||||
@@ -136,12 +136,12 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">严重程度</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">严重程度</Label>
|
||||
<Select value={ruleForm.severity} onValueChange={(v) => setRuleForm({ ...ruleForm, severity: v })}>
|
||||
<SelectTrigger className="h-9 bg-slate-800 border-slate-700 text-slate-200">
|
||||
<SelectTrigger className="h-9 dg-input">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-slate-800 border-slate-700 text-slate-200">
|
||||
<SelectContent className="dg-input">
|
||||
{SEVERITY_OPTIONS.map((s) => (
|
||||
<SelectItem key={s.value} value={s.value}>{s.label}</SelectItem>
|
||||
))}
|
||||
@@ -153,12 +153,12 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
{ruleForm.metric !== 'offline' && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">比较运算符</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">比较运算符</Label>
|
||||
<Select value={ruleForm.operator} onValueChange={(v) => setRuleForm({ ...ruleForm, operator: v })}>
|
||||
<SelectTrigger className="h-9 bg-slate-800 border-slate-700 text-slate-200">
|
||||
<SelectTrigger className="h-9 dg-input">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-slate-800 border-slate-700 text-slate-200">
|
||||
<SelectContent className="dg-input">
|
||||
{OPERATOR_OPTIONS.map((o) => (
|
||||
<SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
|
||||
))}
|
||||
@@ -166,12 +166,12 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">阈值 {ruleForm.metric !== 'port_down' ? '(%)' : ''}</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">阈值 {ruleForm.metric !== 'port_down' ? '(%)' : ''}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={ruleForm.threshold}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, threshold: e.target.value })}
|
||||
className="h-9 bg-slate-800 border-slate-700 text-slate-200"
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,36 +179,36 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
|
||||
{ruleForm.metric === 'port_down' && (
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">监控端口</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">监控端口</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={ruleForm.port}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, port: e.target.value })}
|
||||
placeholder="例如:8080"
|
||||
className="h-9 bg-slate-800 border-slate-700 text-slate-200"
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-slate-300 text-xs">持续时间(秒)</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">持续时间(秒)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={ruleForm.duration}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, duration: e.target.value })}
|
||||
placeholder="指标持续超限多久后告警"
|
||||
className="h-9 bg-slate-800 border-slate-700 text-slate-200"
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-slate-300 text-xs">启用规则</Label>
|
||||
<Label className="text-[var(--dg-text-secondary)] text-xs">启用规则</Label>
|
||||
<Switch checked={ruleForm.enabled} onCheckedChange={(v) => setRuleForm({ ...ruleForm, enabled: v })} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} className="border-slate-700 text-slate-300">取消</Button>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">取消</Button>
|
||||
<Button onClick={handleSave} disabled={saving} className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||
{saving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
|
||||
{editingRule ? '保存' : '创建'}
|
||||
|
||||
@@ -196,12 +196,12 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl bg-slate-900 border-slate-800 text-slate-100">
|
||||
<DialogContent className="max-w-2xl dg-card text-[var(--dg-text-primary)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-slate-100">
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">
|
||||
{server ? `部署 Agent → ${server.hostname}` : '部署 Agent'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-slate-400">
|
||||
<DialogDescription className="dg-text-secondary">
|
||||
{server
|
||||
? `目标主机 ${server.hostname} (${server.ip}),选择安装方式:`
|
||||
: '在目标主机上执行以下命令即可完成 Agent 安装与注册'
|
||||
@@ -210,63 +210,63 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
|
||||
</DialogHeader>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="bg-slate-800 border border-slate-700">
|
||||
<TabsTrigger value="auto" className="data-[state=active]:bg-slate-700">
|
||||
<TabsList className="bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)]">
|
||||
<TabsTrigger value="auto" className="data-[state=active]:bg-[var(--dg-bg-active)]">
|
||||
<Terminal className="h-4 w-4 mr-1.5" />
|
||||
安装Agent
|
||||
</TabsTrigger>
|
||||
{server && (
|
||||
<TabsTrigger value="ssh" className="data-[state=active]:bg-slate-700">
|
||||
<TabsTrigger value="ssh" className="data-[state=active]:bg-[var(--dg-bg-active)]">
|
||||
<Zap className="h-4 w-4 mr-1.5" />
|
||||
SSH一键安装
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{!server && (
|
||||
<TabsTrigger value="ssh" className="data-[state=active]:bg-slate-700 opacity-50 cursor-not-allowed" disabled>
|
||||
<TabsTrigger value="ssh" className="data-[state=active]:bg-[var(--dg-bg-active)] opacity-50 cursor-not-allowed" disabled>
|
||||
<Zap className="h-4 w-4 mr-1.5" />
|
||||
SSH一键安装
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="manual" className="data-[state=active]:bg-slate-700">
|
||||
<TabsTrigger value="manual" className="data-[state=active]:bg-[var(--dg-bg-active)]">
|
||||
手动安装
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 自动安装 */}
|
||||
<TabsContent value="auto" className="mt-4 space-y-3">
|
||||
<p className="text-sm text-slate-400">
|
||||
<p className="text-sm dg-text-secondary">
|
||||
在目标主机执行以下命令,Agent 将自动下载、安装并启动:
|
||||
</p>
|
||||
<div className="relative rounded-lg bg-slate-950 border border-slate-800 p-4">
|
||||
<pre className="text-xs text-emerald-400 font-mono whitespace-pre-wrap break-all pr-10">
|
||||
<div className="relative rounded-lg bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] p-4">
|
||||
<pre className="text-xs dg-text-green font-mono whitespace-pre-wrap break-all pr-10">
|
||||
{installCommand}
|
||||
</pre>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={handleCopy}
|
||||
className="absolute top-2 right-2 h-8 w-8 text-slate-400 hover:text-slate-100 hover:bg-slate-800"
|
||||
className="absolute top-2 right-2 h-8 w-8 dg-text-secondary hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)]"
|
||||
title="复制到剪贴板"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||||
{copied ? <Check className="h-4 w-4 dg-text-green" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 手动复制模式:当自动复制失败时展示可全选的文本域 */}
|
||||
{showManualInput && (
|
||||
<div className="rounded-lg bg-amber-950/30 border border-amber-800/50 p-4">
|
||||
<p className="text-xs text-amber-400 mb-2">
|
||||
<p className="text-xs dg-text-yellow mb-2">
|
||||
⚠️ 自动复制失败。请点击下方输入框全选 (Ctrl+A) 后复制 (Ctrl+C):
|
||||
</p>
|
||||
<textarea
|
||||
readOnly
|
||||
value={installCommand}
|
||||
className="w-full h-24 bg-slate-950 border border-amber-800/30 text-emerald-400 font-mono text-xs p-3 rounded resize-none focus:outline-none focus:border-amber-600"
|
||||
className="w-full h-24 bg-[var(--dg-bg-primary)] border border-amber-800/30 dg-text-green font-mono text-xs p-3 rounded resize-none focus:outline-none focus:border-amber-600"
|
||||
onClick={(e) => (e.target as HTMLTextAreaElement).select()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-start gap-2 text-xs text-slate-500">
|
||||
<div className="flex items-start gap-2 text-xs dg-text-muted">
|
||||
<Terminal className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||
<span>
|
||||
服务器地址已自动检测 — 安装脚本与 Agent 上报均使用 {origin}(通过 Nginx/Vite proxy 转发到后端)。安装成功后将自动连接并开始上报指标。
|
||||
@@ -277,19 +277,19 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
|
||||
{/* SSH 一键安装 */}
|
||||
<TabsContent value="ssh" className="mt-4 space-y-4">
|
||||
{!server ? (
|
||||
<div className="text-center py-12 text-slate-400">
|
||||
<div className="text-center py-12 dg-text-secondary">
|
||||
<Server className="h-10 w-10 mx-auto mb-3 opacity-50" />
|
||||
<p className="text-sm font-medium mb-1">未选择目标主机</p>
|
||||
<p className="text-xs">请先在主机列表中勾选一台主机,再点击「安装 Agent」打开此页面</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="rounded-lg bg-slate-950 border border-slate-800 p-4">
|
||||
<div className="rounded-lg bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] p-4">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Server className="h-5 w-5 text-blue-400" />
|
||||
<Server className="h-5 w-5 dg-text-blue" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-200">{server.hostname}</p>
|
||||
<code className="text-xs text-slate-400">{server.ip}</code>
|
||||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">{server.hostname}</p>
|
||||
<code className="text-xs dg-text-secondary">{server.ip}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -302,49 +302,49 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label className="text-xs text-slate-400">用户名</Label>
|
||||
<Label className="text-xs dg-text-secondary">用户名</Label>
|
||||
<Input
|
||||
value={sshUsername}
|
||||
onChange={e => setSshUsername(e.target.value)}
|
||||
placeholder="root"
|
||||
className="h-8 text-sm bg-slate-900 border-slate-700 text-slate-200 mt-1"
|
||||
className="h-8 text-sm dg-input text-[var(--dg-text-primary)] mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end gap-1">
|
||||
<div className="flex-1">
|
||||
<Label className="text-xs text-slate-400">密码</Label>
|
||||
<Label className="text-xs dg-text-secondary">密码</Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={sshPassword}
|
||||
onChange={e => { setSshPassword(e.target.value); setAuthType('password'); }}
|
||||
placeholder="******"
|
||||
className="h-8 text-sm bg-slate-900 border-slate-700 text-slate-200 mt-1"
|
||||
className="h-8 text-sm dg-input text-[var(--dg-text-primary)] mt-1"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-slate-500 px-1 pb-1">或</span>
|
||||
<span className="text-xs dg-text-muted px-1 pb-1">或</span>
|
||||
<Button variant="ghost" size="sm"
|
||||
onClick={() => { setAuthType('key'); setSshPassword(''); }}
|
||||
className={`text-xs h-7 px-2 ${authType==='key'?'text-blue-400':'text-slate-500'}`}>
|
||||
className={`text-xs h-7 px-2 ${authType==='key'?'dg-text-blue':'dg-text-muted'}`}>
|
||||
密钥
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{authType === 'key' && (
|
||||
<div>
|
||||
<Label className="text-xs text-slate-400">SSH 私钥(PEM 格式)</Label>
|
||||
<Label className="text-xs dg-text-secondary">SSH 私钥(PEM 格式)</Label>
|
||||
<textarea
|
||||
value={sshKey}
|
||||
onChange={e => setSshKey(e.target.value)}
|
||||
placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY-----"
|
||||
rows={3}
|
||||
className="w-full mt-1 bg-slate-900 border border-slate-700 text-slate-200 font-mono text-xs p-2 rounded-md resize-none"
|
||||
className="w-full mt-1 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-xs p-2 rounded-md resize-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-sm text-slate-400 mb-4">
|
||||
<p className="text-sm dg-text-secondary mb-4">
|
||||
通过 SSH 远程连接到目标主机,自动下载并安装 Agent,完成后立即启动运行。
|
||||
</p>
|
||||
<Button
|
||||
@@ -365,7 +365,7 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-start gap-2 text-xs text-slate-500">
|
||||
<div className="flex items-start gap-2 text-xs dg-text-muted">
|
||||
<Terminal className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||
<span>
|
||||
系统将通过 SSH 连接 {server.ip},执行安装脚本。请确保服务器 SSH 凭据已正确配置(编辑服务器可修改密码/密钥)。
|
||||
@@ -379,16 +379,16 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
|
||||
<TabsContent value="manual" className="mt-4 space-y-4">
|
||||
<div className="max-h-[420px] overflow-y-auto space-y-3 pr-1">
|
||||
{manualSteps.map((step) => (
|
||||
<div key={step.title} className="rounded-lg bg-slate-950 border border-slate-800 overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-2.5 bg-slate-800/50">
|
||||
<step.icon className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-sm font-medium text-slate-200">{step.title}</span>
|
||||
<div key={step.title} className="rounded-lg bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-2.5 bg-[var(--dg-bg-tertiary)]/50">
|
||||
<step.icon className="h-4 w-4 dg-text-blue" />
|
||||
<span className="text-sm font-medium text-[var(--dg-text-primary)]">{step.title}</span>
|
||||
</div>
|
||||
<div className="p-3 space-y-1">
|
||||
{step.commands.map((cmd, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<span className="text-slate-600 font-mono text-xs select-none mt-0.5">$</span>
|
||||
<code className="text-xs text-slate-300 font-mono whitespace-pre-wrap break-all flex-1">
|
||||
<span className="dg-text-muted font-mono text-xs select-none mt-0.5">$</span>
|
||||
<code className="text-xs text-[var(--dg-text-secondary)] font-mono whitespace-pre-wrap break-all flex-1">
|
||||
{cmd}
|
||||
</code>
|
||||
</div>
|
||||
@@ -397,7 +397,7 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-slate-500">
|
||||
<p className="text-xs dg-text-muted">
|
||||
完成上述步骤后,Agent 将作为 systemd 服务运行并自动上报指标。
|
||||
</p>
|
||||
</TabsContent>
|
||||
|
||||
@@ -72,15 +72,15 @@ export function HostDetailDialog({
|
||||
|
||||
return (
|
||||
<Dialog open={!!host} onOpenChange={(v) => { if (!v) onClose(); }}>
|
||||
<DialogContent className="max-w-3xl bg-slate-900 border-slate-800 text-slate-100">
|
||||
<DialogContent className="max-w-3xl dg-card text-[var(--dg-text-primary)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-slate-100 flex items-center gap-2">
|
||||
<Server className="h-5 w-5 text-blue-400" />
|
||||
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Server className="h-5 w-5 dg-text-blue" />
|
||||
{host.hostname}
|
||||
{online ? (
|
||||
<Badge className="bg-emerald-500/15 text-emerald-400 border border-emerald-500/30">在线</Badge>
|
||||
<Badge className="bg-emerald-500/15 dg-text-green border border-emerald-500/30">在线</Badge>
|
||||
) : (
|
||||
<Badge className="bg-red-500/15 text-red-400 border border-red-500/30">离线</Badge>
|
||||
<Badge className="bg-red-500/15 dg-text-red border border-red-500/30">离线</Badge>
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">查看主机详情和指标趋势</DialogDescription>
|
||||
@@ -98,28 +98,28 @@ export function HostDetailDialog({
|
||||
{/* 趋势图 */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-slate-200 flex items-center gap-1.5">
|
||||
<Activity className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-sm font-medium text-[var(--dg-text-primary)] flex items-center gap-1.5">
|
||||
<Activity className="h-4 w-4 dg-text-blue" />
|
||||
指标趋势
|
||||
</span>
|
||||
<Select value={String(hours)} onValueChange={(v) => setHours(Number(v))}>
|
||||
<SelectTrigger size="sm" className="h-7 w-28 bg-slate-800 border-slate-700 text-slate-300 text-xs">
|
||||
<SelectTrigger size="sm" className="h-7 w-28 dg-input text-[var(--dg-text-secondary)] text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-slate-800 border-slate-700 text-slate-200">
|
||||
<SelectContent className="dg-input">
|
||||
<SelectItem value="1">最近 1 小时</SelectItem>
|
||||
<SelectItem value="6">最近 6 小时</SelectItem>
|
||||
<SelectItem value="24">最近 24 小时</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="h-56 rounded-lg bg-slate-950 border border-slate-800 p-3">
|
||||
<div className="h-56 rounded-lg bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] p-3">
|
||||
{loading ? (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-slate-500" />
|
||||
<Loader2 className="h-5 w-5 animate-spin dg-text-muted" />
|
||||
</div>
|
||||
) : chartData.length === 0 ? (
|
||||
<div className="h-full flex items-center justify-center text-sm text-slate-500">暂无历史数据</div>
|
||||
<div className="h-full flex items-center justify-center text-sm dg-text-muted">暂无历史数据</div>
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={chartData} margin={{ top: 5, right: 5, bottom: 0, left: -20 }}>
|
||||
@@ -154,16 +154,16 @@ export function HostDetailDialog({
|
||||
|
||||
{/* 端口列表 */}
|
||||
<div>
|
||||
<span className="text-sm font-medium text-slate-200 flex items-center gap-1.5 mb-2">
|
||||
<Network className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-sm font-medium text-[var(--dg-text-primary)] flex items-center gap-1.5 mb-2">
|
||||
<Network className="h-4 w-4 dg-text-blue" />
|
||||
端口发现 ({ports.length})
|
||||
</span>
|
||||
{ports.length === 0 ? (
|
||||
<p className="text-xs text-slate-500">暂无端口数据</p>
|
||||
<p className="text-xs dg-text-muted">暂无端口数据</p>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-1.5 max-h-32 overflow-y-auto">
|
||||
{ports.map((p, idx) => (
|
||||
<Badge key={idx} className="bg-slate-800 text-slate-300 border border-slate-700">
|
||||
<Badge key={idx} className="bg-[var(--dg-bg-tertiary)] text-[var(--dg-text-secondary)] border border-[var(--dg-border)]">
|
||||
<Network className="h-3 w-3 mr-1" />
|
||||
{p.port ?? p}
|
||||
</Badge>
|
||||
@@ -181,7 +181,7 @@ export function HostDetailDialog({
|
||||
<Terminal className="h-4 w-4 mr-1.5" />
|
||||
远程命令
|
||||
</Button>
|
||||
<Button variant="outline" onClick={onClose} className="border-slate-700 text-slate-300">
|
||||
<Button variant="outline" onClick={onClose} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
|
||||
关闭
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -192,9 +192,9 @@ export function HostDetailDialog({
|
||||
|
||||
function InfoCell({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div className="bg-slate-950 border border-slate-800 rounded-lg px-3 py-2">
|
||||
<div className="text-xs text-slate-500">{label}</div>
|
||||
<div className="text-sm text-slate-200 font-medium truncate">{value}</div>
|
||||
<div className="bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-lg px-3 py-2">
|
||||
<div className="text-xs dg-text-muted">{label}</div>
|
||||
<div className="text-sm text-[var(--dg-text-primary)] font-medium truncate">{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ export const OPERATOR_OPTIONS = [
|
||||
|
||||
// 严重程度选项(含徽章配色) // Logan.Li
|
||||
export const SEVERITY_OPTIONS = [
|
||||
{ value: 'critical', label: '严重', color: 'bg-red-500/15 text-red-400 border-red-500/30' },
|
||||
{ value: 'warning', label: '警告', color: 'bg-amber-500/15 text-amber-400 border-amber-500/30' },
|
||||
{ value: 'info', label: '提示', color: 'bg-blue-500/15 text-blue-400 border-blue-500/30' },
|
||||
{ value: 'critical', label: '严重', color: 'bg-red-500/15 dg-text-red border-red-500/30' },
|
||||
{ value: 'warning', label: '警告', color: 'bg-amber-500/15 dg-text-yellow border-amber-500/30' },
|
||||
{ value: 'info', label: '提示', color: 'bg-blue-500/15 dg-text-blue border-blue-500/30' },
|
||||
];
|
||||
|
||||
// 获取指标中文标签 // Logan.Li
|
||||
|
||||
Reference in new issue
Block a user