TDS polish: lt-frame tables, lt-stats-grid link summary, settings-aware refresh
Lint / Python (flake8) (push) Successful in 40s
Lint / JS (eslint) (push) Successful in 8s
Security / Python Security (bandit) (push) Failing after 42s
Test / Python Tests (pytest) (push) Successful in 50s
Lint / Notify on failure (push) Has been skipped
Lint / Deploy (push) Successful in 3s
Lint / Python (flake8) (push) Successful in 40s
Lint / JS (eslint) (push) Successful in 8s
Security / Python Security (bandit) (push) Failing after 42s
Test / Python Tests (pytest) (push) Successful in 50s
Lint / Notify on failure (push) Has been skipped
Lint / Deploy (push) Successful in 3s
- links.html: replace custom link-summary-panel with lt-stats-grid/lt-stat-card showing total interfaces, ports down, errors, and PoE load - suppressions.html: wrap active suppressions and history tables in lt-frame with lt-section-header labels - inspector.html: wire auto-refresh to gandalfSettings (respects interval pill), fix updated timestamp to use locale time Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -427,9 +427,10 @@ function renderInspector(data) {
|
|||||||
const main = document.getElementById('inspector-main');
|
const main = document.getElementById('inspector-main');
|
||||||
const switches = data.unifi_switches || {};
|
const switches = data.unifi_switches || {};
|
||||||
|
|
||||||
const upd = data.updated ? `Updated: ${data.updated}` : '';
|
|
||||||
const updEl = document.getElementById('inspector-updated');
|
const updEl = document.getElementById('inspector-updated');
|
||||||
if (updEl) updEl.textContent = upd;
|
if (updEl && data.updated) {
|
||||||
|
updEl.textContent = 'Updated: ' + new Date(data.updated + (data.updated.includes('Z') ? '' : 'Z')).toLocaleTimeString();
|
||||||
|
}
|
||||||
|
|
||||||
if (!Object.keys(switches).length) {
|
if (!Object.keys(switches).length) {
|
||||||
main.innerHTML = '<p class="empty-state">No switch data available. Monitor may still be initialising.</p>';
|
main.innerHTML = '<p class="empty-state">No switch data available. Monitor may still be initialising.</p>';
|
||||||
@@ -465,7 +466,13 @@ async function loadInspector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadInspector();
|
loadInspector();
|
||||||
lt.autoRefresh.start(loadInspector, 60000);
|
var _inspInterval = (window.gandalfSettings && window.gandalfSettings.refreshInterval) || 60;
|
||||||
|
if (_inspInterval > 0) lt.autoRefresh.start(loadInspector, Math.max(_inspInterval, 15) * 1000);
|
||||||
|
|
||||||
|
window.onGandalfSettingsChanged = function(s) {
|
||||||
|
lt.autoRefresh.stop();
|
||||||
|
if (s.refreshInterval > 0) lt.autoRefresh.start(loadInspector, Math.max(s.refreshInterval, 15) * 1000);
|
||||||
|
};
|
||||||
lt.keys.on('Escape', () => {
|
lt.keys.on('Escape', () => {
|
||||||
if (document.getElementById('inspector-panel').classList.contains('open')) closePanel();
|
if (document.getElementById('inspector-panel').classList.contains('open')) closePanel();
|
||||||
});
|
});
|
||||||
|
|||||||
+39
-21
@@ -384,33 +384,51 @@ function buildLinkSummary(hosts, unifiSwitches) {
|
|||||||
if ((d.tx_errs_rate || 0) > 0 || (d.rx_errs_rate || 0) > 0) errIfaces++;
|
if ((d.tx_errs_rate || 0) > 0 || (d.rx_errs_rate || 0) > 0) errIfaces++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let swTotal = 0, swDown = 0;
|
||||||
for (const sw of Object.values(unifiSwitches || {})) {
|
for (const sw of Object.values(unifiSwitches || {})) {
|
||||||
for (const p of Object.values(sw.ports || {})) {
|
for (const p of Object.values(sw.ports || {})) {
|
||||||
totalPoe += p.poe_power || 0;
|
totalPoe += p.poe_power || 0;
|
||||||
|
swTotal++;
|
||||||
|
if (!p.up) swDown++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const hasAlerts = downIfaces > 0 || errIfaces > 0;
|
const allTotal = totalIfaces + swTotal;
|
||||||
return `
|
const allDown = downIfaces + swDown;
|
||||||
<div class="link-summary-panel ${hasAlerts ? 'link-summary-has-alerts' : ''}">
|
const downColor = allDown > 0 ? 'var(--red)' : 'var(--green)';
|
||||||
<div class="link-summary-grid">
|
const errColor = errIfaces > 0 ? 'var(--amber)' : 'var(--green)';
|
||||||
<div class="link-summary-stat">
|
const downCardCls = allDown > 0 ? ' lt-stat-card--alert' : '';
|
||||||
<span class="lss-label">Total Interfaces</span>
|
const poeCard = totalPoe > 0 ? `
|
||||||
<span class="lss-value">${totalIfaces}</span>
|
<div class="lt-stat-card">
|
||||||
</div>
|
<span class="lt-stat-icon" aria-hidden="true" style="color:var(--amber)">⚡</span>
|
||||||
<div class="link-summary-stat ${downIfaces ? 'lss-alert' : ''}">
|
<div class="lt-stat-info">
|
||||||
<span class="lss-label">Interfaces Down</span>
|
<span class="lt-stat-value" style="color:var(--amber)">${totalPoe.toFixed(1)}</span>
|
||||||
<span class="lss-value ${downIfaces ? 'val-crit' : 'val-good'}">${downIfaces}</span>
|
<span class="lt-stat-label">PoE Load (W)</span>
|
||||||
</div>
|
|
||||||
<div class="link-summary-stat ${errIfaces ? 'lss-alert' : ''}">
|
|
||||||
<span class="lss-label">With Errors</span>
|
|
||||||
<span class="lss-value ${errIfaces ? 'val-warn' : 'val-good'}">${errIfaces}</span>
|
|
||||||
</div>
|
|
||||||
${totalPoe > 0 ? `
|
|
||||||
<div class="link-summary-stat">
|
|
||||||
<span class="lss-label">PoE Load</span>
|
|
||||||
<span class="lss-value">${totalPoe.toFixed(1)} <span class="lss-sub">W</span></span>
|
|
||||||
</div>` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>` : '';
|
||||||
|
return `
|
||||||
|
<div class="lt-stats-grid" style="margin-bottom:16px">
|
||||||
|
<div class="lt-stat-card">
|
||||||
|
<span class="lt-stat-icon" aria-hidden="true" style="color:var(--cyan)">⬡</span>
|
||||||
|
<div class="lt-stat-info">
|
||||||
|
<span class="lt-stat-value" style="color:var(--cyan)">${allTotal}</span>
|
||||||
|
<span class="lt-stat-label">Interfaces</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lt-stat-card${downCardCls}">
|
||||||
|
<span class="lt-stat-icon" aria-hidden="true" style="color:${downColor}">●</span>
|
||||||
|
<div class="lt-stat-info">
|
||||||
|
<span class="lt-stat-value" style="color:${downColor}">${allDown}</span>
|
||||||
|
<span class="lt-stat-label">Ports Down</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lt-stat-card">
|
||||||
|
<span class="lt-stat-icon" aria-hidden="true" style="color:${errColor}">▲</span>
|
||||||
|
<div class="lt-stat-info">
|
||||||
|
<span class="lt-stat-value" style="color:${errColor}">${errIfaces}</span>
|
||||||
|
<span class="lt-stat-label">With Errors</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${poeCard}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+81
-66
@@ -85,33 +85,38 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="active-sup-wrap">
|
<div id="active-sup-wrap">
|
||||||
{% if active %}
|
{% if active %}
|
||||||
<div class="lt-table-wrap">
|
<div class="lt-frame">
|
||||||
<table class="lt-table" id="active-sup-table">
|
<span class="lt-frame-bl">╚</span>
|
||||||
<caption class="lt-sr-only">Active suppression rules</caption>
|
<span class="lt-frame-br">╝</span>
|
||||||
<thead>
|
<div class="lt-section-header">Active Rules</div>
|
||||||
<tr>
|
<div class="lt-table-wrap">
|
||||||
<th>Type</th><th>Target</th><th>Detail</th><th>Reason</th>
|
<table class="lt-table" id="active-sup-table">
|
||||||
<th>By</th><th>Created</th><th>Expires</th><th>Actions</th>
|
<caption class="lt-sr-only">Active suppression rules</caption>
|
||||||
</tr>
|
<thead>
|
||||||
</thead>
|
<tr>
|
||||||
<tbody>
|
<th>Type</th><th>Target</th><th>Detail</th><th>Reason</th>
|
||||||
{% for s in active %}
|
<th>By</th><th>Created</th><th>Expires</th><th>Actions</th>
|
||||||
<tr id="sup-row-{{ s.id }}">
|
</tr>
|
||||||
{%- set _sup_badge = {'host':'badge-warning','interface':'badge-info','unifi_device':'badge-purple','all':'badge-critical'} -%}
|
</thead>
|
||||||
<td><span class="lt-badge {{ _sup_badge.get(s.target_type, 'badge-neutral') }}">{{ s.target_type }}</span></td>
|
<tbody>
|
||||||
<td>{{ s.target_name or 'all' }}</td>
|
{% for s in active %}
|
||||||
<td>{{ s.target_detail or '–' }}</td>
|
<tr id="sup-row-{{ s.id }}">
|
||||||
<td>{{ s.reason }}</td>
|
{%- set _sup_badge = {'host':'badge-warning','interface':'badge-info','unifi_device':'badge-purple','all':'badge-critical'} -%}
|
||||||
<td>{{ s.suppressed_by }}</td>
|
<td><span class="lt-badge {{ _sup_badge.get(s.target_type, 'badge-neutral') }}">{{ s.target_type }}</span></td>
|
||||||
<td class="ts-cell">{{ s.created_at }}</td>
|
<td>{{ s.target_name or 'all' }}</td>
|
||||||
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</td>
|
<td>{{ s.target_detail or '–' }}</td>
|
||||||
<td>
|
<td>{{ s.reason }}</td>
|
||||||
<button class="lt-btn lt-btn-danger lt-btn-sm" data-action="remove-sup" data-sup-id="{{ s.id }}">Remove</button>
|
<td>{{ s.suppressed_by }}</td>
|
||||||
</td>
|
<td class="ts-cell">{{ s.created_at }}</td>
|
||||||
</tr>
|
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</td>
|
||||||
{% endfor %}
|
<td>
|
||||||
</tbody>
|
<button class="lt-btn lt-btn-danger lt-btn-sm" data-action="remove-sup" data-sup-id="{{ s.id }}">Remove</button>
|
||||||
</table>
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="lt-empty-state lt-empty-state--sm" id="no-active-msg">
|
<div class="lt-empty-state lt-empty-state--sm" id="no-active-msg">
|
||||||
@@ -130,36 +135,41 @@
|
|||||||
<span class="g-section-badge">{{ history | length }}</span>
|
<span class="g-section-badge">{{ history | length }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% if history %}
|
{% if history %}
|
||||||
<div class="lt-table-wrap">
|
<div class="lt-frame">
|
||||||
<table class="lt-table lt-table-sm">
|
<span class="lt-frame-bl">╚</span>
|
||||||
<caption class="lt-sr-only">Suppression history</caption>
|
<span class="lt-frame-br">╝</span>
|
||||||
<thead>
|
<div class="lt-section-header">Suppression Log</div>
|
||||||
<tr>
|
<div class="lt-table-wrap">
|
||||||
<th>Type</th><th>Target</th><th>Detail</th><th>Reason</th>
|
<table class="lt-table lt-table-sm">
|
||||||
<th>By</th><th>Created</th><th>Expires</th><th>Active</th>
|
<caption class="lt-sr-only">Suppression history</caption>
|
||||||
</tr>
|
<thead>
|
||||||
</thead>
|
<tr>
|
||||||
<tbody>
|
<th>Type</th><th>Target</th><th>Detail</th><th>Reason</th>
|
||||||
{% for s in history %}
|
<th>By</th><th>Created</th><th>Expires</th><th>Active</th>
|
||||||
<tr class="{% if not s.active %}row-resolved{% endif %}">
|
</tr>
|
||||||
<td>{{ s.target_type }}</td>
|
</thead>
|
||||||
<td>{{ s.target_name or 'all' }}</td>
|
<tbody>
|
||||||
<td>{{ s.target_detail or '–' }}</td>
|
{% for s in history %}
|
||||||
<td>{{ s.reason }}</td>
|
<tr class="{% if not s.active %}row-resolved{% endif %}">
|
||||||
<td>{{ s.suppressed_by }}</td>
|
<td>{{ s.target_type }}</td>
|
||||||
<td class="ts-cell">{{ s.created_at }}</td>
|
<td>{{ s.target_name or 'all' }}</td>
|
||||||
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</td>
|
<td>{{ s.target_detail or '–' }}</td>
|
||||||
<td>
|
<td>{{ s.reason }}</td>
|
||||||
{% if s.active %}
|
<td>{{ s.suppressed_by }}</td>
|
||||||
<span class="lt-badge badge-ok">Yes</span>
|
<td class="ts-cell">{{ s.created_at }}</td>
|
||||||
{% else %}
|
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</td>
|
||||||
<span class="lt-badge badge-neutral">No</span>
|
<td>
|
||||||
{% endif %}
|
{% if s.active %}
|
||||||
</td>
|
<span class="lt-badge badge-ok">Yes</span>
|
||||||
</tr>
|
{% else %}
|
||||||
{% endfor %}
|
<span class="lt-badge badge-neutral">No</span>
|
||||||
</tbody>
|
{% endif %}
|
||||||
</table>
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="lt-empty-state lt-empty-state--sm">
|
<div class="lt-empty-state lt-empty-state--sm">
|
||||||
@@ -237,15 +247,20 @@
|
|||||||
<td><button class="lt-btn lt-btn-danger lt-btn-sm" data-action="remove-sup" data-sup-id="${s.id}">Remove</button></td>
|
<td><button class="lt-btn lt-btn-danger lt-btn-sm" data-action="remove-sup" data-sup-id="${s.id}">Remove</button></td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
wrap.innerHTML = `
|
wrap.innerHTML = `
|
||||||
<div class="lt-table-wrap">
|
<div class="lt-frame">
|
||||||
<table class="lt-table" id="active-sup-table">
|
<span class="lt-frame-bl">╚</span>
|
||||||
<caption class="lt-sr-only">Active suppression rules</caption>
|
<span class="lt-frame-br">╝</span>
|
||||||
<thead><tr>
|
<div class="lt-section-header">Active Rules</div>
|
||||||
<th>Type</th><th>Target</th><th>Detail</th><th>Reason</th>
|
<div class="lt-table-wrap">
|
||||||
<th>By</th><th>Created</th><th>Expires</th><th>Actions</th>
|
<table class="lt-table" id="active-sup-table">
|
||||||
</tr></thead>
|
<caption class="lt-sr-only">Active suppression rules</caption>
|
||||||
<tbody>${tbody}</tbody>
|
<thead><tr>
|
||||||
</table>
|
<th>Type</th><th>Target</th><th>Detail</th><th>Reason</th>
|
||||||
|
<th>By</th><th>Created</th><th>Expires</th><th>Actions</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>${tbody}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user