4a1d949e41
完整 Web 管理平台,涵盖: P0 生产加固: - 会话超时(空闲+绝对) + CSRF 防护 + 登录失败限流 - HTTPS/TLS 证书管理(自签生成/上传/删除/过期提醒) - SSL Bump (HTTPS 透明代理) 可视化配置 - 配置文件 diff 预览 + 二次确认才写入 - 服务控制二次确认 + 操作期间按钮锁定 P1 运维能力: - 日志轮转管理(logrotate) + 日志状态监控 - 告警规则(5xx/命中率/磁盘/客户端流量) - 多 Squid 实例管理 + 一键切换 - squidclient mgr 实时性能指标 - 流量异常检测(突增/大文件/高频小包/新客户端) P2 日志分析增强: - 日志持久化到 SQLite + 历史时间范围查询 - 导出 CSV/JSON(日志/KPI/异常/审计) - GeoIP 地图(Leaflet + ip-api.com 离线可选 GeoLite2) - 每客户端 24h 趋势图 - 自定义 Squid logformat 解析器 P3 体验锦上添花: - 暗/亮主题切换(cookie 持久化) - WebSSH 终端(xterm.js + paramiko) - 完整审计日志 + 用户管理 技术栈: Flask 3.0 + SQLAlchemy 2.0 + SQLite + Chart.js + Leaflet 总代码量: ~16500 行 (15 Python 模块 + 34 模板 + CSS) 路由数: 73
224 lines
11 KiB
HTML
224 lines
11 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}告警规则 - Squid Manager{% endblock %}
|
||
{% block content %}
|
||
<div class="page-header">
|
||
<h1>🚨 告警规则</h1>
|
||
<div class="page-actions">
|
||
<form method="post" action="{{ url_for('alerts_evaluate') }}" style="display:inline;">
|
||
<input type="hidden" name="_csrf" value="{{ csrf_token() }}">
|
||
<button type="submit" class="btn btn-primary btn-small">⚡ 立即评估</button>
|
||
</form>
|
||
<a href="{{ url_for('alerts_history') }}" class="btn btn-secondary btn-small">📜 触发历史</a>
|
||
<button onclick="document.getElementById('add-form').style.display='block'" class="btn btn-primary btn-small">➕ 添加规则</button>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="meta-text">
|
||
基于 access.log 解析指标与磁盘使用情况,按阈值触发告警。
|
||
同一规则在 cooldown 时间窗内只记录一次,避免告警风暴。
|
||
严重度: <span class="badge badge-blue">info</span>
|
||
<span class="badge badge-yellow">warning</span>
|
||
<span class="badge badge-red">critical</span>
|
||
</p>
|
||
|
||
{# active alerts banner #}
|
||
{% set active = [] %}
|
||
{% for status in current_status %}
|
||
{% if status.triggered %}{% set _ = active.append(status) %}{% endif %}
|
||
{% endfor %}
|
||
{% if active %}
|
||
<div class="card" style="border-left: 4px solid #ef4444;">
|
||
<h3 class="card-title">⚠️ 当前活跃告警 ({{ active | length }})</h3>
|
||
<ul class="rule-messages">
|
||
{% for s in active %}
|
||
<li>
|
||
<span class="badge badge-{% if s.severity == 'critical' %}red{% elif s.severity == 'warning' %}yellow{% else %}blue{% endif %}">{{ s.severity }}</span>
|
||
{{ s.message }}
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# add rule form #}
|
||
<div class="card" id="add-form" style="display:none;">
|
||
<h3 class="card-title">添加新规则</h3>
|
||
<form method="post" action="{{ url_for('alerts_add') }}" class="inline-form">
|
||
<input type="hidden" name="_csrf" value="{{ csrf_token() }}">
|
||
<div class="filter-grid">
|
||
<div class="form-group" style="grid-column: span 2;">
|
||
<label>规则名称 <span class="hint">(简短描述,便于审计追溯)</span></label>
|
||
<input type="text" name="name" class="form-input" required placeholder="例如: 高 5xx 错误率">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>指标</label>
|
||
<select name="metric" class="form-input" required>
|
||
{% for m, desc in metrics %}
|
||
<option value="{{ m }}">{{ m }} — {{ desc }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>条件</label>
|
||
<select name="operator" class="form-input" required>
|
||
{% for op, sym in operators %}
|
||
<option value="{{ op }}">{{ sym }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>阈值</label>
|
||
<input type="number" name="threshold" class="form-input" step="0.0001" required value="0.05">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>窗口 (分钟)</label>
|
||
<input type="number" name="window_minutes" class="form-input" min="1" max="1440" value="5">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>冷却 (分钟)</label>
|
||
<input type="number" name="cooldown_minutes" class="form-input" min="1" max="10080" value="30">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>严重度</label>
|
||
<select name="severity" class="form-input">
|
||
<option value="info">info</option>
|
||
<option value="warning" selected>warning</option>
|
||
<option value="critical">critical</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>启用</label>
|
||
<select name="enabled" class="form-input">
|
||
<option value="1" selected>是</option>
|
||
<option value="0">否</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="grid-column: span 2;">
|
||
<label>Webhook URL <span class="hint">(可选。留空仅记审计,非空则 POST JSON)</span></label>
|
||
<input type="text" name="notify_webhook" class="form-input" placeholder="https://hooks.example.com/squid">
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">添加</button>
|
||
<button type="button" onclick="document.getElementById('add-form').style.display='none'" class="btn btn-secondary">取消</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 class="card-title">规则列表</h3>
|
||
<p class="card-desc">共 {{ rules | length }} 条规则。当前值是最近一次评估的快照。
|
||
勾选/取消勾选"启用"列后点击"💾 保存所有规则"即可生效。</p>
|
||
<form method="post" action="{{ url_for('alerts_save') }}">
|
||
<input type="hidden" name="_csrf" value="{{ csrf_token() }}">
|
||
<div class="table-scroll">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:40px;">#</th>
|
||
<th>名称</th>
|
||
<th style="width:130px;">指标</th>
|
||
<th style="width:80px;">条件</th>
|
||
<th style="width:110px;">阈值</th>
|
||
<th style="width:70px;">窗口</th>
|
||
<th style="width:80px;">冷却</th>
|
||
<th style="width:90px;">严重度</th>
|
||
<th style="width:160px;">当前值</th>
|
||
<th style="width:140px;">状态</th>
|
||
<th style="width:80px;">启用</th>
|
||
<th style="width:80px;">删除</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for r in rules %}
|
||
{% set s = status_map.get(loop.index0) %}
|
||
<tr>
|
||
<td>{{ loop.index }}<input type="hidden" name="name[]" value="{{ r.name }}"></td>
|
||
<td>
|
||
{{ r.name }}
|
||
{% if r.notify_webhook %}<br><span class="meta-text">🔗 webhook</span>{% endif %}
|
||
</td>
|
||
<td>
|
||
<select name="metric[]" class="form-input">
|
||
{% for m, desc in metrics %}
|
||
<option value="{{ m }}" {% if r.metric == m %}selected{% endif %}>{{ m }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<select name="operator[]" class="form-input">
|
||
{% for op, sym in operators %}
|
||
<option value="{{ op }}" {% if r.operator == op %}selected{% endif %}>{{ sym }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</td>
|
||
<td><input type="number" step="0.0001" name="threshold[]" value="{{ r.threshold }}" class="form-input"></td>
|
||
<td><input type="number" name="window_minutes[]" value="{{ r.window_minutes }}" min="1" class="form-input"></td>
|
||
<td><input type="number" name="cooldown_minutes[]" value="{{ r.cooldown_minutes }}" min="1" class="form-input"></td>
|
||
<td>
|
||
<select name="severity[]" class="form-input">
|
||
{% for sv in severities %}
|
||
<option value="{{ sv }}" {% if r.severity == sv %}selected{% endif %}>{{ sv }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</td>
|
||
<td>
|
||
{% if s %}
|
||
<code>{{ '%.4f' % s.value }}</code>
|
||
{% else %}
|
||
<span class="meta-text">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if not r.enabled %}
|
||
<span class="badge badge-gray">已禁用</span>
|
||
{% elif s %}
|
||
{% if s.triggered %}
|
||
<span class="badge badge-red">触发</span>
|
||
{% elif s.in_cooldown %}
|
||
<span class="badge badge-yellow">冷却</span>
|
||
{% else %}
|
||
<span class="badge badge-green">正常</span>
|
||
{% endif %}
|
||
{% else %}
|
||
<span class="badge badge-gray">未评估</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<input type="checkbox" name="enabled[]" value="1" {% if r.enabled %}checked{% endif %}>
|
||
</td>
|
||
<td>
|
||
<button type="button" class="btn btn-danger btn-tiny" onclick="this.closest('tr').remove()">删除</button>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% if not rules %}
|
||
<tr><td colspan="12" class="empty-row">暂无规则。点击上方"添加规则"按钮开始,或点击 "立即评估" 先运行默认规则。</td></tr>
|
||
{% endif %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">💾 保存所有规则</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 class="card-title">📖 规则说明</h3>
|
||
<ul>
|
||
<li><strong>触发逻辑</strong>: 计算当前指标值,与阈值按指定运算符 ( > < ≥ ≤ ) 比较,满足则记录并 (可选) POST webhook。</li>
|
||
<li><strong>冷却 (cooldown)</strong>: 同一规则两次触发之间的最小间隔,避免告警风暴。</li>
|
||
<li><strong>窗口 (window_minutes)</strong>: 用于按窗口归一化的指标 (如 <code>client_bytes_per_min</code>);其他指标仅用于备注。</li>
|
||
<li><strong>评估方式</strong>: 点击"立即评估"使用当前 access.log 快照立即跑一遍;要持续监控,配合系统的 cron 调用 <code>/alerts/evaluate</code> 接口即可。</li>
|
||
<li><strong>触发历史</strong>: 持久化记录会写入 <code>audit_log</code>,可在"<a href="{{ url_for('alerts_history') }}">触发历史</a>"查看 (取最近 200 条)。</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<style>
|
||
.rule-messages { list-style: none; padding: 0; margin: 0; }
|
||
.rule-messages li { padding: 6px 0; border-bottom: 1px dashed rgba(148, 163, 184, 0.18); }
|
||
.rule-messages li:last-child { border-bottom: none; }
|
||
</style>
|
||
{% endblock %}
|