feat: 邮件报告增加用户-LD 板使用统计
- 在 _collect_report_data 中聚合 DomainDetail 按用户统计 LD 板使用 - 纯文本版新增【用户-LD 板统计】版块 (用户/LD板/Rack/T-Pod/设计) - HTML 版新增紫色标题表格, 紫色徽章显示 LD 数 - 自动过滤 NONE/UNOWNED/FREE/AVAILABLE 等无主域 - 过滤 T-Pod 占位符 '--' (包括 '24 --' 含 -- 的值)
This commit is contained in:
@@ -286,12 +286,63 @@ def collect_report_data() -> dict:
|
||||
stats_24h = None
|
||||
samples_24h = []
|
||||
|
||||
# ── 按用户聚合 LD 板使用 ──
|
||||
user_board_agg = []
|
||||
try:
|
||||
from app import DomainDetail
|
||||
domain_rows = DomainDetail.query.filter_by(
|
||||
snapshot_id=latest.id
|
||||
).all()
|
||||
if domain_rows:
|
||||
# 按用户聚合: 统计每用户用了多少块不同 LD
|
||||
user_boards: dict[str, dict] = {}
|
||||
for dr in domain_rows:
|
||||
owner = dr.owner or ""
|
||||
if not owner or owner.upper() in ("NONE", "UNOWNED", "FREE", "AVAILABLE"):
|
||||
continue
|
||||
board_key = (dr.rack, dr.cluster, dr.ld_index)
|
||||
if owner not in user_boards:
|
||||
user_boards[owner] = {
|
||||
"user": owner,
|
||||
"boards": set(),
|
||||
"racks": set(),
|
||||
"pods": set(),
|
||||
"designs": set(),
|
||||
}
|
||||
entry = user_boards[owner]
|
||||
entry["boards"].add(board_key)
|
||||
if dr.rack is not None:
|
||||
entry["racks"].add(dr.rack)
|
||||
t_pod = dr.t_pod or ""
|
||||
# 过滤占位符 "--" (包括 "24 --" 这种含 -- 的)
|
||||
if t_pod and "--" not in t_pod and t_pod.strip() != "":
|
||||
entry["pods"].add(t_pod)
|
||||
design = dr.design or ""
|
||||
if design:
|
||||
entry["designs"].add(design)
|
||||
|
||||
# 转为列表并排序 (按 LD 数降序)
|
||||
for owner, info in user_boards.items():
|
||||
boards_sorted = sorted(info["boards"], key=lambda x: (x[0], x[1], x[2]))
|
||||
user_board_agg.append({
|
||||
"user": owner,
|
||||
"ld_count": len(boards_sorted),
|
||||
"boards": ", ".join(f"R{b[0]}C{b[1]}LD{b[2]}" for b in boards_sorted),
|
||||
"racks": ", ".join(str(r) for r in sorted(info["racks"])) if info["racks"] else "—",
|
||||
"pods": ", ".join(sorted(info["pods"])) if info["pods"] else "—",
|
||||
"designs": "; ".join(sorted(info["designs"])) if info["designs"] else "—",
|
||||
})
|
||||
user_board_agg.sort(key=lambda x: (-x["ld_count"], x["user"]))
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
return {
|
||||
"latest": latest,
|
||||
"boards_offline": boards_offline,
|
||||
"jobs_active": jobs_active,
|
||||
"stats_24h": stats_24h,
|
||||
"samples_24h": samples_24h,
|
||||
"user_board_agg": user_board_agg,
|
||||
"now_utc": now,
|
||||
"now_local": datetime.now(),
|
||||
}
|
||||
@@ -349,6 +400,20 @@ def _render_text(data: dict, base_url: str) -> str:
|
||||
lines.append(f" 活跃作业: {latest.active_jobs}")
|
||||
lines.append("")
|
||||
|
||||
# 用户-LD 板统计
|
||||
if data.get("user_board_agg"):
|
||||
lines.append("【用户-LD 板统计】")
|
||||
for ub in data["user_board_agg"]:
|
||||
lines.append(f" {ub['user']}: {ub['ld_count']} 块 LD")
|
||||
lines.append(f" LD 板: {ub['boards']}")
|
||||
if ub["racks"] != "—":
|
||||
lines.append(f" Rack: {ub['racks']}")
|
||||
if ub["pods"] != "—":
|
||||
lines.append(f" T-Pod: {ub['pods']}")
|
||||
if ub["designs"] != "—":
|
||||
lines.append(f" 设计: {ub['designs']}")
|
||||
lines.append("")
|
||||
|
||||
# 24h 趋势
|
||||
if data["stats_24h"]:
|
||||
s = data["stats_24h"]
|
||||
@@ -443,6 +508,32 @@ def _render_html(data: dict, base_url: str) -> str:
|
||||
else:
|
||||
jobs_html = '<p style="color:#888;margin:6px 0">无活跃作业</p>'
|
||||
|
||||
# 用户-LD 板统计 HTML
|
||||
user_board_html = ""
|
||||
if data.get("user_board_agg"):
|
||||
ub_rows = "".join(
|
||||
f'<tr style="border-bottom:1px solid #e9ecef">'
|
||||
f'<td style="padding:6px 12px;font-family:monospace;font-weight:bold;color:#6f42c1">{ub["user"]}</td>'
|
||||
f'<td style="padding:6px 12px;text-align:center"><span style="background:#6f42c1;color:#fff;padding:2px 8px;border-radius:10px;font-size:12px;font-weight:bold">{ub["ld_count"]}</span></td>'
|
||||
f'<td style="padding:6px 12px;font-family:monospace;font-size:13px">{ub["racks"]}</td>'
|
||||
f'<td style="padding:6px 12px;font-family:monospace;font-size:13px">{ub["pods"]}</td>'
|
||||
f'<td style="padding:6px 12px;font-size:13px;color:#555">{ub["designs"]}</td>'
|
||||
f'</tr>'
|
||||
for ub in data["user_board_agg"]
|
||||
)
|
||||
user_board_html = f"""
|
||||
<h2 style="font-size:16px;margin:20px 0 10px;color:#555">👤 用户-LD 板统计</h2>
|
||||
<table style="border-collapse:collapse;width:100%;margin-top:6px">
|
||||
<thead><tr style="background:#6f42c1;color:#fff">
|
||||
<th style="padding:6px 12px;text-align:left">用户</th>
|
||||
<th style="padding:6px 12px;text-align:center">LD 数</th>
|
||||
<th style="padding:6px 12px;text-align:left">Rack</th>
|
||||
<th style="padding:6px 12px;text-align:left">T-Pod</th>
|
||||
<th style="padding:6px 12px;text-align:left">设计</th>
|
||||
</tr></thead>
|
||||
<tbody>{ub_rows}</tbody>
|
||||
</table>"""
|
||||
|
||||
# 24h 趋势 HTML
|
||||
if data["stats_24h"]:
|
||||
s = data["stats_24h"]
|
||||
@@ -526,6 +617,9 @@ def _render_html(data: dict, base_url: str) -> str:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户-LD 板统计 -->
|
||||
{user_board_html}
|
||||
|
||||
<!-- 24h 趋势 -->
|
||||
<h2 style="font-size:16px;margin:20px 0 10px;color:#555">📈 近 24 小时趋势</h2>
|
||||
{trend_html}
|
||||
|
||||
Reference in New Issue
Block a user