Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-28 07:24:56

0001 {% extends 'base.html' %}
0002 {% load static %}
0003 {% load swf_fmt %}
0004 {% load humanize %}
0005 
0006 {% block title %}Alarms — ePIC Production Monitor{% endblock %}
0007 
0008 {% block extra_head %}
0009 <script>
0010   // In-place refresh: fetch the same URL, swap the <main> contents.
0011   // The browser keeps scrollY untouched during innerHTML replacement,
0012   // so there's no reload, no scroll save/restore dance, no ghosting.
0013   (function() {
0014     var INTERVAL_MS = {{ auto_refresh_seconds }} * 1000;
0015 
0016     function reExecuteScripts(root) {
0017       // innerHTML inserts <script> nodes without executing them.
0018       // Re-create each so inline scripts (countdown, scroll handlers)
0019       // run in the new content.
0020       var old = root.querySelectorAll('script');
0021       for (var i = 0; i < old.length; i++) {
0022         var s = document.createElement('script');
0023         if (old[i].src) s.src = old[i].src;
0024         else s.textContent = old[i].textContent;
0025         old[i].parentNode.replaceChild(s, old[i]);
0026       }
0027     }
0028 
0029     async function refresh() {
0030       try {
0031         var resp = await fetch(window.location.href, {
0032           cache: 'no-store',
0033           credentials: 'same-origin',
0034         });
0035         if (!resp.ok) return;
0036         var text = await resp.text();
0037         var doc = new DOMParser().parseFromString(text, 'text/html');
0038         var newMain = doc.querySelector('main');
0039         var curMain = document.querySelector('main');
0040         if (!newMain || !curMain) return;
0041         // Skip if the fetched doc doesn't look like our dashboard
0042         // (e.g. session expired → login page).
0043         if (!newMain.querySelector('.alarms-scope')) return;
0044         curMain.innerHTML = newMain.innerHTML;
0045         reExecuteScripts(curMain);
0046       } catch (e) { /* network blip — try again next tick */ }
0047     }
0048     setInterval(refresh, INTERVAL_MS);
0049   })();
0050 </script>
0051 {% endblock %}
0052 
0053 {% block content %}
0054 <link rel="stylesheet" href="{% static 'css/state-colors.css' %}">
0055 <style>
0056   html { scroll-behavior: auto !important; }
0057   .alarm-section-header {
0058     background: #d8d8d8;
0059     color: #222;
0060     padding: 6px 12px;
0061     border-radius: 3px;
0062     font-size: 1.1em;
0063     font-weight: 600;
0064     margin-top: 1.8em;
0065     margin-bottom: 0.8em;
0066   }
0067   .alarm-section-header .num {
0068     color: #555;
0069     font-weight: 700;
0070     margin-right: 8px;
0071   }
0072   /* Kill Bootstrap's `code` pink-on-white within the alarms dashboard —
0073      identifiers (team names, alarm slugs, entry_ids) should
0074      not read as errors. */
0075   .alarms-scope code,
0076   .alarm-label {
0077     font-family: "SF Mono", Monaco, Menlo, monospace;
0078     font-size: 0.92em;
0079     color: #444;
0080     background: #ececec;
0081     padding: 1px 6px;
0082     border-radius: 3px;
0083   }
0084   .quiet-badge {
0085     background: #a58700; color: #fff; padding: 1px 8px;
0086     border-radius: 3px; font-size: 0.75em; font-weight: 500;
0087     margin-left: 6px;
0088   }
0089   /* Description / body panel — soft grey instead of white card. */
0090   .alarm-desc {
0091     background: #f3f3f3;
0092     color: #333;
0093     padding: 10px 14px;
0094     border-radius: 4px;
0095     max-width: 1000px;
0096     margin-bottom: 0.8em;
0097   }
0098   .alarm-desc .label {
0099     color: #777;
0100     font-size: 0.82em;
0101     text-transform: uppercase;
0102     letter-spacing: 0.05em;
0103     margin-bottom: 4px;
0104   }
0105   .alarm-desc pre {
0106     white-space: pre-wrap;
0107     margin: 0;
0108     color: #333;
0109     font-family: inherit;
0110     font-size: 0.95em;
0111   }
0112 </style>
0113 <div class="container-fluid mt-3 alarms-scope" style="max-width: 1400px;">
0114     <div class="d-flex justify-content-between align-items-baseline mb-2">
0115       <h2 class="mb-0">Alarms</h2>
0116       <div class="text-muted" style="font-size: 0.9em;">
0117         Built at {{ built_at_dt|date:"Y-m-d H:i:s" }} ·
0118         all times {{ built_at_dt|date:"T" }} ·
0119         auto-refresh {{ auto_refresh_seconds }}s
0120       </div>
0121     </div>
0122     <div class="mb-3" style="font-size: 0.92em;">
0123       <a href="#summary">alarm summary</a>
0124       {% for r in summary_rows %}
0125         <span class="text-muted">&middot;</span> <a href="#section-{{ r.entry_id }}">{{ r.title|default:r.name }}</a>
0126       {% endfor %}
0127       <span class="text-muted">&middot;</span> <a href="#recent-runs">recent crons</a>
0128     </div>
0129 
0130     {% if health.status == 'ok' %}
0131         <div class="ok_fill p-3 mb-3" style="border-radius:4px;">
0132             <strong>Cron engine: OK.</strong>
0133             {% for r in health.reasons %}{{ r }} {% endfor %}
0134             Cycle 5 min. Next check in <span id="next-tick-countdown" data-secs="{{ next_check_seconds }}">{{ next_check_seconds }}s</span>.
0135         </div>
0136     {% elif health.status == 'warn' %}
0137         <div class="warning_fill p-3 mb-3" style="border-radius:4px;">
0138             <strong>Cron engine: Warning.</strong>
0139             <ul class="mb-0">{% for r in health.reasons %}<li>{{ r }}</li>{% endfor %}</ul>
0140             <div class="mt-1">Cycle 5 min. Next check in <span id="next-tick-countdown" data-secs="{{ next_check_seconds }}">{{ next_check_seconds }}s</span>.</div>
0141         </div>
0142     {% elif health.status == 'bad' %}
0143         <div class="failed_fill p-3 mb-3" style="border-radius:4px;">
0144             <strong>Cron engine: BAD.</strong>
0145             <ul class="mb-0">{% for r in health.reasons %}<li>{{ r }}</li>{% endfor %}</ul>
0146             <div class="mt-1">Cycle 5 min. Next check in <span id="next-tick-countdown" data-secs="{{ next_check_seconds }}">{{ next_check_seconds }}s</span>.</div>
0147         </div>
0148     {% else %}
0149         <div class="unknown_fill p-3 mb-3" style="border-radius:4px;">
0150             <strong>Cron engine: unknown.</strong>
0151             {% for r in health.reasons %}{{ r }} {% endfor %}
0152             Cycle 5 min. Next check in <span id="next-tick-countdown" data-secs="{{ next_check_seconds }}">{{ next_check_seconds }}s</span>.
0153         </div>
0154     {% endif %}
0155     <script>
0156       (function(){
0157         var el = document.getElementById('next-tick-countdown');
0158         if (!el) return;
0159         var secs = parseInt(el.getAttribute('data-secs'), 10);
0160         function fmt(s) {
0161           if (s <= 0) return 'due any moment';
0162           var m = Math.floor(s / 60), r = s % 60;
0163           return m > 0 ? (m + 'm ' + r + 's') : (r + 's');
0164         }
0165         el.textContent = fmt(secs);
0166         setInterval(function(){
0167           secs -= 1;
0168           el.textContent = fmt(secs);
0169         }, 1000);
0170       })();
0171     </script>
0172 
0173     <section class="mb-4">
0174         <div class="alarm-section-header">
0175           Teams
0176           <span class="text-muted" style="font-size:0.85em; font-weight:400;">
0177             — reusable recipient lists, referenced as <code>@teamname</code>
0178           </span>
0179         </div>
0180         {% if teams %}
0181         <table class="table table-sm table-hover" style="max-width:1100px;">
0182             <thead>
0183                 <tr>
0184                     <th>Name</th>
0185                     <th>Title</th>
0186                     <th>Members</th>
0187                     <th>Modified</th>
0188                     <th></th>
0189                 </tr>
0190             </thead>
0191             <tbody>
0192                 {% for t in teams %}
0193                 <tr>
0194                     <td><code>{{ t.name }}</code></td>
0195                     <td>{{ t.title|default:'' }}</td>
0196                     <td><small>{{ t.content }}</small></td>
0197                     <td><small>{{ t.modified|fmt_dt }}</small></td>
0198                     <td>
0199                         <a class="btn btn-dark-green btn-sm"
0200                            href="{% url 'monitor_app:team_edit' at_name=t.name %}">Edit</a>
0201                     </td>
0202                 </tr>
0203                 {% endfor %}
0204             </tbody>
0205         </table>
0206         {% else %}
0207         <p class="text-muted">No teams defined yet.</p>
0208         {% endif %}
0209 
0210         <a class="btn btn-sm btn-primary"
0211            href="{% url 'monitor_app:team_new' %}">Create team</a>
0212     </section>
0213 
0214     <div id="summary" class="alarm-section-header">
0215       Alarm summary
0216       <span class="text-muted" style="font-size:0.85em; font-weight:400;">
0217         — one row per configured alarm
0218       </span>
0219     </div>
0220     <form method="get" class="d-flex align-items-center gap-2 mb-2">
0221         <label class="text-muted">Since:</label>
0222         <input type="number" name="hours" value="{{ hours }}" min="1" max="720"
0223                class="form-control form-control-sm" style="width:90px;"
0224                title="How far back to look when counting/listing past alarms. View filter only; does not change alarm behavior.">
0225         <span class="text-muted">hours ago</span>
0226         <button class="btn btn-primary btn-sm">Apply</button>
0227     </form>
0228     <table class="table table-sm table-hover">
0229         <thead>
0230             <tr>
0231                 <th>Alarm</th>
0232                 <th title="Per-alarm email switch. When OFF, the alarm's detection still runs and events still appear here — only email notifications are suppressed. Flip in the editor.">Emails</th>
0233                 <th>Alarm tasks in last {{ hours }}h</th>
0234                 <th>Current alarm tasks</th>
0235                 <th>Last alarm</th>
0236             </tr>
0237         </thead>
0238         <tbody>
0239             {% for r in summary_rows %}
0240             <tr>
0241                 <td>
0242                   <a href="#section-{{ r.entry_id }}"><span class="text-muted">Alarm {{ forloop.counter }}:</span> {{ r.title|default:r.name }}</a>
0243                   {% if r.quiet %}<span class="quiet-badge" title="No detections in the last few runs despite prior history — may indicate a silently broken alarm.">quiet</span>{% endif %}
0244                 </td>
0245                 <td class="{% if r.enabled %}ok_fill{% else %}paused_fill{% endif %}"
0246                     title="{% if r.enabled %}Emails are ON — notifications are sent on new detections.{% else %}Emails are OFF — detection still runs and events appear on this dashboard; mail is suppressed.{% endif %}">
0247                     {% if r.enabled %}on{% else %}off{% endif %}
0248                 </td>
0249                 <td class="{% if r.count %}failed_fill{% else %}ok_fill{% endif %}">{{ r.count }}</td>
0250                 <td class="{% if r.active %}failed_fill{% else %}ok_fill{% endif %}">{{ r.active }}</td>
0251                 <td>{% if r.last_fired_dt %}{{ r.last_fired_dt|naturaltime }}{% else %}—{% endif %}</td>
0252             </tr>
0253             {% empty %}
0254             <tr><td colspan="5" class="text-muted">No alarm configs defined.</td></tr>
0255             {% endfor %}
0256         </tbody>
0257     </table>
0258 
0259     {% for sec in sections %}
0260     <section id="section-{{ sec.config.entry_id }}">
0261         <div class="alarm-section-header d-flex justify-content-between align-items-center">
0262           <div>
0263             <span class="num">Alarm {{ forloop.counter }}:</span>
0264             {{ sec.config.title|default:sec.config.name }}
0265             {% if not sec.config.enabled %}
0266               <span class="paused_fill ms-1" style="font-size:0.75em; padding: 2px 10px; border-radius:3px; font-weight:500;"
0267                     title="Emails OFF — detection still runs; only email notifications are suppressed.">emails off</span>
0268             {% endif %}
0269             <span class="text-muted ms-2" style="font-size:0.8em; font-weight:400;">
0270               <code>{{ sec.config.entry_id }}</code>
0271             </span>
0272           </div>
0273           <a class="btn btn-dark-green btn-sm"
0274              href="{% url 'monitor_app:alarm_config_edit' entry_id=sec.config.entry_id %}">Edit</a>
0275         </div>
0276 
0277         <table class="table table-sm mb-2" style="max-width:1000px;">
0278             <tbody>
0279                 <tr><th style="width:180px;">Created / modified</th>
0280                     <td>{{ sec.config.created|fmt_dt }} / {{ sec.config.modified|fmt_dt }}</td></tr>
0281                 <tr><th>Recipients</th>
0282                     <td>{{ sec.config.recipients_text|default:'—' }}</td></tr>
0283                 <tr><th>Source</th>
0284                     <td>
0285                       <a href="https://github.com/BNLNPPS/swf-remote/blob/main/alarms/swf_alarms/alarms/{{ sec.config.name }}.py"
0286                          target="_blank" rel="noopener">alarms/swf_alarms/alarms/{{ sec.config.name }}.py</a>
0287                     </td></tr>
0288             </tbody>
0289         </table>
0290 
0291         <h4 style="font-size: 1em;" class="mt-3 mb-2">
0292             Current alarm tasks —
0293             {% if sec.active %}<span class="text-danger">{{ sec.active }}</span>{% else %}0{% endif %}
0294         </h4>
0295         {% if sec.active_rows %}
0296         <table class="table table-sm table-hover">
0297             <thead>
0298                 <tr>
0299                     <th>Description</th>
0300                     <th>Metric</th>
0301                     <th>First fired</th>
0302                 </tr>
0303             </thead>
0304             <tbody>
0305                 {% for ar in sec.active_rows %}
0306                 <tr>
0307                     <td>
0308                       <a href="{% url 'monitor_app:alarm_task_history' entry_id=sec.config.entry_id %}?key={{ ar.dedupe_key|urlencode }}&hours={{ hours }}">
0309                         {{ ar.subject }}
0310                       </a>
0311                     </td>
0312                     <td>{{ ar.metric|default:'—' }}</td>
0313                     <td>{{ ar.fire_time|fmt_dt }}{% if ar.fire_time_dt %} <span class="text-muted">({{ ar.fire_time_dt|naturaltime }})</span>{% endif %}</td>
0314                 </tr>
0315                 {% endfor %}
0316             </tbody>
0317         </table>
0318         {% else %}
0319             <p class="text-muted">No active alarms right now.</p>
0320         {% endif %}
0321 
0322         {% if sec.config.content %}
0323         <div class="alarm-desc">
0324             <div class="label">Description / email body</div>
0325             <pre>{{ sec.config.content }}</pre>
0326         </div>
0327         {% endif %}
0328         {% if sec.params_table %}
0329         <div style="margin-bottom: 0.8em;">
0330           <div class="text-muted" style="font-size:0.82em; text-transform:uppercase; letter-spacing:0.05em; margin-bottom:4px;">Params</div>
0331           <table class="table table-sm" style="max-width:1100px; font-size:0.9em;">
0332             <thead>
0333               <tr>
0334                 <th>Param</th>
0335                 <th>Value</th>
0336                 <th>Type</th>
0337                 <th>Required</th>
0338                 <th>Default</th>
0339                 <th>Description</th>
0340               </tr>
0341             </thead>
0342             <tbody>
0343               {% for p in sec.params_table %}
0344               <tr{% if p.required %} style="font-weight:600;"{% endif %}>
0345                 <td><code>{{ p.name }}</code></td>
0346                 <td>{% if p.has_value %}<code>{{ p.value }}</code>{% else %}<span class="text-muted">—</span>{% endif %}</td>
0347                 <td><small>{{ p.type }}</small></td>
0348                 <td>{% if p.required %}<span class="text-danger">yes</span>{% else %}no{% endif %}</td>
0349                 <td><small>{% if p.has_default %}{{ p.default|default_if_none:"" }}{% else %}—{% endif %}</small></td>
0350                 <td><small>{{ p.description }}</small></td>
0351               </tr>
0352               {% endfor %}
0353             </tbody>
0354           </table>
0355         </div>
0356         {% endif %}
0357         {% if sec.preview_body %}
0358         <div style="margin-bottom: 0.8em;">
0359           <div class="text-muted" style="font-size:0.82em; text-transform:uppercase; letter-spacing:0.05em; margin-bottom:4px;">Current email preview (what would ship now if emails were on)</div>
0360           <pre style="background:#f3f3f3; color:#333; padding:10px 14px; border:1px solid #e0e0e0; border-radius:4px; font-size:12.5px; margin:0; white-space:pre-wrap; max-width:1100px;"><span class="text-muted">Subject:</span> <strong>{{ sec.preview_subject }}</strong>
0361 {{ sec.preview_body }}</pre>
0362         </div>
0363         {% endif %}
0364     </section>
0365     {% endfor %}
0366 
0367     <div id="recent-runs" class="alarm-section-header">
0368       Recent cron engine runs
0369       <span class="text-muted" style="font-size:0.85em; font-weight:400;">
0370         — each run, with per-alarm breakdown
0371       </span>
0372     </div>
0373     <table class="table table-sm">
0374         <thead>
0375             <tr>
0376                 <th>Started</th>
0377                 <th>Per-alarm results</th>
0378             </tr>
0379         </thead>
0380         <tbody>
0381             {% for r in recent_runs %}
0382             <tr>
0383                 <td style="white-space:nowrap;">{{ r.data.started_at|fmt_dt }}</td>
0384                 <td>
0385                   {% if r.data.per_alarm %}
0386                     {% for alarm_id, pc in r.data.per_alarm.items %}
0387                       <div style="font-size:0.9em;">
0388                         <a href="{% url 'monitor_app:alarm_run_report' run_uuid=r.id entry_id=alarm_id %}"><code>{{ alarm_id }}</code></a>:
0389                         seen {{ pc.alarms_seen|default:0 }}{% if pc.bundle_sent %}, emailed{% endif %}{% if pc.errors %}, <span class="text-danger">error{% if pc.error_message %} — {{ pc.error_message }}{% endif %}</span>{% endif %}
0390                       </div>
0391                     {% endfor %}
0392                   {% else %}
0393                     <span class="text-muted">—</span>
0394                   {% endif %}
0395                 </td>
0396             </tr>
0397             {% endfor %}
0398         </tbody>
0399     </table>
0400 </div>
0401 {% endblock %}