feat: v0.34 健康探针(Architecture B) + Agent v2.1 + 端口监控 + 告警联系人重构
- v0.31: AlertContact 重构(channel_type下拉+webhook_url替代三分立字段) - v0.32: Agent ProcessTop 增强(cmdline/user/cpu_pct/mem_pct) - v0.33: 端口监控 Tab(监听端口自动发现+down联动告警) - v0.34: 健康探针(Agent本地执行HTTP/TCP/Exec→结果随心跳上报) - Agent新增: ContextSwitch/FD/HealthProbe采集器 - 后端新增: health_probe handler + Agent model扩展 - HostMonitor新增Tab: 健康探针/端口监控
This commit is contained in:
1 parent
e1f75f0bc4
commit
d656520428
25 files changed
+1700
-619
No files matched your search
@@ -18,9 +18,9 @@ import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { alertCenterApi, agentApi, alertApi } from '@/services/api';
|
||||
import {
|
||||
type Alert, type AlertRule, mapAlert, getSeverityBadge, getStatusBadge,
|
||||
getChannelTypeLabel,
|
||||
} from './alert-center-helpers';
|
||||
import { AlertDetailDialog, AlertSilenceDialog } from './AlertCenterDetail';
|
||||
import { AlertChannelsTab } from './AlertChannelsTab';
|
||||
import { AlertRuleDialog } from '@/components/monitor/AlertRuleDialog';
|
||||
import { ContactDialog } from './ContactDialog';
|
||||
import { ContactGroupDialog } from './ContactGroupDialog';
|
||||
@@ -265,7 +265,7 @@ const AlertCenter: React.FC = () => {
|
||||
<TabsList className="grid w-full grid-cols-4 lg:w-[480px] h-9">
|
||||
<TabsTrigger value="alerts" className="text-xs">告警列表</TabsTrigger>
|
||||
<TabsTrigger value="rules" className="text-xs">告警规则</TabsTrigger>
|
||||
<TabsTrigger value="contacts" className="text-xs">通知管理</TabsTrigger>
|
||||
<TabsTrigger value="contacts" className="text-xs">报警联系人</TabsTrigger>
|
||||
<TabsTrigger value="groups" className="text-xs">报警联系组</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@@ -501,11 +501,9 @@ const AlertCenter: React.FC = () => {
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* 通知管理 Tab — 渠道 + 联系人合并 */}
|
||||
{/* 报警联系人 Tab */}
|
||||
<TabsContent value="contacts" className="space-y-4">
|
||||
<AlertChannelsTab refreshKey={0} />
|
||||
<div className="border-t border-border pt-4 mt-1">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-sm font-medium">报警联系人</h3>
|
||||
<Button size="sm" onClick={() => { setEditingContact(null); setContactDialogOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
@@ -529,9 +527,16 @@ const AlertCenter: React.FC = () => {
|
||||
{c.phone && <div>📱 {c.phone}</div>}
|
||||
{c.email && <div>📧 {c.email}</div>}
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
{c.dingtalk && <Badge variant="outline" className="text-[10px]">钉钉</Badge>}
|
||||
{c.feishu && <Badge variant="outline" className="text-[10px]">飞书</Badge>}
|
||||
{c.wechat && <Badge variant="outline" className="text-[10px]">企微</Badge>}
|
||||
{c.channel_type && (
|
||||
<Badge variant="outline" className="text-[10px]">
|
||||
{getChannelTypeLabel(c.channel_type)}
|
||||
</Badge>
|
||||
)}
|
||||
{c.webhook_url && (
|
||||
<span className="text-[10px] text-muted-foreground truncate max-w-[200px]" title={c.webhook_url}>
|
||||
{c.webhook_url}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-1.5 pt-1.5">
|
||||
<Button variant="outline" size="sm" className="h-7 text-xs flex-1"
|
||||
@@ -553,7 +558,6 @@ const AlertCenter: React.FC = () => {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 报警联系组 Tab */}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// 报警联系人创建/编辑对话框 // Logan.Li
|
||||
// 报警联系人创建/编辑对话框 — 渠道类型下拉 + webhook URL // Logan.Li
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter,
|
||||
@@ -6,10 +6,20 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { toast } from 'sonner';
|
||||
import { alertApi } from '@/services/api';
|
||||
import type { Contact } from './alert-center-helpers';
|
||||
|
||||
const CHANNEL_OPTIONS = [
|
||||
{ value: 'feishu', label: '飞书', placeholder: 'https://open.feishu.cn/open-apis/bot/v2/hook/...' },
|
||||
{ value: 'dingtalk', label: '钉钉', placeholder: 'https://oapi.dingtalk.com/robot/send?access_token=...' },
|
||||
{ value: 'wechat', label: '企业微信', placeholder: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...' },
|
||||
{ value: 'webhook', label: '自定义Webhook', placeholder: 'https://your-webhook-url...' },
|
||||
];
|
||||
|
||||
export function ContactDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
@@ -21,39 +31,37 @@ export function ContactDialog({
|
||||
editingContact: Contact | null;
|
||||
onSaved: () => void;
|
||||
}) {
|
||||
const [form, setForm] = useState({
|
||||
name: '', phone: '', email: '', dingtalk: '', feishu: '', wechat: '',
|
||||
});
|
||||
const [channelType, setChannelType] = useState('feishu');
|
||||
const [form, setForm] = useState({ name: '', phone: '', email: '', webhook_url: '' });
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (editingContact) {
|
||||
setChannelType(editingContact.channel_type || 'feishu');
|
||||
setForm({
|
||||
name: editingContact.name || '',
|
||||
phone: editingContact.phone || '',
|
||||
email: editingContact.email || '',
|
||||
dingtalk: editingContact.dingtalk || '',
|
||||
feishu: editingContact.feishu || '',
|
||||
wechat: editingContact.wechat || '',
|
||||
webhook_url: editingContact.webhook_url || '',
|
||||
});
|
||||
} else {
|
||||
setForm({ name: '', phone: '', email: '', dingtalk: '', feishu: '', wechat: '' });
|
||||
setChannelType('feishu');
|
||||
setForm({ name: '', phone: '', email: '', webhook_url: '' });
|
||||
}
|
||||
}
|
||||
}, [open, editingContact]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!form.name.trim()) return;
|
||||
if (!form.name.trim() || !form.webhook_url.trim()) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const payload = {
|
||||
name: form.name.trim(),
|
||||
phone: form.phone.trim() || undefined,
|
||||
email: form.email.trim() || undefined,
|
||||
dingtalk: form.dingtalk.trim() || undefined,
|
||||
feishu: form.feishu.trim() || undefined,
|
||||
wechat: form.wechat.trim() || undefined,
|
||||
channel_type: channelType,
|
||||
webhook_url: form.webhook_url.trim(),
|
||||
};
|
||||
if (editingContact) {
|
||||
await alertApi.updateContact(editingContact.id, payload);
|
||||
@@ -71,12 +79,14 @@ export function ContactDialog({
|
||||
}
|
||||
};
|
||||
|
||||
const selectedChannel = CHANNEL_OPTIONS.find(c => c.value === channelType);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingContact ? '编辑报警联系人' : '添加报警联系人'}</DialogTitle>
|
||||
<DialogDescription>配置报警联系人信息(姓名必填,渠道选填)</DialogDescription>
|
||||
<DialogDescription>选择通知渠道并填入 Webhook 地址</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
@@ -87,53 +97,50 @@ export function ContactDialog({
|
||||
placeholder="如:张三"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>手机号</Label>
|
||||
<Input
|
||||
value={form.phone}
|
||||
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
||||
placeholder="如:13800138000"
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-2">
|
||||
<Label>手机号</Label>
|
||||
<Input
|
||||
value={form.phone}
|
||||
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
||||
placeholder="13800138000"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>邮箱</Label>
|
||||
<Input
|
||||
value={form.email}
|
||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||
placeholder="zhangsan@example.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>邮箱</Label>
|
||||
<Input
|
||||
value={form.email}
|
||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||
placeholder="如:zhangsan@example.com"
|
||||
/>
|
||||
<Label>渠道类型 *</Label>
|
||||
<Select value={channelType} onValueChange={setChannelType}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择渠道类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{CHANNEL_OPTIONS.map(opt => (
|
||||
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>钉钉(webhook URL)</Label>
|
||||
<Label>Webhook URL *</Label>
|
||||
<Input
|
||||
value={form.dingtalk}
|
||||
onChange={(e) => setForm({ ...form, dingtalk: e.target.value })}
|
||||
placeholder="https://oapi.dingtalk.com/robot/send?access_token=..."
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>飞书(webhook URL)</Label>
|
||||
<Input
|
||||
value={form.feishu}
|
||||
onChange={(e) => setForm({ ...form, feishu: e.target.value })}
|
||||
placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/..."
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>企业微信(webhook URL)</Label>
|
||||
<Input
|
||||
value={form.wechat}
|
||||
onChange={(e) => setForm({ ...form, wechat: e.target.value })}
|
||||
placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
|
||||
className="font-mono text-sm"
|
||||
value={form.webhook_url}
|
||||
onChange={(e) => setForm({ ...form, webhook_url: e.target.value })}
|
||||
placeholder={selectedChannel?.placeholder || 'https://...'}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>取消</Button>
|
||||
<Button onClick={handleSave} disabled={!form.name.trim() || saving}>
|
||||
<Button onClick={handleSave} disabled={!form.name.trim() || !form.webhook_url.trim() || saving}>
|
||||
{saving ? '保存中...' : editingContact ? '保存修改' : '添加联系人'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -60,9 +60,8 @@ export interface Contact {
|
||||
name: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
dingtalk?: string;
|
||||
feishu?: string;
|
||||
wechat?: string;
|
||||
channel_type?: string;
|
||||
webhook_url?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user