From a08b09ea37dc9bdc8742af094a30b45939772a2e Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 22:13:49 -0700 Subject: [PATCH 01/30] initial commit for dashboard --- viewer/app.py | 3 + viewer/functions/dashboard_queries.py | 289 ++++++++++++++++++ viewer/pages/dashboard.py | 76 +++++ viewer/pages/route.py | 2 + viewer/static/assets/css/dashboard.css | 364 ++++++++++++++++++++++ viewer/templates/dashboard/detail.html | 371 +++++++++++++++++++++++ viewer/templates/dashboard/overview.html | 252 +++++++++++++++ viewer/templates/parts/nav.html | 4 + 8 files changed, 1361 insertions(+) create mode 100644 viewer/functions/dashboard_queries.py create mode 100644 viewer/pages/dashboard.py create mode 100644 viewer/static/assets/css/dashboard.css create mode 100644 viewer/templates/dashboard/detail.html create mode 100644 viewer/templates/dashboard/overview.html diff --git a/viewer/app.py b/viewer/app.py index 0aa2b3e1d..f39dede1c 100755 --- a/viewer/app.py +++ b/viewer/app.py @@ -32,6 +32,7 @@ from pages.route import ( # noqa: E402 checkout_api, component_api, config_api, + dashboard_bp, error_api, history_api, picture_data_api, @@ -126,6 +127,8 @@ with app.app_context(): ### Pages ## Top Page app.register_blueprint(toppage_api) + ## Dashboard + app.register_blueprint(dashboard_bp, url_prefix="/dashboard") ## Component/Result Page app.register_blueprint(component_api) ## History Page diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py new file mode 100644 index 000000000..701a0f5d9 --- /dev/null +++ b/viewer/functions/dashboard_queries.py @@ -0,0 +1,289 @@ +from __future__ import annotations + +import csv +import io + +from bson.objectid import ObjectId +from functions.common import localdb, userdb + + +def get_stage_config(): + """Get stage configuration from userdb.QC.stages""" + return userdb.QC.stages.find_one({"code": "MODULE"}) + + +def get_enabled_stages(stage_config): + """Filter out disabled stages""" + return [ + stage + for stage in stage_config["stage_flow"] + if not stage_config["disabled_tests"].get(stage, {}).get("disabled", 0) + ] + + +def get_modules_overview(enabled_stages, search=""): + """Get all modules with stage status matrix""" + match_stage = {"componentType": {"$in": ["module", "bare_module"]}} + + # Add search filter + if search: + match_stage["$or"] = [ + {"serialNumber": {"$regex": search, "$options": "i"}}, + { + "properties": { + "$elemMatch": { + "code": "ALTERNATIVE_IDENTIFIER", + "value": {"$regex": search, "$options": "i"}, + } + } + }, + ] + + pipeline = [ + {"$match": match_stage}, + { + "$lookup": { + "from": "QC.module.status", + "localField": "_id", + "foreignField": "component", + "as": "qc_status", + } + }, + { + "$project": { + "serialNumber": 1, + "componentType": 1, + "currentStage": 1, + "properties": 1, + "qc_status": {"$arrayElemAt": ["$qc_status", 0]}, + } + }, + ] + + modules = list(localdb.component.aggregate(pipeline)) + + # Filter out archived modules and add calculated fields + non_archived_modules = [] + stage_config = get_stage_config() + + for module in modules: + # Check if module is archived + is_archived = any( + tag["name"].lower() == "archived" + for tag in userdb.viewer.tag.docs.find({"componentid": str(module["_id"])}) + ) + + if not is_archived: + # Extract alternative ID + module["altID"] = next( + ( + prop["value"] + for prop in module.get("properties", []) + if prop["code"] == "ALTERNATIVE_IDENTIFIER" and prop.get("value") + ), + None, + ) + + # Calculate stage status for each enabled stage + module["stage_status"] = {} + if module.get("qc_status"): + for stage in enabled_stages: + module["stage_status"][stage] = calculate_stage_status( + module["qc_status"], stage, stage_config + ) + + non_archived_modules.append(module) + + return non_archived_modules + + +def calculate_stage_status(qc_status, stage, stage_config): + """Calculate status for a specific stage with hover details""" + # Check if stage is marked as alternative (skipped) + upload_status = qc_status.get("upload_status", {}).get(stage, "-1") + if upload_status == "alternative": + return { + "status": "alternative", + "details": {"message": "Stage skipped (alternative path)"}, + } + + # Check upload status + if upload_status == "-1": + return { + "status": "not_uploaded", + "details": {"message": "Stage not uploaded to production DB"}, + } + + # Get required tests for this stage + required_tests = stage_config["stage_test"].get(stage, []) + if not required_tests: + return { + "status": "no_tests", + "details": {"message": "No tests required for this stage"}, + } + + # Analyze test results + qc_results = qc_status.get("QC_results", {}).get(stage, {}) + test_details = [] + passed_tests = 0 + failed_tests = 0 + completed_tests = 0 + + for test in required_tests: + test_result_id = qc_results.get(test, "-1") + test_name = stage_config["test_items"].get(test, test) + + if test_result_id == "-1": + test_details.append( + {"name": test_name, "status": "not_done", "passed": None} + ) + else: + completed_tests += 1 + # Look up actual test result + try: + test_result = localdb.QC.result.find_one( + {"_id": ObjectId(test_result_id)} + ) + if test_result: + passed = test_result.get("passed", False) + test_details.append( + { + "name": test_name, + "status": "pass" if passed else "fail", + "passed": passed, + "result_id": str(test_result_id), + } + ) + if passed: + passed_tests += 1 + else: + failed_tests += 1 + else: + test_details.append( + {"name": test_name, "status": "missing", "passed": None} + ) + except Exception: + test_details.append( + {"name": test_name, "status": "error", "passed": None} + ) + + # Determine overall stage status + if failed_tests > 0: + status = "fail" + elif completed_tests == len(required_tests): + status = "pass" + elif completed_tests > 0: + status = "partial" + else: + status = "not_started" + + return { + "status": status, + "details": { + "total_tests": len(required_tests), + "completed_tests": completed_tests, + "passed_tests": passed_tests, + "failed_tests": failed_tests, + "test_details": test_details, + }, + } + + +def get_module_details(serial_number): + """Get detailed information for a specific module""" + pipeline = [ + {"$match": {"serialNumber": serial_number}}, + { + "$lookup": { + "from": "QC.module.status", + "localField": "_id", + "foreignField": "component", + "as": "qc_status", + } + }, + { + "$project": { + "serialNumber": 1, + "componentType": 1, + "currentStage": 1, + "properties": 1, + "sys": 1, + "dbVersion": 1, + "qc_status": {"$arrayElemAt": ["$qc_status", 0]}, + } + }, + ] + + modules = list(localdb.component.aggregate(pipeline)) + if not modules: + return None + + module = modules[0] + + # Check if module is archived + is_archived = any( + tag["name"].lower() == "archived" + for tag in userdb.viewer.tag.docs.find({"componentid": str(module["_id"])}) + ) + + if is_archived: + return None + + # Extract alternative ID + module["altID"] = next( + ( + prop["value"] + for prop in module.get("properties", []) + if prop["code"] == "ALTERNATIVE_IDENTIFIER" and prop.get("value") + ), + None, + ) + + # Get children using childParentRelation collection + children_relations = list( + userdb.childParentRelation.find({"parent": str(module["_id"])}) + ) + module["children"] = [] + + for relation in children_relations: + try: + child = localdb.component.find_one({"_id": ObjectId(relation["child"])}) + if child: + module["children"].append(child) + except Exception: + continue + + return module + + +def export_modules_csv(modules, stages): + """Export module status matrix to CSV""" + output = io.StringIO() + writer = csv.writer(output) + + # Header row + header = ["Serial Number", "Alt ID", "Component Type", "Current Stage"] + header.extend([stage.replace("MODULE/", "") for stage in stages]) + writer.writerow(header) + + # Data rows + for module in modules: + row = [ + module.get("serialNumber", ""), + module.get("altID", ""), + module.get("componentType", ""), + module.get("currentStage", ""), + ] + + for stage in stages: + stage_data = module.get("stage_status", {}).get(stage, {}) + status = ( + stage_data.get("status", "unknown") + if isinstance(stage_data, dict) + else stage_data + ) + row.append(status) + + writer.writerow(row) + + return output.getvalue() diff --git a/viewer/pages/dashboard.py b/viewer/pages/dashboard.py new file mode 100644 index 000000000..4b3e1d4b9 --- /dev/null +++ b/viewer/pages/dashboard.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from datetime import datetime + +from flask import Blueprint, abort, jsonify, make_response, render_template, request +from functions.dashboard_queries import ( + export_modules_csv, + get_enabled_stages, + get_module_details, + get_modules_overview, + get_stage_config, +) + +dashboard_bp = Blueprint("dashboard", __name__) + + +@dashboard_bp.route("/") +def overview(): + """Dashboard overview page showing all modules in a status matrix""" + search = request.args.get("search", "") + stage_config = get_stage_config() + enabled_stages = get_enabled_stages(stage_config) + modules = get_modules_overview(enabled_stages, search) + + return render_template( + "dashboard/overview.html", + modules=modules, + stages=enabled_stages, + stage_config=stage_config, + search=search, + ) + + +@dashboard_bp.route("/export") +def export_csv(): + """Export dashboard data as CSV""" + stage_config = get_stage_config() + enabled_stages = get_enabled_stages(stage_config) + modules = get_modules_overview(enabled_stages) + + csv_data = export_modules_csv(modules, enabled_stages) + response = make_response(csv_data) + response.headers["Content-Disposition"] = ( + "attachment; filename=module_dashboard.csv" + ) + response.headers["Content-type"] = "text/csv" + return response + + +@dashboard_bp.route("/") +def detail(serial_number): + """Detailed module dashboard page""" + module = get_module_details(serial_number) + if not module: + abort(404) + + stage_config = get_stage_config() + enabled_stages = get_enabled_stages(stage_config) + + return render_template( + "dashboard/detail.html", + module=module, + stage_config=stage_config, + enabled_stages=enabled_stages, + ) + + +@dashboard_bp.route("/api/refresh") +def api_refresh(): + """API endpoint for auto-refresh functionality""" + search = request.args.get("search", "") + stage_config = get_stage_config() + enabled_stages = get_enabled_stages(stage_config) + modules = get_modules_overview(enabled_stages, search) + + return jsonify({"modules": modules, "timestamp": datetime.now().isoformat()}) diff --git a/viewer/pages/route.py b/viewer/pages/route.py index 332cf0ac5..37ab4c408 100644 --- a/viewer/pages/route.py +++ b/viewer/pages/route.py @@ -4,6 +4,7 @@ from pages.api import rest_api from pages.checkout import checkout_api from pages.component import component_api from pages.config import config_api +from pages.dashboard import dashboard_bp from pages.error import error_api from pages.history import history_api from pages.picture_data import picture_data_api @@ -23,6 +24,7 @@ __all__ = ( "checkout_api", "component_api", "config_api", + "dashboard_bp", "error_api", "history_api", "picture_data_api", diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css new file mode 100644 index 000000000..c59ce4098 --- /dev/null +++ b/viewer/static/assets/css/dashboard.css @@ -0,0 +1,364 @@ +/* Dashboard Overview Styles */ +.dashboard-matrix { + font-size: 0.85rem; + white-space: nowrap; +} + +.fixed-column { + position: sticky; + left: 0; + background: white; + z-index: 10; + border-right: 2px solid #dee2e6; + min-width: 150px; +} + +.stage-header { + writing-mode: vertical-lr; + text-orientation: mixed; + min-width: 35px; + max-width: 35px; + font-size: 0.7rem; + padding: 8px 4px !important; + text-align: center; + vertical-align: bottom; +} + +.stage-cell { + text-align: center; + padding: 6px 4px !important; + vertical-align: middle; +} + +/* Status Indicator Styles */ +.status-indicator { + width: 22px; + height: 22px; + border-radius: 4px; + margin: 0 auto; + border: 1px solid #ddd; + cursor: help; + transition: all 0.2s ease; +} + +.status-indicator:hover { + transform: scale(1.1); + box-shadow: 0 2px 8px rgba(0,0,0,0.2); +} + +/* Status Colors */ +.status-indicator.pass { + background: #28a745; + border-color: #28a745; +} + +.status-indicator.fail { + background: #dc3545; + border-color: #dc3545; +} + +.status-indicator.partial { + background: #ffc107; + border-color: #ffc107; +} + +.status-indicator.not_started { + background: #e9ecef; + border-color: #adb5bd; +} + +.status-indicator.not_uploaded { + background: white; + border: 3px solid #fd7e14; +} + +.status-indicator.alternative { + background: #6c757d; + opacity: 0.5; + border-color: #6c757d; +} + +.status-indicator.no_tests { + background: #6c757d; + border-color: #6c757d; +} + +.status-indicator.unknown { + background: #343a40; + border-color: #343a40; +} + +/* Row Hover Effects */ +.module-row:hover { + background-color: #f8f9fa !important; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.module-row:hover .fixed-column { + background-color: #f8f9fa !important; +} + +/* Legend Styles */ +.dashboard-legend { + background: #f8f9fa; + padding: 15px; + border-radius: 8px; + border: 1px solid #e9ecef; + margin-top: 20px; +} + +.legend-items { + display: flex; + flex-wrap: wrap; + gap: 15px; + margin-top: 10px; +} + +.legend-items span { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.9rem; + padding: 4px 8px; + background: white; + border-radius: 4px; + border: 1px solid #e9ecef; +} + +.legend-items .status-indicator { + width: 16px; + height: 16px; + cursor: default; +} + +.legend-items .status-indicator:hover { + transform: none; + box-shadow: none; +} + +/* Tooltip Improvements */ +.tooltip-inner { + max-width: 300px; + text-align: left; +} + +/* Search and Controls */ +.form-control:focus { + border-color: #80bdff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.btn-group .badge { + font-size: 0.75em; +} + +/* Detail Page Styles */ +.accordion-button:not(.collapsed) { + background-color: #e7f1ff; + border-color: #b6d7ff; +} + +.accordion-button:focus { + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} + +.serialNumber { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-weight: bold; +} + +/* Stage Status Badges in Detail View */ +.stage-progress { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 15px; +} + +.stage-progress .progress { + flex: 1; + height: 8px; +} + +/* Property Tables */ +.table td.fw-bold { + width: 30%; + color: #495057; +} + +/* Child Component Cards */ +.card-title a { + color: #0d6efd; + text-decoration: none; +} + +.card-title a:hover { + text-decoration: underline; +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .stage-header { + font-size: 0.6rem; + min-width: 30px; + max-width: 30px; + } + + .status-indicator { + width: 20px; + height: 20px; + } +} + +@media (max-width: 768px) { + .dashboard-matrix { + font-size: 0.75rem; + } + + .fixed-column { + min-width: 120px; + } + + .stage-header { + font-size: 0.55rem; + min-width: 25px; + max-width: 25px; + } + + .status-indicator { + width: 18px; + height: 18px; + } + + .legend-items { + flex-direction: column; + gap: 8px; + } + + .legend-items span { + justify-content: flex-start; + } +} + +@media (max-width: 576px) { + .dashboard-legend { + padding: 10px; + } + + .stage-header { + font-size: 0.5rem; + min-width: 22px; + max-width: 22px; + padding: 4px 2px !important; + } + + .status-indicator { + width: 16px; + height: 16px; + } + + .fixed-column { + min-width: 100px; + font-size: 0.8rem; + } +} + +/* Loading States */ +.btn .fa-spinner { + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Print Styles */ +@media print { + .dashboard-legend, + .btn, + .form-control, + .accordion-button { + display: none !important; + } + + .accordion-collapse { + display: block !important; + } + + .status-indicator { + border: 2px solid black !important; + print-color-adjust: exact; + } +} + +/* Accessibility Improvements */ +.status-indicator:focus { + outline: 2px solid #0d6efd; + outline-offset: 2px; +} + +.module-row:focus-within { + background-color: #fff3cd !important; + outline: 2px solid #0d6efd; +} + +/* Animation for Auto-refresh */ +.refresh-animation { + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.7; + } + 100% { + opacity: 1; + } +} + +/* Search Highlighting */ +.search-highlight { + background-color: #fff3cd; + padding: 2px 4px; + border-radius: 3px; +} + +/* Summary Cards */ +.card .card-title { + font-size: 2rem; + font-weight: bold; + color: #0d6efd; +} + +.card .card-text { + color: #6c757d; + font-size: 0.9rem; +} + +/* Status Matrix Scrolling */ +.table-responsive { + scrollbar-width: thin; + scrollbar-color: #ced4da #f8f9fa; +} + +.table-responsive::-webkit-scrollbar { + height: 8px; +} + +.table-responsive::-webkit-scrollbar-track { + background: #f8f9fa; +} + +.table-responsive::-webkit-scrollbar-thumb { + background: #ced4da; + border-radius: 4px; +} + +.table-responsive::-webkit-scrollbar-thumb:hover { + background: #adb5bd; +} \ No newline at end of file diff --git a/viewer/templates/dashboard/detail.html b/viewer/templates/dashboard/detail.html new file mode 100644 index 000000000..3f3405bd8 --- /dev/null +++ b/viewer/templates/dashboard/detail.html @@ -0,0 +1,371 @@ +{% import 'widgets/datetime.html' as datetime %} +{% extends "layout.html" %} + +{% block stylesheet %} + +{% endblock %} + +{% block body %} +
+ + + + +
+
+

+ {{ module.serialNumber }} + {% if module.altID %} + ({{ module.altID }}) + {% endif %} +

+

+ + {{ module.componentType.replace('_', ' ').title() }} + + {% if module.currentStage %} + {{ module.currentStage.replace('MODULE/', '') }} + {% endif %} +

+ +
+ +
+
+ + +
+
+
+
+
+ +
+ +
+

+ +

+
+
+
+
+ + + + + + + + + + + + + + + + + + {% if module.qc_status %} + + + + + + + + + {% endif %} +
Serial Number{{ module.serialNumber }}
Alternative ID{{ module.altID or 'Not Set' }}
Component Type{{ module.componentType }}
Current Stage{{ module.currentStage or 'Not Set' }}
Latest Synced Stage{{ module.qc_status.latestSyncedStage or 'Not Set' }}
Status{{ module.qc_status.status }}
+
+
+ + {% if module.sys %} + + + + + + + + + + + + + {% endif %} + + + + + {% if module.qc_status %} + + + + + {% endif %} +
Created{{ datetime.widget(module.sys.cts) }}
Last Modified{{ datetime.widget(module.sys.mts) }}
System Revision{{ module.sys.rev }}
DB Version{{ module.dbVersion or 'Not Set' }}
Production DB Version{{ module.qc_status.proddbVersion or 'Not Set' }}
+
+
+
+
+
+ + {% if module.qc_status %} + + {% for stage in enabled_stages %} + {% set stage_tests = stage_config.stage_test.get(stage, []) %} + {% set qc_results = module.qc_status.QC_results.get(stage, {}) %} + {% set qc_results_pdb = module.qc_status.QC_results_pdb.get(stage, {}) %} + {% set upload_status = module.qc_status.upload_status.get(stage, '-1') %} + + {% if stage_tests and upload_status != 'alternative' %} +
+

+ +

+
+
+ + {% set completed_tests = [] %} + {% set failed_tests = [] %} + {% for test in stage_tests %} + {% set local_result_id = qc_results.get(test, '-1') %} + {% if local_result_id != '-1' %} + {% set completed_tests = completed_tests.append(test) %} + {% endif %} + {% endfor %} + +
+
+
+ Progress: {{ completed_tests|length }}/{{ stage_tests|length }} tests completed +
+
+ Upload Status: + {% if upload_status == '1' %} + Uploaded to Production DB + {% else %} + Not Uploaded + {% endif %} +
+
Stage: {{ stage_config.stages.get(stage, stage) }}
+
+
+ + + + + + + + + + + + + {% for test in stage_tests %} + {% set local_result_id = qc_results.get(test, '-1') %} + {% set pdb_result_id = qc_results_pdb.get(test, '-1') %} + {% set test_name = stage_config.test_items.get(test, test) %} + + + + + + + {% endfor %} + +
TestLocal ResultProduction DB ResultActions
{{ test_name }} + {% if local_result_id == '-1' %} + Not Done + {% else %} + Completed + ID: {{ local_result_id }} + {% endif %} + + {% if pdb_result_id == '-1' %} + Not Done + {% else %} + Completed + ID: {{ pdb_result_id }} + {% endif %} + + {% if local_result_id != '-1' %} + + View Details + + {% else %} + No result + {% endif %} +
+
+
+
+ {% endif %} + {% endfor %} + {% endif %} + + +
+

+ +

+
+
+ {% if module.properties %} + + + + + + + + + + + + {% for prop in module.properties %} + + + + + + + + {% endfor %} + +
CodeNameValueData TypeLast Updated
{{ prop.code }}{{ prop.name or '—' }} + {% if prop.dataType == 'codeTable' and prop.codeTable and prop.value %} + {% set found_value = false %} + {% for item in prop.codeTable %} + {% if item.code == prop.value %} + {{ item.value }} + {% set found_value = true %} + {% endif %} + {% endfor %} + {% if not found_value %} + {{ prop.value }} + {% endif %} + {% elif prop.dataType == 'boolean' %} + + {{ 'True' if prop.value else 'False' }} + + {% else %} + {{ prop.value or '—' }} + {% endif %} + + {{ prop.dataType or 'unknown' }} + {% if prop.required %} + Required + {% endif %} + {{ datetime.widget(prop.dateTime) if prop.dateTime else '—' }}
+ {% else %} +
No properties defined for this component.
+ {% endif %} +
+
+
+ + + {% if module.children %} +
+

+ +

+
+
+ {% for child in module.children %} +
+
+
+ + {{ child.serialNumber }} + + {{ child.componentType }} +
+
+ {% if child.properties %} +
+ + {% for prop in child.properties[:5] %} + + + + + {% endfor %} + {% if child.properties|length > 5 %} + + + + {% endif %} +
{{ prop.name or prop.code }} + {% if prop.dataType == 'codeTable' and prop.codeTable and prop.value %} + {% for item in prop.codeTable %} + {% if item.code == prop.value %}{{ item.value }}{% endif %} + {% endfor %} + {% else %} + {{ prop.value or '—' }} + {% endif %} +
+ ... and {{ child.properties|length - 5 }} more properties +
+
+ {% endif %} +
+ {% endfor %} +
+
+
+ {% endif %} +
+
+ + +{% endblock %} diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html new file mode 100644 index 000000000..6aa438859 --- /dev/null +++ b/viewer/templates/dashboard/overview.html @@ -0,0 +1,252 @@ +{% import 'widgets/datetime.html' as datetime %} +{% extends "layout.html" %} + +{% block stylesheet %} + +{% endblock %} + +{% block body %} +
+
+

Module Dashboard

+
+ + Export CSV +
+
+ + +
+
+
+ + + {% if search %} + Clear + {% endif %} +
+
+
+
+ + +
+
+
+ + +
+
+
+
+
{{ modules|length }}
+

Total Modules

+
+
+
+
+
+
+
{{ stages|length }}
+

Active Stages

+
+
+
+
+
+
+
{{ modules|selectattr('componentType', 'equalto', 'module')|list|length }}
+

Modules

+
+
+
+
+
+
+
{{ modules|selectattr('componentType', 'equalto', 'bare_module')|list|length }}
+

Bare Modules

+
+
+
+
+ + {% if modules %} + +
+ + + + + + + + {% for stage in stages %} + + {% endfor %} + + + + {% for module in modules %} + + + + + + {% for stage in stages %} + {% set stage_data = module.stage_status.get(stage, {}) %} + {% set status = stage_data.status if stage_data.get('status') else 'unknown' %} + {% set details = stage_data.get('details', {}) %} + + {% endfor %} + + {% endfor %} + +
Serial NumberAlt IDTypeCurrent Stage + {{ stage.replace('MODULE/', '').replace('_', ' ') }} +
{{ module.serialNumber }}{{ module.altID or '—' }} + + {{ module.componentType.replace('_', ' ').title() }} + + {{ module.currentStage.replace('MODULE/', '') if module.currentStage else '—' }} +
+
+
+ {% else %} +
+ + No modules found{% if search %}matching "{{ search }}"{% endif %}. +
+ {% endif %} + + +
+ Status Legend: +
+
+ All Tests Pass
+
+ Any Test Failed
+
+ Partially Complete
+
+ Not Started
+
+ Not Uploaded
+
+ Alternative/Skipped
+
+ No Tests Required
+
+
+
+ + +{% endblock %} + +{# Helper macro for tooltip rendering #} +{% macro render_tooltip(stage, status, details, stage_config) %} + {% if status == 'alternative' %} + {{ stage.replace('MODULE/', '') }}
{{ details.message or 'Stage skipped' }} + {% elif status == 'not_uploaded' %} + {{ stage.replace('MODULE/', '') }}
{{ details.message or 'Not uploaded' }} + {% elif status in ['pass', 'fail', 'partial'] %} + {{ stage.replace('MODULE/', '') }}
+ Tests: {{ details.completed_tests or 0 }}/{{ details.total_tests or 0 }}
+ {% if details.passed_tests %}✅ Passed: {{ details.passed_tests }}
{% endif %} + {% if details.failed_tests %}❌ Failed: {{ details.failed_tests }}
{% endif %} +
+ {% for test in details.test_details or [] %} + {{ test.name }}: + {% if test.status == 'pass' %} + ✅ + {% elif test.status == 'fail' %} + ❌ + {% else %} + ⏳ + {% endif %}
+ {% endfor %} +
+ {% elif status == 'no_tests' %} + {{ stage.replace('MODULE/', '') }}
No tests required + {% else %} + {{ stage.replace('MODULE/', '') }}
{{ status.replace('_', ' ').title() }} + {% endif %} +{% endmacro %} diff --git a/viewer/templates/parts/nav.html b/viewer/templates/parts/nav.html index b61512b09..dcee45ef1 100644 --- a/viewer/templates/parts/nav.html +++ b/viewer/templates/parts/nav.html @@ -46,6 +46,10 @@ QC Tests
  • |
  • +
  • + Dashboard +
  • +
  • |
  • YARR Scans
  • -- GitLab From 2c0ad310f3eba5a4cac3943de9d9d4a98176f843 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 22:22:18 -0700 Subject: [PATCH 02/30] fix routing --- viewer/templates/dashboard/detail.html | 4 ++-- viewer/templates/dashboard/overview.html | 6 +++--- viewer/templates/parts/nav.html | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/viewer/templates/dashboard/detail.html b/viewer/templates/dashboard/detail.html index 3f3405bd8..e7ddacc61 100644 --- a/viewer/templates/dashboard/detail.html +++ b/viewer/templates/dashboard/detail.html @@ -11,7 +11,7 @@ @@ -36,7 +36,7 @@
    @@ -29,7 +29,7 @@ /> {% if search %} - Clear + Clear {% endif %}
    @@ -176,7 +176,7 @@ btn.disabled = true; const search = document.getElementById("searchInput").value; - const url = '{{ url_for("dashboard_bp.api_refresh") }}' + (search ? `?search=${encodeURIComponent(search)}` : ""); + const url = '{{ url_for("dashboard.api_refresh") }}' + (search ? `?search=${encodeURIComponent(search)}` : ""); fetch(url) .then((response) => response.json()) diff --git a/viewer/templates/parts/nav.html b/viewer/templates/parts/nav.html index dcee45ef1..855dfa4b7 100644 --- a/viewer/templates/parts/nav.html +++ b/viewer/templates/parts/nav.html @@ -47,7 +47,7 @@
  • |
  • - Dashboard + Dashboard
  • |
  • -- GitLab From fe1076350b2813ca5df8a7a06a663db1aa5593f1 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 22:45:36 -0700 Subject: [PATCH 03/30] more debugging information --- viewer/functions/dashboard_queries.py | 9 ++- viewer/templates/dashboard/detail.html | 85 ++++++++++++++++++++++-- viewer/templates/dashboard/overview.html | 13 ++-- viewer/templates/parts/nav.html | 4 -- viewer/templates/toppage.html | 3 + 5 files changed, 97 insertions(+), 17 deletions(-) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index 701a0f5d9..b2ebbdadf 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -23,7 +23,7 @@ def get_enabled_stages(stage_config): def get_modules_overview(enabled_stages, search=""): """Get all modules with stage status matrix""" - match_stage = {"componentType": {"$in": ["module", "bare_module"]}} + match_stage = {"componentType": "module"} # Add search filter if search: @@ -91,6 +91,13 @@ def get_modules_overview(enabled_stages, search=""): module["stage_status"][stage] = calculate_stage_status( module["qc_status"], stage, stage_config ) + else: + # No QC status data - mark all stages as not started + for stage in enabled_stages: + module["stage_status"][stage] = { + "status": "not_started", + "details": {"message": "No QC status data available"}, + } non_archived_modules.append(module) diff --git a/viewer/templates/dashboard/detail.html b/viewer/templates/dashboard/detail.html index e7ddacc61..0b6f1a608 100644 --- a/viewer/templates/dashboard/detail.html +++ b/viewer/templates/dashboard/detail.html @@ -62,7 +62,14 @@

    -

    @@ -133,6 +140,55 @@
    {% if module.qc_status %} + +
    +

    + +

    +
    +
    +
    QC Status Keys:
    +
      + {% for key in module.qc_status.keys() %} +
    • {{ key }}: {{ module.qc_status[key].__class__.__name__ if module.qc_status[key] else 'None' }}
    • + {% endfor %} +
    + {% if module.qc_status.get('QC_results') %} +
    QC Results by Stage:
    +
      + {% for stage, results in module.qc_status.QC_results.items() %} +
    • {{ stage }}: {{ results|length if results else 'No results' }} results
    • + {% endfor %} +
    + {% endif %} + {% if module.qc_status.get('upload_status') %} +
    Upload Status by Stage:
    +
      + {% for stage, status in module.qc_status.upload_status.items() %} +
    • {{ stage }}: {{ status }}
    • + {% endfor %} +
    + {% endif %} +
    Enabled Stages:
    +
      + {% for stage in enabled_stages %} + {% set stage_tests = stage_config.stage_test.get(stage, []) %} +
    • {{ stage }}: {{ stage_tests|length }} tests configured
    • + {% endfor %} +
    +
    +
    +
    + {% for stage in enabled_stages %} {% set stage_tests = stage_config.stage_test.get(stage, []) %} @@ -143,7 +199,14 @@ {% if stage_tests and upload_status != 'alternative' %}

    -

    @@ -306,7 +376,14 @@ {% if module.children %}

    - diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index f7f79f99d..3a5c278bb 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -96,7 +96,7 @@ {% for module in modules %} - + {{ module.serialNumber }} {{ module.altID or '—' }} @@ -112,9 +112,9 @@
    @@ -201,10 +201,7 @@ // Initialize tooltips document.addEventListener("DOMContentLoaded", function () { - var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); - var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { - return new bootstrap.Tooltip(tooltipTriggerEl); - }); + $('[data-toggle="tooltip"]').tooltip(); }); // Add keyboard shortcut for search diff --git a/viewer/templates/parts/nav.html b/viewer/templates/parts/nav.html index 855dfa4b7..b61512b09 100644 --- a/viewer/templates/parts/nav.html +++ b/viewer/templates/parts/nav.html @@ -46,10 +46,6 @@ QC Tests

  • |
  • -
  • - Dashboard -
  • -
  • |
  • YARR Scans
  • diff --git a/viewer/templates/toppage.html b/viewer/templates/toppage.html index 8ddcf782f..0bda9f61a 100644 --- a/viewer/templates/toppage.html +++ b/viewer/templates/toppage.html @@ -85,6 +85,9 @@

      Browse Modules

    +

    +   Module Dashboard +

      Browse PCBs

    -- GitLab From 182fcfff9afb4dc0a27d42946580ea557d80a0a6 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 23:00:05 -0700 Subject: [PATCH 04/30] fix qc_status query --- viewer/functions/dashboard_queries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index b2ebbdadf..433920624 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -203,8 +203,8 @@ def get_module_details(serial_number): { "$lookup": { "from": "QC.module.status", - "localField": "_id", - "foreignField": "component", + "let": {"idStr": {"$toString": "$_id"}}, + "pipeline": [{"$match": {"$expr": {"$eq": ["$component", "$$idStr"]}}}], "as": "qc_status", } }, -- GitLab From 99404f5355a4072cc4d0d9bb86da507d1aa83eee Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 23:11:02 -0700 Subject: [PATCH 05/30] improve detail view.. hopefully we fix dashboard --- viewer/functions/dashboard_queries.py | 4 +- viewer/static/assets/css/dashboard.css | 32 ++++ viewer/templates/dashboard/detail.html | 233 ++++++++++++++----------- 3 files changed, 168 insertions(+), 101 deletions(-) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index 433920624..a18bb8890 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -44,8 +44,8 @@ def get_modules_overview(enabled_stages, search=""): { "$lookup": { "from": "QC.module.status", - "localField": "_id", - "foreignField": "component", + "let": {"idStr": {"$toString": "$_id"}}, + "pipeline": [{"$match": {"$expr": {"$eq": ["$component", "$$idStr"]}}}], "as": "qc_status", } }, diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index c59ce4098..dcc9c6398 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -340,6 +340,38 @@ font-size: 0.9rem; } +/* Detail Page Matrix Styles */ +.stage-row { + cursor: pointer; +} + +.stage-row:hover { + background-color: #f8f9fa; +} + +.test-indicators .badge { + font-size: 0.7rem; +} + +.stage-details { + border-left: 3px solid #0d6efd; +} + +.stage-details .card-sm { + border: 1px solid #e9ecef; +} + +.stage-details .card-sm:hover { + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.btn-xs { + padding: 0.125rem 0.25rem; + font-size: 0.75rem; + line-height: 1.2; + border-radius: 0.2rem; +} + /* Status Matrix Scrolling */ .table-responsive { scrollbar-width: thin; diff --git a/viewer/templates/dashboard/detail.html b/viewer/templates/dashboard/detail.html index 0b6f1a608..40ad00d9f 100644 --- a/viewer/templates/dashboard/detail.html +++ b/viewer/templates/dashboard/detail.html @@ -189,117 +189,152 @@ - - {% for stage in enabled_stages %} - {% set stage_tests = stage_config.stage_test.get(stage, []) %} - {% set qc_results = module.qc_status.QC_results.get(stage, {}) %} - {% set qc_results_pdb = module.qc_status.QC_results_pdb.get(stage, {}) %} - {% set upload_status = module.qc_status.upload_status.get(stage, '-1') %} - - {% if stage_tests and upload_status != 'alternative' %} -
    -

    - -

    -
    -
    - - {% set completed_tests = [] %} - {% set failed_tests = [] %} - {% for test in stage_tests %} - {% set local_result_id = qc_results.get(test, '-1') %} - {% if local_result_id != '-1' %} - {% set completed_tests = completed_tests.append(test) %} - {% endif %} - {% endfor %} - -
    -
    -
    - Progress: {{ completed_tests|length }}/{{ stage_tests|length }} tests completed -
    -
    - Upload Status: - {% if upload_status == '1' %} - Uploaded to Production DB - {% else %} - Not Uploaded - {% endif %} -
    -
    Stage: {{ stage_config.stages.get(stage, stage) }}
    -
    -
    + +
    +

    + +

    +
    +
    +
    + + + + + + + + + + + + {% for stage in enabled_stages %} + {% set stage_tests = stage_config.stage_test.get(stage, []) %} + {% set qc_results = module.qc_status.QC_results.get(stage, {}) %} + {% set qc_results_pdb = module.qc_status.QC_results_pdb.get(stage, {}) %} + {% set upload_status = module.qc_status.upload_status.get(stage, '-1') %} - -
    StageUpload StatusProgressTestsActions
    - - - - - - - - - - {% for test in stage_tests %} - {% set local_result_id = qc_results.get(test, '-1') %} - {% set pdb_result_id = qc_results_pdb.get(test, '-1') %} - {% set test_name = stage_config.test_items.get(test, test) %} - - + {% if stage_tests %} + + + - {% endfor %} - -
    TestLocal ResultProduction DB ResultActions
    {{ test_name }}
    + {{ stage.replace('MODULE/', '') }} + {{ stage_config.stages.get(stage, stage) }} + - {% if local_result_id == '-1' %} - Not Done + {% if upload_status == '1' %} + Uploaded + {% elif upload_status == 'alternative' %} + Alternative {% else %} - Completed - ID: {{ local_result_id }} + Not Uploaded {% endif %} - {% if pdb_result_id == '-1' %} - Not Done - {% else %} - Completed - ID: {{ pdb_result_id }} - {% endif %} + {% set completed_count = 0 %} + {% for test in stage_tests %} + {% set local_result_id = qc_results.get(test, '-1') %} + {% if local_result_id != '-1' %} + {% set completed_count = completed_count + 1 %} + {% endif %} + {% endfor %} +
    + {% set progress_pct = (completed_count / stage_tests|length * 100) if stage_tests|length > 0 else 0 %} +
    + {{ completed_count }}/{{ stage_tests|length }} +
    +
    - {% if local_result_id != '-1' %} - - View Details - - {% else %} - No result - {% endif %} +
    + {% for test in stage_tests %} + {% set local_result_id = qc_results.get(test, '-1') %} + {% set test_name = stage_config.test_items.get(test, test) %} + + {{ test_name[:8] }}{{ '...' if test_name|length > 8 else '' }} + + {% endfor %} +
    +
    +
    -
    + + + +
    +
    {{ stage.replace('MODULE/', '') }} Test Details
    +
    + {% for test in stage_tests %} + {% set local_result_id = qc_results.get(test, '-1') %} + {% set pdb_result_id = qc_results_pdb.get(test, '-1') %} + {% set test_name = stage_config.test_items.get(test, test) %} +
    +
    +
    +
    {{ test_name }}
    +
    + + Local: + {% if local_result_id != '-1' %} + ✓ {{ local_result_id }} + {% else %} + Not Done + {% endif %} + + + PDB: + {% if pdb_result_id != '-1' %} + ✓ {{ pdb_result_id }} + {% else %} + Not Done + {% endif %} + +
    + {% if local_result_id != '-1' %} + + View Result + + {% endif %} +
    +
    +
    + {% endfor %} +
    +
    + + + {% endif %} + {% endfor %} + +
    - {% endif %} - {% endfor %} +
    +
    {% endif %} -- GitLab From 9d8aa7f02c4740f9afda8cf71652b1b778266bc3 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 23:31:03 -0700 Subject: [PATCH 06/30] fix up stylistic tweaks more --- viewer/static/assets/css/dashboard.css | 27 +- viewer/templates/dashboard/detail.html | 604 ++++++++--------------- viewer/templates/dashboard/overview.html | 73 ++- 3 files changed, 276 insertions(+), 428 deletions(-) diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index dcc9c6398..f8ff94e4a 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -139,8 +139,33 @@ /* Tooltip Improvements */ .tooltip-inner { - max-width: 300px; + max-width: 400px; text-align: left; + padding: 0; +} + +.tooltip-inner .card { + margin: 0; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.tooltip-inner .card-header { + background-color: #f8f9fa; + border-bottom: 1px solid #dee2e6; +} + +.tooltip-inner .list-group-item { + border-left: none; + border-right: none; + font-size: 0.85rem; +} + +.tooltip-inner .list-group-item:first-child { + border-top: none; +} + +.tooltip-inner .list-group-item:last-child { + border-bottom: none; } /* Search and Controls */ diff --git a/viewer/templates/dashboard/detail.html b/viewer/templates/dashboard/detail.html index 40ad00d9f..042ff5186 100644 --- a/viewer/templates/dashboard/detail.html +++ b/viewer/templates/dashboard/detail.html @@ -58,420 +58,224 @@
    -
    - -
    -

    - -

    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - {% if module.qc_status %} - - - - - - - - - {% endif %} -
    Serial Number{{ module.serialNumber }}
    Alternative ID{{ module.altID or 'Not Set' }}
    Component Type{{ module.componentType }}
    Current Stage{{ module.currentStage or 'Not Set' }}
    Latest Synced Stage{{ module.qc_status.latestSyncedStage or 'Not Set' }}
    Status{{ module.qc_status.status }}
    -
    -
    - - {% if module.sys %} - - - - - - - - - - - - - {% endif %} - - - - - {% if module.qc_status %} - - - - - {% endif %} -
    Created{{ datetime.widget(module.sys.cts) }}
    Last Modified{{ datetime.widget(module.sys.mts) }}
    System Revision{{ module.sys.rev }}
    DB Version{{ module.dbVersion or 'Not Set' }}
    Production DB Version{{ module.qc_status.proddbVersion or 'Not Set' }}
    -
    -
    -
    -
    + +
    +
    + + + + + + + + + + + + + + + + + + {% if module.qc_status %} + + + + + {% endif %} +
    Serial Number{{ module.serialNumber }}
    Alternative ID{{ module.altID or 'Not Set' }}
    Component Type{{ module.componentType }}
    Current Stage{{ module.currentStage or 'Not Set' }}
    Status{{ module.qc_status.status }}
    +
    + + {% if module.sys %} + + + + + + + + + {% endif %} + + + + + {% if module.qc_status %} + + + + + {% endif %} +
    Created{{ datetime.widget(module.sys.cts) }}
    Last Modified{{ datetime.widget(module.sys.mts) }}
    DB Version{{ module.dbVersion or 'Not Set' }}
    Production DB Version{{ module.qc_status.proddbVersion or 'Not Set' }}
    +
    +
    - {% if module.qc_status %} - -
    -

    - -

    -
    -
    -
    QC Status Keys:
    -
      - {% for key in module.qc_status.keys() %} -
    • {{ key }}: {{ module.qc_status[key].__class__.__name__ if module.qc_status[key] else 'None' }}
    • - {% endfor %} -
    - {% if module.qc_status.get('QC_results') %} -
    QC Results by Stage:
    -
      - {% for stage, results in module.qc_status.QC_results.items() %} -
    • {{ stage }}: {{ results|length if results else 'No results' }} results
    • - {% endfor %} -
    - {% endif %} - {% if module.qc_status.get('upload_status') %} -
    Upload Status by Stage:
    -
      - {% for stage, status in module.qc_status.upload_status.items() %} -
    • {{ stage }}: {{ status }}
    • - {% endfor %} -
    - {% endif %} -
    Enabled Stages:
    -
      - {% for stage in enabled_stages %} - {% set stage_tests = stage_config.stage_test.get(stage, []) %} -
    • {{ stage }}: {{ stage_tests|length }} tests configured
    • - {% endfor %} -
    -
    -
    -
    - - -
    -

    - -

    -
    -
    -
    - - - - - - - - - - - - {% for stage in enabled_stages %} - {% set stage_tests = stage_config.stage_test.get(stage, []) %} - {% set qc_results = module.qc_status.QC_results.get(stage, {}) %} - {% set qc_results_pdb = module.qc_status.QC_results_pdb.get(stage, {}) %} - {% set upload_status = module.qc_status.upload_status.get(stage, '-1') %} + {% if module.qc_status %} + +

    QC Test Results

    +
    +
    StageUpload StatusProgressTestsActions
    + + + + + + + + + + {% for stage in enabled_stages %} + {% set stage_tests = stage_config.stage_test.get(stage, []) %} + {% set qc_results = module.qc_status.QC_results.get(stage, {}) %} + {% set qc_results_pdb = module.qc_status.QC_results_pdb.get(stage, {}) %} + {% set upload_status = module.qc_status.upload_status.get(stage, '-1') %} - {% if stage_tests %} - - - - - - - - - - - - {% endif %} - {% endfor %} - -
    StageUpload StatusTests StatusActions
    - {{ stage.replace('MODULE/', '') }} - {{ stage_config.stages.get(stage, stage) }} - - {% if upload_status == '1' %} - Uploaded - {% elif upload_status == 'alternative' %} - Alternative - {% else %} - Not Uploaded - {% endif %} - - {% set completed_count = 0 %} - {% for test in stage_tests %} - {% set local_result_id = qc_results.get(test, '-1') %} - {% if local_result_id != '-1' %} - {% set completed_count = completed_count + 1 %} - {% endif %} - {% endfor %} -
    - {% set progress_pct = (completed_count / stage_tests|length * 100) if stage_tests|length > 0 else 0 %} -
    - {{ completed_count }}/{{ stage_tests|length }} -
    -
    -
    -
    - {% for test in stage_tests %} - {% set local_result_id = qc_results.get(test, '-1') %} - {% set test_name = stage_config.test_items.get(test, test) %} - - {{ test_name[:8] }}{{ '...' if test_name|length > 8 else '' }} - - {% endfor %} -
    -
    - -
    -
    -
    {{ stage.replace('MODULE/', '') }} Test Details
    -
    - {% for test in stage_tests %} - {% set local_result_id = qc_results.get(test, '-1') %} - {% set pdb_result_id = qc_results_pdb.get(test, '-1') %} - {% set test_name = stage_config.test_items.get(test, test) %} -
    -
    -
    -
    {{ test_name }}
    -
    - - Local: - {% if local_result_id != '-1' %} - ✓ {{ local_result_id }} - {% else %} - Not Done - {% endif %} - - - PDB: - {% if pdb_result_id != '-1' %} - ✓ {{ pdb_result_id }} - {% else %} - Not Done - {% endif %} - -
    - {% if local_result_id != '-1' %} - - View Result - - {% endif %} -
    -
    -
    - {% endfor %} -
    -
    -
    -
    -
    -
    -
    - {% endif %} + {% if stage_tests %} + + + {{ stage.replace('MODULE/', '') }} + {{ stage_config.stages.get(stage, stage) }} + + + {% if upload_status == '1' %} + Uploaded + {% elif upload_status == 'alternative' %} + Alternative + {% else %} + Not Uploaded + {% endif %} + + +
    + {% for test in stage_tests %} + {% set local_result_id = qc_results.get(test, '-1') %} + {% set pdb_result_id = qc_results_pdb.get(test, '-1') %} + {% set test_name = stage_config.test_items.get(test, test) %} - -
    -

    - -

    -
    -
    - {% if module.properties %} - - - - - - - - - - - - {% for prop in module.properties %} - - - - - - - - {% endfor %} - -
    CodeNameValueData TypeLast Updated
    {{ prop.code }}{{ prop.name or '—' }} - {% if prop.dataType == 'codeTable' and prop.codeTable and prop.value %} - {% set found_value = false %} - {% for item in prop.codeTable %} - {% if item.code == prop.value %} - {{ item.value }} - {% set found_value = true %} - {% endif %} - {% endfor %} - {% if not found_value %} - {{ prop.value }} - {% endif %} - {% elif prop.dataType == 'boolean' %} - - {{ 'True' if prop.value else 'False' }} + {% if pdb_result_id != '-1' %} + + {{ test_name[:8] }}{{ '...' if test_name|length > 8 else '' }} + + {% elif local_result_id != '-1' %} + + {{ test_name[:8] }}{{ '...' if test_name|length > 8 else '' }} {% else %} - {{ prop.value or '—' }} - {% endif %} - - {{ prop.dataType or 'unknown' }} - {% if prop.required %} - Required + + {{ test_name[:8] }}{{ '...' if test_name|length > 8 else '' }} + {% endif %} - {{ datetime.widget(prop.dateTime) if prop.dateTime else '—' }}
    - {% else %} -
    No properties defined for this component.
    - {% endif %} -
    -
    + {% endfor %} +
    + + + + + + + + +
    +
    {{ stage.replace('MODULE/', '') }} Test Details
    +
    + {% for test in stage_tests %} + {% set local_result_id = qc_results.get(test, '-1') %} + {% set pdb_result_id = qc_results_pdb.get(test, '-1') %} + {% set test_name = stage_config.test_items.get(test, test) %} +
    +
    +
    +
    {{ test_name }}
    +
    + + Local: + {% if local_result_id != '-1' %} + {{ local_result_id }} + {% else %} + Not Done + {% endif %} + + + PDB: + {% if pdb_result_id != '-1' %} + {{ pdb_result_id }} + {% else %} + Not Done + {% endif %} + +
    + {% if local_result_id != '-1' %} + + View Result + + {% endif %} +
    +
    +
    + {% endfor %} +
    +
    + + + {% endif %} + {% endfor %} + +
    + {% endif %} - - {% if module.children %} -
    -

    - -

    -
    -
    - {% for child in module.children %} -
    -
    -
    - - {{ child.serialNumber }} - - {{ child.componentType }} -
    -
    - {% if child.properties %} -
    - - {% for prop in child.properties[:5] %} - - - - + + {% if module.children %} +

    + Sub-Components {{ module.children|length }} +

    +
    + {% for child in module.children %} +
    +
    +
    +
    + + {{ child.serialNumber }} + + {{ child.componentType }} +
    +
    + {% if child.properties %} +
    + {% for prop in child.properties[:3] %} + + {{ prop.name or prop.code }}: + {% if prop.dataType == 'codeTable' and prop.codeTable and prop.value %} + {% for item in prop.codeTable %} + {% if item.code == prop.value %}{{ item.value }}{% endif %} {% endfor %} - {% if child.properties|length > 5 %} -
    - - - {% endif %} -
    {{ prop.name or prop.code }} - {% if prop.dataType == 'codeTable' and prop.codeTable and prop.value %} - {% for item in prop.codeTable %} - {% if item.code == prop.value %}{{ item.value }}{% endif %} - {% endfor %} - {% else %} - {{ prop.value or '—' }} - {% endif %} -
    - ... and {{ child.properties|length - 5 }} more properties -
    -
    + {% else %} + {{ prop.value or '—' }} + {% endif %} + + {% endfor %} + {% if child.properties|length > 3 %} + ... and {{ child.properties|length - 3 }} more {% endif %}
    - {% endfor %} + {% endif %}
    -
    - {% endif %} -
    + {% endfor %} +
    + {% endif %} {% endblock %} -{# Helper macro for tooltip rendering #} +{# Helper macro for popover content rendering #} {% macro render_tooltip(stage, status, details, stage_config) %} -
    -
    -
    {{ stage.replace('MODULE/', '') }}
    +
    +
    {{ stage_config.stages.get(stage, stage) }}
    -
    - {% if status == 'alternative' %} -

    {{ details.message or 'Stage skipped' }}

    - {% elif status == 'not_uploaded' %} -

    {{ details.message or 'Not uploaded' }}

    - {% elif status in ['pass', 'fail', 'partial'] %} -

    Tests: {{ details.completed_tests or 0 }}/{{ details.total_tests or 0 }}

    - {% if details.passed_tests %} -

    Passed: {{ details.passed_tests }}

    - {% endif %} - {% if details.failed_tests %} -

    Failed: {{ details.failed_tests }}

    - {% endif %} - {% if details.test_details %} -
      - {% for test in details.test_details %} -
    • {{ details.message or 'Stage skipped' }}

      + {% elif status == 'not_uploaded' %} +

      {{ details.message or 'Not uploaded' }}

      + {% elif status in ['pass', 'fail', 'partial'] %} +

      Tests: {{ details.completed_tests or 0 }}/{{ details.total_tests or 0 }}

      + {% if details.passed_tests %} +

      Passed: {{ details.passed_tests }}

      + {% endif %} + {% if details.failed_tests %} +

      Failed: {{ details.failed_tests }}

      + {% endif %} + {% if details.test_details %} +
        + {% for test in details.test_details %} +
      • + {{ test.name }} + - {{ test.name }} - - {% if test.status == 'pass' %} - - {% elif test.status == 'fail' %} - - {% else %} - - {% endif %} - -
      • - {% endfor %} -
      - {% endif %} - {% elif status == 'no_tests' %} -

      No tests required

      - {% else %} -

      {{ status.replace('_', ' ').title() }}

      + {% if test.status == 'pass' %} + + {% elif test.status == 'fail' %} + + {% else %} + + {% endif %} + +
    • + {% endfor %} +
    {% endif %} -
    + {% elif status == 'no_tests' %} +

    No tests required

    + {% else %} +

    {{ status.replace('_', ' ').title() }}

    + {% endif %}
    {% endmacro %} -- GitLab From 1eb66f71c926e872fd5d5f3c8eff6fe9db992aec Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 23:58:19 -0700 Subject: [PATCH 08/30] fix macro --- viewer/templates/dashboard/overview.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 29f9ed9b7..8cd1c07d3 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -110,12 +110,13 @@ {% set status = stage_data.status if stage_data.get('status') else 'unknown' %} {% set details = stage_data.get('details', {}) %} +
    -- GitLab From 860f3bf74af4b7de80a37b93eb23c56b6b3c6088 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 00:38:30 -0700 Subject: [PATCH 09/30] fix current stage logic --- viewer/templates/dashboard/detail.html | 9 ++++++--- viewer/templates/dashboard/overview.html | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/viewer/templates/dashboard/detail.html b/viewer/templates/dashboard/detail.html index 37f4842f0..39d0d5d33 100644 --- a/viewer/templates/dashboard/detail.html +++ b/viewer/templates/dashboard/detail.html @@ -76,7 +76,9 @@ Current Stage - {{ module.currentStage or 'Not Set' }} + + {{ module.qc_status.stage.replace('MODULE/', '') if module.qc_status and module.qc_status.stage else module.currentStage.replace('MODULE/', '') if module.currentStage else 'Not Set' }} + {% if module.qc_status %} @@ -133,10 +135,11 @@ {% set upload_status = module.qc_status.upload_status.get(stage, '-1') %} {% if stage_tests %} - + {% set current_stage = module.qc_status.stage if module.qc_status and module.qc_status.stage else module.currentStage %} + {{ stage.replace('MODULE/', '') }} - {% if stage == module.currentStage %} + {% if stage == current_stage %} {% endif %} {{ stage_config.stages.get(stage, stage) }} diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 8cd1c07d3..f7b4623cc 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -104,12 +104,15 @@ {{ module.componentType.replace('_', ' ').title() }} - {{ module.currentStage.replace('MODULE/', '') if module.currentStage else '—' }} + + {{ module.qc_status.stage.replace('MODULE/', '') if module.qc_status and module.qc_status.stage else module.currentStage.replace('MODULE/', '') if module.currentStage else '—' }} + {% for stage in stages %} {% set stage_data = module.stage_status.get(stage, {}) %} {% set status = stage_data.status if stage_data.get('status') else 'unknown' %} {% set details = stage_data.get('details', {}) %} - + {% set current_stage = module.qc_status.stage if module.qc_status and module.qc_status.stage else module.currentStage %} +
    Date: Fri, 19 Sep 2025 00:49:49 -0700 Subject: [PATCH 10/30] improve upload statusing --- viewer/functions/dashboard_queries.py | 8 +---- viewer/static/assets/css/dashboard.css | 12 +++++-- viewer/templates/dashboard/overview.html | 44 ++++++++++++++---------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index a18bb8890..6ea12b178 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -114,13 +114,6 @@ def calculate_stage_status(qc_status, stage, stage_config): "details": {"message": "Stage skipped (alternative path)"}, } - # Check upload status - if upload_status == "-1": - return { - "status": "not_uploaded", - "details": {"message": "Stage not uploaded to production DB"}, - } - # Get required tests for this stage required_tests = stage_config["stage_test"].get(stage, []) if not required_tests: @@ -192,6 +185,7 @@ def calculate_stage_status(qc_status, stage, stage_config): "passed_tests": passed_tests, "failed_tests": failed_tests, "test_details": test_details, + "uploaded": upload_status == "1", }, } diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index 457bc3462..2360b618a 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -67,9 +67,15 @@ border-color: #adb5bd; } -.status-indicator.not_uploaded { - background: white; - border: 3px solid #fd7e14; +/* Not uploaded overlay - stripes on top of any background */ +.status-indicator.not-uploaded { + background-image: repeating-linear-gradient( + 45deg, + transparent, + transparent 10px, + #aaa 10px, + #aaa 20px + ); } .status-indicator.alternative { diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index f7b4623cc..75b8bb96b 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -43,15 +43,15 @@
    -
    +
    {{ modules|length }}
    -

    Total Modules

    +

    Active Modules

    -
    +
    {{ stages|length }}
    @@ -59,19 +59,11 @@
    -
    +
    -
    {{ modules|selectattr('componentType', 'equalto', 'module')|list|length }}
    -

    Modules

    -
    -
    -
    -
    -
    -
    -
    {{ modules|selectattr('componentType', 'equalto', 'bare_module')|list|length }}
    -

    Bare Modules

    +
    {{ modules|selectattr('qc_status')|list|length }}
    +

    With QC Data

    @@ -112,10 +104,11 @@ {% set status = stage_data.status if stage_data.get('status') else 'unknown' %} {% set details = stage_data.get('details', {}) %} {% set current_stage = module.qc_status.stage if module.qc_status and module.qc_status.stage else module.currentStage %} + {% set upload_status = module.qc_status.upload_status.get(stage, '-1') if module.qc_status else '-1' %}
    All Tests Pass +
    + All Tests Pass (Not Uploaded)
    Any Test Failed
    +
    + Any Test Failed (Not Uploaded)
    Partially Complete
    - Not Started
    + Partially Complete (Not Uploaded)
    - Not Uploaded
    + Not Started
    @@ -169,6 +170,11 @@ No Tests Required
    +
    + Note: Striped patterns indicate test results exist but are not uploaded to Production DB +
    -- GitLab From 045c86bc378e80fd756fd938c6459022bc4452b6 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:02:49 -0700 Subject: [PATCH 11/30] fix the stickiness --- viewer/templates/dashboard/overview.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 75b8bb96b..586ba1293 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -71,14 +71,14 @@ {% if modules %} -
    +
    - + - - - - + + + + {% for stage in stages %}
    Serial NumberAlt IDTypeCurrent StageSerial NumberAlt IDTypeCurrent Stage {{ stage.replace('MODULE/', '').replace('_', ' ') }} -- GitLab From 697ec9ed8c8d02807b22c4fcc59e2fa86c9dab7d Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:13:29 -0700 Subject: [PATCH 12/30] improve gradient more --- viewer/static/assets/css/dashboard.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index 2360b618a..56fb01f6d 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -70,11 +70,11 @@ /* Not uploaded overlay - stripes on top of any background */ .status-indicator.not-uploaded { background-image: repeating-linear-gradient( - 45deg, + 315deg, transparent, - transparent 10px, - #aaa 10px, - #aaa 20px + transparent 4px, + #999 4px, + #999 6px ); } -- GitLab From 07031240c6a30d5665d703029ec3b63bb5ac38f7 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:16:38 -0700 Subject: [PATCH 13/30] drop sticky --- viewer/templates/dashboard/overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 586ba1293..9303e7cad 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -73,7 +73,7 @@
    - + -- GitLab From 86c9aeef291c09df6b30d42acc2ea3a6497357e2 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:25:24 -0700 Subject: [PATCH 14/30] sorting modules --- viewer/functions/dashboard_queries.py | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index 6ea12b178..ed7d61179 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -101,6 +101,47 @@ def get_modules_overview(enabled_stages, search=""): non_archived_modules.append(module) + # Sort modules by progress - furthest along first + def calculate_progress_score(module, enabled_stages, stage_config): + """Calculate progress score based on current stage, upload status, and test completion""" + score = 0 + + # Primary: Current stage position (10, 20, 30, 40, etc.) + current_stage = module.get("qc_status", {}).get("stage") or module.get( + "currentStage" + ) + if current_stage and current_stage in enabled_stages: + stage_position = enabled_stages.index(current_stage) + score += (stage_position + 1) * 10 # 10, 20, 30, 40, etc. + + # Secondary: Upload status (+1 if uploaded, +0 if not) + upload_status = ( + module.get("qc_status", {}) + .get("upload_status", {}) + .get(current_stage, "-1") + ) + if upload_status == "1": + score += 1 + + # Tertiary: -1 for each incomplete test in current stage + stage_tests = stage_config["stage_test"].get(current_stage, []) + qc_results = ( + module.get("qc_status", {}).get("QC_results", {}).get(current_stage, {}) + ) + + for test in stage_tests: + test_result_id = qc_results.get(test, "-1") + if test_result_id == "-1": + score -= 1 # Penalty for incomplete test + + return score + + # Sort by progress score (descending - highest first) + non_archived_modules.sort( + key=lambda m: calculate_progress_score(m, enabled_stages, stage_config), + reverse=True, + ) + return non_archived_modules -- GitLab From b8879ddbac28ab28065faa5a4ef6163e7f46dc6f Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:33:37 -0700 Subject: [PATCH 15/30] drop search functionality --- viewer/functions/dashboard_queries.py | 16 +-------- viewer/pages/dashboard.py | 9 ++--- viewer/templates/dashboard/overview.html | 44 +++++++++--------------- 3 files changed, 20 insertions(+), 49 deletions(-) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index ed7d61179..4d1f4fc6b 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -21,24 +21,10 @@ def get_enabled_stages(stage_config): ] -def get_modules_overview(enabled_stages, search=""): +def get_modules_overview(enabled_stages): """Get all modules with stage status matrix""" match_stage = {"componentType": "module"} - # Add search filter - if search: - match_stage["$or"] = [ - {"serialNumber": {"$regex": search, "$options": "i"}}, - { - "properties": { - "$elemMatch": { - "code": "ALTERNATIVE_IDENTIFIER", - "value": {"$regex": search, "$options": "i"}, - } - } - }, - ] - pipeline = [ {"$match": match_stage}, { diff --git a/viewer/pages/dashboard.py b/viewer/pages/dashboard.py index 4b3e1d4b9..a63c616fd 100644 --- a/viewer/pages/dashboard.py +++ b/viewer/pages/dashboard.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import datetime -from flask import Blueprint, abort, jsonify, make_response, render_template, request +from flask import Blueprint, abort, jsonify, make_response, render_template from functions.dashboard_queries import ( export_modules_csv, get_enabled_stages, @@ -17,17 +17,15 @@ dashboard_bp = Blueprint("dashboard", __name__) @dashboard_bp.route("/") def overview(): """Dashboard overview page showing all modules in a status matrix""" - search = request.args.get("search", "") stage_config = get_stage_config() enabled_stages = get_enabled_stages(stage_config) - modules = get_modules_overview(enabled_stages, search) + modules = get_modules_overview(enabled_stages) return render_template( "dashboard/overview.html", modules=modules, stages=enabled_stages, stage_config=stage_config, - search=search, ) @@ -68,9 +66,8 @@ def detail(serial_number): @dashboard_bp.route("/api/refresh") def api_refresh(): """API endpoint for auto-refresh functionality""" - search = request.args.get("search", "") stage_config = get_stage_config() enabled_stages = get_enabled_stages(stage_config) - modules = get_modules_overview(enabled_stages, search) + modules = get_modules_overview(enabled_stages) return jsonify({"modules": modules, "timestamp": datetime.now().isoformat()}) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 9303e7cad..7094923c7 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -15,29 +15,16 @@ - +
    -
    -
    - - - {% if search %} - Clear - {% endif %} - -
    -
    -
    +
    +
    + + Export CSV +
    @@ -77,10 +64,10 @@
    - - + + {% for stage in stages %} - {% endfor %} @@ -218,13 +205,14 @@ }); }); - // Add keyboard shortcut for search + // Initialize tablesorter document.addEventListener("DOMContentLoaded", function () { - document.addEventListener("keydown", function (e) { - if (e.ctrlKey && e.key === "f") { - e.preventDefault(); - document.getElementById("searchInput").focus(); - } + $("#dashboardTable").tablesorter({ + widgets: ["filter", "zebra", "columns"], + usNumberFormat: false, + sortReset: true, + sortRestart: true, + theme: "bootstrap", }); }); -- GitLab From 3bfdb3e0d27abd1d4d20d0d3dfd793ad63476c5a Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:39:53 -0700 Subject: [PATCH 16/30] fix up tablesorter, disable --- viewer/templates/dashboard/overview.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 7094923c7..61ac4f1bb 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -60,14 +60,14 @@
    Serial Number Alt ID
    Serial Number Alt IDTypeCurrent StageTypeCurrent Stage + {{ stage.replace('MODULE/', '').replace('_', ' ') }}
    - + - + - + {% for stage in stages %} - {% endfor %} -- GitLab From c31844109351ee91e01a2628ffd288c350d55f7d Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:50:46 -0700 Subject: [PATCH 17/30] drop unused styles --- viewer/static/assets/css/dashboard.css | 100 +------------------------ 1 file changed, 2 insertions(+), 98 deletions(-) diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index 56fb01f6d..39a861c23 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -1,18 +1,3 @@ -/* Dashboard Overview Styles */ -.dashboard-matrix { - font-size: 0.85rem; - white-space: nowrap; -} - -.fixed-column { - position: sticky; - left: 0; - background: white; - z-index: 10; - border-right: 2px solid #dee2e6; - min-width: 150px; -} - .stage-header { writing-mode: vertical-lr; text-orientation: mixed; @@ -94,17 +79,6 @@ border-color: #343a40; } -/* Row Hover Effects */ -.module-row:hover { - background-color: #f8f9fa !important; - cursor: pointer; - transition: background-color 0.2s ease; -} - -.module-row:hover .fixed-column { - background-color: #f8f9fa !important; -} - /* Legend Styles */ .dashboard-legend { background: #f8f9fa; @@ -144,50 +118,6 @@ } -/* Search and Controls */ -.form-control:focus { - border-color: #80bdff; - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); -} - -.btn-group .badge { - font-size: 0.75em; -} - -/* Detail Page Styles */ -.accordion-button:not(.collapsed) { - background-color: #e7f1ff; - border-color: #b6d7ff; -} - -.accordion-button:focus { - box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); -} - -.serialNumber { - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; - font-weight: bold; -} - -/* Stage Status Badges in Detail View */ -.stage-progress { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 15px; -} - -.stage-progress .progress { - flex: 1; - height: 8px; -} - -/* Property Tables */ -.table td.fw-bold { - width: 30%; - color: #495057; -} - /* Child Component Cards */ .card-title a { color: #0d6efd; @@ -322,13 +252,6 @@ } } -/* Search Highlighting */ -.search-highlight { - background-color: #fff3cd; - padding: 2px 4px; - border-radius: 3px; -} - /* Summary Cards */ .card .card-title { font-size: 2rem; @@ -373,25 +296,6 @@ border-radius: 0.2rem; } -/* Status Matrix Scrolling */ -.table-responsive { - scrollbar-width: thin; - scrollbar-color: #ced4da #f8f9fa; -} - -.table-responsive::-webkit-scrollbar { - height: 8px; -} - -.table-responsive::-webkit-scrollbar-track { - background: #f8f9fa; +.tablesorter-bootstrap > tbody > tr > td.stage-cell.table-primary { + background-color: #b8daff; /* needed to override tablesorter-bootstrap */ } - -.table-responsive::-webkit-scrollbar-thumb { - background: #ced4da; - border-radius: 4px; -} - -.table-responsive::-webkit-scrollbar-thumb:hover { - background: #adb5bd; -} \ No newline at end of file -- GitLab From 57ef1b3b97879166c641c444cdd0df756b31d487 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:54:15 -0700 Subject: [PATCH 18/30] drop rowspan --- viewer/templates/dashboard/overview.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 61ac4f1bb..fd4c2a385 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -62,10 +62,10 @@
    Serial NumberSerial Number Alt IDTypeType Current Stage + {{ stage.replace('MODULE/', '').replace('_', ' ') }}
    - - - - + + + + {% for stage in stages %}
    Serial NumberAlt IDTypeCurrent StageSerial NumberAlt IDTypeCurrent Stage {{ stage.replace('MODULE/', '').replace('_', ' ') }} -- GitLab From cd8db588f6a5a2ce3414b43a192b05bf6e0a59f8 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:57:04 -0700 Subject: [PATCH 19/30] add important --- viewer/static/assets/css/dashboard.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index 39a861c23..c24d1efd9 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -296,6 +296,6 @@ border-radius: 0.2rem; } -.tablesorter-bootstrap > tbody > tr > td.stage-cell.table-primary { - background-color: #b8daff; /* needed to override tablesorter-bootstrap */ +td.stage-cell.table-primary { + background-color: #b8daff !important; /* needed to override tablesorter-bootstrap */ } -- GitLab From 45ca94f773dcd388d4085584f75c192f74af7949 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 01:59:07 -0700 Subject: [PATCH 20/30] flip notests and not started colors --- viewer/static/assets/css/dashboard.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index c24d1efd9..0b611020b 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -47,7 +47,7 @@ border-color: #ffc107; } -.status-indicator.not_started { +.status-indicator.no_tests { background: #e9ecef; border-color: #adb5bd; } @@ -69,7 +69,7 @@ border-color: #6c757d; } -.status-indicator.no_tests { +.status-indicator.not_started { background: #6c757d; border-color: #6c757d; } -- GitLab From f625db79d778fee2809d2260f739b8463ba23689 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 02:07:14 -0700 Subject: [PATCH 21/30] one last change to html --- viewer/templates/dashboard/overview.html | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index fd4c2a385..03da813d7 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -9,22 +9,15 @@

    Module Dashboard

    -
    - - Export CSV -
    -
    - -
    -
    + +
    - - Export CSV - + + Export CSV
    @@ -112,7 +105,7 @@ {% else %}
    - No modules found{% if search %}matching "{{ search }}"{% endif %}. + No modules found.
    {% endif %} @@ -173,8 +166,7 @@ btn.innerHTML = ' Refreshing...'; btn.disabled = true; - const search = document.getElementById("searchInput").value; - const url = '{{ url_for("dashboard.api_refresh") }}' + (search ? `?search=${encodeURIComponent(search)}` : ""); + const url = '{{ url_for("dashboard.api_refresh") }}'; fetch(url) .then((response) => response.json()) -- GitLab From 7d9cefc69ef931cce3cfa96f9b727483781efe64 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 02:12:55 -0700 Subject: [PATCH 22/30] last change to html i swear --- viewer/templates/dashboard/overview.html | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 03da813d7..c5977320e 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -11,14 +11,12 @@

    Module Dashboard

    -
    -
    - - -
    - - Export CSV +
    + +
    + + Export CSV
    -- GitLab From 1edb7cb71b84d6d073da47170de84c4e5182fab9 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 02:15:48 -0700 Subject: [PATCH 23/30] fix table highlighting --- viewer/templates/dashboard/detail.html | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/viewer/templates/dashboard/detail.html b/viewer/templates/dashboard/detail.html index 39d0d5d33..d0b9b8064 100644 --- a/viewer/templates/dashboard/detail.html +++ b/viewer/templates/dashboard/detail.html @@ -46,14 +46,6 @@ Component Details
    -
    -
    - - -
    -
    @@ -119,7 +111,7 @@

    QC Test Results

    - + -- GitLab From 1efd7aff837a0a95c94d2b695431a12020e74c20 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 02:16:55 -0700 Subject: [PATCH 24/30] Revert "flip notests and not started colors" This reverts commit 45ca94f773dcd388d4085584f75c192f74af7949. --- viewer/static/assets/css/dashboard.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index 0b611020b..c24d1efd9 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -47,7 +47,7 @@ border-color: #ffc107; } -.status-indicator.no_tests { +.status-indicator.not_started { background: #e9ecef; border-color: #adb5bd; } @@ -69,7 +69,7 @@ border-color: #6c757d; } -.status-indicator.not_started { +.status-indicator.no_tests { background: #6c757d; border-color: #6c757d; } -- GitLab From 1ded9577b73c555ad26f35f1a310d532621ec0df Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 20:02:37 -0700 Subject: [PATCH 25/30] make sure we handle disabled tests within a stage too --- viewer/functions/dashboard_queries.py | 28 +++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index 4d1f4fc6b..e30970374 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -109,8 +109,19 @@ def get_modules_overview(enabled_stages): if upload_status == "1": score += 1 - # Tertiary: -1 for each incomplete test in current stage - stage_tests = stage_config["stage_test"].get(current_stage, []) + # Tertiary: -1 for each incomplete test in current stage (excluding disabled tests) + all_tests = stage_config["stage_test"].get(current_stage, []) + disabled_tests = stage_config.get("disabled_tests", {}).get( + current_stage, {} + ) + + # Filter out disabled tests + stage_tests = [ + test + for test in all_tests + if not disabled_tests.get(test, {}).get("disabled", False) + ] + qc_results = ( module.get("qc_status", {}).get("QC_results", {}).get(current_stage, {}) ) @@ -141,8 +152,17 @@ def calculate_stage_status(qc_status, stage, stage_config): "details": {"message": "Stage skipped (alternative path)"}, } - # Get required tests for this stage - required_tests = stage_config["stage_test"].get(stage, []) + # Get required tests for this stage, excluding disabled ones + all_tests = stage_config["stage_test"].get(stage, []) + disabled_tests = stage_config.get("disabled_tests", {}).get(stage, {}) + + # Filter out disabled tests + required_tests = [ + test + for test in all_tests + if not disabled_tests.get(test, {}).get("disabled", False) + ] + if not required_tests: return { "status": "no_tests", -- GitLab From bae8ff3b6a3aa78423d9c232a765abd01c3b60c8 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 19 Sep 2025 20:12:32 -0700 Subject: [PATCH 26/30] should be using the 'tests' array for disabled tests under 'disabled_tests' instead of looking at whether each test had a 'disabled' key --- viewer/functions/dashboard_queries.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index e30970374..2a838a98b 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -111,15 +111,16 @@ def get_modules_overview(enabled_stages): # Tertiary: -1 for each incomplete test in current stage (excluding disabled tests) all_tests = stage_config["stage_test"].get(current_stage, []) - disabled_tests = stage_config.get("disabled_tests", {}).get( + disabled_tests_config = stage_config.get("disabled_tests", {}).get( current_stage, {} ) + # Get list of disabled tests (they're stored in a 'tests' array) + disabled_tests_list = disabled_tests_config.get("tests", []) + # Filter out disabled tests stage_tests = [ - test - for test in all_tests - if not disabled_tests.get(test, {}).get("disabled", False) + test for test in all_tests if test not in disabled_tests_list ] qc_results = ( @@ -154,14 +155,13 @@ def calculate_stage_status(qc_status, stage, stage_config): # Get required tests for this stage, excluding disabled ones all_tests = stage_config["stage_test"].get(stage, []) - disabled_tests = stage_config.get("disabled_tests", {}).get(stage, {}) + disabled_tests_config = stage_config.get("disabled_tests", {}).get(stage, {}) + + # Get list of disabled tests (they're stored in a 'tests' array) + disabled_tests_list = disabled_tests_config.get("tests", []) # Filter out disabled tests - required_tests = [ - test - for test in all_tests - if not disabled_tests.get(test, {}).get("disabled", False) - ] + required_tests = [test for test in all_tests if test not in disabled_tests_list] if not required_tests: return { -- GitLab From f5f940259ad4c5bbb2b25b57b22daffb22cf121a Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Mon, 22 Sep 2025 12:40:46 -0700 Subject: [PATCH 27/30] fix pagination --- viewer/pages/toppage.py | 17 ++++++++++++++--- viewer/templates/components_table.html | 16 +++++++++------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/viewer/pages/toppage.py b/viewer/pages/toppage.py index 0cfd6afeb..0e507a0ff 100644 --- a/viewer/pages/toppage.py +++ b/viewer/pages/toppage.py @@ -299,7 +299,7 @@ def show_comps(): for tag in userdb.viewer.tag.docs.find( {"componentid": str(component.get("_id"))} ): - component["tags"].append(tag.get("name").upper()) + component["tags"].append(tag.get("name").lower()) if not doShowAll and "archived" in component.get("tags", []): continue @@ -307,7 +307,18 @@ def show_comps(): filtered_components.append(component) nModulesTotal = len(filtered_components) - maxPage = len(filtered_components) // compsPerPage + 1 + maxPage = ( + (len(filtered_components) + compsPerPage - 1) // compsPerPage + if len(filtered_components) > 0 + else 1 + ) + + # Validate page number - redirect to valid page if out of bounds + if page < 1: + page = 1 + elif page > maxPage: + page = maxPage + minRange = max(1, page - 2) maxRange = min(maxPage, minRange + 3) pageRange = list(range(minRange, maxRange + 1)) @@ -322,7 +333,7 @@ def show_comps(): cmp_ids = [] for component in filtered_components[ - (page - 1) * compsPerPage : page * compsPerPage - 1 + (page - 1) * compsPerPage : page * compsPerPage ]: logger.debug("component: {}".format(component.get("serialNumber"))) cmp_id = str(component["_id"]) diff --git a/viewer/templates/components_table.html b/viewer/templates/components_table.html index e466edfda..32c2ff6c8 100644 --- a/viewer/templates/components_table.html +++ b/viewer/templates/components_table.html @@ -111,14 +111,16 @@ {% endif %} {% for stage in stages %} - {% endfor %} @@ -86,7 +93,7 @@ {% for stage in stages %} - {% endfor %} -- GitLab From d1cbd01103720b1b1a006b4e80c9d73e48531b73 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Mon, 22 Sep 2025 13:09:14 -0700 Subject: [PATCH 30/30] fix up using warning for required and not done --- viewer/templates/dashboard/overview.html | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 49b523888..56a223b82 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -288,15 +288,18 @@ {% for test in all_tests %} {% set test_name = stage_config.test_items.get(test, test) %} {% set is_disabled = test in disabled_tests_list %} + {% set is_required = not is_disabled %}
  • {{ test_name }} - + {% if is_disabled %} Disabled + {% elif is_required %} + Required {% else %} - Enabled + Optional {% endif %}
  • -- GitLab
    Stage Upload Status - {% for subcomponent in component['cps'] %} - {{ subcomponent['name'] }} -  {{ subcomponent['typeinfo'] }} - {% if subcomponent['hex'] != '' %} + {% for subcomponent in component.get('cps', []) %} + {% if subcomponent.get('name') %} + {{ subcomponent['name'] }} +  {{ subcomponent.get('typeinfo', '') }} + {% if subcomponent.get('hex', '') != '' %} + {% endif %} +
    {% endif %} -
    {% endfor %}
    -- GitLab From 2825b09ea5414d2723213ee67bb290f48dabda35 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Mon, 22 Sep 2025 12:51:57 -0700 Subject: [PATCH 28/30] show all stages, indicate which ones are disabled, etc. --- viewer/functions/dashboard_queries.py | 44 ++++++++++++----- viewer/pages/dashboard.py | 7 ++- viewer/static/assets/css/dashboard.css | 6 +++ viewer/templates/dashboard/overview.html | 61 +++++++++++++++++++++++- 4 files changed, 102 insertions(+), 16 deletions(-) diff --git a/viewer/functions/dashboard_queries.py b/viewer/functions/dashboard_queries.py index 2a838a98b..1173299ac 100644 --- a/viewer/functions/dashboard_queries.py +++ b/viewer/functions/dashboard_queries.py @@ -21,8 +21,15 @@ def get_enabled_stages(stage_config): ] -def get_modules_overview(enabled_stages): +def get_all_stages(stage_config): + """Get all stages including disabled ones""" + return stage_config["stage_flow"] + + +def get_modules_overview(enabled_stages, all_stages=None): """Get all modules with stage status matrix""" + if all_stages is None: + all_stages = enabled_stages match_stage = {"componentType": "module"} pipeline = [ @@ -70,20 +77,33 @@ def get_modules_overview(enabled_stages): None, ) - # Calculate stage status for each enabled stage + # Calculate stage status for all stages (including disabled ones) module["stage_status"] = {} if module.get("qc_status"): - for stage in enabled_stages: - module["stage_status"][stage] = calculate_stage_status( - module["qc_status"], stage, stage_config - ) + for stage in all_stages: + if stage in enabled_stages: + module["stage_status"][stage] = calculate_stage_status( + module["qc_status"], stage, stage_config + ) + else: + # Disabled stage + module["stage_status"][stage] = { + "status": "disabled", + "details": {"message": "Stage is disabled"}, + } else: - # No QC status data - mark all stages as not started - for stage in enabled_stages: - module["stage_status"][stage] = { - "status": "not_started", - "details": {"message": "No QC status data available"}, - } + # No QC status data + for stage in all_stages: + if stage in enabled_stages: + module["stage_status"][stage] = { + "status": "not_started", + "details": {"message": "No QC status data available"}, + } + else: + module["stage_status"][stage] = { + "status": "disabled", + "details": {"message": "Stage is disabled"}, + } non_archived_modules.append(module) diff --git a/viewer/pages/dashboard.py b/viewer/pages/dashboard.py index a63c616fd..d94295932 100644 --- a/viewer/pages/dashboard.py +++ b/viewer/pages/dashboard.py @@ -5,6 +5,7 @@ from datetime import datetime from flask import Blueprint, abort, jsonify, make_response, render_template from functions.dashboard_queries import ( export_modules_csv, + get_all_stages, get_enabled_stages, get_module_details, get_modules_overview, @@ -19,12 +20,14 @@ def overview(): """Dashboard overview page showing all modules in a status matrix""" stage_config = get_stage_config() enabled_stages = get_enabled_stages(stage_config) - modules = get_modules_overview(enabled_stages) + all_stages = get_all_stages(stage_config) + modules = get_modules_overview(enabled_stages, all_stages) return render_template( "dashboard/overview.html", modules=modules, - stages=enabled_stages, + stages=all_stages, + enabled_stages=enabled_stages, stage_config=stage_config, ) diff --git a/viewer/static/assets/css/dashboard.css b/viewer/static/assets/css/dashboard.css index c24d1efd9..9a3cbf212 100644 --- a/viewer/static/assets/css/dashboard.css +++ b/viewer/static/assets/css/dashboard.css @@ -79,6 +79,12 @@ border-color: #343a40; } +.status-indicator.disabled { + background: #e9ecef; + border: 2px dashed #adb5bd; + opacity: 0.7; +} + /* Legend Styles */ .dashboard-legend { background: #f8f9fa; diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index c5977320e..5dce1752f 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -58,7 +58,14 @@ Type Current Stage + {{ stage.replace('MODULE/', '').replace('_', ' ') }}
    No Tests Required +
    + Stage Disabled
    {% endmacro %} + +{# Helper macro for stage header tooltips #} +{% macro render_stage_tooltip(stage, stage_config, enabled_stages) %} +
    +
    + {{ stage.replace('MODULE/', '').replace('_', ' ') }} +
    + + {% if stage in enabled_stages %} +

    Stage is enabled

    + + {% set all_tests = stage_config.stage_test.get(stage, []) %} + {% set disabled_tests_config = stage_config.get('disabled_tests', {}).get(stage, {}) %} + {% set disabled_tests_list = disabled_tests_config.get('tests', []) %} + + {% if all_tests %} +
    + Tests ({{ all_tests|length }} total): +
    +
      + {% for test in all_tests %} + {% set test_name = stage_config.test_items.get(test, test) %} + {% set is_disabled = test in disabled_tests_list %} +
    • + {{ test_name }} + + {% if is_disabled %} + Disabled + {% else %} + Enabled + {% endif %} + +
    • + {% endfor %} +
    + {% else %} +

    No tests configured for this stage

    + {% endif %} + {% else %} +

    Stage is disabled

    +

    This stage is not currently active in the workflow

    + {% endif %} +
    +{% endmacro %} -- GitLab From b0fba48bb112c97e1b3f2aaa42d95976f0d14b7c Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Mon, 22 Sep 2025 12:57:01 -0700 Subject: [PATCH 29/30] single quote --- viewer/templates/dashboard/overview.html | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/viewer/templates/dashboard/overview.html b/viewer/templates/dashboard/overview.html index 5dce1752f..49b523888 100644 --- a/viewer/templates/dashboard/overview.html +++ b/viewer/templates/dashboard/overview.html @@ -58,14 +58,13 @@
    Type Current Stage + + {{ stage.replace('MODULE/', '').replace('_', ' ') }}