feat: add configurable infrastructure alerts
This commit is contained in:
@@ -12,16 +12,19 @@ import {
|
||||
Network,
|
||||
RefreshCw,
|
||||
Server,
|
||||
Settings2,
|
||||
ShieldAlert,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
adminApi,
|
||||
type InfrastructureAlert,
|
||||
type InfrastructureAlertSettings,
|
||||
type InfrastructureAlertThresholds,
|
||||
type InfrastructureMetricPoint,
|
||||
type InfrastructureMonitoringOverview,
|
||||
type InfrastructureMonitoringRange,
|
||||
} from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, Chart, Table, Tag, type TableColumn } from '@/components/ui';
|
||||
import { Breadcrumb, Button, Chart, Input, Modal, Table, Tag, type TableColumn } from '@/components/ui';
|
||||
import './AdminSystemMonitoringPage.css';
|
||||
|
||||
const RANGE_OPTIONS: Array<{ value: InfrastructureMonitoringRange; label: string }> = [
|
||||
@@ -166,6 +169,11 @@ export function AdminSystemMonitoringPage() {
|
||||
const [overview, setOverview] = useState<InfrastructureMonitoringOverview | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [settings, setSettings] = useState<InfrastructureAlertSettings | null>(null);
|
||||
const [draftThresholds, setDraftThresholds] = useState<InfrastructureAlertThresholds>({});
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [settingsError, setSettingsError] = useState('');
|
||||
const [savingSettings, setSavingSettings] = useState(false);
|
||||
const requestSequence = useRef(0);
|
||||
const pendingRequests = useRef(0);
|
||||
|
||||
@@ -189,8 +197,38 @@ export function AdminSystemMonitoringPage() {
|
||||
}
|
||||
}, [range]);
|
||||
|
||||
const loadSettings = useCallback(async () => {
|
||||
try {
|
||||
const result = await adminApi.getInfrastructureAlertThresholds();
|
||||
setSettings(result);
|
||||
setDraftThresholds(result.thresholds);
|
||||
setSettingsError('');
|
||||
} catch (reason) {
|
||||
setSettingsError(reason instanceof Error ? reason.message : '告警阈值加载失败');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const saveSettings = useCallback(async () => {
|
||||
if (!settings) return;
|
||||
setSavingSettings(true);
|
||||
setSettingsError('');
|
||||
try {
|
||||
const result = await adminApi.updateInfrastructureAlertThresholds({ configVersion: settings.configVersion, thresholds: draftThresholds });
|
||||
setSettings(result);
|
||||
setDraftThresholds(result.thresholds);
|
||||
setShowSettings(false);
|
||||
window.dispatchEvent(new Event('cmpp-infrastructure-alert-count-refresh'));
|
||||
await loadData(true);
|
||||
} catch (reason) {
|
||||
setSettingsError(reason instanceof Error ? reason.message : '告警阈值保存失败');
|
||||
} finally {
|
||||
setSavingSettings(false);
|
||||
}
|
||||
}, [draftThresholds, loadData, settings]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadData(true);
|
||||
void loadSettings();
|
||||
const intervalId = window.setInterval(() => {
|
||||
if (document.visibilityState === 'visible') void loadData();
|
||||
}, 30_000);
|
||||
@@ -203,7 +241,7 @@ export function AdminSystemMonitoringPage() {
|
||||
window.clearInterval(intervalId);
|
||||
document.removeEventListener('visibilitychange', handleVisibility);
|
||||
};
|
||||
}, [loadData]);
|
||||
}, [loadData, loadSettings]);
|
||||
|
||||
const status = STATUS_COPY[overview?.summary.overallStatus ?? 'unknown'];
|
||||
const cpuOption = useMemo(() => makeTrendOption({
|
||||
@@ -232,7 +270,7 @@ export function AdminSystemMonitoringPage() {
|
||||
<div>
|
||||
<Breadcrumb items={['系统管理', '系统监控']} />
|
||||
<div className="system-monitoring-title-row">
|
||||
<p>服务器资源、核心服务与活动告警</p>
|
||||
<p>服务器资源、核心服务与活动告警,数据由 Prometheus 采集与计算</p>
|
||||
<Tag tone={status.tone}>{status.label}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
@@ -283,29 +321,6 @@ export function AdminSystemMonitoringPage() {
|
||||
<article className="surface system-monitoring-metric"><div className="system-monitoring-metric__icon is-green"><Network size={19} /></div><div><span>网络吞吐</span><strong>{formatRate(totalNetworkRate(metrics?.networkReceiveBytesPerSecond, metrics?.networkTransmitBytesPerSecond))}</strong><small>接收 {formatRate(metrics?.networkReceiveBytesPerSecond ?? null)} · 发送 {formatRate(metrics?.networkTransmitBytesPerSecond ?? null)}</small></div></article>
|
||||
</div>
|
||||
|
||||
<section className="surface system-monitoring-service-metrics">
|
||||
<header>
|
||||
<div><Database size={18} /><strong>服务关键指标</strong></div>
|
||||
<span>固定低基数聚合,不含手机号、短信ID或SQL文本</span>
|
||||
</header>
|
||||
<div className="system-monitoring-service-metric-grid">
|
||||
{(overview?.serviceMetrics ?? []).map((group) => (
|
||||
<article key={group.key}>
|
||||
<div className="system-monitoring-service-metric-title">
|
||||
<strong>{group.name}</strong>
|
||||
<Tag tone={group.available ? 'success' : 'neutral'}>{group.available ? '已采集' : '待采集'}</Tag>
|
||||
</div>
|
||||
{group.metrics.length ? group.metrics.map((metric) => (
|
||||
<div className="system-monitoring-service-metric-row" key={metric.key}>
|
||||
<span>{metric.label}</span>
|
||||
<strong>{formatServiceMetric(metric.value, metric.unit)}</strong>
|
||||
</div>
|
||||
)) : <div className="system-monitoring-service-metric-empty">已监控服务可用性,待原生容量指标接入</div>}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="system-monitoring-main-grid">
|
||||
<div className="system-monitoring-chart-stack">
|
||||
<article className="surface system-monitoring-chart-card"><header><div><Cpu size={17} /><strong>CPU 趋势</strong></div><span>{formatPercent(metrics?.cpuUsagePercent ?? null)}</span></header>{overview?.trends.cpuUsagePercent.length ? <Chart height={230} option={cpuOption} /> : <EmptyChart />}</article>
|
||||
@@ -332,10 +347,40 @@ export function AdminSystemMonitoringPage() {
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<section className="surface system-monitoring-alerts">
|
||||
<section className="surface system-monitoring-service-metrics">
|
||||
<header>
|
||||
<div><Database size={18} /><strong>服务关键指标</strong></div>
|
||||
<div className="system-monitoring-service-actions"><span>固定低基数聚合,不含手机号、短信ID或SQL文本</span><Button icon={<Settings2 size={15} />} onClick={() => setShowSettings(true)} variant="ghost">告警阈值设置</Button></div>
|
||||
</header>
|
||||
<div className="system-monitoring-service-metric-grid">
|
||||
{(overview?.serviceMetrics ?? []).map((group) => (
|
||||
<article key={group.key}>
|
||||
<div className="system-monitoring-service-metric-title"><strong>{group.name}</strong><Tag tone={group.available ? 'success' : 'neutral'}>{group.available ? '已采集' : '待采集'}</Tag></div>
|
||||
{group.metrics.length ? group.metrics.map((metric) => <div className="system-monitoring-service-metric-row" key={metric.key}><span>{metric.label}</span><strong>{formatServiceMetric(metric.value, metric.unit)}</strong></div>) : <div className="system-monitoring-service-metric-empty">已监控服务可用性,待原生容量指标接入</div>}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="surface system-monitoring-alerts" id="active-alerts">
|
||||
<header><div><AlertTriangle size={18} /><strong>活动告警</strong><Tag tone={overview?.summary.activeAlerts ? 'warning' : 'success'}>{overview?.summary.activeAlerts ?? 0}</Tag></div><span><Clock3 size={14} /> 刷新于 {formatTime(overview?.collectedAt ?? null)}</span></header>
|
||||
<Table columns={alertColumns} data={overview?.alerts ?? []} emptyText={overview?.available ? '当前没有活动告警' : '监控不可用,无法读取活动告警'} pagination={false} rowKey="fingerprint" />
|
||||
</section>
|
||||
|
||||
<Modal footer={<><Button onClick={() => setShowSettings(false)} variant="ghost">取消</Button><Button disabled={savingSettings || !settings} onClick={() => void saveSettings()}>{savingSettings ? '验证并应用中' : '保存并应用'}</Button></>} onClose={() => setShowSettings(false)} open={showSettings} title="Prometheus 告警阈值设置">
|
||||
<div className="system-monitoring-threshold-dialog">
|
||||
<div className="system-monitoring-threshold-note"><ShieldAlert size={17} /><span>仅允许修改固定指标的数值阈值。保存时先由 promtool 校验,再原子替换规则并热加载 Prometheus。</span></div>
|
||||
{settings?.definitions.map((definition) => (
|
||||
<div className="system-monitoring-threshold-row" key={definition.key}>
|
||||
<div><strong>{definition.label}</strong><small>单位:{definition.unit}</small></div>
|
||||
<Input label="警告阈值" max={definition.max} min={definition.min} onChange={(event) => setDraftThresholds((current) => ({ ...current, [definition.key]: { ...current[definition.key], warning: Number(event.target.value) } }))} step={definition.step} type="number" value={draftThresholds[definition.key]?.warning ?? ''} />
|
||||
<Input label="严重阈值" max={definition.max} min={definition.min} onChange={(event) => setDraftThresholds((current) => ({ ...current, [definition.key]: { ...current[definition.key], critical: Number(event.target.value) } }))} step={definition.step} type="number" value={draftThresholds[definition.key]?.critical ?? ''} />
|
||||
</div>
|
||||
))}
|
||||
{settings?.applyStatus === 'failed' ? <div className="system-monitoring-unavailable"><AlertTriangle size={18} /><div><strong>上次应用失败</strong><span>{settings.lastError}</span></div></div> : null}
|
||||
{settingsError ? <div className="system-monitoring-unavailable"><AlertTriangle size={18} /><div><strong>阈值配置不可用</strong><span>{settingsError}</span></div></div> : null}
|
||||
</div>
|
||||
</Modal>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user