Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-12 09:36:19

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 routeMode = '{{ nav_mode|default:""|escapejs }}';
0011         var mode = 'production';
0012         try {
0013             if (routeMode === 'testbed' || routeMode === 'production') {
0014                 mode = routeMode;
0015                 localStorage.setItem('navMode', mode);
0016             } else {
0017                 mode = localStorage.getItem('navMode') || 'production';
0018             }
0019         } catch (e) {}
0020         if (mode !== 'testbed') mode = 'production';
0021         document.documentElement.setAttribute('data-nav-mode', mode);
0022     })();
0023     </script>
0024     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
0025     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
0026     <link rel="stylesheet" href="{% static 'css/style.css' %}">
0027     <link rel="stylesheet" href="{% static 'css/state-colors.css' %}">
0028     <link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/dataTables.bootstrap5.min.css">
0029     <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
0030     <script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
0031     <script src="https://cdn.datatables.net/1.13.8/js/dataTables.bootstrap5.min.js"></script>
0032     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css">
0033     <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
0034     <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
0035     <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
0036     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
0037     <style>
0038       body {
0039         margin: 0;
0040         padding: 0;
0041       }
0042       main {
0043         padding: 0.65rem 1rem 1rem;
0044         position: relative;
0045       }
0046       main > .container,
0047       main > .container-fluid {
0048         margin-top: 0 !important;
0049       }
0050       main > h1,
0051       main > h2,
0052       main > .container > h1,
0053       main > .container > h2,
0054       main > .container-fluid > h1,
0055       main > .container-fluid > h2,
0056       main > .container > div.mb-3 > h1,
0057       main > .container > div.mb-3 > h2,
0058       main > .container > div.mb-4 > div > h1,
0059       main > .container > div.mb-4 > div > h2,
0060       main > .container > .d-flex h1,
0061       main > .container > .d-flex h2,
0062       main > .container-fluid > div.mb-3 > h1,
0063       main > .container-fluid > div.mb-3 > h2,
0064       main > .container-fluid > div.mb-4 > div > h1,
0065       main > .container-fluid > div.mb-4 > div > h2,
0066       main > .container-fluid > .d-flex h1,
0067       main > .container-fluid > .d-flex h2 {
0068         font-size: 1.7rem;
0069         line-height: 1.2;
0070         margin-top: 0 !important;
0071       }
0072       main > h1,
0073       main > h2,
0074       main > .container > h1,
0075       main > .container > h2,
0076       main > .container-fluid > h1,
0077       main > .container-fluid > h2,
0078       main > .container > div.mb-3 > h1,
0079       main > .container > div.mb-3 > h2,
0080       main > .container > div.mb-4 > div > h1,
0081       main > .container > div.mb-4 > div > h2,
0082       main > .container-fluid > div.mb-3 > h1,
0083       main > .container-fluid > div.mb-3 > h2,
0084       main > .container-fluid > div.mb-4 > div > h1,
0085       main > .container-fluid > div.mb-4 > div > h2 {
0086         margin-bottom: 0.2rem !important;
0087       }
0088       main > .container > .d-flex h1,
0089       main > .container > .d-flex h2,
0090       main > .container-fluid > .d-flex h1,
0091       main > .container-fluid > .d-flex h2 {
0092         margin-bottom: 0 !important;
0093       }
0094       /* Button roles (see CLAUDE.md "Button roles" — the AI-facing table):
0095          constructive create/save/new/import = btn-primary; object verbs
0096          edit/copy on existing items = btn-dark-green; consequential commits
0097          submit/lock/publish/approve = btn-success; suggestion-bar Apply =
0098          btn-warning btn-apply; destructive delete = btn-danger; neutral
0099          cancel/utility = btn-secondary; btn-outline-* reserved for stateful
0100          toggles where fill signals on/off. All btn-sm. */
0101       .btn.btn-dark-green {
0102         background-color: #166534; border-color: #166534; color: #fff;
0103       }
0104       .btn.btn-dark-green:hover, .btn.btn-dark-green:focus {
0105         background-color: #14532d; border-color: #14532d; color: #fff;
0106       }
0107       .btn.btn-dark-green:disabled, .btn.btn-dark-green.disabled {
0108         background-color: #6c757d; border-color: #6c757d; color: #e9ecef;
0109       }
0110       #js-error-overlay {
0111         position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
0112         z-index: 100000; max-width: 720px; width: calc(100vw - 2rem);
0113         background: #c9302c; color: #fff; padding: 1.25rem 1.5rem;
0114         border: 3px solid #7a1a18; border-radius: .5rem;
0115         box-shadow: 0 10px 30px rgba(0,0,0,.4);
0116         font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
0117         display: none;
0118       }
0119       #js-error-overlay h3 { margin: 0 0 .5rem; font-size: 1.1rem; }
0120       #js-error-overlay pre {
0121         background: rgba(0,0,0,.25); color: #fff; padding: .5rem .75rem;
0122         border-radius: .25rem; margin: .5rem 0 0; font-size: .8rem;
0123         white-space: pre-wrap; word-break: break-word; max-height: 40vh; overflow: auto;
0124       }
0125       #js-error-overlay .close-btn {
0126         position: absolute; top: .5rem; right: .75rem;
0127         background: transparent; border: none; color: #fff; font-size: 1.5rem;
0128         cursor: pointer; line-height: 1;
0129       }
0130       nav {
0131         display: flex;
0132         align-items: center;
0133         gap: .8em;
0134       }
0135       .sticky-header {
0136         position: sticky;
0137         top: 0;
0138         z-index: 1030;
0139       }
0140       /* Latching table headers: tables that fit the page latch beneath the
0141          nav while the page scrolls. A table wider than its available width
0142          is moved into an adaptive two-axis scroll frame; there the header
0143          latches at the frame top and the first two columns latch at the
0144          left. swf-latch-head opts any other table into the house behavior.
0145          border-collapse borders do not travel with a stuck header, so the
0146          header's frame is drawn as an inset shadow. */
0147       table.dataTable thead th,
0148       table.swf-sortable thead th,
0149       table.swf-latch-head thead th {
0150         /* !important: DataTables' stylesheet pins sortable th to
0151            position:relative with a more specific selector. */
0152         position: sticky !important;
0153         top: var(--swf-nav-height, 0px);
0154         z-index: 100;
0155         background-color: var(--bs-body-bg, #fff);
0156         box-shadow: inset 1px 0 0 #dee2e6, inset 0 1px 0 #dee2e6,
0157                     inset 0 -1px 0 #dee2e6;
0158       }
0159       .swf-table-frame {
0160         max-width: 100%;
0161         width: 100%;
0162       }
0163       .swf-table-frame.swf-wide-table-scroll {
0164         max-height: var(--swf-table-max-height, 70vh);
0165         overflow: auto;
0166         overscroll-behavior: contain;
0167         scrollbar-gutter: stable;
0168       }
0169       .swf-wide-table-scroll > table {
0170         margin-bottom: 0 !important;
0171       }
0172       .swf-wide-table-scroll > table thead th {
0173         top: 0;
0174       }
0175       /* Wide tables pin two leading columns by default so a compact ID can
0176          retain the useful name beside it. data-swf-pin-columns can lower
0177          that number for an exceptional table. Solid cell backgrounds keep
0178          horizontally scrolling columns from showing through. */
0179       .swf-wide-table[data-swf-active-pin-columns="1"] > tbody > tr > :first-child,
0180       .swf-wide-table[data-swf-active-pin-columns="2"] > tbody > tr > :first-child {
0181         position: sticky;
0182         left: 0;
0183         z-index: 50;
0184         background-color: var(--bs-body-bg, #fff);
0185       }
0186       .swf-wide-table[data-swf-active-pin-columns="2"] > tbody > tr > :nth-child(2) {
0187         position: sticky;
0188         left: var(--swf-first-column-width, 0px);
0189         z-index: 50;
0190         background-color: var(--bs-body-bg, #fff);
0191         box-shadow: inset -1px 0 0 #dee2e6;
0192       }
0193       .swf-wide-table.table-striped[data-swf-active-pin-columns="1"] > tbody > tr:nth-of-type(odd) > :first-child,
0194       .swf-wide-table.table-striped[data-swf-active-pin-columns="2"] > tbody > tr:nth-of-type(odd) > :first-child,
0195       .swf-wide-table.table-striped[data-swf-active-pin-columns="2"] > tbody > tr:nth-of-type(odd) > :nth-child(2) {
0196         background-color: var(--bs-tertiary-bg, #f2f2f2);
0197       }
0198       /* Header cells at the two-axis intersection sit above both the body
0199          columns and the rest of the latching header. */
0200       .swf-wide-table[data-swf-active-pin-columns="1"] thead th:first-child,
0201       .swf-wide-table[data-swf-active-pin-columns="2"] thead th:first-child {
0202         left: 0;
0203         z-index: 101;
0204       }
0205       .swf-wide-table[data-swf-active-pin-columns="2"] thead th:nth-child(2) {
0206         left: var(--swf-first-column-width, 0px);
0207         z-index: 101;
0208       }
0209       .nav-spacer {
0210         flex: 1 1 auto;
0211       }
0212       .nav-auth {
0213         display: flex;
0214         gap: .8em;
0215       }
0216       .main-time-info {
0217         color: #667085;
0218         float: right;
0219         font-size: .75rem;
0220         margin: -.25rem 0 .25rem 1rem;
0221         white-space: nowrap;
0222       }
0223       nav a {
0224         color: #ADD8E6;
0225       }
0226       nav a.nav-active {
0227         text-shadow: 0 0 .65px currentColor, 0 0 .65px currentColor;
0228       }
0229       /* Dropdown Button */
0230       .dropbtn {
0231         background-color: inherit;
0232         color: #8fc7e8;
0233         text-decoration: none;
0234         padding: 0;
0235         font-size: inherit;
0236         border: none;
0237         cursor: pointer;
0238       }
0239       .dropbtn.nav-active {
0240         text-shadow: 0 0 .65px currentColor, 0 0 .65px currentColor;
0241       }
0242 
0243       /* The container <div> - needed to position the dropdown content */
0244       .dropdown {
0245         position: relative;
0246         display: inline-block;
0247         margin-right: .35em;
0248       }
0249 
0250       /* Dropdown Content (Hidden by Default) */
0251       .dropdown-content {
0252         display: none;
0253         position: absolute;
0254         background-color: #f9f9f9;
0255         min-width: 160px;
0256         box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
0257         z-index: 1;
0258       }
0259 
0260       /* Links inside the dropdown */
0261       .dropdown-content a {
0262         color: black;
0263         padding: 12px 16px;
0264         text-decoration: none;
0265         display: block;
0266       }
0267       .dropdown-content a.nav-active {
0268         background-color: #e9ecef;
0269       }
0270 
0271       /* Change color of dropdown links on hover */
0272       .dropdown-content a:hover {background-color: #f1f1f1}
0273 
0274       /* Show the dropdown menu on hover */
0275       .dropdown:hover .dropdown-content {
0276         display: block;
0277       }
0278       /* Nav mode flipper */
0279       .nav-mode { display: none; }
0280       .nav-mode.active { display: contents; }
0281       html[data-nav-mode="production"] .nav-production,
0282       html[data-nav-mode="testbed"] .nav-testbed { display: contents; }
0283       #mode-label {
0284         border-right: 1px solid rgba(255,255,255,.2);
0285         color: #fff;
0286         font-weight: 700;
0287         padding-right: 12px;
0288         margin-right: 4px;
0289       }
0290       #mode-label .mode-title { display: none; }
0291       html[data-nav-mode="production"] #mode-label .mode-title-production,
0292       html[data-nav-mode="testbed"] #mode-label .mode-title-testbed { display: inline; }
0293       .nav-label {
0294         color: #fff;
0295         font-weight: 700;
0296       }
0297       .nav-panda-label {
0298         margin-left: 1.5em;
0299       }
0300       .nav-analysis {
0301         margin-left: 1.5em;
0302       }
0303       .nav-system-error { color: #ff8174 !important; font-weight: 700; }
0304       /* One-click copy icon for IDs — see swf_fmt.copy_btn template tag */
0305       .copy-btn {
0306         background: none; border: none; color: #6c757d; cursor: pointer;
0307         padding: 0 .25rem; font-size: .85em; line-height: 1; vertical-align: baseline;
0308       }
0309       .copy-btn:hover { color: #0d6efd; }
0310       .copy-btn.copied { color: #198754; }
0311       /* AI presence — the universal convention for material attributable to
0312          the AI (proposals, recommendation filters, AI-authored blocks):
0313          dark purple ink on light purple ground. Tune the system-wide AI
0314          look here and nowhere else. */
0315       :root {
0316         --ai-fg: #5b21b6;
0317         --ai-bg: #f8f4fe;
0318         /* Lavender ground always wears a 1px border in the ink color. */
0319         --ai-border: var(--ai-fg);
0320       }
0321       .ai-attr {
0322         color: var(--ai-fg);
0323         background-color: var(--ai-bg);
0324         border: 1px solid var(--ai-border);
0325         border-radius: .5em;
0326         padding: 0 .45em;
0327         font-weight: 600;
0328       }
0329       /* Purple ink always sits on lavender ground — the universal pairing. */
0330       .ai-attr-text {
0331         color: var(--ai-fg); background-color: var(--ai-bg);
0332         border: 1px solid var(--ai-border);
0333         border-radius: .35em; padding: 0 .3em; font-weight: 600;
0334       }
0335       /* Lavender fill for AI-owned containers (home-page boxes, panels). */
0336       .ai-fill {
0337         background-color: var(--ai-bg);
0338         border: 1px solid var(--ai-border) !important;
0339       }
0340       button.ai-attr:hover, a.ai-attr:hover {
0341         background-color: var(--ai-fg); color: #fff;
0342         border-color: var(--ai-fg);
0343       }
0344       /* Checkboxes app-wide: Bootstrap's default border is too light
0345          to see; an unticked box must read as present. */
0346       .form-check-input, input[type="checkbox"] {
0347         border: 1.5px solid #495057;
0348       }
0349     </style>
0350 </head>
0351 <body>
0352     <div id="js-error-overlay" role="alert">
0353         <button class="close-btn" onclick="document.getElementById('js-error-overlay').style.display='none';">&times;</button>
0354         <h3>JavaScript error</h3>
0355         <div id="js-error-msg"></div>
0356         <pre id="js-error-detail"></pre>
0357     </div>
0358     <script>
0359     (function() {
0360         function show(title, detail) {
0361             var box = document.getElementById('js-error-overlay');
0362             var msg = document.getElementById('js-error-msg');
0363             var det = document.getElementById('js-error-detail');
0364             if (!box || !msg || !det) return;
0365             msg.textContent = title || 'Error';
0366             det.textContent = detail || '';
0367             box.style.display = 'block';
0368         }
0369         window.addEventListener('error', function(ev) {
0370             var err = ev.error;
0371             var loc = (ev.filename || '') + (ev.lineno ? ':' + ev.lineno + ':' + (ev.colno || 0) : '');
0372             var stack = err && err.stack ? err.stack : '';
0373             show(ev.message || 'Script error', (loc ? loc + '\n\n' : '') + stack);
0374         });
0375         window.addEventListener('unhandledrejection', function(ev) {
0376             var r = ev.reason;
0377             var msg = (r && r.message) ? r.message : String(r);
0378             var stack = (r && r.stack) ? r.stack : '';
0379             show('Unhandled promise rejection: ' + msg, stack);
0380         });
0381     })();
0382     // One-click copy-to-clipboard handler for .copy-btn buttons rendered by
0383     // the copy_btn template tag (swf_fmt). Swaps the icon to a checkmark for
0384     // 1s on success so the user sees the click landed.
0385     document.addEventListener('click', function(ev) {
0386         var btn = ev.target.closest('.copy-btn');
0387         if (!btn) return;
0388         ev.preventDefault();
0389         var text = btn.getAttribute('data-copy') || '';
0390         if (!text || !navigator.clipboard) return;
0391         navigator.clipboard.writeText(text).then(function() {
0392             var icon = btn.querySelector('i');
0393             if (!icon) return;
0394             var prev = icon.className;
0395             icon.className = 'bi bi-clipboard-check';
0396             btn.classList.add('copied');
0397             setTimeout(function() {
0398                 icon.className = prev;
0399                 btn.classList.remove('copied');
0400             }, 1000);
0401         });
0402     });
0403     // Near-zero-latency tooltips via Bootstrap 5 for elements with title=.
0404     // Scoped to state-filled cells and the copy button so we don't spin up
0405     // a tooltip for every cell on every page. show delay 50ms, hide 0ms.
0406     document.addEventListener('DOMContentLoaded', function() {
0407         if (!window.bootstrap || !bootstrap.Tooltip) return;
0408         function init(root) {
0409             root.querySelectorAll(
0410                 'td[title]:not([data-bs-tooltip-init]), .copy-btn[title]:not([data-bs-tooltip-init])'
0411             ).forEach(function(el) {
0412                 if (!el.getAttribute('title')) return;
0413                 el.setAttribute('data-bs-tooltip-init', '1');
0414                 new bootstrap.Tooltip(el, {delay: {show: 50, hide: 0}});
0415             });
0416         }
0417         init(document);
0418         // DataTables redraws replace rows; re-init after each draw.
0419         if (window.jQuery) {
0420             jQuery(document).on('draw.dt', function(_, settings) {
0421                 init(settings.nTable || document);
0422             });
0423         }
0424     });
0425     </script>
0426     <div class="sticky-header">
0427     <nav>
0428         <div class="dropdown" id="mode-flipper">
0429             <button class="dropbtn" id="mode-label">
0430                 <span class="mode-title mode-title-production">epicprod</span>
0431                 <span class="mode-title mode-title-testbed">ePIC Testbed</span>
0432             </button>
0433             <div class="dropdown-content">
0434                 <a href="{% url 'monitor_app:prod_hub' %}" id="flip-production" onclick="setNavMode('production');">epicprod - ePIC Production</a>
0435                 <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>
0436             </div>
0437         </div>
0438 
0439         {# ── Testbed mode ── #}
0440         <span class="nav-mode nav-testbed">
0441         <div class="dropdown" id="system-status-dropdown">
0442             <button class="dropbtn{% if active_nav.workflows %} nav-active{% endif %}">Workflows</button>
0443             <div class="dropdown-content">
0444                 <a href="{% url 'monitor_app:workflows_home' %}" class="{% if active_nav.workflows %}nav-active{% endif %}">Views</a>
0445                 <a href="{% url 'monitor_app:workflow_definitions_list' %}" class="{% if active_nav.workflows %}nav-active{% endif %}">Workflow definitions</a>
0446                 <a href="{% url 'monitor_app:workflow_executions_list' %}" class="{% if active_nav.workflows %}nav-active{% endif %}">Workflow executions</a>
0447                 <a href="{% url 'monitor_app:namespaces_list' %}" class="{% if active_nav.workflows %}nav-active{% endif %}">Namespaces</a>
0448             </div>
0449         </div>
0450         <div class="dropdown">
0451             <button class="dropbtn{% if active_nav.files %} nav-active{% endif %}">Files</button>
0452             <div class="dropdown-content">
0453                 <a href="{% url 'monitor_app:stf_files_list' %}" class="{% if active_nav.files %}nav-active{% endif %}">STF Files</a>
0454                 <a href="{% url 'monitor_app:fastmon_files_list' %}" class="{% if active_nav.files %}nav-active{% endif %}">STF Samples</a>
0455                 <a href="{% url 'monitor_app:tf_slices_list' %}" class="{% if active_nav.files %}nav-active{% endif %}">TF Slices</a>
0456             </div>
0457         </div>
0458         <a href="{% url 'monitor_app:workflow_agents_list' %}" class="{% if active_nav.agents %}nav-active{% endif %}">Agents</a>
0459         <a href="{% url 'monitor_app:subscribers_list' %}" class="{% if active_nav.subscribers %}nav-active{% endif %}">Subscribers</a>
0460         <a href="{% url 'monitor_app:workflow_messages' %}" class="{% if active_nav.messages %}nav-active{% endif %}">Messages</a>
0461         <a href="{% url 'monitor_app:log_summary' %}" class="{% if active_nav.logs %}nav-active{% endif %}">Logs</a>
0462         <a href="{% url 'monitor_app:database_tables_list' %}" class="{% if active_nav.database %}nav-active{% endif %}">Database</a>
0463         <a href="{% url 'monitor_app:persistent_state' %}" class="{% if active_nav.state %}nav-active{% endif %}">State</a>
0464         <div class="dropdown">
0465             <button class="dropbtn{% if active_nav.panda_rucio %} nav-active{% endif %}">PanDA/Rucio</button>
0466             <div class="dropdown-content">
0467                 <a href="{% url 'monitor_app:panda_hub' %}" class="{% if active_nav.panda_hub %}nav-active{% endif %}">PanDA/Rucio Hub</a>
0468                 <a href="{% url 'monitor_app:panda_activity' %}" class="{% if active_nav.panda_activity %}nav-active{% endif %}">Activity</a>
0469                 <a href="{% url 'monitor_app:panda_jobs_list' %}" class="{% if active_nav.panda_jobs %}nav-active{% endif %}">Jobs</a>
0470                 <a href="{% url 'monitor_app:panda_tasks_list' %}" class="{% if active_nav.panda_tasks %}nav-active{% endif %}">Tasks</a>
0471                 <a href="{% url 'monitor_app:panda_errors_list' %}" class="{% if active_nav.panda_errors %}nav-active{% endif %}">Errors</a>
0472                 <a href="{% url 'monitor_app:panda_diagnostics_list' %}" class="{% if active_nav.panda_diagnostics %}nav-active{% endif %}">Diagnostics</a>
0473                 <a href="{% url 'monitor_app:epic_queues_list' %}" class="{% if active_nav.panda_queues %}nav-active{% endif %}">EIC PanDA Queues</a>
0474                 <a href="{% url 'monitor_app:panda_database_tables_list' %}" class="{% if active_nav.panda_database %}nav-active{% endif %}">PanDA Database</a>
0475                 <a href="{% url 'monitor_app:idds_database_tables_list' %}" class="{% if active_nav.idds_database %}nav-active{% endif %}">iDDS Database</a>
0476                 <a href="{% url 'monitor_app:rucio_endpoints_list' %}" class="{% if active_nav.rucio_endpoints %}nav-active{% endif %}">Rucio Endpoints</a>
0477             </div>
0478         </div>
0479         </span>
0480 
0481         {# ── Production mode ── #}
0482         {# SYNC OBLIGATION: swf-remote renders its own copy of this production nav (swf-remote src/templates/base.html) for its natively served pages. #}
0483         {# Any change to this nav block must be mirrored there — notify/hand off to the devcloud (ec2dev) side with every nav change. #}
0484         <span class="nav-mode nav-production">
0485         <div class="dropdown">
0486             <button class="dropbtn{% if active_nav.requests %} nav-active{% endif %}">Requests</button>
0487             <div class="dropdown-content">
0488                 <a href="https://docs.google.com/forms/d/e/1FAIpQLScDqiEaHayAcwBDGAWa4W6k-6yUFzS-XiWuhLolpy64mLk5FA/viewform" target="_blank" rel="noopener">Google request form</a>
0489                 <a href="{% url 'pcs:pcs_request_composer' %}">Request Production</a>
0490                 <a href="{% url 'pcs:questionnaires_list' %}">Request list</a>
0491             </div>
0492         </div>
0493         <div class="dropdown">
0494             <button class="dropbtn{% if active_nav.pcs %} nav-active{% endif %}">PCS</button>
0495             <div class="dropdown-content">
0496                 <a href="{% url 'pcs:pcs_hub' %}" class="{% if active_nav.pcs_hub %}nav-active{% endif %}">PCS Hub</a>
0497                 <a href="{% url 'pcs:physics_categories_list' %}" class="{% if active_nav.pcs_categories %}nav-active{% endif %}">Physics Categories</a>
0498                 <a href="{% url 'pcs:tag_compose' tag_type='p' %}" class="{% if active_nav.pcs_physics_tags %}nav-active{% endif %}">Physics Tags</a>
0499                 <a href="{% url 'pcs:tag_compose' tag_type='e' %}" class="{% if active_nav.pcs_evgen_tags %}nav-active{% endif %}">EvGen Tags</a>
0500                 <a href="{% url 'pcs:tag_compose' tag_type='s' %}" class="{% if active_nav.pcs_simu_tags %}nav-active{% endif %}">Simu Tags</a>
0501                 <a href="{% url 'pcs:tag_compose' tag_type='r' %}" class="{% if active_nav.pcs_reco_tags %}nav-active{% endif %}">Reco Tags</a>
0502                 <a href="{% url 'pcs:tag_compose' tag_type='k' %}" class="{% if active_nav.pcs_background_tags %}nav-active{% endif %}">Background Tags</a>
0503                 <a href="{% url 'pcs:datasets_compose' %}" class="{% if active_nav.pcs_datasets %}nav-active{% endif %}">Datasets</a>
0504                 <a href="{% url 'pcs:prod_configs_compose' %}" class="{% if active_nav.pcs_configs %}nav-active{% endif %}">Prod Configs</a>
0505                 <a href="{% url 'pcs:prod_task_compose' %}?tab=tasks" class="{% if active_nav.pcs_tasks %}nav-active{% endif %}">Prod Tasks</a>
0506             </div>
0507         </div>
0508         <div class="dropdown">
0509             <button class="dropbtn{% if active_nav.campaigns %} nav-active{% endif %}">Campaigns</button>
0510             <div class="dropdown-content">
0511                 <a href="{% url 'pcs:pcs_catalog' %}" class="{% if active_nav.campaigns %}nav-active{% endif %}">Campaign Catalog</a>
0512                 <a href="{% url 'pcs:pcs_catalog' %}?lifecycle=current&amp;view=progress">Campaign Progress</a>
0513                 <a href="{% url 'pcs:pcs_physics_configs' %}">Physics configs by campaign</a>
0514                 <a href="{% url 'pcs:pcs_campaign_plan' %}">Campaign Plan</a>
0515                 <a href="{% url 'ai:ai_narratives' %}">Campaign Narratives</a>
0516                 <a href="{% url 'monitor_app:ai_content_list' %}?subject_type=campaign_task,campaign">Campaign Assessments</a>
0517             </div>
0518         </div>
0519         <div class="dropdown">
0520             <button class="dropbtn{% if active_nav.ai_content %} nav-active{% endif %}">AI</button>
0521             <div class="dropdown-content">
0522                 <a href="{% url 'monitor_app:ai_content_list' %}" class="{% if active_nav.ai_content %}nav-active{% endif %}">Assessments</a>
0523                 <a href="{% url 'ai:ai_proposals' %}">Proposals</a>
0524             </div>
0525         </div>
0526         <strong class="nav-label nav-panda-label">PanDA</strong>
0527         <a href="{% url 'monitor_app:panda_activity' %}" class="{% if active_nav.panda_activity %}nav-active{% endif %}">Activity</a>
0528         <a href="{% url 'monitor_app:panda_tasks_list' %}" class="{% if active_nav.panda_tasks %}nav-active{% endif %}">Tasks</a>
0529         <a href="{% url 'monitor_app:panda_jobs_list' %}" class="{% if active_nav.panda_jobs %}nav-active{% endif %}">Jobs</a>
0530         <a href="{% url 'monitor_app:panda_errors_list' %}" class="{% if active_nav.panda_errors %}nav-active{% endif %}">Errors</a>
0531         <a href="{% url 'monitor_app:log_list' %}?app_name=epicprod" class="{% if active_nav.logs %}nav-active{% endif %}">Logs</a>
0532         <a href="{% url 'monitor_app:alarms_dashboard' %}" class="{% if active_nav.alarms %}nav-active{% endif %}">Alarms</a>
0533         <a href="{% url 'monitor_app:panda_diagnostics_list' %}" class="{% if active_nav.panda_diagnostics %}nav-active{% endif %}">Diagnostics</a>
0534         <div class="dropdown">
0535             <button class="dropbtn{% if active_nav.sites %} nav-active{% endif %}">Sites</button>
0536             <div class="dropdown-content">
0537                 <a href="{% url 'monitor_app:compute_usage' %}" class="{% if active_nav.compute_usage %}nav-active{% endif %}">Site usage</a>
0538                 <a href="{% url 'monitor_app:epic_queues_list' %}" class="{% if active_nav.panda_queues %}nav-active{% endif %}">PanDA queues</a>
0539             </div>
0540         </div>
0541         <a href="{% url 'monitor_app:analysis' %}" class="nav-analysis{% if active_nav.analysis %} nav-active{% endif %}">Analysis</a>
0542         </span>
0543         <span class="nav-spacer"></span>
0544         <div class="dropdown">
0545             <button class="dropbtn{% if system_status_overall == 'error' %} nav-system-error{% endif %}{% if active_nav.system or active_nav.snapper or active_nav.canary or active_nav.about %} nav-active{% endif %}"
0546                     id="system-status-nav-link"
0547                     data-system-status="{{ system_status_overall|default:'unknown' }}"
0548                     data-system-status-url="{% url 'monitor_app:system_status_json' %}"
0549                     {% if system_status_latest_checked_at %}data-system-checked-at="{{ system_status_latest_checked_at|date:'c' }}"{% endif %}
0550                     title="{{ system_status_reason|default:'' }}">System</button>
0551             <div class="dropdown-content">
0552                 <a href="{% url 'monitor_app:system_status' %}"
0553                    id="system-status-dropdown-link"
0554                    class="{% if system_status_overall == 'error' %}nav-system-error{% endif %}{% if active_nav.system %} nav-active{% endif %}">System Status</a>
0555                 <a href="{% url 'snapper_ai:snapper_report' scope='epicprod' %}"
0556                    class="{% if active_nav.snapper %}nav-active{% endif %}">Snapper</a>
0557                 <a href="{% url 'canary:canary_page' %}"
0558                    class="{% if active_nav.canary %}nav-active{% endif %}">Canary</a>
0559                 {% if user.is_authenticated and user.is_staff %}
0560                     <a href="{% url 'admin:index' %}">Admin</a>
0561                 {% endif %}
0562                 <a href="{% url 'monitor_app:about' %}" class="{% if active_nav.about %}nav-active{% endif %}">About</a>
0563             </div>
0564         </div>
0565         <div class="nav-auth">
0566             {% if user.is_authenticated %}
0567                 <a href="{% url 'monitor_app:account' %}" class="{% if active_nav.account %}nav-active{% endif %}">{{ user.username }}</a>
0568                 <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>
0569             {% else %}
0570                 <a href="{% url 'login' %}?next={{ request.get_full_path|urlencode }}">Login</a>
0571             {% endif %}
0572         </div>
0573     </nav>
0574     </div>
0575     <main>
0576         {% timezone "America/New_York" %}
0577         <div class="main-time-info">
0578             {% if is_tunnel %}
0579             <a href="https://pandaserver02.sdcc.bnl.gov/swf-monitor{{ request.path_info }}{% if request.META.QUERY_STRING %}?{{ request.META.QUERY_STRING }}{% endif %}"
0580                target="_blank" rel="noopener">Go to page at pandaserver02</a> &middot;
0581             {% else %}
0582             <a href="{{ external_face_base_url }}/prod{{ request.path_info }}{% if request.META.QUERY_STRING %}?{{ request.META.QUERY_STRING }}{% endif %}"
0583                target="_blank" rel="noopener">Go to page at {{ external_face_base_url|cut:"https://" }}</a> &middot;
0584             {% endif %}
0585             Built at {% now "Ymd H:i:s" %} {% now "T" %}
0586         </div>
0587         {% endtimezone %}
0588         {% if messages %}
0589             {% for message in messages %}
0590                 <div class="alert alert-{{ message.tags|default:'info' }} alert-dismissible fade show w-100 mb-0 rounded-0" role="alert"
0591                      style="background-color: #e9ecef; border: none; border-bottom: 1px solid #dee2e6; padding: 20px;">
0592                     {% if 'safe' in message.tags %}
0593                         {{ message|safe }}
0594                     {% else %}
0595                         {{ message }}
0596                     {% endif %}
0597                     <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
0598                 </div>
0599             {% endfor %}
0600         {% endif %}
0601         {% block content %}{% endblock %}
0602     </main>
0603 <script>
0604 function setNavMode(mode) {
0605     localStorage.setItem('navMode', mode);
0606     applyNavMode(mode);
0607 }
0608 function applyNavMode(mode) {
0609     if (mode !== 'testbed') mode = 'production';
0610     document.documentElement.setAttribute('data-nav-mode', mode);
0611     const ft = document.getElementById('flip-testbed');
0612     const fp = document.getElementById('flip-production');
0613     if (ft) ft.classList.toggle('nav-active', mode === 'testbed');
0614     if (fp) fp.classList.toggle('nav-active', mode === 'production');
0615 }
0616 (function() {
0617     var mode = document.documentElement.getAttribute('data-nav-mode')
0618             || 'production';
0619     applyNavMode(mode);
0620 })();
0621 (function() {
0622     const staleAfterSeconds = 15 * 60;
0623     const dropdown = document.getElementById('system-status-dropdown');
0624     const link = document.getElementById('system-status-nav-link');
0625     if (!link) return;
0626     const dropdownLink = document.getElementById('system-status-dropdown-link');
0627     const statusUrl = link.getAttribute('data-system-status-url');
0628     function refreshSystemNav() {
0629         const status = link.getAttribute('data-system-status') || 'unknown';
0630         const checkedAt = link.getAttribute('data-system-checked-at');
0631         let stale = false;
0632         if (checkedAt) {
0633             const ts = Date.parse(checkedAt);
0634             if (!Number.isNaN(ts)) {
0635                 stale = (Date.now() - ts) / 1000 > staleAfterSeconds;
0636             }
0637         }
0638         const showError = status === 'error' || stale;
0639         link.classList.toggle('nav-system-error', showError);
0640         if (dropdownLink) dropdownLink.classList.toggle('nav-system-error', showError);
0641         if (stale) {
0642             link.title = 'System status is stale by more than 15 minutes.';
0643         }
0644     }
0645     function pollSystemStatus() {
0646         if (!statusUrl || !window.fetch) return;
0647         fetch(statusUrl, {
0648             cache: 'no-store',
0649             credentials: 'same-origin',
0650             headers: {'Accept': 'application/json'}
0651         }).then(function(resp) {
0652             if (!resp.ok) throw new Error('HTTP ' + resp.status);
0653             return resp.json();
0654         }).then(function(data) {
0655             link.setAttribute('data-system-status', data.overall_status || 'unknown');
0656             if (data.latest_checked_at) {
0657                 link.setAttribute('data-system-checked-at', data.latest_checked_at);
0658             }
0659             link.title = data.overall_reason || '';
0660             refreshSystemNav();
0661         }).catch(function() {
0662             refreshSystemNav();
0663         });
0664     }
0665     refreshSystemNav();
0666     pollSystemStatus();
0667     if (dropdown) {
0668         dropdown.addEventListener('mouseenter', pollSystemStatus);
0669         dropdown.addEventListener('focusin', pollSystemStatus);
0670     }
0671     setInterval(refreshSystemNav, 10000);
0672     setInterval(pollSystemStatus, 60000);
0673 })();
0674 </script>
0675     <script>
0676       /* Fit an in-page document editor to the visible window: if the
0677          document overflows its panel, grow the panel to use nearly the
0678          full viewport height. The app-wide utility for editing large
0679          documents — call swfFitEditor(el) when opening an editor, or give
0680          the textarea the swf-fit-editor class to fit on focus. */
0681       window.swfFitEditor = function (el) {
0682         if (!el) return;
0683         const margin = 24;
0684         const avail = window.innerHeight - el.getBoundingClientRect().top - margin;
0685         if (el.scrollHeight > el.clientHeight && avail > el.clientHeight) {
0686           el.style.height = Math.max(avail, 240) + 'px';
0687         }
0688       };
0689       document.addEventListener('focusin', function (e) {
0690         if (e.target.matches && e.target.matches('textarea.swf-fit-editor')) {
0691           window.swfFitEditor(e.target);
0692         }
0693       });
0694 
0695       /* The app-wide relative-time ticker: give any element
0696          data-relative-time="<ISO timestamp>" and its text renders and
0697          ticks as 'just now' / 'Ns ago' / 'Nm ago' / 'Nh ago' / 'Nd ago'. */
0698       window.swfFormatElapsed = function (seconds) {
0699         if (seconds < 5) return 'just now';
0700         if (seconds < 60) return seconds + 's ago';
0701         const minutes = Math.floor(seconds / 60);
0702         if (minutes < 60) return minutes + 'm ago';
0703         const hours = Math.floor(minutes / 60);
0704         if (hours < 48) return hours + 'h ago';
0705         return Math.floor(hours / 24) + 'd ago';
0706       };
0707       (function () {
0708         function tick() {
0709           const now = Date.now();
0710           document.querySelectorAll('[data-relative-time]').forEach(function (el) {
0711             const ts = Date.parse(el.getAttribute('data-relative-time'));
0712             if (Number.isNaN(ts)) return;
0713             el.textContent = window.swfFormatElapsed(
0714               Math.max(0, Math.floor((now - ts) / 1000)));
0715           });
0716         }
0717         tick();
0718         setInterval(tick, 1000);
0719       })();
0720       // Latching table headers latch beneath the sticky nav bar; expose
0721       // its measured height to the CSS above.
0722       (function () {
0723         function setNavHeightVar() {
0724           const nav = document.querySelector('.sticky-header');
0725           document.documentElement.style.setProperty(
0726             '--swf-nav-height', (nav ? nav.offsetHeight : 0) + 'px');
0727         }
0728         setNavHeightVar();
0729         window.addEventListener('resize', setNavHeightVar);
0730       })();
0731       /* Adaptive house tables. Narrow tables retain window scrolling and
0732          the nav-offset header latch. Only a table whose rendered width
0733          exceeds its available width gets a fitted two-axis scroll frame,
0734          a frame-top header latch, and two pinned leading columns. */
0735       (function () {
0736         const tableSelector =
0737           'table.dataTable, table.swf-sortable, table.swf-latch-head';
0738         let tableFrameUpdatePending = false;
0739         let tableFrameFitPending = false;
0740 
0741         function ensureTableFrame(table) {
0742           if (table.parentElement &&
0743               table.parentElement.classList.contains('swf-table-frame')) {
0744             return table.parentElement;
0745           }
0746           const frame = document.createElement('div');
0747           frame.className = 'swf-table-frame';
0748           table.parentNode.insertBefore(frame, table);
0749           frame.appendChild(table);
0750           return frame;
0751         }
0752 
0753         function requestedPinnedColumns(table) {
0754           const value = table.getAttribute('data-swf-pin-columns');
0755           if (value === null) return 2;
0756           const parsed = Number.parseInt(value, 10);
0757           return Number.isFinite(parsed) ? Math.max(0, Math.min(2, parsed)) : 2;
0758         }
0759 
0760         function fitTableFrame(frame) {
0761           if (!frame.classList.contains('swf-wide-table-scroll')) return;
0762           const nav = document.querySelector('.sticky-header');
0763           const navHeight = nav ? nav.offsetHeight : 0;
0764           const top = Math.max(frame.getBoundingClientRect().top, navHeight);
0765           const fullAvailable = Math.max(120, window.innerHeight - navHeight - 16);
0766           const available = Math.max(
0767             Math.min(240, fullAvailable), window.innerHeight - top - 16);
0768           frame.style.setProperty('--swf-table-max-height', available + 'px');
0769         }
0770 
0771         function updateTableFrame(table) {
0772           if (!table.matches(tableSelector) || !table.parentNode) return;
0773           const frame = ensureTableFrame(table);
0774           const availableWidth = frame.getBoundingClientRect().width;
0775           const isWide = table.scrollWidth > availableWidth + 1;
0776 
0777           frame.classList.toggle('swf-wide-table-scroll', isWide);
0778           table.classList.toggle('swf-wide-table', isWide);
0779           if (!isWide) {
0780             table.removeAttribute('data-swf-active-pin-columns');
0781             table.style.removeProperty('--swf-first-column-width');
0782             frame.style.removeProperty('--swf-table-max-height');
0783             return;
0784           }
0785 
0786           const headerCells = table.querySelectorAll('thead tr:first-child > th');
0787           const pinned = Math.min(requestedPinnedColumns(table), headerCells.length);
0788           table.setAttribute('data-swf-active-pin-columns', String(pinned));
0789           if (pinned > 1) {
0790             table.style.setProperty(
0791               '--swf-first-column-width',
0792               headerCells[0].getBoundingClientRect().width + 'px');
0793           } else {
0794             table.style.removeProperty('--swf-first-column-width');
0795           }
0796           fitTableFrame(frame);
0797         }
0798 
0799         function updateAllTableFrames() {
0800           tableFrameUpdatePending = false;
0801           document.querySelectorAll(tableSelector).forEach(updateTableFrame);
0802         }
0803 
0804         function scheduleTableFrameUpdate() {
0805           if (tableFrameUpdatePending) return;
0806           tableFrameUpdatePending = true;
0807           window.requestAnimationFrame(updateAllTableFrames);
0808         }
0809 
0810         function scheduleTableFrameFit() {
0811           if (tableFrameFitPending) return;
0812           tableFrameFitPending = true;
0813           window.requestAnimationFrame(function () {
0814             tableFrameFitPending = false;
0815             document.querySelectorAll(
0816               '.swf-table-frame.swf-wide-table-scroll'
0817             ).forEach(fitTableFrame);
0818           });
0819         }
0820 
0821         window.swfUpdateTableFrames = scheduleTableFrameUpdate;
0822         $(function () {
0823           // House sortable tables get chrome-free DataTables sorting before
0824           // the adaptive frame measures their rendered column widths.
0825           $('table.swf-sortable').DataTable({
0826             paging: false, searching: false, info: false, order: []
0827           });
0828           scheduleTableFrameUpdate();
0829         });
0830         $(document).on('draw.dt column-sizing.dt', scheduleTableFrameUpdate);
0831         window.addEventListener('resize', scheduleTableFrameUpdate);
0832         window.addEventListener('scroll', scheduleTableFrameFit, {passive: true});
0833       })();
0834     </script>
0835 </body>
0836 </html>