File indexing completed on 2026-06-26 08:40:26
0001 {% load static tz %}
0002 <!DOCTYPE html>
0003 <html lang="en">
0004 <head>
0005 <meta charset="UTF-8">
0006 <meta name="viewport" content="width=device-width, initial-scale=1.0">
0007 <title>{% block title %}SWF Monitor{% endblock %}</title>
0008 <script>
0009 (function() {
0010 var mode = 'production';
0011 try {
0012 mode = localStorage.getItem('navMode') || 'production';
0013 } catch (e) {}
0014 if (mode !== 'testbed') mode = 'production';
0015 document.documentElement.setAttribute('data-nav-mode', mode);
0016 })();
0017 </script>
0018 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
0019 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
0020 <link rel="stylesheet" href="{% static 'css/style.css' %}">
0021 <link rel="stylesheet" href="{% static 'css/state-colors.css' %}">
0022 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css">
0023 <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
0024 <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
0025 <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
0026 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
0027 <style>
0028 body {
0029 margin: 0;
0030 padding: 0;
0031 }
0032 main {
0033 padding: 0.65rem 1rem 1rem;
0034 position: relative;
0035 }
0036 main > .container,
0037 main > .container-fluid {
0038 margin-top: 0 !important;
0039 }
0040 main > h1,
0041 main > h2,
0042 main > .container > h1,
0043 main > .container > h2,
0044 main > .container-fluid > h1,
0045 main > .container-fluid > h2,
0046 main > .container > div.mb-3 > h1,
0047 main > .container > div.mb-3 > h2,
0048 main > .container > div.mb-4 > div > h1,
0049 main > .container > div.mb-4 > div > h2,
0050 main > .container > .d-flex h1,
0051 main > .container > .d-flex h2,
0052 main > .container-fluid > div.mb-3 > h1,
0053 main > .container-fluid > div.mb-3 > h2,
0054 main > .container-fluid > div.mb-4 > div > h1,
0055 main > .container-fluid > div.mb-4 > div > h2,
0056 main > .container-fluid > .d-flex h1,
0057 main > .container-fluid > .d-flex h2 {
0058 font-size: 1.7rem;
0059 line-height: 1.2;
0060 margin-top: 0 !important;
0061 }
0062 main > h1,
0063 main > h2,
0064 main > .container > h1,
0065 main > .container > h2,
0066 main > .container-fluid > h1,
0067 main > .container-fluid > h2,
0068 main > .container > div.mb-3 > h1,
0069 main > .container > div.mb-3 > h2,
0070 main > .container > div.mb-4 > div > h1,
0071 main > .container > div.mb-4 > div > h2,
0072 main > .container-fluid > div.mb-3 > h1,
0073 main > .container-fluid > div.mb-3 > h2,
0074 main > .container-fluid > div.mb-4 > div > h1,
0075 main > .container-fluid > div.mb-4 > div > h2 {
0076 margin-bottom: 0.2rem !important;
0077 }
0078 main > .container > .d-flex h1,
0079 main > .container > .d-flex h2,
0080 main > .container-fluid > .d-flex h1,
0081 main > .container-fluid > .d-flex h2 {
0082 margin-bottom: 0 !important;
0083 }
0084 .btn.btn-dark-green {
0085 background-color: #166534; border-color: #166534; color: #fff;
0086 }
0087 .btn.btn-dark-green:hover, .btn.btn-dark-green:focus {
0088 background-color: #14532d; border-color: #14532d; color: #fff;
0089 }
0090 .btn.btn-dark-green:disabled, .btn.btn-dark-green.disabled {
0091 background-color: #6c757d; border-color: #6c757d; color: #e9ecef;
0092 }
0093 #js-error-overlay {
0094 position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
0095 z-index: 100000; max-width: 720px; width: calc(100vw - 2rem);
0096 background: #c9302c; color: #fff; padding: 1.25rem 1.5rem;
0097 border: 3px solid #7a1a18; border-radius: .5rem;
0098 box-shadow: 0 10px 30px rgba(0,0,0,.4);
0099 font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
0100 display: none;
0101 }
0102 #js-error-overlay h3 { margin: 0 0 .5rem; font-size: 1.1rem; }
0103 #js-error-overlay pre {
0104 background: rgba(0,0,0,.25); color: #fff; padding: .5rem .75rem;
0105 border-radius: .25rem; margin: .5rem 0 0; font-size: .8rem;
0106 white-space: pre-wrap; word-break: break-word; max-height: 40vh; overflow: auto;
0107 }
0108 #js-error-overlay .close-btn {
0109 position: absolute; top: .5rem; right: .75rem;
0110 background: transparent; border: none; color: #fff; font-size: 1.5rem;
0111 cursor: pointer; line-height: 1;
0112 }
0113 nav {
0114 display: flex;
0115 align-items: center;
0116 gap: .8em;
0117 }
0118 .sticky-header {
0119 position: sticky;
0120 top: 0;
0121 z-index: 1030;
0122 }
0123 .nav-spacer {
0124 flex: 1 1 auto;
0125 }
0126 .nav-auth {
0127 display: flex;
0128 gap: .8em;
0129 }
0130 .main-time-info {
0131 color: #667085;
0132 float: right;
0133 font-size: .75rem;
0134 margin: -.25rem 0 .25rem 1rem;
0135 white-space: nowrap;
0136 }
0137 nav a {
0138 color: #ADD8E6;
0139 }
0140 nav a.nav-active {
0141 text-shadow: 0 0 .65px currentColor, 0 0 .65px currentColor;
0142 }
0143 /* Dropdown Button */
0144 .dropbtn {
0145 background-color: inherit;
0146 color: #8fc7e8;
0147 text-decoration: none;
0148 padding: 0;
0149 font-size: inherit;
0150 border: none;
0151 cursor: pointer;
0152 }
0153 .dropbtn.nav-active {
0154 text-shadow: 0 0 .65px currentColor, 0 0 .65px currentColor;
0155 }
0156
0157 /* The container <div> - needed to position the dropdown content */
0158 .dropdown {
0159 position: relative;
0160 display: inline-block;
0161 margin-right: .35em;
0162 }
0163
0164 /* Dropdown Content (Hidden by Default) */
0165 .dropdown-content {
0166 display: none;
0167 position: absolute;
0168 background-color: #f9f9f9;
0169 min-width: 160px;
0170 box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
0171 z-index: 1;
0172 }
0173
0174 /* Links inside the dropdown */
0175 .dropdown-content a {
0176 color: black;
0177 padding: 12px 16px;
0178 text-decoration: none;
0179 display: block;
0180 }
0181 .dropdown-content a.nav-active {
0182 background-color: #e9ecef;
0183 }
0184
0185 /* Change color of dropdown links on hover */
0186 .dropdown-content a:hover {background-color: #f1f1f1}
0187
0188 /* Show the dropdown menu on hover */
0189 .dropdown:hover .dropdown-content {
0190 display: block;
0191 }
0192 /* Nav mode flipper */
0193 .nav-mode { display: none; }
0194 .nav-mode.active { display: contents; }
0195 html[data-nav-mode="production"] .nav-production,
0196 html[data-nav-mode="testbed"] .nav-testbed { display: contents; }
0197 #mode-label {
0198 border-right: 1px solid rgba(255,255,255,.2);
0199 color: #fff;
0200 padding-right: 12px;
0201 margin-right: 4px;
0202 }
0203 #mode-label .mode-title { display: none; }
0204 html[data-nav-mode="production"] #mode-label .mode-title-production,
0205 html[data-nav-mode="testbed"] #mode-label .mode-title-testbed { display: inline; }
0206 .nav-label {
0207 color: #fff;
0208 font-weight: 700;
0209 }
0210 .nav-panda-label {
0211 margin-left: 1.5em;
0212 }
0213 .nav-system-error { color: #ff8174 !important; font-weight: 700; }
0214 /* One-click copy icon for IDs — see swf_fmt.copy_btn template tag */
0215 .copy-btn {
0216 background: none; border: none; color: #6c757d; cursor: pointer;
0217 padding: 0 .25rem; font-size: .85em; line-height: 1; vertical-align: baseline;
0218 }
0219 .copy-btn:hover { color: #0d6efd; }
0220 .copy-btn.copied { color: #198754; }
0221 </style>
0222 </head>
0223 <body>
0224 <div id="js-error-overlay" role="alert">
0225 <button class="close-btn" onclick="document.getElementById('js-error-overlay').style.display='none';">×</button>
0226 <h3>JavaScript error</h3>
0227 <div id="js-error-msg"></div>
0228 <pre id="js-error-detail"></pre>
0229 </div>
0230 <script>
0231 (function() {
0232 function show(title, detail) {
0233 var box = document.getElementById('js-error-overlay');
0234 var msg = document.getElementById('js-error-msg');
0235 var det = document.getElementById('js-error-detail');
0236 if (!box || !msg || !det) return;
0237 msg.textContent = title || 'Error';
0238 det.textContent = detail || '';
0239 box.style.display = 'block';
0240 }
0241 window.addEventListener('error', function(ev) {
0242 var err = ev.error;
0243 var loc = (ev.filename || '') + (ev.lineno ? ':' + ev.lineno + ':' + (ev.colno || 0) : '');
0244 var stack = err && err.stack ? err.stack : '';
0245 show(ev.message || 'Script error', (loc ? loc + '\n\n' : '') + stack);
0246 });
0247 window.addEventListener('unhandledrejection', function(ev) {
0248 var r = ev.reason;
0249 var msg = (r && r.message) ? r.message : String(r);
0250 var stack = (r && r.stack) ? r.stack : '';
0251 show('Unhandled promise rejection: ' + msg, stack);
0252 });
0253 })();
0254 // One-click copy-to-clipboard handler for .copy-btn buttons rendered by
0255 // the copy_btn template tag (swf_fmt). Swaps the icon to a checkmark for
0256 // 1s on success so the user sees the click landed.
0257 document.addEventListener('click', function(ev) {
0258 var btn = ev.target.closest('.copy-btn');
0259 if (!btn) return;
0260 ev.preventDefault();
0261 var text = btn.getAttribute('data-copy') || '';
0262 if (!text || !navigator.clipboard) return;
0263 navigator.clipboard.writeText(text).then(function() {
0264 var icon = btn.querySelector('i');
0265 if (!icon) return;
0266 var prev = icon.className;
0267 icon.className = 'bi bi-clipboard-check';
0268 btn.classList.add('copied');
0269 setTimeout(function() {
0270 icon.className = prev;
0271 btn.classList.remove('copied');
0272 }, 1000);
0273 });
0274 });
0275 // Near-zero-latency tooltips via Bootstrap 5 for elements with title=.
0276 // Scoped to state-filled cells and the copy button so we don't spin up
0277 // a tooltip for every cell on every page. show delay 50ms, hide 0ms.
0278 document.addEventListener('DOMContentLoaded', function() {
0279 if (!window.bootstrap || !bootstrap.Tooltip) return;
0280 function init(root) {
0281 root.querySelectorAll(
0282 'td[title]:not([data-bs-tooltip-init]), .copy-btn[title]:not([data-bs-tooltip-init])'
0283 ).forEach(function(el) {
0284 if (!el.getAttribute('title')) return;
0285 el.setAttribute('data-bs-tooltip-init', '1');
0286 new bootstrap.Tooltip(el, {delay: {show: 50, hide: 0}});
0287 });
0288 }
0289 init(document);
0290 // DataTables redraws replace rows; re-init after each draw.
0291 if (window.jQuery) {
0292 jQuery(document).on('draw.dt', function(_, settings) {
0293 init(settings.nTable || document);
0294 });
0295 }
0296 });
0297 </script>
0298 <div class="sticky-header">
0299 <nav>
0300 <div class="dropdown" id="mode-flipper">
0301 <button class="dropbtn" id="mode-label">
0302 <span class="mode-title mode-title-production">epicprod</span>
0303 <span class="mode-title mode-title-testbed">ePIC Testbed</span>
0304 </button>
0305 <div class="dropdown-content">
0306 <a href="{% url 'monitor_app:prod_hub' %}" id="flip-production" onclick="setNavMode('production');">epicprod - ePIC Production</a>
0307 <a href="{% if is_tunnel %}https://pandaserver02.sdcc.bnl.gov/swf-monitor/testbed/{% else %}{% url 'monitor_app:testbed_hub' %}{% endif %}" id="flip-testbed" onclick="setNavMode('testbed');">ePIC Testbed</a>
0308 </div>
0309 </div>
0310
0311 {# ── Testbed mode ── #}
0312 <span class="nav-mode nav-testbed">
0313 <div class="dropdown" id="system-status-dropdown">
0314 <button class="dropbtn{% if active_nav.workflows %} nav-active{% endif %}">Workflows</button>
0315 <div class="dropdown-content">
0316 <a href="{% url 'monitor_app:workflows_home' %}" class="{% if active_nav.workflows %}nav-active{% endif %}">Views</a>
0317 <a href="{% url 'monitor_app:workflow_definitions_list' %}" class="{% if active_nav.workflows %}nav-active{% endif %}">Workflow definitions</a>
0318 <a href="{% url 'monitor_app:workflow_executions_list' %}" class="{% if active_nav.workflows %}nav-active{% endif %}">Workflow executions</a>
0319 <a href="{% url 'monitor_app:namespaces_list' %}" class="{% if active_nav.workflows %}nav-active{% endif %}">Namespaces</a>
0320 </div>
0321 </div>
0322 <div class="dropdown">
0323 <button class="dropbtn{% if active_nav.files %} nav-active{% endif %}">Files</button>
0324 <div class="dropdown-content">
0325 <a href="{% url 'monitor_app:stf_files_list' %}" class="{% if active_nav.files %}nav-active{% endif %}">STF Files</a>
0326 <a href="{% url 'monitor_app:fastmon_files_list' %}" class="{% if active_nav.files %}nav-active{% endif %}">STF Samples</a>
0327 <a href="{% url 'monitor_app:tf_slices_list' %}" class="{% if active_nav.files %}nav-active{% endif %}">TF Slices</a>
0328 </div>
0329 </div>
0330 <a href="{% url 'monitor_app:workflow_agents_list' %}" class="{% if active_nav.agents %}nav-active{% endif %}">Agents</a>
0331 <a href="{% url 'monitor_app:subscribers_list' %}" class="{% if active_nav.subscribers %}nav-active{% endif %}">Subscribers</a>
0332 <a href="{% url 'monitor_app:workflow_messages' %}" class="{% if active_nav.messages %}nav-active{% endif %}">Messages</a>
0333 <a href="{% url 'monitor_app:log_summary' %}" class="{% if active_nav.logs %}nav-active{% endif %}">Logs</a>
0334 <a href="{% url 'monitor_app:database_tables_list' %}" class="{% if active_nav.database %}nav-active{% endif %}">Database</a>
0335 <a href="{% url 'monitor_app:persistent_state' %}" class="{% if active_nav.state %}nav-active{% endif %}">State</a>
0336 <div class="dropdown">
0337 <button class="dropbtn{% if active_nav.panda_rucio %} nav-active{% endif %}">PanDA/Rucio</button>
0338 <div class="dropdown-content">
0339 <a href="{% url 'monitor_app:panda_hub' %}" class="{% if active_nav.panda_hub %}nav-active{% endif %}">PanDA/Rucio Hub</a>
0340 <a href="{% url 'monitor_app:panda_activity' %}" class="{% if active_nav.panda_activity %}nav-active{% endif %}">Activity</a>
0341 <a href="{% url 'monitor_app:panda_jobs_list' %}" class="{% if active_nav.panda_jobs %}nav-active{% endif %}">Jobs</a>
0342 <a href="{% url 'monitor_app:panda_tasks_list' %}" class="{% if active_nav.panda_tasks %}nav-active{% endif %}">Tasks</a>
0343 <a href="{% url 'monitor_app:panda_errors_list' %}" class="{% if active_nav.panda_errors %}nav-active{% endif %}">Errors</a>
0344 <a href="{% url 'monitor_app:panda_diagnostics_list' %}" class="{% if active_nav.panda_diagnostics %}nav-active{% endif %}">Diagnostics</a>
0345 <a href="{% url 'monitor_app:epic_queues_list' %}" class="{% if active_nav.panda_queues %}nav-active{% endif %}">EIC PanDA Queues</a>
0346 <a href="{% url 'monitor_app:panda_database_tables_list' %}" class="{% if active_nav.panda_database %}nav-active{% endif %}">PanDA Database</a>
0347 <a href="{% url 'monitor_app:idds_database_tables_list' %}" class="{% if active_nav.idds_database %}nav-active{% endif %}">iDDS Database</a>
0348 <a href="{% url 'monitor_app:rucio_endpoints_list' %}" class="{% if active_nav.rucio_endpoints %}nav-active{% endif %}">Rucio Endpoints</a>
0349 </div>
0350 </div>
0351 </span>
0352
0353 {# ── Production mode ── #}
0354 <span class="nav-mode nav-production">
0355 <a href="{% url 'pcs:questionnaires_list' %}" class="{% if active_nav.requests %}nav-active{% endif %}">Requests</a>
0356 <div class="dropdown">
0357 <button class="dropbtn{% if active_nav.pcs %} nav-active{% endif %}">PCS</button>
0358 <div class="dropdown-content">
0359 <a href="{% url 'pcs:pcs_hub' %}" class="{% if active_nav.pcs_hub %}nav-active{% endif %}">PCS Hub</a>
0360 <a href="{% url 'pcs:physics_categories_list' %}" class="{% if active_nav.pcs_categories %}nav-active{% endif %}">Physics Categories</a>
0361 <a href="{% url 'pcs:tag_compose' tag_type='p' %}" class="{% if active_nav.pcs_physics_tags %}nav-active{% endif %}">Physics Tags</a>
0362 <a href="{% url 'pcs:tag_compose' tag_type='e' %}" class="{% if active_nav.pcs_evgen_tags %}nav-active{% endif %}">EvGen Tags</a>
0363 <a href="{% url 'pcs:tag_compose' tag_type='s' %}" class="{% if active_nav.pcs_simu_tags %}nav-active{% endif %}">Simu Tags</a>
0364 <a href="{% url 'pcs:tag_compose' tag_type='r' %}" class="{% if active_nav.pcs_reco_tags %}nav-active{% endif %}">Reco Tags</a>
0365 <a href="{% url 'pcs:tag_compose' tag_type='k' %}" class="{% if active_nav.pcs_background_tags %}nav-active{% endif %}">Background Tags</a>
0366 <a href="{% url 'pcs:datasets_compose' %}" class="{% if active_nav.pcs_datasets %}nav-active{% endif %}">Datasets</a>
0367 <a href="{% url 'pcs:prod_configs_compose' %}" class="{% if active_nav.pcs_configs %}nav-active{% endif %}">Prod Configs</a>
0368 <a href="{% url 'pcs:prod_task_compose' %}?tab=tasks" class="{% if active_nav.pcs_tasks %}nav-active{% endif %}">Prod Tasks</a>
0369 </div>
0370 </div>
0371 <a href="{% url 'pcs:pcs_catalog' %}" class="{% if active_nav.campaigns %}nav-active{% endif %}">Campaigns</a>
0372 <strong class="nav-label nav-panda-label">PanDA</strong>
0373 <a href="{% url 'monitor_app:panda_activity' %}" class="{% if active_nav.panda_activity %}nav-active{% endif %}">Activity</a>
0374 <a href="{% url 'monitor_app:panda_tasks_list' %}" class="{% if active_nav.panda_tasks %}nav-active{% endif %}">Tasks</a>
0375 <a href="{% url 'monitor_app:panda_jobs_list' %}" class="{% if active_nav.panda_jobs %}nav-active{% endif %}">Jobs</a>
0376 <a href="{% url 'monitor_app:panda_errors_list' %}" class="{% if active_nav.panda_errors %}nav-active{% endif %}">Errors</a>
0377 <a href="{% url 'monitor_app:panda_diagnostics_list' %}" class="{% if active_nav.panda_diagnostics %}nav-active{% endif %}">Diagnostics</a>
0378 <a href="{% url 'monitor_app:epic_queues_list' %}" class="{% if active_nav.panda_queues %}nav-active{% endif %}">Queues</a>
0379 <a href="{% url 'monitor_app:alarms_dashboard' %}" class="{% if active_nav.alarms %}nav-active{% endif %}">Alarms</a>
0380 </span>
0381 <span class="nav-spacer"></span>
0382 <div class="dropdown">
0383 <button class="dropbtn{% if system_status_overall == 'error' %} nav-system-error{% endif %}{% if active_nav.system or active_nav.about %} nav-active{% endif %}"
0384 id="system-status-nav-link"
0385 data-system-status="{{ system_status_overall|default:'unknown' }}"
0386 data-system-status-url="{% url 'monitor_app:system_status_json' %}"
0387 {% if system_status_latest_checked_at %}data-system-checked-at="{{ system_status_latest_checked_at|date:'c' }}"{% endif %}
0388 title="{{ system_status_reason|default:'' }}">System</button>
0389 <div class="dropdown-content">
0390 <a href="{% url 'monitor_app:system_status' %}"
0391 id="system-status-dropdown-link"
0392 class="{% if system_status_overall == 'error' %}nav-system-error{% endif %}{% if active_nav.system %} nav-active{% endif %}">System Status</a>
0393 {% if user.is_authenticated and user.is_staff %}
0394 <a href="{% url 'admin:index' %}">Admin</a>
0395 {% endif %}
0396 <a href="{% url 'monitor_app:about' %}" class="{% if active_nav.about %}nav-active{% endif %}">About</a>
0397 </div>
0398 </div>
0399 <div class="nav-auth">
0400 {% if user.is_authenticated %}
0401 <a href="{% url 'monitor_app:account' %}" class="{% if active_nav.account %}nav-active{% endif %}">Account</a>
0402 <form method="post" action="{% url 'logout' %}" style="display:inline;margin:0;">{% csrf_token %}<button type="submit" style="background:none;border:none;color:inherit;cursor:pointer;padding:0;font:inherit;">Logout</button></form>
0403 {% else %}
0404 <a href="{% url 'login' %}">Login</a>
0405 {% endif %}
0406 </div>
0407 </nav>
0408 </div>
0409 <main>
0410 {% timezone "America/New_York" %}
0411 <div class="main-time-info">Built at {% now "Ymd H:i:s" %} {% now "T" %}</div>
0412 {% endtimezone %}
0413 {% if messages %}
0414 {% for message in messages %}
0415 <div class="alert alert-{{ message.tags|default:'info' }} alert-dismissible fade show w-100 mb-0 rounded-0" role="alert"
0416 style="background-color: #e9ecef; border: none; border-bottom: 1px solid #dee2e6; padding: 20px;">
0417 {% if 'safe' in message.tags %}
0418 {{ message|safe }}
0419 {% else %}
0420 {{ message }}
0421 {% endif %}
0422 <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
0423 </div>
0424 {% endfor %}
0425 {% endif %}
0426 {% block content %}{% endblock %}
0427 </main>
0428 <script>
0429 function setNavMode(mode) {
0430 localStorage.setItem('navMode', mode);
0431 applyNavMode(mode);
0432 }
0433 function applyNavMode(mode) {
0434 if (mode !== 'testbed') mode = 'production';
0435 document.documentElement.setAttribute('data-nav-mode', mode);
0436 const ft = document.getElementById('flip-testbed');
0437 const fp = document.getElementById('flip-production');
0438 if (ft) ft.classList.toggle('nav-active', mode === 'testbed');
0439 if (fp) fp.classList.toggle('nav-active', mode === 'production');
0440 }
0441 (function() {
0442 var mode = localStorage.getItem('navMode') || 'production';
0443 applyNavMode(mode);
0444 })();
0445 (function() {
0446 const staleAfterSeconds = 15 * 60;
0447 const dropdown = document.getElementById('system-status-dropdown');
0448 const link = document.getElementById('system-status-nav-link');
0449 if (!link) return;
0450 const dropdownLink = document.getElementById('system-status-dropdown-link');
0451 const statusUrl = link.getAttribute('data-system-status-url');
0452 function refreshSystemNav() {
0453 const status = link.getAttribute('data-system-status') || 'unknown';
0454 const checkedAt = link.getAttribute('data-system-checked-at');
0455 let stale = false;
0456 if (checkedAt) {
0457 const ts = Date.parse(checkedAt);
0458 if (!Number.isNaN(ts)) {
0459 stale = (Date.now() - ts) / 1000 > staleAfterSeconds;
0460 }
0461 }
0462 const showError = status === 'error' || stale;
0463 link.classList.toggle('nav-system-error', showError);
0464 if (dropdownLink) dropdownLink.classList.toggle('nav-system-error', showError);
0465 if (stale) {
0466 link.title = 'System status is stale by more than 15 minutes.';
0467 }
0468 }
0469 function pollSystemStatus() {
0470 if (!statusUrl || !window.fetch) return;
0471 fetch(statusUrl, {
0472 cache: 'no-store',
0473 credentials: 'same-origin',
0474 headers: {'Accept': 'application/json'}
0475 }).then(function(resp) {
0476 if (!resp.ok) throw new Error('HTTP ' + resp.status);
0477 return resp.json();
0478 }).then(function(data) {
0479 link.setAttribute('data-system-status', data.overall_status || 'unknown');
0480 if (data.latest_checked_at) {
0481 link.setAttribute('data-system-checked-at', data.latest_checked_at);
0482 }
0483 link.title = data.overall_reason || '';
0484 refreshSystemNav();
0485 }).catch(function() {
0486 refreshSystemNav();
0487 });
0488 }
0489 refreshSystemNav();
0490 pollSystemStatus();
0491 if (dropdown) {
0492 dropdown.addEventListener('mouseenter', pollSystemStatus);
0493 dropdown.addEventListener('focusin', pollSystemStatus);
0494 }
0495 setInterval(refreshSystemNav, 10000);
0496 setInterval(pollSystemStatus, 60000);
0497 })();
0498 </script>
0499 </body>
0500 </html>