fix: db.get_or_404 -> query.get_or_404 + rename to Palladium Monitor

- 修复删除快照时 AttributeError: SQLAlchemy.get_or_404 (低版本兼容)
- UI/标题/邮件主题从 'Palladium Z1 Monitor' 改为 'Palladium Monitor'
This commit is contained in:
Your Name
2026-07-08 14:46:18 +08:00
parent 89dc6b4449
commit 6726524c50
17 changed files with 46 additions and 46 deletions
+5 -5
View File
@@ -1,6 +1,6 @@
# Palladium Z1 Monitor
# Palladium Monitor
LD 逻辑板使用监控系统 — 解析 `test_server -short` 输出,可视化展示 Palladium Z1 仿真器的逻辑板状态、逻辑板利用率、作业信息和 T-Pod 可用性。
LD 逻辑板使用监控系统 — 解析 `test_server -short` 输出,可视化展示 Palladium 仿真器的逻辑板状态、逻辑板利用率、作业信息和 T-Pod 可用性。
## 功能
@@ -37,7 +37,7 @@ python3 app.py
### 方式二:API 推送 (collect.py + 一键安装)
在 Palladium Z1 主机上部署采集 agent,**推荐**用一键脚本(处理 env 加载、crontab、依赖):
在 Palladium 主机上部署采集 agent,**推荐**用一键脚本(处理 env 加载、crontab、依赖):
```bash
# 把 palladium-monitor/ 整个目录 scp 到 Z1 主机, 然后:
@@ -194,7 +194,7 @@ gunicorn --workers 2 --bind 0.0.0.0:5100 app:app
| `SMTP_USE_TLS` | ❌ | `true` | 是否用 STARTTLS (端口 587 时设为 `true`) |
| `SMTP_USE_SSL` | ❌ | `false` | 是否用隐式 SSL (端口 465 时设为 `true`) |
| `MAIL_FROM` | 启用时必填 | — | 发件人邮箱 |
| `MAIL_FROM_NAME` | ❌ | `Palladium Z1 Monitor` | 发件人显示名 |
| `MAIL_FROM_NAME` | ❌ | `Palladium Monitor` | 发件人显示名 |
| `MAIL_TO` | 启用时必填 | — | 收件人列表, 英文逗号分隔 (如 `alice@x.com,bob@y.com`) |
| `MAIL_SEND_HOUR` | ❌ | `9` | 每天发送时间-小时 (0-23) |
| `MAIL_SEND_MINUTE` | ❌ | `0` | 每天发送时间-分钟 (0-59) |
@@ -251,7 +251,7 @@ ExecStart=/usr/bin/gunicorn --workers 2 --bind 0.0.0.0:5100 app:app
#### 邮件内容示例
每日报告包含:
- **主题**: `[Palladium Z1] 每日报告 YYYY-MM-DD — 利用率 X.X%`
- **主题**: `[Palladium] 每日报告 YYYY-MM-DD — 利用率 X.X%`
- **正文 (HTML + 纯文本)**: 系统信息 / 当前指标卡片 / 近 24h 趋势统计 + 样本柱状图 / 离线板列表 / 活跃作业列表
- **多部分**: 同时发 HTML 和纯文本, 邮件客户端自动选最合适的渲染
+5 -5
View File
@@ -1,9 +1,9 @@
from typing import List, Dict, Tuple, Set, Any, Optional, Union
#!/usr/bin/env python3
"""
Palladium Z1 Monitor — LD逻辑板使用监控系统
Palladium Monitor — LD逻辑板使用监控系统
解析 `test_server -short` 输出,展示 Palladium Z1 仿真器的
解析 `test_server -short` 输出,展示 Palladium 仿真器的
逻辑板 (LD) 状态、逻辑板利用率、作业信息和 T-Pod 可用性。
支持两种数据来源:
@@ -520,7 +520,7 @@ def history():
@app.route("/snapshot/<int:sid>")
def snapshot_detail(sid):
"""单条快照详情。"""
snapshot = db.get_or_404(ScanSnapshot, sid)
snapshot = ScanSnapshot.query.get_or_404(sid)
stats = {
"total_boards": snapshot.total_boards,
"online_boards": snapshot.online_boards,
@@ -681,7 +681,7 @@ def api_history():
@app.route("/api/snapshot/<int:sid>/delete", methods=["POST"])
def delete_snapshot(sid):
"""删除指定快照。"""
snapshot = db.get_or_404(ScanSnapshot, sid)
snapshot = ScanSnapshot.query.get_or_404(sid)
db.session.delete(snapshot)
db.session.commit()
return redirect(url_for("history"))
@@ -1270,7 +1270,7 @@ if __name__ == "__main__":
seed_sample_data()
start_email_scheduler()
print("════════════════════════════════════════════════════════")
print(" Palladium Z1 Monitor")
print(" Palladium Monitor")
print(" http://0.0.0.0:5100")
print("════════════════════════════════════════════════════════")
app.run(host="0.0.0.0", port=5100, debug=True)
+3 -3
View File
@@ -1,9 +1,9 @@
from typing import List, Dict, Tuple, Set, Any, Optional, Union
#!/usr/bin/env python3
"""
collect.py — Palladium Z1 远程数据采集推送脚本
collect.py — Palladium 远程数据采集推送脚本
在 Palladium Z1 主机上运行,执行 test_server -short 并将结果推送到监控服务器。
在 Palladium 主机上运行,执行 test_server -short 并将结果推送到监控服务器。
用法:
python3 collect.py # 默认配置
@@ -153,7 +153,7 @@ def push_to_monitor(url: str, raw_output: str, source: str,
# ═══════════════════════════════════════════════════════════════════════════
def main() -> int:
parser = argparse.ArgumentParser(
description="Palladium Z1 数据采集推送 (cron 友好, 带重试和日志)",
description="Palladium 数据采集推送 (cron 友好, 带重试和日志)",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("--url", default=DEFAULT_URL,
+10 -10
View File
@@ -1,6 +1,6 @@
from typing import List, Dict, Tuple, Set, Any, Optional, Union
"""
mailer.py — Palladium Z1 每日报告邮件发送
mailer.py — Palladium 每日报告邮件发送
通过 SMTP 把"今日快照 + 24h 趋势"汇总发送到指定邮箱。
所有配置都从环境变量读 (生产建议用 systemd EnvironmentFile 或 .env)。
@@ -79,7 +79,7 @@ def _build_config_from_env() -> dict:
"smtp_use_tls": _bool("SMTP_USE_TLS", True),
"smtp_use_ssl": _bool("SMTP_USE_SSL", False),
"from_addr": os.environ.get("MAIL_FROM", "").strip(),
"from_name": os.environ.get("MAIL_FROM_NAME", "Palladium Z1 Monitor").strip(),
"from_name": os.environ.get("MAIL_FROM_NAME", "Palladium Monitor").strip(),
"to_addrs": [a.strip() for a in os.environ.get("MAIL_TO", "").split(",") if a.strip()],
"send_hour": _int_env("MAIL_SEND_HOUR", 9),
"send_minute": _int_env("MAIL_SEND_MINUTE", 0),
@@ -109,7 +109,7 @@ def _build_config_from_dict(raw: Dict[str, str]) -> dict:
"smtp_use_tls": _b("smtp_use_tls", True),
"smtp_use_ssl": _b("smtp_use_ssl", False),
"from_addr": _s("from_addr"),
"from_name": _s("from_name") or "Palladium Z1 Monitor",
"from_name": _s("from_name") or "Palladium Monitor",
"to_addrs": [a.strip() for a in _s("to_addrs").split(",") if a.strip()],
"send_hour": _i("send_hour", 9),
"send_minute": _i("send_minute", 0),
@@ -138,7 +138,7 @@ def save_config(data: dict) -> int:
"smtp_use_tls": str(data.get("smtp_use_tls", True)).lower(),
"smtp_use_ssl": str(data.get("smtp_use_ssl", False)).lower(),
"from_addr": str(data.get("from_addr", "")).strip(),
"from_name": str(data.get("from_name", "Palladium Z1 Monitor")).strip(),
"from_name": str(data.get("from_name", "Palladium Monitor")).strip(),
"to_addrs": to_addrs_str.strip(),
"send_hour": str(data.get("send_hour", 9)),
"send_minute": str(data.get("send_minute", 0)),
@@ -310,11 +310,11 @@ def _render_text(data: dict, base_url: str) -> str:
"""纯文本版本 (HTML 不支持时回退)。"""
latest = data["latest"]
if not latest:
return "Palladium Z1 Monitor — 当前无快照数据, 无法生成报告。\n"
return "Palladium Monitor — 当前无快照数据, 无法生成报告。\n"
lines = []
title_date = _fmt_ts(latest.timestamp, "%Y-%m-%d")
lines.append(f"Palladium Z1 每日报告 — {title_date}")
lines.append(f"Palladium 每日报告 — {title_date}")
lines.append("=" * 60)
lines.append(f"生成时间: {_fmt_ts(data['now_utc'])} UTC")
lines.append(f"快照时间: {_fmt_ts(latest.timestamp)}")
@@ -381,7 +381,7 @@ def _render_html(data: dict, base_url: str) -> str:
if not latest:
return (
'<html><body style="font-family:sans-serif;padding:20px">'
'<h2>Palladium Z1 Monitor</h2>'
'<h2>Palladium Monitor</h2>'
'<p>当前无快照数据, 无法生成报告。</p>'
'</body></html>'
)
@@ -475,7 +475,7 @@ def _render_html(data: dict, base_url: str) -> str:
<!-- Header -->
<div style="background:linear-gradient(135deg,#007bff,#0056b3);color:#fff;padding:24px 28px">
<h1 style="margin:0;font-size:22px">⚡ Palladium Z1 每日报告</h1>
<h1 style="margin:0;font-size:22px">⚡ Palladium 每日报告</h1>
<div style="margin-top:6px;opacity:0.9;font-size:14px">{title_date}</div>
</div>
@@ -532,7 +532,7 @@ def _render_html(data: dict, base_url: str) -> str:
<!-- Footer -->
<div style="background:#f8f9fa;padding:16px 28px;border-top:1px solid #e0e0e0;
font-size:12px;color:#888;text-align:center">
由 <a href="{base_url}/" style="color:#007bff;text-decoration:none">Palladium Z1 Monitor</a> 自动生成 ·
由 <a href="{base_url}/" style="color:#007bff;text-decoration:none">Palladium Monitor</a> 自动生成 ·
{_fmt_ts(data['now_utc'], '%Y-%m-%d %H:%M:%S')} UTC
</div>
@@ -552,7 +552,7 @@ def render_report(data: dict, cfg: dict) -> Tuple[str, str, str]:
else:
title_date = _fmt_ts(data["now_utc"], "%Y-%m-%d")
util = f"{latest.utilization:.1f}%" if latest else ""
subject = f"[Palladium Z1] 每日报告 {title_date} — 利用率 {util}"
subject = f"[Palladium] 每日报告 {title_date} — 利用率 {util}"
html = _render_html(data, cfg["monitor_base_url"])
text = _render_text(data, cfg["monitor_base_url"])
return subject, html, text
+2 -2
View File
@@ -1,6 +1,6 @@
# Palladium Z1 Monitor
# Palladium Monitor
LD 逻辑板使用监控系统 — 解析 `test_server -short` 输出,可视化展示 Palladium Z1 仿真器的逻辑板状态、逻辑板利用率、作业信息和 T-Pod 可用性。
LD 逻辑板使用监控系统 — 解析 `test_server -short` 输出,可视化展示 Palladium 仿真器的逻辑板状态、逻辑板利用率、作业信息和 T-Pod 可用性。
## 功能
+5 -5
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""
Palladium Z1 Monitor — LD逻辑板使用监控系统
Palladium Monitor — LD逻辑板使用监控系统
解析 `test_server -short` 输出,展示 Palladium Z1 仿真器的
解析 `test_server -short` 输出,展示 Palladium 仿真器的
逻辑板 (LD) 状态、逻辑板利用率、作业信息和 T-Pod 可用性。
"""
@@ -362,7 +362,7 @@ def history():
@app.route("/snapshot/<int:sid>")
def snapshot_detail(sid):
snapshot = db.get_or_404(ScanSnapshot, sid)
snapshot = ScanSnapshot.query.get_or_404(sid)
stats = _snapshot_stats(snapshot)
racks_data = _build_racks(sid)
jobs_data = JobInfo.query.filter_by(snapshot_id=sid).all()
@@ -434,7 +434,7 @@ def api_history():
@app.route("/api/snapshot/<int:sid>/delete", methods=["POST"])
def delete_snapshot(sid):
snapshot = db.get_or_404(ScanSnapshot, sid)
snapshot = ScanSnapshot.query.get_or_404(sid)
db.session.delete(snapshot)
db.session.commit()
return redirect(url_for("history"))
@@ -627,7 +627,7 @@ if __name__ == "__main__":
db.create_all()
seed_sample_data()
print("════════════════════════════════════════════════════════")
print(" Palladium Z1 Monitor")
print(" Palladium Monitor")
print(" http://0.0.0.0:5100")
print("════════════════════════════════════════════════════════")
app.run(host="0.0.0.0", port=5100, debug=True)
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════
# collect.sh — Palladium Z1 数据采集脚本 (Shell 版, 无需 Python)
# collect.sh — Palladium 数据采集脚本 (Shell 版, 无需 Python)
#
# 在 Palladium 主机上运行, 执行 test_server -short 并推送到监控服务器
#
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Palladium Z1 Monitor{% endblock %}</title>
<title>{% block title %}Palladium Monitor{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% block head %}{% endblock %}
</head>
@@ -11,7 +11,7 @@
<nav class="topnav">
<div class="nav-brand">
<span class="nav-logo"></span>
<span class="nav-title">Palladium Z1 Monitor</span>
<span class="nav-title">Palladium Monitor</span>
</div>
<div class="nav-links">
<a href="{{ url_for('dashboard') }}" class="{% if request.endpoint == 'dashboard' %}active{% endif %}">仪表板</a>
@@ -32,7 +32,7 @@
<div class="modal-body">
<p class="modal-hint">在 Palladium 主机执行 <code>test_server -short</code>,将输出粘贴到下方:</p>
<textarea name="raw_output" rows="20" class="paste-textarea"
placeholder="Emulator: sc01_emu Hardware: Palladium Z1 ..."></textarea>
placeholder="Emulator: sc01_emu Hardware: Palladium ..."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closePasteModal()">取消</button>
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}仪表板 — Palladium Z1 Monitor{% endblock %}
{% block title %}仪表板 — Palladium Monitor{% endblock %}
{% block head %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
{% endblock %}
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}历史记录 — Palladium Z1 Monitor{% endblock %}
{% block title %}历史记录 — Palladium Monitor{% endblock %}
{% block content %}
<div class="section-header">
<h2>📅 历史快照</h2>
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}快照 #{{ snapshot.id }} — Palladium Z1 Monitor{% endblock %}
{% block title %}快照 #{{ snapshot.id }} — Palladium Monitor{% endblock %}
{% block content %}
<div class="snapshot-header">
<div>
+1 -1
View File
@@ -1,5 +1,5 @@
/* ═══════════════════════════════════════════════════════════════
Palladium Z1 Monitor — Dark Theme Dashboard
Palladium Monitor — Dark Theme Dashboard
═══════════════════════════════════════════════════════════════ */
:root {
--bg-primary: #0d1117;
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Palladium Z1 Monitor{% endblock %}</title>
<title>{% block title %}Palladium Monitor{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% block head %}{% endblock %}
</head>
@@ -11,7 +11,7 @@
<nav class="topnav">
<div class="nav-brand">
<span class="nav-logo"></span>
<span class="nav-title">Palladium Z1 Monitor</span>
<span class="nav-title">Palladium Monitor</span>
</div>
<div class="nav-links">
<a href="{{ url_for('dashboard') }}" class="{% if request.endpoint == 'dashboard' %}active{% endif %}">仪表板</a>
@@ -36,7 +36,7 @@
<div class="modal-body">
<p class="modal-hint">在 Palladium 主机执行 <code>test_server -short</code>,将输出粘贴到下方:</p>
<textarea name="raw_output" rows="20" class="paste-textarea"
placeholder="Emulator: sc01_emu Hardware: Palladium Z1 ..."></textarea>
placeholder="Emulator: sc01_emu Hardware: Palladium ..."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closePasteModal()">取消</button>
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}仪表板 — Palladium Z1 Monitor{% endblock %}
{% block title %}仪表板 — Palladium Monitor{% endblock %}
{% block head %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
+2 -2
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}邮件配置 — Palladium Z1 Monitor{% endblock %}
{% block title %}邮件配置 — Palladium Monitor{% endblock %}
{% block content %}
@@ -81,7 +81,7 @@
</div>
<div class="form-group">
<label for="cfg_from_name">发件人名称</label>
<input type="text" id="cfg_from_name" placeholder="Palladium Z1 Monitor">
<input type="text" id="cfg_from_name" placeholder="Palladium Monitor">
</div>
</div>
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}历史记录 — Palladium Z1 Monitor{% endblock %}
{% block title %}历史记录 — Palladium Monitor{% endblock %}
{% block content %}
<div class="section-header">
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}快照 #{{ snapshot.id }} — Palladium Z1 Monitor{% endblock %}
{% block title %}快照 #{{ snapshot.id }} — Palladium Monitor{% endblock %}
{% block content %}
<div class="snapshot-header">