diff --git a/mailer.py b/mailer.py index c9834c6..b05d11e 100644 --- a/mailer.py +++ b/mailer.py @@ -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 = '

无活跃作业

' + # 用户-LD 板统计 HTML + user_board_html = "" + if data.get("user_board_agg"): + ub_rows = "".join( + f'' + f'{ub["user"]}' + f'{ub["ld_count"]}' + f'{ub["racks"]}' + f'{ub["pods"]}' + f'{ub["designs"]}' + f'' + for ub in data["user_board_agg"] + ) + user_board_html = f""" +

👤 用户-LD 板统计

+ + + + + + + + + {ub_rows} +
用户LD 数RackT-Pod设计
""" + # 24h 趋势 HTML if data["stats_24h"]: s = data["stats_24h"] @@ -526,6 +617,9 @@ def _render_html(data: dict, base_url: str) -> str: + + {user_board_html} +

📈 近 24 小时趋势

{trend_html}