TDS polish: lt-frame tables, lt-stats-grid link summary, settings-aware refresh
- 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 switches = data.unifi_switches || {};
|
||||
|
||||
const upd = data.updated ? `Updated: ${data.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) {
|
||||
main.innerHTML = '<p class="empty-state">No switch data available. Monitor may still be initialising.</p>';
|
||||
@@ -465,7 +466,13 @@ async function 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', () => {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
let swTotal = 0, swDown = 0;
|
||||
for (const sw of Object.values(unifiSwitches || {})) {
|
||||
for (const p of Object.values(sw.ports || {})) {
|
||||
totalPoe += p.poe_power || 0;
|
||||
swTotal++;
|
||||
if (!p.up) swDown++;
|
||||
}
|
||||
}
|
||||
const hasAlerts = downIfaces > 0 || errIfaces > 0;
|
||||
return `
|
||||
<div class="link-summary-panel ${hasAlerts ? 'link-summary-has-alerts' : ''}">
|
||||
<div class="link-summary-grid">
|
||||
<div class="link-summary-stat">
|
||||
<span class="lss-label">Total Interfaces</span>
|
||||
<span class="lss-value">${totalIfaces}</span>
|
||||
</div>
|
||||
<div class="link-summary-stat ${downIfaces ? 'lss-alert' : ''}">
|
||||
<span class="lss-label">Interfaces Down</span>
|
||||
<span class="lss-value ${downIfaces ? 'val-crit' : 'val-good'}">${downIfaces}</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>` : ''}
|
||||
const allTotal = totalIfaces + swTotal;
|
||||
const allDown = downIfaces + swDown;
|
||||
const downColor = allDown > 0 ? 'var(--red)' : 'var(--green)';
|
||||
const errColor = errIfaces > 0 ? 'var(--amber)' : 'var(--green)';
|
||||
const downCardCls = allDown > 0 ? ' lt-stat-card--alert' : '';
|
||||
const poeCard = totalPoe > 0 ? `
|
||||
<div class="lt-stat-card">
|
||||
<span class="lt-stat-icon" aria-hidden="true" style="color:var(--amber)">⚡</span>
|
||||
<div class="lt-stat-info">
|
||||
<span class="lt-stat-value" style="color:var(--amber)">${totalPoe.toFixed(1)}</span>
|
||||
<span class="lt-stat-label">PoE Load (W)</span>
|
||||
</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>`;
|
||||
}
|
||||
|
||||
|
||||
+81
-66
@@ -85,33 +85,38 @@
|
||||
</div>
|
||||
<div id="active-sup-wrap">
|
||||
{% if active %}
|
||||
<div class="lt-table-wrap">
|
||||
<table class="lt-table" id="active-sup-table">
|
||||
<caption class="lt-sr-only">Active suppression rules</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<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>
|
||||
{% for s in active %}
|
||||
<tr id="sup-row-{{ s.id }}">
|
||||
{%- set _sup_badge = {'host':'badge-warning','interface':'badge-info','unifi_device':'badge-purple','all':'badge-critical'} -%}
|
||||
<td><span class="lt-badge {{ _sup_badge.get(s.target_type, 'badge-neutral') }}">{{ s.target_type }}</span></td>
|
||||
<td>{{ s.target_name or 'all' }}</td>
|
||||
<td>{{ s.target_detail or '–' }}</td>
|
||||
<td>{{ s.reason }}</td>
|
||||
<td>{{ s.suppressed_by }}</td>
|
||||
<td class="ts-cell">{{ s.created_at }}</td>
|
||||
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="lt-frame">
|
||||
<span class="lt-frame-bl">╚</span>
|
||||
<span class="lt-frame-br">╝</span>
|
||||
<div class="lt-section-header">Active Rules</div>
|
||||
<div class="lt-table-wrap">
|
||||
<table class="lt-table" id="active-sup-table">
|
||||
<caption class="lt-sr-only">Active suppression rules</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<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>
|
||||
{% for s in active %}
|
||||
<tr id="sup-row-{{ s.id }}">
|
||||
{%- set _sup_badge = {'host':'badge-warning','interface':'badge-info','unifi_device':'badge-purple','all':'badge-critical'} -%}
|
||||
<td><span class="lt-badge {{ _sup_badge.get(s.target_type, 'badge-neutral') }}">{{ s.target_type }}</span></td>
|
||||
<td>{{ s.target_name or 'all' }}</td>
|
||||
<td>{{ s.target_detail or '–' }}</td>
|
||||
<td>{{ s.reason }}</td>
|
||||
<td>{{ s.suppressed_by }}</td>
|
||||
<td class="ts-cell">{{ s.created_at }}</td>
|
||||
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<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>
|
||||
</div>
|
||||
{% if history %}
|
||||
<div class="lt-table-wrap">
|
||||
<table class="lt-table lt-table-sm">
|
||||
<caption class="lt-sr-only">Suppression history</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th><th>Target</th><th>Detail</th><th>Reason</th>
|
||||
<th>By</th><th>Created</th><th>Expires</th><th>Active</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in history %}
|
||||
<tr class="{% if not s.active %}row-resolved{% endif %}">
|
||||
<td>{{ s.target_type }}</td>
|
||||
<td>{{ s.target_name or 'all' }}</td>
|
||||
<td>{{ s.target_detail or '–' }}</td>
|
||||
<td>{{ s.reason }}</td>
|
||||
<td>{{ s.suppressed_by }}</td>
|
||||
<td class="ts-cell">{{ s.created_at }}</td>
|
||||
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</td>
|
||||
<td>
|
||||
{% if s.active %}
|
||||
<span class="lt-badge badge-ok">Yes</span>
|
||||
{% else %}
|
||||
<span class="lt-badge badge-neutral">No</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="lt-frame">
|
||||
<span class="lt-frame-bl">╚</span>
|
||||
<span class="lt-frame-br">╝</span>
|
||||
<div class="lt-section-header">Suppression Log</div>
|
||||
<div class="lt-table-wrap">
|
||||
<table class="lt-table lt-table-sm">
|
||||
<caption class="lt-sr-only">Suppression history</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th><th>Target</th><th>Detail</th><th>Reason</th>
|
||||
<th>By</th><th>Created</th><th>Expires</th><th>Active</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in history %}
|
||||
<tr class="{% if not s.active %}row-resolved{% endif %}">
|
||||
<td>{{ s.target_type }}</td>
|
||||
<td>{{ s.target_name or 'all' }}</td>
|
||||
<td>{{ s.target_detail or '–' }}</td>
|
||||
<td>{{ s.reason }}</td>
|
||||
<td>{{ s.suppressed_by }}</td>
|
||||
<td class="ts-cell">{{ s.created_at }}</td>
|
||||
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</td>
|
||||
<td>
|
||||
{% if s.active %}
|
||||
<span class="lt-badge badge-ok">Yes</span>
|
||||
{% else %}
|
||||
<span class="lt-badge badge-neutral">No</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<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>
|
||||
</tr>`).join('');
|
||||
wrap.innerHTML = `
|
||||
<div class="lt-table-wrap">
|
||||
<table class="lt-table" id="active-sup-table">
|
||||
<caption class="lt-sr-only">Active suppression rules</caption>
|
||||
<thead><tr>
|
||||
<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 class="lt-frame">
|
||||
<span class="lt-frame-bl">╚</span>
|
||||
<span class="lt-frame-br">╝</span>
|
||||
<div class="lt-section-header">Active Rules</div>
|
||||
<div class="lt-table-wrap">
|
||||
<table class="lt-table" id="active-sup-table">
|
||||
<caption class="lt-sr-only">Active suppression rules</caption>
|
||||
<thead><tr>
|
||||
<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>`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user