|
@@ -134,7 +134,7 @@
|
|
|
var g = groupOf(n);
|
|
var g = groupOf(n);
|
|
|
var s = map[g.label] || (map[g.label] = {label: g.label, big: g.big, nodes: [], alarms: 0, offline: 0, maxsev: -1, latmax: 0, up: 0});
|
|
var s = map[g.label] || (map[g.label] = {label: g.label, big: g.big, nodes: [], alarms: 0, offline: 0, maxsev: -1, latmax: 0, up: 0});
|
|
|
s.nodes.push(n);
|
|
s.nodes.push(n);
|
|
|
- s.offline += (+n.term_offline || 0);
|
|
|
|
|
|
|
+ s.offline += (n.offline || n.oper === 'down') ? 1 : 0;
|
|
|
if (n.alarms && n.alarms.length) {
|
|
if (n.alarms && n.alarms.length) {
|
|
|
s.alarms += n.alarms.length;
|
|
s.alarms += n.alarms.length;
|
|
|
var sev = (n.maxsev != null && +n.maxsev >= 0) ? +n.maxsev : -1;
|
|
var sev = (n.maxsev != null && +n.maxsev >= 0) ? +n.maxsev : -1;
|
|
@@ -577,15 +577,24 @@
|
|
|
var p = parseName(n.name || '');
|
|
var p = parseName(n.name || '');
|
|
|
var r = String(p.role || '').toLowerCase();
|
|
var r = String(p.role || '').toLowerCase();
|
|
|
var tier = String(n.tier || '').toLowerCase();
|
|
var tier = String(n.tier || '').toLowerCase();
|
|
|
|
|
+ var childCount = parseInt(n.children, 10);
|
|
|
|
|
+ if (isNaN(childCount)) childCount = -1;
|
|
|
if (configuredCore(n)) return 'core';
|
|
if (configuredCore(n)) return 'core';
|
|
|
// Backward-compatible fallback only when the core list is absent (old
|
|
// Backward-compatible fallback only when the core list is absent (old
|
|
|
// config). An explicitly empty list means the administrator wants no
|
|
// config). An explicitly empty list means the administrator wants no
|
|
|
// configured core column.
|
|
// configured core column.
|
|
|
if (!(CFG.core_rules instanceof Array) && (/^core(\s|-)/i.test(String(n.name || '')) || groupOf(n).label === 'Core')) return 'core';
|
|
if (!(CFG.core_rules instanceof Array) && (/^core(\s|-)/i.test(String(n.name || '')) || groupOf(n).label === 'Core')) return 'core';
|
|
|
- if (tier === 'core' || r === 'core') return 'main';
|
|
|
|
|
- if (r === 'dist') return 'dist';
|
|
|
|
|
- if (r === 'access') return 'access';
|
|
|
|
|
- if (tier.indexOf('dist') === 0) return 'dist';
|
|
|
|
|
|
|
+ // STP topology is the truth for Dist vs Access: a switch with downstream
|
|
|
|
|
+ // children is a distribution point even when its hostname says Access.
|
|
|
|
|
+ // This fixes the 3301/4504 cases where the old snapshot tier was a root
|
|
|
|
|
+ // tier but the device is really an aggregation switch.
|
|
|
|
|
+ if (tier === 'core') {
|
|
|
|
|
+ if (childCount > 0 || r === 'dist') return 'dist';
|
|
|
|
|
+ return 'main';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (childCount > 0) return 'dist';
|
|
|
|
|
+ if (r === 'dist' && tier !== 'access') return 'dist';
|
|
|
|
|
+ if (tier === 'distribution') return 'dist';
|
|
|
return 'access';
|
|
return 'access';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -970,6 +979,7 @@
|
|
|
'curve-style': 'bezier', 'z-index-compare': 'manual', 'z-index': 11
|
|
'curve-style': 'bezier', 'z-index-compare': 'manual', 'z-index': 11
|
|
|
}},
|
|
}},
|
|
|
{selector: 'node.alarm', style: {'border-width': 6}},
|
|
{selector: 'node.alarm', style: {'border-width': 6}},
|
|
|
|
|
+ {selector: 'node.offline', style: {'border-color': '#C0392B', 'border-width': 6, 'border-style': 'dashed', 'opacity': 0.9}},
|
|
|
{selector: 'node.dim', style: {'opacity': 0.12}},
|
|
{selector: 'node.dim', style: {'opacity': 0.12}},
|
|
|
{selector: 'edge.dim', style: {'opacity': 0.08}},
|
|
{selector: 'edge.dim', style: {'opacity': 0.08}},
|
|
|
{selector: 'edge.hl', style: {'width': 5, 'line-color': '#FFD54A', 'opacity': 1, 'z-index-compare': 'manual', 'z-index': 45}},
|
|
{selector: 'edge.hl', style: {'width': 5, 'line-color': '#FFD54A', 'opacity': 1, 'z-index-compare': 'manual', 'z-index': 45}},
|
|
@@ -1052,6 +1062,7 @@
|
|
|
buildAlarms(snap);
|
|
buildAlarms(snap);
|
|
|
buildOffline(snap);
|
|
buildOffline(snap);
|
|
|
buildTerminals(snap);
|
|
buildTerminals(snap);
|
|
|
|
|
+ buildHistory();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function renderFull(snap) {
|
|
function renderFull(snap) {
|
|
@@ -1086,6 +1097,7 @@
|
|
|
var bg = p.role === 'core' ? TIER_BG.core : (p.role === 'main' ? '#2E75B6' : (p.role === 'dist' ? TIER_BG.dist : TIER_BG.access));
|
|
var bg = p.role === 'core' ? TIER_BG.core : (p.role === 'main' ? '#2E75B6' : (p.role === 'dist' ? TIER_BG.dist : TIER_BG.access));
|
|
|
var bd = bg;
|
|
var bd = bg;
|
|
|
if (n.alarms && n.alarms.length) { cls.push('alarm'); bd = '#E45959'; }
|
|
if (n.alarms && n.alarms.length) { cls.push('alarm'); bd = '#E45959'; }
|
|
|
|
|
+ if (n.offline || n.oper === 'down') cls.push('offline');
|
|
|
if (n.latency_ms != null) {
|
|
if (n.latency_ms != null) {
|
|
|
if (+n.latency_ms > LATB) cls.push('lat-bad');
|
|
if (+n.latency_ms > LATB) cls.push('lat-bad');
|
|
|
else if (+n.latency_ms > LATW) cls.push('lat-warn');
|
|
else if (+n.latency_ms > LATW) cls.push('lat-warn');
|
|
@@ -1093,7 +1105,8 @@
|
|
|
if (p.role === 'core') cls.push('root');
|
|
if (p.role === 'core') cls.push('root');
|
|
|
els.push({group: 'nodes', data: {
|
|
els.push({group: 'nodes', data: {
|
|
|
id: n.id, type: 'switch', name: n.name, ip: n.ip, tier: n.tier, depth: n.depth, hostid: n.hostid, oper: n.oper,
|
|
id: n.id, type: 'switch', name: n.name, ip: n.ip, tier: n.tier, depth: n.depth, hostid: n.hostid, oper: n.oper,
|
|
|
- maxsev: n.maxsev, alarms: n.alarms || [], term_online: n.term_online, term_offline: n.term_offline, latency_ms: n.latency_ms,
|
|
|
|
|
|
|
+ offline: !!(n.offline || n.oper === 'down'), last_online: n.last_online,
|
|
|
|
|
+ maxsev: n.maxsev, alarms: n.alarms || [], term_online: n.term_online, term_offline: 0, latency_ms: n.latency_ms,
|
|
|
label: switchLabel(n, p.role, p.net), tw: p.w - 12, bg: bg, bd: bd, w: p.w, h: p.h,
|
|
label: switchLabel(n, p.role, p.net), tw: p.w - 12, bg: bg, bd: bd, w: p.w, h: p.h,
|
|
|
group: p.group + ' · ' + netLabel(p.net), blockGroup: p.group, net: p.net, role: p.role
|
|
group: p.group + ' · ' + netLabel(p.net), blockGroup: p.group, net: p.net, role: p.role
|
|
|
}, position: {x: p.x, y: p.y}, classes: cls.join(' ')});
|
|
}, position: {x: p.x, y: p.y}, classes: cls.join(' ')});
|
|
@@ -1131,7 +1144,7 @@
|
|
|
var bySw = {};
|
|
var bySw = {};
|
|
|
((lastSnap && lastSnap.terminals) || []).forEach(function (tm) {
|
|
((lastSnap && lastSnap.terminals) || []).forEach(function (tm) {
|
|
|
if (!byId[tm.sw_ip]) return;
|
|
if (!byId[tm.sw_ip]) return;
|
|
|
- if (onlyOff && terminalOnline(tm)) return;
|
|
|
|
|
|
|
+ if (!terminalOnline(tm)) return;
|
|
|
(bySw[tm.sw_ip] = bySw[tm.sw_ip] || []).push(tm);
|
|
(bySw[tm.sw_ip] = bySw[tm.sw_ip] || []).push(tm);
|
|
|
});
|
|
});
|
|
|
Object.keys(bySw).forEach(function (sw) {
|
|
Object.keys(bySw).forEach(function (sw) {
|
|
@@ -1289,7 +1302,7 @@
|
|
|
var bySw = {};
|
|
var bySw = {};
|
|
|
(snap.terminals || []).forEach(function (tm) {
|
|
(snap.terminals || []).forEach(function (tm) {
|
|
|
if (!switchIds[tm.sw_ip]) return;
|
|
if (!switchIds[tm.sw_ip]) return;
|
|
|
- if (onlyOff && tm.online) return;
|
|
|
|
|
|
|
+ if (!terminalOnline(tm)) return;
|
|
|
(bySw[tm.sw_ip] = bySw[tm.sw_ip] || []).push(tm);
|
|
(bySw[tm.sw_ip] = bySw[tm.sw_ip] || []).push(tm);
|
|
|
});
|
|
});
|
|
|
Object.keys(bySw).forEach(function (sw) {
|
|
Object.keys(bySw).forEach(function (sw) {
|
|
@@ -1299,7 +1312,7 @@
|
|
|
var col = k % 9, row = Math.floor(k / 9);
|
|
var col = k % 9, row = Math.floor(k / 9);
|
|
|
var x = base.x - 54 + col * 13;
|
|
var x = base.x - 54 + col * 13;
|
|
|
var y = base.y + NODE_H / 2 + 16 + row * 13;
|
|
var y = base.y + NODE_H / 2 + 16 + row * 13;
|
|
|
- els.push({group: 'nodes', data: {id: 't_' + tm.mac, type: 'term', bg: tm.online ? '#2ECC71' : '#95a5a6', mac: tm.mac, ip: tm.ip, online: tm.online},
|
|
|
|
|
|
|
+ els.push({group: 'nodes', data: {id: 't_' + tm.mac, type: 'term', bg: '#2ECC71', mac: tm.mac, ip: tm.ip, online: true},
|
|
|
position: {x: x, y: y}, selectable: false});
|
|
position: {x: x, y: y}, selectable: false});
|
|
|
els.push({group: 'edges', data: {id: 'te_' + tm.mac, type: 'term', source: sw, target: 't_' + tm.mac}});
|
|
els.push({group: 'edges', data: {id: 'te_' + tm.mac, type: 'term', source: sw, target: 't_' + tm.mac}});
|
|
|
});
|
|
});
|
|
@@ -1314,7 +1327,8 @@
|
|
|
function updateStats(snap) {
|
|
function updateStats(snap) {
|
|
|
var s = snap.stats || {};
|
|
var s = snap.stats || {};
|
|
|
var txt = '● ' + (s.switches || 0) + ' | ⇄ ' + (s.edges || 0) + ' | ' +
|
|
var txt = '● ' + (s.switches || 0) + ' | ⇄ ' + (s.edges || 0) + ' | ' +
|
|
|
- (s.term_total || 0) + '(' + (s.term_online || 0) + '/' + (s.term_offline || 0) + ') | ⚠ ' + (s.alarms || 0);
|
|
|
|
|
|
|
+ (s.term_total || 0) + ' ' + t('l2.terminals', 'terminals') + ' | ' +
|
|
|
|
|
+ (s.switches_offline || 0) + ' ' + t('l2.offline_short', 'offline SW') + ' | ⚠ ' + (s.alarms || 0);
|
|
|
var up = snap.generated_iso ? (' · ' + t('l2.updated', 'Updated') + ' ' + snap.generated_iso) : '';
|
|
var up = snap.generated_iso ? (' · ' + t('l2.updated', 'Updated') + ' ' + snap.generated_iso) : '';
|
|
|
el('l2-stats').textContent = txt + up;
|
|
el('l2-stats').textContent = txt + up;
|
|
|
}
|
|
}
|
|
@@ -1336,7 +1350,7 @@
|
|
|
'<span class="lg-box" style="background:#E74C3C;border-radius:2px;margin-left:8px"></span>>' + LATB + 'ms</div>';
|
|
'<span class="lg-box" style="background:#E74C3C;border-radius:2px;margin-left:8px"></span>>' + LATB + 'ms</div>';
|
|
|
h += '<div class="lg-row"><span class="lg-box" style="background:#E45959;border-radius:2px"></span>' + t('l2.alarms', 'Alarms') + '</div>';
|
|
h += '<div class="lg-row"><span class="lg-box" style="background:#E45959;border-radius:2px"></span>' + t('l2.alarms', 'Alarms') + '</div>';
|
|
|
h += '<div class="lg-row"><span class="lg-box" style="background:#2ECC71;border-radius:50%"></span>' + t('l2.term_on', 'Online') +
|
|
h += '<div class="lg-row"><span class="lg-box" style="background:#2ECC71;border-radius:50%"></span>' + t('l2.term_on', 'Online') +
|
|
|
- '<span class="lg-box" style="background:#95a5a6;border-radius:50%;margin-left:8px"></span>' + t('l2.term_off', 'Offline') + '</div>';
|
|
|
|
|
|
|
+ '<span class="lg-box" style="background:#C0392B;border-radius:2px;margin-left:8px"></span>' + t('l2.offline', 'Offline switch') + '</div>';
|
|
|
h += '<div class="lg-row" style="opacity:.72;margin-top:4px">' + t('l2.click_hint', 'Hover a switch to highlight its full upstream path') + '</div>';
|
|
h += '<div class="lg-row" style="opacity:.72;margin-top:4px">' + t('l2.click_hint', 'Hover a switch to highlight its full upstream path') + '</div>';
|
|
|
el('l2-legend').innerHTML = h;
|
|
el('l2-legend').innerHTML = h;
|
|
|
}
|
|
}
|
|
@@ -1372,28 +1386,46 @@
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function fmtTs(ts) {
|
|
|
|
|
+ if (!ts) return '—';
|
|
|
|
|
+ try {
|
|
|
|
|
+ var d = new Date(Number(ts) * 1000);
|
|
|
|
|
+ var p = function (x) { return (x < 10 ? '0' : '') + x; };
|
|
|
|
|
+ return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes());
|
|
|
|
|
+ } catch (e) { return '—'; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function buildOffline(snap) {
|
|
function buildOffline(snap) {
|
|
|
- var off = (snap.terminals || []).filter(function (tm) { return !terminalOnline(tm); });
|
|
|
|
|
|
|
+ var off = (snap.nodes || []).filter(function (n) { return n.offline || n.oper === 'down'; });
|
|
|
el('l2-offline-count').textContent = off.length;
|
|
el('l2-offline-count').textContent = off.length;
|
|
|
- if (!off.length) { el('l2-offline-list').innerHTML = '<div class="panel-empty">' + t('l2.offline_empty', 'No offline terminals.') + '</div>'; return; }
|
|
|
|
|
|
|
+ if (!off.length) { el('l2-offline-list').innerHTML = '<div class="panel-empty">' + t('l2.offline_empty', 'No offline switches.') + '</div>'; return; }
|
|
|
|
|
+ off.sort(function (a, b) {
|
|
|
|
|
+ var ra = roleOf(a), rb = roleOf(b);
|
|
|
|
|
+ if (ra !== rb) return ra === 'dist' ? -1 : 1;
|
|
|
|
|
+ return String(a.name || '').localeCompare(String(b.name || ''));
|
|
|
|
|
+ });
|
|
|
var h = '';
|
|
var h = '';
|
|
|
- off.forEach(function (tm) {
|
|
|
|
|
- h += '<div class="off-row" data-mac="' + esc(tm.mac) + '">' +
|
|
|
|
|
- '<div class="off-main"><div class="off-mac">' + esc(tm.mac) + '</div>' +
|
|
|
|
|
- '<div class="off-sub">' + esc(speedText(tm.speed)) + ' · ' + esc(tm.sw_name || tm.sw_ip) + ' · ' + esc(tm.port || '') + '</div></div></div>';
|
|
|
|
|
|
|
+ off.forEach(function (n) {
|
|
|
|
|
+ var role = roleOf(n);
|
|
|
|
|
+ var roleTxt = role === 'dist' ? t('l2.dist', 'Distribution') : t('l2.access', 'Access');
|
|
|
|
|
+ h += '<div class="off-row sw-row" data-id="' + esc(n.id) + '">' +
|
|
|
|
|
+ '<span class="term-dot" style="background:#C0392B"></span>' +
|
|
|
|
|
+ '<div class="off-main"><div class="off-mac">' + esc(n.name || n.ip) + '</div>' +
|
|
|
|
|
+ '<div class="off-sub">' + esc(roleTxt) + ' · ' + esc(n.ip || '') +
|
|
|
|
|
+ ' · ' + t('l2.last_online', 'last online') + ': ' + esc(fmtTs(n.last_online)) + '</div></div></div>';
|
|
|
});
|
|
});
|
|
|
var box = el('l2-offline-list');
|
|
var box = el('l2-offline-list');
|
|
|
box.innerHTML = h;
|
|
box.innerHTML = h;
|
|
|
box.querySelectorAll('.off-row').forEach(function (r) {
|
|
box.querySelectorAll('.off-row').forEach(function (r) {
|
|
|
r.addEventListener('click', function () {
|
|
r.addEventListener('click', function () {
|
|
|
- var mac = r.getAttribute('data-mac');
|
|
|
|
|
- revealById('t_' + mac);
|
|
|
|
|
|
|
+ var id = r.getAttribute('data-id');
|
|
|
|
|
+ if (id) revealById(id);
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function buildTerminals(snap) {
|
|
function buildTerminals(snap) {
|
|
|
- var list = (snap.terminals || []).filter(function (tm) { return !onlyOff || !terminalOnline(tm); });
|
|
|
|
|
|
|
+ var list = (snap.terminals || []).filter(function (tm) { return terminalOnline(tm); });
|
|
|
if (el('l2-term-count')) el('l2-term-count').textContent = list.length;
|
|
if (el('l2-term-count')) el('l2-term-count').textContent = list.length;
|
|
|
var box = el('l2-term-list');
|
|
var box = el('l2-term-list');
|
|
|
if (!box) return;
|
|
if (!box) return;
|
|
@@ -1411,12 +1443,10 @@
|
|
|
});
|
|
});
|
|
|
var h = '';
|
|
var h = '';
|
|
|
list.forEach(function (tm) {
|
|
list.forEach(function (tm) {
|
|
|
- var on = terminalOnline(tm);
|
|
|
|
|
h += '<div class="off-row term-row" data-mac="' + esc(tm.mac) + '">' +
|
|
h += '<div class="off-row term-row" data-mac="' + esc(tm.mac) + '">' +
|
|
|
- '<span class="term-dot" style="background:' + (on ? '#2ECC71' : '#95a5a6') + '"></span>' +
|
|
|
|
|
|
|
+ '<span class="term-dot" style="background:#2ECC71"></span>' +
|
|
|
'<div class="off-main"><div class="off-mac">' + esc(tm.mac) + '</div>' +
|
|
'<div class="off-main"><div class="off-mac">' + esc(tm.mac) + '</div>' +
|
|
|
'<div class="off-sub"><span class="term-rate">' + esc(speedText(tm.speed)) + '</span> · ' +
|
|
'<div class="off-sub"><span class="term-rate">' + esc(speedText(tm.speed)) + '</span> · ' +
|
|
|
- (on ? t('l2.term_on', 'online') : t('l2.term_off', 'offline')) + ' · ' +
|
|
|
|
|
esc(tm.sw_name || tm.sw_ip || '') + ' · ' + esc(tm.port || '') + '</div></div></div>';
|
|
esc(tm.sw_name || tm.sw_ip || '') + ' · ' + esc(tm.port || '') + '</div></div></div>';
|
|
|
});
|
|
});
|
|
|
box.innerHTML = h;
|
|
box.innerHTML = h;
|
|
@@ -1425,6 +1455,42 @@
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function buildHistory() {
|
|
|
|
|
+ var box = el('l2-history-list');
|
|
|
|
|
+ if (!box) return;
|
|
|
|
|
+ var list = window.L2_HISTORY || [];
|
|
|
|
|
+ var used = window.L2_META && window.L2_META.usedTs ? String(window.L2_META.usedTs) : '';
|
|
|
|
|
+ var h = '<div class="off-row hist-row' + (used ? '' : ' active') + '" data-ts=""><div class="off-main">' +
|
|
|
|
|
+ '<div class="off-mac">' + t('l2.history_live', 'Live / latest') + '</div>' +
|
|
|
|
|
+ '<div class="off-sub">' + t('l2.history_live_hint', 'Newest snapshot, auto refresh') + '</div></div></div>';
|
|
|
|
|
+ if (!list.length) {
|
|
|
|
|
+ h += '<div class="panel-empty">' + t('l2.history_empty', 'No history snapshots yet.') + '</div>';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ list.forEach(function (it) {
|
|
|
|
|
+ var st = it.stats || {};
|
|
|
|
|
+ var ts = String(it.ts || '');
|
|
|
|
|
+ var sub = (st.switches != null ? st.switches + ' ' + t('l2.unit_sw', 'sw') : '') +
|
|
|
|
|
+ (st.term_total != null ? ' · ' + st.term_total + ' ' + t('l2.terminals', 'terminals') : '') +
|
|
|
|
|
+ (st.switches_offline ? ' · ' + st.switches_offline + ' ' + t('l2.offline_short', 'offline SW') : '');
|
|
|
|
|
+ h += '<div class="off-row hist-row' + (ts === used ? ' active' : '') + '" data-ts="' + esc(ts) + '">' +
|
|
|
|
|
+ '<div class="off-main"><div class="off-mac">' + esc(it.captured_at || fmtTs(it.ts)) + '</div>' +
|
|
|
|
|
+ '<div class="off-sub">' + esc(sub) + '</div></div></div>';
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ box.innerHTML = h;
|
|
|
|
|
+ box.querySelectorAll('.hist-row').forEach(function (r) {
|
|
|
|
|
+ r.addEventListener('click', function () {
|
|
|
|
|
+ var ts = r.getAttribute('data-ts');
|
|
|
|
|
+ var u = new URL(window.location.href);
|
|
|
|
|
+ u.searchParams.set('action', 'l2.topology.view');
|
|
|
|
|
+ if (ts) u.searchParams.set('ts', ts);
|
|
|
|
|
+ else u.searchParams.delete('ts');
|
|
|
|
|
+ window.location.href = u.toString();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ if (el('l2-history-count')) el('l2-history-count').textContent = list.length;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]; }); }
|
|
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]; }); }
|
|
|
|
|
|
|
|
/* ---------------- interactions ---------------- */
|
|
/* ---------------- interactions ---------------- */
|
|
@@ -1453,11 +1519,6 @@
|
|
|
pinnedTerm = tm.mac;
|
|
pinnedTerm = tm.mac;
|
|
|
showTerm = true;
|
|
showTerm = true;
|
|
|
if (el('l2-showterm')) el('l2-showterm').checked = true;
|
|
if (el('l2-showterm')) el('l2-showterm').checked = true;
|
|
|
- // A search hit must be visible even when the offline-only filter is on.
|
|
|
|
|
- if (onlyOff && terminalOnline(tm)) {
|
|
|
|
|
- onlyOff = false;
|
|
|
|
|
- if (el('l2-onlyoff')) el('l2-onlyoff').checked = false;
|
|
|
|
|
- }
|
|
|
|
|
if (!cy || cy.getElementById(id).empty()) render(lastSnap);
|
|
if (!cy || cy.getElementById(id).empty()) render(lastSnap);
|
|
|
setTimeout(function () {
|
|
setTimeout(function () {
|
|
|
// Center the switch that the terminal hangs under, then flash the
|
|
// Center the switch that the terminal hangs under, then flash the
|
|
@@ -1507,11 +1568,13 @@
|
|
|
var arr = d.alarms || [];
|
|
var arr = d.alarms || [];
|
|
|
var sev = (d.maxsev != null && d.maxsev >= 0) ? ('<span style="color:' + SEV_COLORS[d.maxsev] + '">● ' + SEV_NAMES[d.maxsev] + '</span>') : '—';
|
|
var sev = (d.maxsev != null && d.maxsev >= 0) ? ('<span style="color:' + SEV_COLORS[d.maxsev] + '">● ' + SEV_NAMES[d.maxsev] + '</span>') : '—';
|
|
|
var lat = (d.latency_ms != null) ? (d.latency_ms + ' ms') : '—';
|
|
var lat = (d.latency_ms != null) ? (d.latency_ms + ' ms') : '—';
|
|
|
|
|
+ var stat = (d.offline || d.oper === 'down') ? ('<span style="color:#E45959">' + esc(t('l2.offline', 'Offline')) + '</span>') : esc(d.oper || 'up');
|
|
|
var html = '<b>' + esc(d.name) + '</b><br>' + esc(d.ip) + ' · ' + esc(d.group || '') + '<br>' +
|
|
var html = '<b>' + esc(d.name) + '</b><br>' + esc(d.ip) + ' · ' + esc(d.group || '') + '<br>' +
|
|
|
- 'Tier: ' + esc(d.tier) + ' · ' + (d.oper || '') + '<br>' +
|
|
|
|
|
|
|
+ 'Tier: ' + esc(d.tier) + ' · ' + stat + '<br>' +
|
|
|
'Latency: ' + lat + '<br>' +
|
|
'Latency: ' + lat + '<br>' +
|
|
|
- 'Terminals: ' + (d.term_online || 0) + ' on / ' + (d.term_offline || 0) + ' off<br>' +
|
|
|
|
|
|
|
+ 'Terminals: ' + (d.term_online || 0) + '<br>' +
|
|
|
'Alarm: ' + sev;
|
|
'Alarm: ' + sev;
|
|
|
|
|
+ if (d.offline || d.oper === 'down') html += '<br>' + esc(t('l2.last_online', 'last online')) + ': ' + esc(fmtTs(d.last_online));
|
|
|
if (arr.length) html += '<hr>' + arr.map(function (a) { return esc(a.name || a.description || ''); }).join('<br>');
|
|
if (arr.length) html += '<hr>' + arr.map(function (a) { return esc(a.name || a.description || ''); }).join('<br>');
|
|
|
showTip(e, html);
|
|
showTip(e, html);
|
|
|
});
|
|
});
|
|
@@ -1573,6 +1636,7 @@
|
|
|
var idx = [];
|
|
var idx = [];
|
|
|
(snap.nodes || []).forEach(function (n) { idx.push({id: n.id, label: n.name, sub: n.ip, kind: 'sw'}); });
|
|
(snap.nodes || []).forEach(function (n) { idx.push({id: n.id, label: n.name, sub: n.ip, kind: 'sw'}); });
|
|
|
(snap.terminals || []).forEach(function (tm) {
|
|
(snap.terminals || []).forEach(function (tm) {
|
|
|
|
|
+ if (!terminalOnline(tm)) return;
|
|
|
idx.push({id: 't_' + tm.mac, label: tm.mac, sub: (tm.ip || '') + ' · ' + speedText(tm.speed) + ' · ' + (tm.sw_name || ''), kind: 'term', mac: tm.mac});
|
|
idx.push({id: 't_' + tm.mac, label: tm.mac, sub: (tm.ip || '') + ' · ' + speedText(tm.speed) + ' · ' + (tm.sw_name || ''), kind: 'term', mac: tm.mac});
|
|
|
});
|
|
});
|
|
|
return idx;
|
|
return idx;
|
|
@@ -1651,11 +1715,6 @@
|
|
|
if (back) back.style.display = 'none';
|
|
if (back) back.style.display = 'none';
|
|
|
|
|
|
|
|
el('l2-showterm').addEventListener('change', function () { showTerm = el('l2-showterm').checked; render(lastSnap); });
|
|
el('l2-showterm').addEventListener('change', function () { showTerm = el('l2-showterm').checked; render(lastSnap); });
|
|
|
- el('l2-onlyoff').addEventListener('change', function () {
|
|
|
|
|
- onlyOff = el('l2-onlyoff').checked;
|
|
|
|
|
- if (onlyOff && !showTerm) { showTerm = true; el('l2-showterm').checked = true; }
|
|
|
|
|
- render(lastSnap);
|
|
|
|
|
- });
|
|
|
|
|
el('l2-zin').addEventListener('click', function () { if (cy) cy.zoom({level: cy.zoom() * 1.25, renderedPosition: {x: cy.width() / 2, y: cy.height() / 2}}); });
|
|
el('l2-zin').addEventListener('click', function () { if (cy) cy.zoom({level: cy.zoom() * 1.25, renderedPosition: {x: cy.width() / 2, y: cy.height() / 2}}); });
|
|
|
el('l2-zout').addEventListener('click', function () { if (cy) cy.zoom({level: cy.zoom() / 1.25, renderedPosition: {x: cy.width() / 2, y: cy.height() / 2}}); });
|
|
el('l2-zout').addEventListener('click', function () { if (cy) cy.zoom({level: cy.zoom() / 1.25, renderedPosition: {x: cy.width() / 2, y: cy.height() / 2}}); });
|
|
|
el('l2-fit').addEventListener('click', function () { if (cy) cy.fit(cy.elements(), 50); });
|
|
el('l2-fit').addEventListener('click', function () { if (cy) cy.fit(cy.elements(), 50); });
|