From b791c4329994cb70c94695be127a8487871740d5 Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Mon, 12 Jan 2026 14:07:33 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-01-12 14:07:33) --- .last-branch | 2 +- .../src/backend/app/main/routes_inbox.py | 55 ++++++++++--------- .../src/templates/main/inbox.html | 24 +++++--- docs/changelog.md | 7 +++ 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/.last-branch b/.last-branch index 0de8a79..2df962a 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260112-12-vspc-company-mapping-popup-visible +v20260112-13-vspc-company-mapping-popup-ui diff --git a/containers/backupchecks/src/backend/app/main/routes_inbox.py b/containers/backupchecks/src/backend/app/main/routes_inbox.py index 7428277..c03e584 100644 --- a/containers/backupchecks/src/backend/app/main/routes_inbox.py +++ b/containers/backupchecks/src/backend/app/main/routes_inbox.py @@ -4,6 +4,8 @@ from .routes_shared import _format_datetime, _log_admin_event, _send_mail_messag from ..email_utils import extract_best_html_from_eml, is_effectively_blank_html import time +import re +import html as _html @main_bp.route("/inbox") @login_required @@ -149,34 +151,35 @@ def inbox_message_detail(message_id: int): for obj in MailObject.query.filter_by(mail_message_id=msg.id).order_by(MailObject.object_name.asc()).all() ] - # For VSPC Active Alarms summary messages, objects are typically not persisted until approval. - # To enable multi-company mapping in the Inbox, we expose the company list derived from parsing - # the stored HTML body (best-effort, without persisting objects). - bsw = (getattr(msg, "backup_software", "") or "").strip() - btype = (getattr(msg, "backup_type", "") or "").strip() - jname = (getattr(msg, "job_name", "") or "").strip() - if not objects and bsw == "Veeam" and btype == "Service Provider Console" and jname == "Active alarms summary": - try: - from ..parsers.veeam import _parse_vspc_active_alarms_from_html + # VSPC multi-company emails (e.g. "Active alarms summary") may not store parsed objects yet. + # Extract company names from the stored body so the UI can offer a dedicated mapping workflow. + vspc_companies: list[str] = [] + try: + bsw = (getattr(msg, "backup_software", "") or "").strip().lower() + btype = (getattr(msg, "backup_type", "") or "").strip().lower() + jname = (getattr(msg, "job_name", "") or "").strip().lower() - parsed_objs, _status, _msg = _parse_vspc_active_alarms_from_html(body_html or "") - companies: list[str] = [] - seen: set[str] = set() - for o in parsed_objs or []: - name = str((o or {}).get("name") or "") - ix = name.find(" | ") - if ix > 0: - c = name[:ix].strip() - if c and c not in seen: - seen.add(c) - companies.append(c) - if companies: - meta["vspc_companies"] = companies - except Exception: - # Never fail the message detail endpoint due to best-effort parsing. - pass + if bsw == "veeam" and btype == "service provider console" and jname == "active alarms summary": + raw = text_body if not _is_blank_text(text_body) else (html_body or "") + if raw: + txt = raw + # Best-effort HTML to text + if "<" in txt and ">" in txt: + txt = re.sub(r"<[^>]+>", " ", txt) + txt = _html.unescape(txt) + txt = re.sub(r"\s+", " ", txt).strip() - return jsonify({"status": "ok", "meta": meta, "body_html": body_html, "objects": objects}) + seen = set() + for m2 in re.finditer(r"\bCompany:\s*([^\(\r\n]+?)\s*\(\s*alarms?\s*:", txt, flags=re.IGNORECASE): + cname = (m2.group(1) or "").strip() + if cname and cname not in seen: + seen.add(cname) + vspc_companies.append(cname) + except Exception: + vspc_companies = [] + + + return jsonify({"status": "ok", "meta": meta, "body_html": body_html, "objects": objects, "vspc_companies": vspc_companies}) @main_bp.route("/inbox/message//eml") diff --git a/containers/backupchecks/src/templates/main/inbox.html b/containers/backupchecks/src/templates/main/inbox.html index c482277..3c53241 100644 --- a/containers/backupchecks/src/templates/main/inbox.html +++ b/containers/backupchecks/src/templates/main/inbox.html @@ -509,6 +509,11 @@ function findCustomerIdByName(name) { // reset mapBtn.classList.add("d-none"); if (approveBtn) approveBtn.classList.remove("d-none"); + var ciReset = document.getElementById("msg_customer_input"); + if (ciReset) { + ciReset.removeAttribute("disabled"); + ciReset.placeholder = "Select customer"; + } var bsw = String(meta.backup_software || "").trim(); var btype = String(meta.backup_type || "").trim(); @@ -518,14 +523,11 @@ function findCustomerIdByName(name) { return; } - var companies = []; - if (Array.isArray(meta.vspc_companies) && meta.vspc_companies.length) { - meta.vspc_companies.forEach(function (c) { - c = String(c || "").trim(); - if (c) companies.push(c); - }); - } else { - // fallback: derive from parsed objects (when available) + var companies = (data.vspc_companies || meta.vspc_companies || []); + if (!Array.isArray(companies)) companies = []; + + // Fallback for older stored messages where companies were embedded in object names. + if (!companies.length) { var objs = data.objects || []; var seen = {}; objs.forEach(function (o) { @@ -543,6 +545,12 @@ function findCustomerIdByName(name) { // Show mapping button; hide regular approve mapBtn.classList.remove("d-none"); if (approveBtn) approveBtn.classList.add("d-none"); + var ci = document.getElementById("msg_customer_input"); + if (ci) { + ci.value = ""; + ci.setAttribute("disabled", "disabled"); + ci.placeholder = "Use \"Map companies\""; + } mapBtn.onclick = function () { var tbody = document.getElementById("vspcCompanyMapTbody"); diff --git a/docs/changelog.md b/docs/changelog.md index 0f80404..f1edd4d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -206,6 +206,13 @@ - Exposed parsed VSPC company list via the inbox message detail endpoint. - Updated inbox popup logic to trigger the dedicated VSPC company-mapping workflow based on detected companies instead of parsed objects. +--- + +## v20260112-13-vspc-company-mapping-popup-ui +- Added VSPC company extraction to the inbox message detail response so multi-company summaries expose a company list even when no objects are stored. +- Updated the Inbox message modal to show the “Map companies” button based on the returned VSPC company list (with fallback to legacy object-name parsing). +- Disabled the standard Customer selector when VSPC company mapping is available to avoid using the wrong approval flow. + ================================================================================================================================================ ## v0.1.19 This release delivers a broad set of improvements focused on reliability, transparency, and operational control across mail processing, administrative auditing, and Run Checks workflows. The changes aim to make message handling more robust, provide better insight for administrators, and give operators clearer and more flexible control when reviewing backup runs. -- 2.45.2