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
162 lines
6.8 KiB
HTML
162 lines
6.8 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}TLS 证书 - Squid Manager{% endblock %}
|
||
{% block content %}
|
||
<div class="page-header">
|
||
<h1>🔐 TLS 证书管理</h1>
|
||
<div class="page-actions">
|
||
<span class="meta-text">共 {{ certs | length }} 个证书 · 目录 <code>{{ cert_dir }}</code></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<p class="card-desc">
|
||
HTTPS 反代需要 TLS 证书。这里可以一键生成<strong>自签名证书</strong>用于快速测试,
|
||
也可以上传由 Let's Encrypt / 商业 CA 签发的正式证书(<code>.pem</code> / <code>.crt</code> + <code>.key</code>)。
|
||
<br>
|
||
<strong>⚠️ 生产环境强烈建议</strong>: 使用 <code>nginx</code> + <code>Let's Encrypt</code> 反向代理并强制 HTTPS,
|
||
而不是把本 Web 平台直接暴露在公网。
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 class="card-title">🪄 生成自签名证书</h3>
|
||
<p class="card-desc">立即可用的测试证书,有效期可选 (1 ~ 3650 天)。建议仅用于内网 / 临时调试。</p>
|
||
<form method="post" action="{{ url_for('config_tls_generate') }}">
|
||
<input type="hidden" name="_csrf" value="{{ csrf_token() }}">
|
||
<div class="filter-grid">
|
||
<div class="form-group">
|
||
<label>Common Name (CN) <span class="hint">主机名 / 域名, 例如 proxy.example.com</span></label>
|
||
<input type="text" name="common_name" required minlength="1" maxlength="253"
|
||
placeholder="proxy.example.com" class="form-input">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>有效期 (天) <span class="hint">默认 365, 最大 3650</span></label>
|
||
<input type="number" name="days" value="365" min="1" max="3650" class="form-input">
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">🔧 生成自签名证书</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 class="card-title">⬆ 上传证书</h3>
|
||
<p class="card-desc">
|
||
支持 <code>.pem</code> / <code>.crt</code> / <code>.key</code>。单个文件上限 <strong>1 MB</strong>。
|
||
上传后请在表中核对<strong>主题 (CN)</strong>与<strong>到期时间</strong>。
|
||
</p>
|
||
<form method="post" action="{{ url_for('config_tls_upload') }}"
|
||
enctype="multipart/form-data">
|
||
<input type="hidden" name="_csrf" value="{{ csrf_token() }}">
|
||
<div class="form-group">
|
||
<label>证书 / 私钥文件 (.pem / .crt / .key)</label>
|
||
<input type="file" name="certfile" required accept=".pem,.crt,.key" class="form-input">
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">📤 上传</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 class="card-title">📜 已有证书</h3>
|
||
{% if certs %}
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>文件名</th>
|
||
<th>类型</th>
|
||
<th>主题 (CN)</th>
|
||
<th>到期</th>
|
||
<th>状态</th>
|
||
<th>SHA-256 指纹</th>
|
||
<th>大小</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for c in certs %}
|
||
<tr>
|
||
<td><code>{{ c.name }}</code></td>
|
||
<td>
|
||
{% if c.is_key %}
|
||
<span class="badge badge-blue">KEY</span>
|
||
{% elif c.ext == '.pem' %}
|
||
<span class="badge badge-purple">PEM</span>
|
||
{% else %}
|
||
<span class="badge badge-cyan">CRT</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if c.parsed_ok and c.cn %}
|
||
{{ c.cn }}
|
||
{% elif c.parsed_ok %}
|
||
<span class="meta-text">—</span>
|
||
{% else %}
|
||
<span class="badge badge-gray">{{ c.parse_error[:30] }}</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if c.parsed_ok and c.not_after %}
|
||
<span title="{{ c.expires_iso }}">{{ c.not_after }}</span>
|
||
{% else %}
|
||
<span class="meta-text">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if c.parsed_ok %}
|
||
{% if c.status == 'valid' %}
|
||
<span class="badge badge-green">有效 ({{ c.days_remaining }} 天)</span>
|
||
{% elif c.status == 'expiring' %}
|
||
<span class="badge badge-yellow">即将过期 ({{ c.days_remaining }} 天)</span>
|
||
{% elif c.status == 'expired' %}
|
||
<span class="badge badge-red">已过期</span>
|
||
{% else %}
|
||
<span class="badge badge-gray">未知</span>
|
||
{% endif %}
|
||
{% else %}
|
||
<span class="badge badge-gray">未识别</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if c.sha256 %}
|
||
<code class="meta-text" title="{{ c.sha256 }}">{{ c.sha256[:16] }}…</code>
|
||
{% else %}
|
||
<span class="meta-text">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>{{ c.size }} B</td>
|
||
<td>
|
||
<form method="post" action="{{ url_for('config_tls_delete') }}"
|
||
style="display:inline;"
|
||
onsubmit="return confirm('确认删除证书 {{ c.name }}? 此操作不可恢复。');">
|
||
<input type="hidden" name="_csrf" value="{{ csrf_token() }}">
|
||
<input type="hidden" name="name" value="{{ c.name }}">
|
||
<button type="submit" class="btn btn-small btn-warning">🗑 删除</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div class="card-empty">
|
||
<div class="empty-icon">🔐</div>
|
||
<p>暂无证书。先点击上方「生成自签名证书」或上传一个 .pem / .crt 文件。</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 class="card-title">ℹ️ 使用提示</h3>
|
||
<ul>
|
||
<li>所有证书都存储在 <code>{{ cert_dir }}/</code>,文件权限建议 <code>600</code></li>
|
||
<li>自签名证书浏览器会提示<strong>不受信任</strong>,仅适合内网 / 临时测试</li>
|
||
<li>推荐的正式方案见 README 中的 <strong>TLS / HTTPS 部署指南</strong></li>
|
||
<li>删除私钥 (<code>.key</code>) 后,对应的证书将无法继续用于 HTTPS</li>
|
||
<li>上传的 .pem 同时包含证书和私钥时,<strong>请确保只有 root 才能访问本目录</strong></li>
|
||
</ul>
|
||
</div>
|
||
{% endblock %}
|