Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27a84de317 | |||
| 89b5a69325 | |||
| 23810cf180 | |||
| 16400b0359 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"thresholds": {
|
"thresholds": {
|
||||||
"cpu": 75,
|
"cpu": 75,
|
||||||
"memory": 80,
|
|
||||||
"disk": 85,
|
"disk": 85,
|
||||||
"interval": 60
|
"interval": 60,
|
||||||
|
"memory": 80,
|
||||||
|
"cpu_temp": 70
|
||||||
},
|
},
|
||||||
"web_settings": {
|
"web_settings": {
|
||||||
"external_ip": "121.40.164.32",
|
"external_ip": "121.40.164.32",
|
||||||
|
|||||||
30388
logs/app.log
30388
logs/app.log
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,9 @@ def register_email_routes(app):
|
|||||||
def api_email_send():
|
def api_email_send():
|
||||||
"""发送邮件通知"""
|
"""发送邮件通知"""
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
log_message(f"📨 收到邮件请求: {data}")
|
||||||
if not data:
|
if not data:
|
||||||
|
log_message(f"❌ 无效数据: content-type={request.content_type}")
|
||||||
return jsonify({'error': '无效数据'}), 400
|
return jsonify({'error': '无效数据'}), 400
|
||||||
|
|
||||||
to_email = data.get('to')
|
to_email = data.get('to')
|
||||||
@@ -26,33 +28,40 @@ def register_email_routes(app):
|
|||||||
body = data.get('body')
|
body = data.get('body')
|
||||||
|
|
||||||
if not to_email or not subject or not body:
|
if not to_email or not subject or not body:
|
||||||
|
log_message(f"❌ 缺少参数: to={to_email}, subject={subject}, body_len={len(body) if body else 0}")
|
||||||
return jsonify({'error': '缺少必要参数'}), 400
|
return jsonify({'error': '缺少必要参数'}), 400
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# SMTP配置 (从环境变量或默认值)
|
# SMTP配置 (从环境变量或默认值)
|
||||||
smtp_host = os.environ.get('SMTP_HOST', 'mail.tphai.com')
|
smtp_host = os.environ.get('SMTP_HOST', 'mail.tphai.com')
|
||||||
smtp_port = int(os.environ.get('SMTP_PORT', 587))
|
smtp_port = int(os.environ.get('SMTP_PORT', 587))
|
||||||
smtp_user = os.environ.get('SMTP_USER', 'favor@tphai.com')
|
smtp_user = os.environ.get('SMTP_USER', 'hz4th_coder@tphai.com')
|
||||||
smtp_pass = os.environ.get('SMTP_PASS', 'favor@!')
|
smtp_pass = os.environ.get('SMTP_PASS', 'hz4th_coder@!')
|
||||||
|
|
||||||
# 创建邮件
|
log_message(f"📧 准备发送邮件: to={to_email}, subject={subject}, user={smtp_user}")
|
||||||
|
|
||||||
|
# 创建邮件(完全对齐 send_email.py 的方式)
|
||||||
|
from email.utils import formataddr, formatdate
|
||||||
msg = MIMEMultipart()
|
msg = MIMEMultipart()
|
||||||
msg['From'] = smtp_user
|
msg['From'] = formataddr(('黄庄4号程序员', smtp_user))
|
||||||
msg['To'] = to_email
|
msg['To'] = to_email
|
||||||
msg['Subject'] = subject
|
msg['Subject'] = subject
|
||||||
|
msg['Date'] = formatdate(localtime=True)
|
||||||
|
|
||||||
# 添加正文
|
# 添加正文
|
||||||
msg.attach(MIMEText(body, 'plain', 'utf-8'))
|
msg.attach(MIMEText(body, 'plain', 'utf-8'))
|
||||||
|
|
||||||
# 发送邮件
|
# 发送邮件(无SSL模式,完全对齐 send_email.py)
|
||||||
server = smtplib.SMTP(smtp_host, smtp_port)
|
server = smtplib.SMTP(smtp_host, smtp_port)
|
||||||
|
server.ehlo()
|
||||||
server.login(smtp_user, smtp_pass)
|
server.login(smtp_user, smtp_pass)
|
||||||
server.send_message(msg)
|
# 使用 sendmail 而非 send_message,与已验证成功的脚本一致
|
||||||
|
server.sendmail(smtp_user, [to_email], msg.as_string())
|
||||||
server.quit()
|
server.quit()
|
||||||
|
|
||||||
log_message(f"邮件发送成功: {subject} -> {to_email}")
|
log_message(f"✅ 邮件发送成功: {subject} -> {to_email}")
|
||||||
|
|
||||||
return jsonify({'message': '邮件发送成功'})
|
return jsonify({'message': '邮件发送成功'})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_message(f"邮件发送失败: {e}")
|
log_message(f"❌ 邮件发送失败: {e}")
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
@@ -23,6 +23,10 @@ def get_html_template():
|
|||||||
template_content = template_content.replace('v3.4.0', APP_VERSION)
|
template_content = template_content.replace('v3.4.0', APP_VERSION)
|
||||||
template_content = template_content.replace('v3.4.1', APP_VERSION) # 预留未来版本
|
template_content = template_content.replace('v3.4.1', APP_VERSION) # 预留未来版本
|
||||||
|
|
||||||
|
# 替换默认IP地址
|
||||||
|
template_content = template_content.replace("'192.168.2.17'", "'121.40.164.32'")
|
||||||
|
template_content = template_content.replace('"192.168.2.17"', '"121.40.164.32"')
|
||||||
|
|
||||||
return template_content
|
return template_content
|
||||||
except:
|
except:
|
||||||
# 如果文件不存在,返回基本模板
|
# 如果文件不存在,返回基本模板
|
||||||
|
|||||||
@@ -698,17 +698,32 @@
|
|||||||
<i class="ri-hard-drive-2-line text-orange-400"></i>
|
<i class="ri-hard-drive-2-line text-orange-400"></i>
|
||||||
<span class="text-gray-200 text-sm">磁盘使用率</span>
|
<span class="text-gray-200 text-sm">磁盘使用率</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="flex items-center gap-2 bg-gray-700/50 px-3 py-2 rounded-lg cursor-pointer hover:bg-gray-700 col-span-2">
|
||||||
|
<input type="checkbox" value="service_down" class="email-rule-resource w-4 h-4" onchange="onResourceChange()">
|
||||||
|
<i class="ri-shut-down-line text-yellow-400"></i>
|
||||||
|
<span class="text-gray-200 text-sm">服务下线监控</span>
|
||||||
|
<span class="text-gray-500 text-xs">检测指定服务长期不在线</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-500 text-xs mt-1">勾选多项可随机组合监控,任一资源超标即触发通知</p>
|
<p class="text-gray-500 text-xs mt-1">勾选多项可随机组合监控,任一资源超标即触发通知</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 服务选择器(仅当勾选「服务下线监控」时显示) -->
|
||||||
|
<div class="mb-3" id="serviceDownSelector" style="display:none;">
|
||||||
|
<label class="block text-gray-400 text-sm mb-1">监控服务 *</label>
|
||||||
|
<select id="emailRuleServiceId" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" onchange="onServiceDownChange()">
|
||||||
|
<option value="">-- 请选择要监控的服务 --</option>
|
||||||
|
</select>
|
||||||
|
<p class="text-gray-500 text-xs mt-1">选择需要监控下线状态的服务</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="block text-gray-400 text-sm mb-1">触发阈值 *</label>
|
<label class="block text-gray-400 text-sm mb-1">触发阈值 *</label>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<input type="number" id="emailRuleThreshold" required min="1" class="w-20 bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" value="80">
|
<input type="number" id="emailRuleThreshold" required min="1" class="w-20 bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" value="80">
|
||||||
<span class="text-gray-400" id="emailRuleThresholdUnit">% / °C</span>
|
<span class="text-gray-400" id="emailRuleThresholdUnit">% / °C</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-500 text-xs mt-1">对不同资源类型使用统一阈值(如CPU用%则填百分比,温度用°C则填温度值)</p>
|
<p class="text-gray-500 text-xs mt-1" id="emailRuleThresholdHint">对不同资源类型使用统一阈值(如CPU用%则填百分比,温度用°C则填温度值)</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -1245,6 +1260,8 @@
|
|||||||
fetch('/api/system/stats').then(r => r.json()).then(sysData => {
|
fetch('/api/system/stats').then(r => r.json()).then(sysData => {
|
||||||
if (sysData.available) checkThresholds(sysData);
|
if (sysData.available) checkThresholds(sysData);
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
// 同时检查服务下线邮件规则
|
||||||
|
checkEmailRules(null);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载失败:', e);
|
console.error('加载失败:', e);
|
||||||
@@ -1561,7 +1578,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceNames = { 'cpu': 'CPU', 'cpu_temp': 'CPU温度', 'memory': '内存', 'disk': '磁盘' };
|
const resourceNames = { 'cpu': 'CPU', 'cpu_temp': 'CPU温度', 'memory': '内存', 'disk': '磁盘', 'service_down': '服务下线' };
|
||||||
const resourceUnits = { 'cpu': '%', 'cpu_temp': '°C', 'memory': '%', 'disk': '%' };
|
const resourceUnits = { 'cpu': '%', 'cpu_temp': '°C', 'memory': '%', 'disk': '%' };
|
||||||
|
|
||||||
list.innerHTML = rules.map(rule => {
|
list.innerHTML = rules.map(rule => {
|
||||||
@@ -1575,14 +1592,19 @@
|
|||||||
|
|
||||||
// 显示资源列表(支持多资源和旧版单资源兼容)
|
// 显示资源列表(支持多资源和旧版单资源兼容)
|
||||||
const resources = rule.resources || (rule.resource ? [rule.resource] : []);
|
const resources = rule.resources || (rule.resource ? [rule.resource] : []);
|
||||||
const resourceDisplay = resources.map(r => resourceNames[r] || r).join('+');
|
let resourceDisplay;
|
||||||
|
if (resources.includes('service_down') && rule.serviceName) {
|
||||||
|
resourceDisplay = `🔴 ${rule.serviceName} 下线 ≥ ${rule.threshold}分钟`;
|
||||||
|
} else {
|
||||||
|
resourceDisplay = resources.map(r => resourceNames[r] || r).join('+') + ' ≥ ' + rule.threshold;
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="flex items-center justify-between bg-gray-700/50 px-3 py-2 rounded-lg">
|
<div class="flex items-center justify-between bg-gray-700/50 px-3 py-2 rounded-lg">
|
||||||
<div class="flex items-center gap-3 flex-wrap">
|
<div class="flex items-center gap-3 flex-wrap">
|
||||||
<span class="px-2 py-0.5 rounded text-xs ${rule.enabled ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'}">${rule.enabled ? '启用' : '禁用'}</span>
|
<span class="px-2 py-0.5 rounded text-xs ${rule.enabled ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'}">${rule.enabled ? '启用' : '禁用'}</span>
|
||||||
<span class="text-gray-200">${rule.name}</span>
|
<span class="text-gray-200">${rule.name}</span>
|
||||||
<span class="text-gray-400 text-xs">${resourceDisplay} ≥ ${rule.threshold}</span>
|
<span class="text-gray-400 text-xs">${resourceDisplay}</span>
|
||||||
<span class="text-blue-400/70 text-xs"><i class="ri-flashlight-line"></i> ${triggerDisplay}</span>
|
<span class="text-blue-400/70 text-xs"><i class="ri-flashlight-line"></i> ${triggerDisplay}</span>
|
||||||
<span class="text-gray-400 text-xs">→ ${rule.email}</span>
|
<span class="text-gray-400 text-xs">→ ${rule.email}</span>
|
||||||
${silentInfo ? `<span class="text-yellow-400/70 text-xs"><i class="ri-moon-line"></i> ${silentInfo}</span>` : ''}
|
${silentInfo ? `<span class="text-yellow-400/70 text-xs"><i class="ri-moon-line"></i> ${silentInfo}</span>` : ''}
|
||||||
@@ -1602,9 +1624,13 @@
|
|||||||
document.getElementById('emailRuleName').value = '';
|
document.getElementById('emailRuleName').value = '';
|
||||||
// 重置资源复选框
|
// 重置资源复选框
|
||||||
document.querySelectorAll('.email-rule-resource').forEach(cb => cb.checked = false);
|
document.querySelectorAll('.email-rule-resource').forEach(cb => cb.checked = false);
|
||||||
|
document.getElementById('serviceDownSelector').style.display = 'none';
|
||||||
|
document.getElementById('emailRuleServiceId').value = '';
|
||||||
document.getElementById('emailRuleThreshold').value = 80;
|
document.getElementById('emailRuleThreshold').value = 80;
|
||||||
document.getElementById('emailRuleThresholdUnit').textContent = '% / °C';
|
document.getElementById('emailRuleThresholdUnit').textContent = '% / °C';
|
||||||
|
document.getElementById('emailRuleThresholdHint').textContent = '对不同资源类型使用统一阈值';
|
||||||
document.getElementById('emailRuleTriggerType').value = 'single';
|
document.getElementById('emailRuleTriggerType').value = 'single';
|
||||||
|
document.getElementById('emailRuleTriggerType').disabled = false;
|
||||||
document.getElementById('emailRuleTriggerParam').value = 3;
|
document.getElementById('emailRuleTriggerParam').value = 3;
|
||||||
document.getElementById('triggerParamsDiv').style.display = 'none';
|
document.getElementById('triggerParamsDiv').style.display = 'none';
|
||||||
document.getElementById('emailRuleAddress').value = '';
|
document.getElementById('emailRuleAddress').value = '';
|
||||||
@@ -1629,24 +1655,73 @@
|
|||||||
const selected = getSelectedResources();
|
const selected = getSelectedResources();
|
||||||
const unitEl = document.getElementById('emailRuleThresholdUnit');
|
const unitEl = document.getElementById('emailRuleThresholdUnit');
|
||||||
const thresholdEl = document.getElementById('emailRuleThreshold');
|
const thresholdEl = document.getElementById('emailRuleThreshold');
|
||||||
|
const hintEl = document.getElementById('emailRuleThresholdHint');
|
||||||
|
const serviceSelector = document.getElementById('serviceDownSelector');
|
||||||
|
const triggerTypeEl = document.getElementById('emailRuleTriggerType');
|
||||||
|
const triggerParamsDiv = document.getElementById('triggerParamsDiv');
|
||||||
|
|
||||||
if (selected.length === 0) {
|
const hasServiceDown = selected.includes('service_down');
|
||||||
unitEl.textContent = '% / °C';
|
|
||||||
} else if (selected.length === 1 && selected[0] === 'cpu_temp') {
|
// 显示/隐藏服务选择器
|
||||||
unitEl.textContent = '°C';
|
if (hasServiceDown) {
|
||||||
if (parseInt(thresholdEl.value) > 120) thresholdEl.value = 80;
|
serviceSelector.style.display = 'block';
|
||||||
thresholdEl.max = 120;
|
populateServiceSelector();
|
||||||
thresholdEl.min = 30;
|
// 服务下线使用分钟作为阈值单位
|
||||||
} else {
|
unitEl.textContent = '分钟';
|
||||||
unitEl.textContent = '%';
|
thresholdEl.value = 5;
|
||||||
thresholdEl.max = 100;
|
thresholdEl.max = 1440;
|
||||||
thresholdEl.min = 1;
|
thresholdEl.min = 1;
|
||||||
if (selected.includes('cpu_temp')) {
|
hintEl.textContent = '服务连续离线超过此分钟数即触发通知';
|
||||||
|
// 服务下线固定为duration类型
|
||||||
|
if (triggerTypeEl) {
|
||||||
|
triggerTypeEl.value = 'duration';
|
||||||
|
triggerTypeEl.disabled = true;
|
||||||
|
}
|
||||||
|
if (triggerParamsDiv) triggerParamsDiv.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
serviceSelector.style.display = 'none';
|
||||||
|
if (triggerTypeEl) triggerTypeEl.disabled = false;
|
||||||
|
updateTriggerParams();
|
||||||
|
|
||||||
|
if (selected.length === 0) {
|
||||||
unitEl.textContent = '% / °C';
|
unitEl.textContent = '% / °C';
|
||||||
|
hintEl.textContent = '对不同资源类型使用统一阈值';
|
||||||
|
} else if (selected.length === 1 && selected[0] === 'cpu_temp') {
|
||||||
|
unitEl.textContent = '°C';
|
||||||
|
if (parseInt(thresholdEl.value) > 120) thresholdEl.value = 80;
|
||||||
|
thresholdEl.max = 120;
|
||||||
|
thresholdEl.min = 30;
|
||||||
|
hintEl.textContent = 'CPU温度超过此值即触发通知';
|
||||||
|
} else {
|
||||||
|
unitEl.textContent = '%';
|
||||||
|
thresholdEl.max = 100;
|
||||||
|
thresholdEl.min = 1;
|
||||||
|
hintEl.textContent = '对不同资源类型使用统一阈值';
|
||||||
|
if (selected.includes('cpu_temp')) {
|
||||||
|
unitEl.textContent = '% / °C';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function populateServiceSelector() {
|
||||||
|
const sel = document.getElementById('emailRuleServiceId');
|
||||||
|
const currentVal = sel.value;
|
||||||
|
// 收集所有web类型服务
|
||||||
|
const webServices = projects.filter(p => p.type === 'web');
|
||||||
|
let options = '<option value="">-- 请选择要监控的服务 --</option>';
|
||||||
|
webServices.forEach(p => {
|
||||||
|
const portsStr = p.ports ? p.ports.join(',') : '';
|
||||||
|
const selected = p.id === currentVal ? ' selected' : '';
|
||||||
|
options += `<option value="${p.id}"${selected}>${p.name} (端口: ${portsStr})</option>`;
|
||||||
|
});
|
||||||
|
sel.innerHTML = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onServiceDownChange() {
|
||||||
|
// 将来可以在这里做联动
|
||||||
|
}
|
||||||
|
|
||||||
function updateTriggerParams() {
|
function updateTriggerParams() {
|
||||||
const triggerType = document.getElementById('emailRuleTriggerType').value;
|
const triggerType = document.getElementById('emailRuleTriggerType').value;
|
||||||
const paramsDiv = document.getElementById('triggerParamsDiv');
|
const paramsDiv = document.getElementById('triggerParamsDiv');
|
||||||
@@ -1704,6 +1779,10 @@
|
|||||||
if (cb) cb.checked = true;
|
if (cb) cb.checked = true;
|
||||||
});
|
});
|
||||||
onResourceChange();
|
onResourceChange();
|
||||||
|
// 服务下线相关
|
||||||
|
if (rule.serviceId) {
|
||||||
|
document.getElementById('emailRuleServiceId').value = rule.serviceId;
|
||||||
|
}
|
||||||
document.getElementById('emailRuleThreshold').value = rule.threshold;
|
document.getElementById('emailRuleThreshold').value = rule.threshold;
|
||||||
document.getElementById('emailRuleTriggerType').value = rule.triggerType || 'single';
|
document.getElementById('emailRuleTriggerType').value = rule.triggerType || 'single';
|
||||||
document.getElementById('emailRuleTriggerParam').value = rule.triggerParam || 3;
|
document.getElementById('emailRuleTriggerParam').value = rule.triggerParam || 3;
|
||||||
@@ -1732,6 +1811,7 @@
|
|||||||
const silentEnd = document.getElementById('emailRuleSilentEnd').value;
|
const silentEnd = document.getElementById('emailRuleSilentEnd').value;
|
||||||
const silentEnabled = document.getElementById('emailRuleSilentEnabled').checked;
|
const silentEnabled = document.getElementById('emailRuleSilentEnabled').checked;
|
||||||
const enabled = document.getElementById('emailRuleEnabled').checked;
|
const enabled = document.getElementById('emailRuleEnabled').checked;
|
||||||
|
const serviceId = document.getElementById('emailRuleServiceId').value;
|
||||||
|
|
||||||
if (!name || !email) {
|
if (!name || !email) {
|
||||||
alert('请填写完整信息');
|
alert('请填写完整信息');
|
||||||
@@ -1743,23 +1823,32 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 服务下线必须选择具体服务
|
||||||
|
if (resources.includes('service_down') && !serviceId) {
|
||||||
|
alert('请选择要监控的服务');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rules = getEmailRules();
|
const rules = getEmailRules();
|
||||||
|
|
||||||
if (ruleId) {
|
if (ruleId) {
|
||||||
// 编辑
|
// 编辑
|
||||||
const idx = rules.findIndex(r => r.id === ruleId);
|
const idx = rules.findIndex(r => r.id === ruleId);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
rules[idx] = { ...rules[idx], name, resources, threshold, triggerType, triggerParam, email, interval, silentStart, silentEnd, silentEnabled, enabled };
|
rules[idx] = { ...rules[idx], name, resources, threshold, triggerType, triggerParam, email, interval, silentStart, silentEnd, silentEnabled, enabled, serviceId };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增
|
// 新增
|
||||||
|
const svc = projects.find(p => p.id === serviceId);
|
||||||
rules.push({
|
rules.push({
|
||||||
id: 'rule_' + Date.now(),
|
id: 'rule_' + Date.now(),
|
||||||
name, resources, threshold, triggerType, triggerParam, email, interval, silentStart, silentEnd, silentEnabled, enabled,
|
name, resources, threshold, triggerType, triggerParam, email, interval, silentStart, silentEnd, silentEnabled, enabled,
|
||||||
|
serviceId, serviceName: svc ? svc.name : '',
|
||||||
|
offlineSince: null, // 服务下线开始时间
|
||||||
lastSent: 0,
|
lastSent: 0,
|
||||||
exceedCount: 0, // 连续超过次数
|
exceedCount: 0,
|
||||||
exceedDuration: 0, // 累计超过时间
|
exceedDuration: 0,
|
||||||
recentValues: [] // 最近采样值
|
recentValues: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1818,6 +1907,33 @@
|
|||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
let value;
|
let value;
|
||||||
|
|
||||||
|
// 服务下线监控 - 特殊处理(不需要data参数)
|
||||||
|
if (resource === 'service_down') {
|
||||||
|
const svcId = rule.serviceId;
|
||||||
|
if (!svcId) continue;
|
||||||
|
const svc = projects.find(p => p.id === svcId);
|
||||||
|
if (!svc) continue;
|
||||||
|
const isOnline = svc.status?.status === 'running';
|
||||||
|
|
||||||
|
if (isOnline) {
|
||||||
|
rule.offlineSince = null;
|
||||||
|
} else {
|
||||||
|
if (!rule.offlineSince) {
|
||||||
|
rule.offlineSince = now;
|
||||||
|
}
|
||||||
|
const offlineMinutes = (now - rule.offlineSince) / 60000;
|
||||||
|
if (offlineMinutes >= rule.threshold) {
|
||||||
|
const svcName = svc.name || rule.serviceName || svcId;
|
||||||
|
exceededResources.push(`🔴 ${svcName} 已离线 ${Math.round(offlineMinutes)} 分钟 (阈值: ${rule.threshold} 分钟)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 系统资源监控需要 data 参数
|
||||||
|
if (!data) continue;
|
||||||
|
|
||||||
if (resource === 'cpu_temp') {
|
if (resource === 'cpu_temp') {
|
||||||
// CPU温度取两个Package中较高的那个
|
// CPU温度取两个Package中较高的那个
|
||||||
const t0 = data.cpu?.temp_package_0;
|
const t0 = data.cpu?.temp_package_0;
|
||||||
|
|||||||
Reference in New Issue
Block a user