Files
squid-manager/diff_tool.py
T
Hermes Agent 4a1d949e41 feat: 完整 Squid Web Manager 平台 - P0-P3 全功能
完整 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
2026-07-15 10:19:21 +08:00

123 lines
4.7 KiB
Python

"""Unified diff generator for squid.conf changes.
Provides a small, pure-Python unified-diff renderer. We avoid Python's
``difflib.unified_diff`` because we want tight control over hunk sizing
and color classes used by the UI.
"""
from __future__ import annotations
import difflib
from typing import Iterable
def unified_diff(old: str, new: str, context: int = 3, lineterm: str = "\n") -> list[dict]:
"""Return a list of diff lines as dicts: {kind, text, old_lineno, new_lineno}.
kind: 'equal' | 'add' | 'del' | 'hunk' | 'meta'
"""
old_lines = old.splitlines(keepends=False)
new_lines = new.splitlines(keepends=False)
sm = difflib.SequenceMatcher(a=old_lines, b=new_lines, autojunk=False)
out: list[dict] = []
old_no = 0
new_no = 0
def push_meta(text: str):
out.append({"kind": "meta", "text": text, "old_lineno": None, "new_lineno": None})
push_meta("--- 当前配置 (existing)")
push_meta("+++ 新配置 (proposed)")
for tag, i1, i2, j1, j2 in sm.get_opcodes():
if tag == "equal":
# show context lines (limit to first/last of long equal runs)
block = old_lines[i1:i2]
if len(block) <= context * 2 + 1:
for k, ln in enumerate(block):
old_no += 1
new_no += 1
out.append({
"kind": "equal", "text": ln,
"old_lineno": old_no, "new_lineno": new_no,
})
else:
# head
for k in range(context):
old_no += 1; new_no += 1
out.append({
"kind": "equal", "text": old_lines[i1 + k],
"old_lineno": old_no, "new_lineno": new_no,
})
out.append({
"kind": "meta", "text": f"... ({i2 - i1 - 2 * context} equal lines skipped) ...",
"old_lineno": None, "new_lineno": None,
})
old_no += (i2 - i1 - 2 * context)
new_no += (i2 - i1 - 2 * context)
# tail
for k in range(i2 - context, i2):
old_no += 1; new_no += 1
out.append({
"kind": "equal", "text": old_lines[k],
"old_lineno": old_no, "new_lineno": new_no,
})
elif tag == "replace":
for ln in old_lines[i1:i2]:
old_no += 1
out.append({
"kind": "del", "text": ln,
"old_lineno": old_no, "new_lineno": None,
})
for ln in new_lines[j1:j2]:
new_no += 1
out.append({
"kind": "add", "text": ln,
"old_lineno": None, "new_lineno": new_no,
})
elif tag == "delete":
for ln in old_lines[i1:i2]:
old_no += 1
out.append({
"kind": "del", "text": ln,
"old_lineno": old_no, "new_lineno": None,
})
elif tag == "insert":
for ln in new_lines[j1:j2]:
new_no += 1
out.append({
"kind": "add", "text": ln,
"old_lineno": None, "new_lineno": new_no,
})
return out
def summarize(diff: list[dict]) -> dict:
"""Return {adds, dels, total_changed}."""
adds = sum(1 for d in diff if d["kind"] == "add")
dels = sum(1 for d in diff if d["kind"] == "del")
return {"adds": adds, "dels": dels, "total_changed": adds + dels}
def render_html(diff: list[dict]) -> str:
"""Render diff to HTML with classes used by static/style.css."""
parts = ['<div class="diff-container">']
for d in diff:
kind = d["kind"]
text = d["text"]
# escape HTML
esc = (text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"))
old_n = d.get("old_lineno") or ""
new_n = d.get("new_lineno") or ""
if kind == "add":
parts.append(f'<div class="diff-line diff-add"><span class="diff-ln">{old_n:>4}</span><span class="diff-ln">{new_n:>4}</span> + {esc}</div>')
elif kind == "del":
parts.append(f'<div class="diff-line diff-del"><span class="diff-ln">{old_n:>4}</span><span class="diff-ln">{new_n:>4}</span> - {esc}</div>')
elif kind == "equal":
parts.append(f'<div class="diff-line diff-ctx"><span class="diff-ln">{old_n:>4}</span><span class="diff-ln">{new_n:>4}</span> {esc}</div>')
else: # meta
parts.append(f'<div class="diff-line diff-hunk">{esc}</div>')
parts.append("</div>")
return "\n".join(parts)