|
@@ -20,8 +20,11 @@
|
|
|
var TIER_BG_DARK = {core:'#1B4A72', dist:'#2A5E93', access:'#3E6B2E'};
|
|
var TIER_BG_DARK = {core:'#1B4A72', dist:'#2A5E93', access:'#3E6B2E'};
|
|
|
var SEV_COLORS = ['#97AAB3','#7499FF','#FFC859','#FFA059','#E97659','#E45959'];
|
|
var SEV_COLORS = ['#97AAB3','#7499FF','#FFC859','#FFA059','#E97659','#E45959'];
|
|
|
|
|
|
|
|
- var NODE_W = 190, NODE_H = 56, GX = 26, GY = 18, PADX = 20, PADY = 42;
|
|
|
|
|
|
|
+ var NODE_W = 156, NODE_H = 50, GX = 26, GY = 18, PADX = 20, PADY = 42;
|
|
|
|
|
+ var DGAP_X = 18, DGAP_Y = 26;
|
|
|
var GROUP_GAP_X = 46, GROUP_GAP_Y = 48, MAXW = 2800;
|
|
var GROUP_GAP_X = 46, GROUP_GAP_Y = 48, MAXW = 2800;
|
|
|
|
|
+ // overview ("全景") tiles
|
|
|
|
|
+ var TILE_W = 232, TILE_H = 116, TILE_GAP_X = 24, TILE_GAP_Y = 22;
|
|
|
|
|
|
|
|
var cy = null;
|
|
var cy = null;
|
|
|
var dark = false;
|
|
var dark = false;
|
|
@@ -31,6 +34,7 @@
|
|
|
var countdown = 0;
|
|
var countdown = 0;
|
|
|
var timer = null;
|
|
var timer = null;
|
|
|
var lastSnap = null;
|
|
var lastSnap = null;
|
|
|
|
|
+ var viewGroup = null; // null = overview, otherwise workshop label
|
|
|
|
|
|
|
|
function el(id) { return document.getElementById(id); }
|
|
function el(id) { return document.getElementById(id); }
|
|
|
function t(k, d) { return (T[k] != null) ? T[k] : (d != null ? d : k); }
|
|
function t(k, d) { return (T[k] != null) ? T[k] : (d != null ? d : k); }
|
|
@@ -90,68 +94,208 @@
|
|
|
return {label: CFG.other_label || 'Other', big: false};
|
|
return {label: CFG.other_label || 'Other', big: false};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* ---------------- layout ---------------- */
|
|
|
|
|
- function computeLayout(snap) {
|
|
|
|
|
- var groups = {};
|
|
|
|
|
|
|
+ /* ---------------- overview layout (workshop tiles) ---------------- */
|
|
|
|
|
+ function groupStats(snap) {
|
|
|
|
|
+ var map = {};
|
|
|
|
|
+ var nodeById = {};
|
|
|
(snap.nodes || []).forEach(function (n) {
|
|
(snap.nodes || []).forEach(function (n) {
|
|
|
|
|
+ nodeById[n.id] = n;
|
|
|
var g = groupOf(n);
|
|
var g = groupOf(n);
|
|
|
- if (!groups[g.label]) groups[g.label] = {label: g.label, big: g.big, nodes: []};
|
|
|
|
|
- if (g.big) groups[g.label].big = true;
|
|
|
|
|
- groups[g.label].nodes.push(n);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- var order = Object.keys(groups).sort(function (a, b) {
|
|
|
|
|
- function rank(l) {
|
|
|
|
|
- if (/core/i.test(l)) return 0;
|
|
|
|
|
- if (/data\s*center/i.test(l)) return 1;
|
|
|
|
|
- if (/^\d+$/.test(l)) return 1000 + parseInt(l, 10);
|
|
|
|
|
- return 5000;
|
|
|
|
|
|
|
+ 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.offline += (+n.term_offline || 0);
|
|
|
|
|
+ if (n.alarms && n.alarms.length) {
|
|
|
|
|
+ s.alarms += n.alarms.length;
|
|
|
|
|
+ var sev = (n.maxsev != null && +n.maxsev >= 0) ? +n.maxsev : -1;
|
|
|
|
|
+ n.alarms.forEach(function (a) { if (a && a.sev != null && +a.sev > sev) sev = +a.sev; });
|
|
|
|
|
+ if (sev > s.maxsev) s.maxsev = sev;
|
|
|
}
|
|
}
|
|
|
- return rank(a) - rank(b);
|
|
|
|
|
|
|
+ if (n.latency_ms != null && +n.latency_ms > s.latmax) s.latmax = +n.latency_ms;
|
|
|
});
|
|
});
|
|
|
|
|
+ (snap.edges || []).forEach(function (e) {
|
|
|
|
|
+ var a = nodeById[e.source], b = nodeById[e.target];
|
|
|
|
|
+ if (!a || !b) return;
|
|
|
|
|
+ var ga = groupOf(a), gb = groupOf(b);
|
|
|
|
|
+ if (ga.label === gb.label) return;
|
|
|
|
|
+ // count links that leave this workshop towards a core switch
|
|
|
|
|
+ if ((/core/i.test(a.tier || '') || /core/i.test(a.name || '')) && map[gb.label]) map[gb.label].up++;
|
|
|
|
|
+ if ((/core/i.test(b.tier || '') || /core/i.test(b.name || '')) && map[ga.label]) map[ga.label].up++;
|
|
|
|
|
+ });
|
|
|
|
|
+ return map;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var pos = {}, frames = [];
|
|
|
|
|
- var cursorX = 0, cursorY = 0, rowH = 0, minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
|
|
|
|
|
+ function groupRank(l) {
|
|
|
|
|
+ if (/core/i.test(l)) return 0;
|
|
|
|
|
+ if (/data\s*center/i.test(l)) return 1;
|
|
|
|
|
+ var m = /^(\d{4})([A-Za-z]?)$/.exec(l);
|
|
|
|
|
+ if (m) return 1000 + parseInt(m[1], 10) * 40 + (m[2] ? m[2].toUpperCase().charCodeAt(0) - 64 : 0);
|
|
|
|
|
+ return 5000;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- order.forEach(function (label) {
|
|
|
|
|
- var g = groups[label];
|
|
|
|
|
- g.nodes.sort(function (a, b) {
|
|
|
|
|
- var da = a.depth || 0, db = b.depth || 0;
|
|
|
|
|
- if (da !== db) return da - db;
|
|
|
|
|
- return (a.name || '').localeCompare(b.name || '');
|
|
|
|
|
|
|
+ function computeOverview(snap) {
|
|
|
|
|
+ var stats = groupStats(snap);
|
|
|
|
|
+ var labels = Object.keys(stats).sort(function (a, b) { return groupRank(a) - groupRank(b); });
|
|
|
|
|
+ var special = labels.filter(function (l) { return groupRank(l) < 1000; });
|
|
|
|
|
+ var others = labels.filter(function (l) { return groupRank(l) >= 1000; });
|
|
|
|
|
+
|
|
|
|
|
+ var COLS = Math.max(4, Math.min(7, Math.ceil(Math.sqrt(Math.max(1, others.length)))));
|
|
|
|
|
+ var gridW = COLS * TILE_W + (COLS - 1) * TILE_GAP_X;
|
|
|
|
|
+ var pos = {}, tiles = [], y = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (special.length) {
|
|
|
|
|
+ var sW = special.length * TILE_W + (special.length - 1) * TILE_GAP_X;
|
|
|
|
|
+ var sx = Math.max(0, (gridW - sW) / 2);
|
|
|
|
|
+ special.forEach(function (l) {
|
|
|
|
|
+ tiles.push({label: l, x: sx, y: y, w: TILE_W, h: TILE_H});
|
|
|
|
|
+ pos[l] = {x: sx + TILE_W / 2, y: y + TILE_H / 2};
|
|
|
|
|
+ sx += TILE_W + TILE_GAP_X;
|
|
|
});
|
|
});
|
|
|
- var n = g.nodes.length;
|
|
|
|
|
- var cols = Math.min(4, Math.max(1, Math.ceil(Math.sqrt(n))));
|
|
|
|
|
- var rows = Math.ceil(n / cols);
|
|
|
|
|
- var innerW = cols * NODE_W + (cols - 1) * GX;
|
|
|
|
|
- var innerH = rows * NODE_H + (rows - 1) * GY;
|
|
|
|
|
- var boxW = innerW + PADX * 2;
|
|
|
|
|
- var boxH = innerH + PADY + 12;
|
|
|
|
|
-
|
|
|
|
|
- if (cursorX > 0 && cursorX + boxW > MAXW) { cursorX = 0; cursorY += rowH + GROUP_GAP_Y; rowH = 0; }
|
|
|
|
|
-
|
|
|
|
|
- g.nodes.forEach(function (nd, i) {
|
|
|
|
|
- var rr = Math.floor(i / cols), cc = i % cols;
|
|
|
|
|
- pos[nd.id] = {
|
|
|
|
|
- x: cursorX + PADX + cc * (NODE_W + GX) + NODE_W / 2,
|
|
|
|
|
- y: cursorY + PADY + rr * (NODE_H + GY) + NODE_H / 2
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ y += TILE_H + 46;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var maxX = gridW, maxY = y;
|
|
|
|
|
+ for (var i = 0; i < others.length; i += COLS) {
|
|
|
|
|
+ var row = others.slice(i, i + COLS);
|
|
|
|
|
+ var rW = row.length * TILE_W + (row.length - 1) * TILE_GAP_X;
|
|
|
|
|
+ var rx = Math.max(0, (gridW - rW) / 2);
|
|
|
|
|
+ row.forEach(function (l, j) {
|
|
|
|
|
+ tiles.push({label: l, x: rx + j * (TILE_W + TILE_GAP_X), y: y, w: TILE_W, h: TILE_H});
|
|
|
|
|
+ pos[l] = {x: rx + j * (TILE_W + TILE_GAP_X) + TILE_W / 2, y: y + TILE_H / 2};
|
|
|
});
|
|
});
|
|
|
- frames.push({label: label, big: g.big, x: cursorX, y: cursorY, w: boxW, h: boxH});
|
|
|
|
|
- minX = Math.min(minX, cursorX); minY = Math.min(minY, cursorY);
|
|
|
|
|
- maxX = Math.max(maxX, cursorX + boxW); maxY = Math.max(maxY, cursorY + boxH);
|
|
|
|
|
|
|
+ y += TILE_H + TILE_GAP_Y;
|
|
|
|
|
+ maxY = y - TILE_GAP_Y;
|
|
|
|
|
+ maxX = Math.max(maxX, rx + rW);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var root = {x: -34, y: -64, w: maxX + 68, h: maxY + 64 + 40};
|
|
|
|
|
+ root.cx = root.x + root.w / 2; root.cy = root.y + root.h / 2;
|
|
|
|
|
+ return {stats: stats, labels: labels, pos: pos, tiles: tiles, root: root};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function aggregateEdges(snap) {
|
|
|
|
|
+ var gid = {};
|
|
|
|
|
+ (snap.nodes || []).forEach(function (n) { gid[n.id] = groupOf(n).label; });
|
|
|
|
|
+ var pairs = {};
|
|
|
|
|
+ (snap.edges || []).forEach(function (e) {
|
|
|
|
|
+ var a = gid[e.source], b = gid[e.target];
|
|
|
|
|
+ if (!a || !b || a === b) return;
|
|
|
|
|
+ var k = (a < b) ? (a + '\u0001' + b) : (b + '\u0001' + a);
|
|
|
|
|
+ var p = pairs[k] || (pairs[k] = {a: a, b: b, count: 0, speed: 0, down: false});
|
|
|
|
|
+ p.count++;
|
|
|
|
|
+ if ((parseFloat(e.speed) || 0) > p.speed) p.speed = parseFloat(e.speed) || 0;
|
|
|
|
|
+ if (e.state && e.state !== 'up') p.down = true;
|
|
|
|
|
+ });
|
|
|
|
|
+ return Object.keys(pairs).map(function (k) { return pairs[k]; });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- cursorX += boxW + GROUP_GAP_X;
|
|
|
|
|
- rowH = Math.max(rowH, boxH);
|
|
|
|
|
|
|
+ /* ---------------- detail layout (one workshop, tidy tree) ---------------- */
|
|
|
|
|
+ function computeDetail(snap, label) {
|
|
|
|
|
+ var all = snap.nodes || [];
|
|
|
|
|
+ var byId = {}, nodes = [];
|
|
|
|
|
+ all.forEach(function (n) { byId[n.id] = n; if (groupOf(n).label === label) nodes.push(n); });
|
|
|
|
|
+ var ids = {};
|
|
|
|
|
+ nodes.forEach(function (n) { ids[n.id] = true; });
|
|
|
|
|
+ var internal = [], external = [];
|
|
|
|
|
+ (snap.edges || []).forEach(function (e) {
|
|
|
|
|
+ var a = ids[e.source], b = ids[e.target];
|
|
|
|
|
+ if (a && b) internal.push(e);
|
|
|
|
|
+ else if (a || b) external.push(e);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- var root = null;
|
|
|
|
|
- if (frames.length) {
|
|
|
|
|
- var m = 28;
|
|
|
|
|
- root = {x: minX - m, y: minY - m - 30, w: (maxX - minX) + 2 * m, h: (maxY - minY) + 2 * m + 30};
|
|
|
|
|
- root.cx = root.x + root.w / 2; root.cy = root.y + root.h / 2;
|
|
|
|
|
|
|
+ var adj = {}, kids = {};
|
|
|
|
|
+ nodes.forEach(function (n) { adj[n.id] = []; kids[n.id] = []; });
|
|
|
|
|
+ internal.forEach(function (e) { adj[e.source].push(e.target); adj[e.target].push(e.source); });
|
|
|
|
|
+
|
|
|
|
|
+ function rankNode(n) {
|
|
|
|
|
+ var tier = n.tier || '';
|
|
|
|
|
+ if (/core/i.test(tier)) return 0;
|
|
|
|
|
+ if (/dist/i.test(tier)) return 1;
|
|
|
|
|
+ return 2;
|
|
|
}
|
|
}
|
|
|
- return {pos: pos, frames: frames, root: root};
|
|
|
|
|
|
|
+ var roots = nodes.slice().sort(function (a, b) {
|
|
|
|
|
+ var ra = rankNode(a), rb = rankNode(b);
|
|
|
|
|
+ if (ra !== rb) return ra - rb;
|
|
|
|
|
+ return adj[b.id].length - adj[a.id].length;
|
|
|
|
|
+ });
|
|
|
|
|
+ var seen = {}, orderedRoots = [];
|
|
|
|
|
+ roots.forEach(function (r) {
|
|
|
|
|
+ if (seen[r.id]) return;
|
|
|
|
|
+ orderedRoots.push(r);
|
|
|
|
|
+ var queue = [r.id]; seen[r.id] = true;
|
|
|
|
|
+ while (queue.length) {
|
|
|
|
|
+ var cur = queue.shift();
|
|
|
|
|
+ adj[cur].forEach(function (nb) {
|
|
|
|
|
+ if (seen[nb]) return;
|
|
|
|
|
+ seen[nb] = true;
|
|
|
|
|
+ kids[cur].push(nb);
|
|
|
|
|
+ queue.push(nb);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ nodes.forEach(function (n) { if (!seen[n.id]) { seen[n.id] = true; orderedRoots.push(n); } });
|
|
|
|
|
+
|
|
|
|
|
+ // Compact blocks: each dist root gets a grid of access switches underneath it.
|
|
|
|
|
+ var pos = {}, blockX = 0, maxBlockH = 0;
|
|
|
|
|
+ orderedRoots.forEach(function (rootN) {
|
|
|
|
|
+ var d1 = kids[rootN.id] || [];
|
|
|
|
|
+ var cols = Math.max(1, Math.min(5, Math.ceil(Math.sqrt(Math.max(1, d1.length) * 1.2))));
|
|
|
|
|
+ var blockW = cols * NODE_W + (cols - 1) * DGAP_X;
|
|
|
|
|
+ pos[rootN.id] = {x: blockX + blockW / 2, y: 0};
|
|
|
|
|
+ d1.forEach(function (c, i) {
|
|
|
|
|
+ var col = i % cols, row = Math.floor(i / cols);
|
|
|
|
|
+ pos[c] = {x: blockX + col * (NODE_W + DGAP_X) + NODE_W / 2, y: (row + 1) * (NODE_H + DGAP_Y)};
|
|
|
|
|
+ });
|
|
|
|
|
+ var rows = Math.ceil(d1.length / cols);
|
|
|
|
|
+ var d2 = [];
|
|
|
|
|
+ d1.forEach(function (c) {
|
|
|
|
|
+ (kids[c] || []).forEach(function (k) { if (k !== rootN.id) d2.push(k); });
|
|
|
|
|
+ });
|
|
|
|
|
+ var y2 = (rows + 1) * (NODE_H + DGAP_Y) + 16;
|
|
|
|
|
+ d2.forEach(function (id, i) {
|
|
|
|
|
+ pos[id] = {x: blockX + (i % cols) * (NODE_W + DGAP_X) + NODE_W / 2, y: y2 + Math.floor(i / cols) * (NODE_H + DGAP_Y)};
|
|
|
|
|
+ });
|
|
|
|
|
+ maxBlockH = Math.max(maxBlockH, y2 + Math.ceil(d2.length / cols) * (NODE_H + DGAP_Y));
|
|
|
|
|
+ blockX += blockW + 70;
|
|
|
|
|
+ });
|
|
|
|
|
+ var missing = [];
|
|
|
|
|
+ nodes.forEach(function (n) { if (!pos[n.id]) missing.push(n.id); });
|
|
|
|
|
+ missing.forEach(function (id, i) {
|
|
|
|
|
+ pos[id] = {x: (i % 6) * (NODE_W + DGAP_X) + NODE_W / 2, y: maxBlockH + 70 + Math.floor(i / 6) * (NODE_H + DGAP_Y)};
|
|
|
|
|
+ });
|
|
|
|
|
+ var totalW = Math.max(NODE_W, blockX - 70);
|
|
|
|
|
+
|
|
|
|
|
+ var ghosts = [], ghostSeen = {};
|
|
|
|
|
+ external.forEach(function (e) {
|
|
|
|
|
+ var inside = ids[e.source] ? e.source : e.target;
|
|
|
|
|
+ var outside = ids[e.source] ? e.target : e.source;
|
|
|
|
|
+ if (!ghostSeen[outside]) {
|
|
|
|
|
+ ghostSeen[outside] = {id: outside, to: inside, node: byId[outside], speed: e.speed, state: e.state};
|
|
|
|
|
+ ghosts.push(ghostSeen[outside]);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ var ghostAt = {};
|
|
|
|
|
+ ghosts.forEach(function (g) {
|
|
|
|
|
+ var base = pos[g.to] || {x: 80};
|
|
|
|
|
+ var k = ghostAt[g.to] || 0;
|
|
|
|
|
+ ghostAt[g.to] = k + 1;
|
|
|
|
|
+ g.x = base.x + (k ? (k % 2 ? 1 : -1) * 95 * Math.ceil(k / 2) : 0);
|
|
|
|
|
+ g.y = -86 - Math.floor(k / 2) * 48;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ var maxY = 0, minX = 0, maxX2 = totalW;
|
|
|
|
|
+ Object.keys(pos).forEach(function (id) {
|
|
|
|
|
+ maxY = Math.max(maxY, pos[id].y + NODE_H);
|
|
|
|
|
+ minX = Math.min(minX, pos[id].x - NODE_W / 2);
|
|
|
|
|
+ maxX2 = Math.max(maxX2, pos[id].x + NODE_W / 2);
|
|
|
|
|
+ });
|
|
|
|
|
+ ghosts.forEach(function (g) {
|
|
|
|
|
+ minX = Math.min(minX, g.x - 95);
|
|
|
|
|
+ maxX2 = Math.max(maxX2, g.x + 95);
|
|
|
|
|
+ });
|
|
|
|
|
+ var top = ghosts.length ? -168 : -120;
|
|
|
|
|
+ var root = {x: minX - 38, y: top, w: (maxX2 - minX) + 76, h: maxY - top + 54};
|
|
|
|
|
+ root.cx = root.x + root.w / 2; root.cy = root.y + root.h / 2;
|
|
|
|
|
+ return {nodes: nodes, byId: byId, pos: pos, ghosts: ghosts, root: root, totalW: totalW, ids: ids, internal: internal};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* ---------------- speed colour ---------------- */
|
|
/* ---------------- speed colour ---------------- */
|
|
@@ -171,6 +315,12 @@
|
|
|
if (s >= 1000) return 2.6;
|
|
if (s >= 1000) return 2.6;
|
|
|
return 1.8;
|
|
return 1.8;
|
|
|
}
|
|
}
|
|
|
|
|
+ function speedText(speed) {
|
|
|
|
|
+ var s = parseFloat(speed) || 0;
|
|
|
|
|
+ if (!s) return '?';
|
|
|
|
|
+ if (s >= 1000) return (s / 1000) + 'G';
|
|
|
|
|
+ return s + 'M';
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
/* ---------------- style ---------------- */
|
|
/* ---------------- style ---------------- */
|
|
|
function styleFor(d) {
|
|
function styleFor(d) {
|
|
@@ -198,11 +348,49 @@
|
|
|
'font-size': 28, 'font-weight': 900, 'color': d ? '#cfe0ff' : '#33465f',
|
|
'font-size': 28, 'font-weight': 900, 'color': d ? '#cfe0ff' : '#33465f',
|
|
|
'text-margin-y': 10, 'events': 'no', 'z-index-compare': 'manual', 'z-index': 0
|
|
'text-margin-y': 10, 'events': 'no', 'z-index-compare': 'manual', 'z-index': 0
|
|
|
}},
|
|
}},
|
|
|
|
|
+ {selector: 'node[type="tile"]', style: {
|
|
|
|
|
+ 'width': 'data(w)', 'height': 'data(h)', 'shape': 'round-rectangle',
|
|
|
|
|
+ 'background-color': 'data(bg)', 'background-opacity': 0.97,
|
|
|
|
|
+ 'border-width': 2.5, 'border-color': 'data(bd)',
|
|
|
|
|
+ 'label': 'data(label)', 'color': 'data(fg)',
|
|
|
|
|
+ 'font-size': 16, 'font-weight': 700, 'text-valign': 'center', 'text-halign': 'center',
|
|
|
|
|
+ 'text-wrap': 'wrap', 'text-max-width': TILE_W - 26, 'line-height': 1.45,
|
|
|
|
|
+ 'padding': '6px', 'overlay-opacity': 0, 'z-index-compare': 'manual', 'z-index': 20
|
|
|
|
|
+ }},
|
|
|
|
|
+ {selector: 'node[type="tile"].alarm', style: {'border-width': 5}},
|
|
|
|
|
+ {selector: 'node[type="tile"].core', style: {'border-width': 3.5}},
|
|
|
|
|
+ {selector: 'node[type="ghost"]', style: {
|
|
|
|
|
+ 'width': 190, 'height': 40, 'shape': 'round-rectangle',
|
|
|
|
|
+ 'background-color': d ? '#52667f' : '#c8d3e2', 'background-opacity': 0.55,
|
|
|
|
|
+ 'border-width': 1.2, 'border-style': 'dashed', 'border-color': d ? '#7d93ad' : '#8fa3bc',
|
|
|
|
|
+ 'label': 'data(label)', 'color': d ? '#dce8fa' : '#3b4f68',
|
|
|
|
|
+ 'font-size': 11, 'font-weight': 600, 'text-wrap': 'wrap', 'text-max-width': 176,
|
|
|
|
|
+ 'z-index-compare': 'manual', 'z-index': 15
|
|
|
|
|
+ }},
|
|
|
{selector: 'node[type="term"]', style: {
|
|
{selector: 'node[type="term"]', style: {
|
|
|
'width': 12, 'height': 12, 'shape': 'ellipse', 'background-color': 'data(bg)',
|
|
'width': 12, 'height': 12, 'shape': 'ellipse', 'background-color': 'data(bg)',
|
|
|
'border-width': 1, 'border-color': d ? '#2a3648' : '#cfd8e3', 'label': '',
|
|
'border-width': 1, 'border-color': d ? '#2a3648' : '#cfd8e3', 'label': '',
|
|
|
'z-index-compare': 'manual', 'z-index': 30
|
|
'z-index-compare': 'manual', 'z-index': 30
|
|
|
}},
|
|
}},
|
|
|
|
|
+ {selector: 'edge[type="tilelink"]', style: {
|
|
|
|
|
+ 'width': 'data(w)', 'line-color': 'data(color)', 'opacity': 0.55,
|
|
|
|
|
+ 'curve-style': 'bezier', 'target-arrow-shape': 'none',
|
|
|
|
|
+ 'z-index-compare': 'manual', 'z-index': 5
|
|
|
|
|
+ }},
|
|
|
|
|
+ {selector: 'edge[type="ghost"]', style: {
|
|
|
|
|
+ 'width': 1.2, 'line-color': d ? '#6b809b' : '#a9b6c8', 'line-style': 'dashed', 'opacity': 0.7,
|
|
|
|
|
+ 'curve-style': 'bezier', 'target-arrow-shape': 'none',
|
|
|
|
|
+ 'z-index-compare': 'manual', 'z-index': 8
|
|
|
|
|
+ }},
|
|
|
|
|
+ {selector: 'edge[type="uplink"]', style: {
|
|
|
|
|
+ 'width': 'data(w)', 'line-color': 'data(color)', 'line-style': 'data(ls)', 'opacity': 0.95,
|
|
|
|
|
+ 'curve-style': 'bezier', 'target-arrow-shape': 'none',
|
|
|
|
|
+ 'label': 'data(lbl)', 'font-size': 10, 'font-weight': 700,
|
|
|
|
|
+ 'color': d ? '#dce8fa' : '#25405f',
|
|
|
|
|
+ 'text-background-color': d ? '#0d1b2a' : '#ffffff', 'text-background-opacity': 0.88,
|
|
|
|
|
+ 'text-background-padding': '2px', 'text-rotation': 'autorotate',
|
|
|
|
|
+ 'z-index-compare': 'manual', 'z-index': 40
|
|
|
|
|
+ }},
|
|
|
{selector: 'edge', style: {
|
|
{selector: 'edge', style: {
|
|
|
'width': 'data(w)', 'line-color': 'data(color)', 'line-style': 'data(ls)',
|
|
'width': 'data(w)', 'line-color': 'data(color)', 'line-style': 'data(ls)',
|
|
|
'curve-style': 'bezier', 'opacity': 0.92, 'target-arrow-shape': 'none',
|
|
'curve-style': 'bezier', 'opacity': 0.92, 'target-arrow-shape': 'none',
|
|
@@ -213,6 +401,9 @@
|
|
|
'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.dim', style: {'opacity': 0.12}},
|
|
|
|
|
+ {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: 'node.lat-warn', style: {'border-color': '#F39C12'}},
|
|
{selector: 'node.lat-warn', style: {'border-color': '#F39C12'}},
|
|
|
{selector: 'node.lat-bad', style: {'border-color': '#E74C3C', 'border-width': 6}},
|
|
{selector: 'node.lat-bad', style: {'border-color': '#E74C3C', 'border-width': 6}},
|
|
|
{selector: 'node.hl', style: {'border-color': '#ff0', 'border-width': 7, 'overlay-color': '#ff0', 'overlay-opacity': 0.25, 'overlay-padding': 6}},
|
|
{selector: 'node.hl', style: {'border-color': '#ff0', 'border-width': 7, 'overlay-color': '#ff0', 'overlay-opacity': 0.25, 'overlay-padding': 6}},
|
|
@@ -221,35 +412,100 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* ---------------- render ---------------- */
|
|
/* ---------------- render ---------------- */
|
|
|
|
|
+ function applyElements(els) {
|
|
|
|
|
+ if (!cy) {
|
|
|
|
|
+ cy = cytoscape({
|
|
|
|
|
+ container: el('l2-cy'),
|
|
|
|
|
+ elements: els,
|
|
|
|
|
+ style: styleFor(dark),
|
|
|
|
|
+ layout: {name: 'preset', fit: true, padding: 40},
|
|
|
|
|
+ wheelSensitivity: 0.2,
|
|
|
|
|
+ minZoom: 0.15, maxZoom: 4
|
|
|
|
|
+ });
|
|
|
|
|
+ bindCy();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ cy.elements().remove();
|
|
|
|
|
+ cy.add(els);
|
|
|
|
|
+ cy.style(styleFor(dark));
|
|
|
|
|
+ cy.layout({name: 'preset', fit: true, padding: 40}).run();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function render(snap) {
|
|
function render(snap) {
|
|
|
if (!snap || !snap.nodes) {
|
|
if (!snap || !snap.nodes) {
|
|
|
el('l2-cy').innerHTML = '<div class="l2-empty">' + t('l2.no_data', 'No data') + '</div>';
|
|
el('l2-cy').innerHTML = '<div class="l2-empty">' + t('l2.no_data', 'No data') + '</div>';
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
lastSnap = snap;
|
|
lastSnap = snap;
|
|
|
- var L = computeLayout(snap);
|
|
|
|
|
- var els = [];
|
|
|
|
|
|
|
+ if (viewGroup) {
|
|
|
|
|
+ var exists = (snap.nodes || []).some(function (n) { return groupOf(n).label === viewGroup; });
|
|
|
|
|
+ if (!exists) viewGroup = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (viewGroup) renderDetail(snap); else renderOverview(snap);
|
|
|
|
|
+ if (el('l2-back')) el('l2-back').style.display = viewGroup ? '' : 'none';
|
|
|
|
|
+ updateStats(snap);
|
|
|
|
|
+ buildLegend();
|
|
|
|
|
+ buildAlarms(snap);
|
|
|
|
|
+ buildOffline(snap);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (L.root) {
|
|
|
|
|
- els.push({group: 'nodes', data: {id: '__root', type: 'root', label: CFG.root_label || 'COBCO', w: L.root.w, h: L.root.h},
|
|
|
|
|
- position: {x: L.root.cx, y: L.root.cy}, selectable: false, grabbable: false});
|
|
|
|
|
|
|
+ function renderOverview(snap) {
|
|
|
|
|
+ var O = computeOverview(snap), els = [], idx = {};
|
|
|
|
|
+ if (O.root) {
|
|
|
|
|
+ els.push({group: 'nodes', data: {id: '__root', type: 'root', label: CFG.root_label || 'COBCO', w: O.root.w, h: O.root.h},
|
|
|
|
|
+ position: {x: O.root.cx, y: O.root.cy}, selectable: false, grabbable: false});
|
|
|
}
|
|
}
|
|
|
- L.frames.forEach(function (f, i) {
|
|
|
|
|
- els.push({group: 'nodes', data: {id: 'grp_' + i, type: 'frame', label: f.label, w: f.w, h: f.h, fs: f.big ? 20 : 14},
|
|
|
|
|
- position: {x: f.x + f.w / 2, y: f.y + f.h / 2}, selectable: false, grabbable: false});
|
|
|
|
|
|
|
+ O.tiles.forEach(function (tile, i) {
|
|
|
|
|
+ var s = O.stats[tile.label] || {nodes: [], alarms: 0, offline: 0, maxsev: -1};
|
|
|
|
|
+ idx[tile.label] = 'grp_' + i;
|
|
|
|
|
+ var isCore = /core/i.test(tile.label) || /data\s*center/i.test(tile.label);
|
|
|
|
|
+ var bd = s.alarms ? '#E45959' : (dark ? '#41597c' : '#b9c8dc');
|
|
|
|
|
+ var bg = isCore ? TIER_BG.core : (dark ? '#22344e' : '#eef3fb');
|
|
|
|
|
+ var fg = isCore ? '#ffffff' : (dark ? '#dfe9fb' : '#1e3352');
|
|
|
|
|
+ var lbl = tile.label + '\n' + s.nodes.length + ' ' + t('l2.unit_sw', 'sw') +
|
|
|
|
|
+ (s.up ? ' ↑ ' + s.up : '') + (s.alarms ? ' ⚠ ' + s.alarms : '') + (s.offline ? ' ◌ ' + s.offline : '');
|
|
|
|
|
+ var cls = [];
|
|
|
|
|
+ if (s.alarms) cls.push('alarm');
|
|
|
|
|
+ if (isCore) cls.push('core');
|
|
|
|
|
+ els.push({group: 'nodes', data: {
|
|
|
|
|
+ id: idx[tile.label], type: 'tile', group: tile.label,
|
|
|
|
|
+ n: s.nodes.length, alarms: s.alarms, offline: s.offline, maxsev: s.maxsev, up: s.up,
|
|
|
|
|
+ label: lbl, bg: bg, fg: fg, bd: bd, w: TILE_W, h: TILE_H
|
|
|
|
|
+ }, position: {x: tile.x + tile.w / 2, y: tile.y + tile.h / 2}, classes: cls.join(' ')});
|
|
|
});
|
|
});
|
|
|
|
|
+ applyElements(els);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ function renderDetail(snap) {
|
|
|
|
|
+ var D = computeDetail(snap, viewGroup), els = [];
|
|
|
|
|
+ if (D.root) {
|
|
|
|
|
+ els.push({group: 'nodes', data: {id: '__root', type: 'root', label: (CFG.root_label || 'COBCO') + ' / ' + viewGroup, w: D.root.w, h: D.root.h},
|
|
|
|
|
+ position: {x: D.root.cx, y: D.root.cy}, selectable: false, grabbable: false});
|
|
|
|
|
+ }
|
|
|
|
|
+ (D.ghosts || []).forEach(function (g) {
|
|
|
|
|
+ var nm = (g.node && g.node.name) || g.id;
|
|
|
|
|
+ var ip = (g.node && g.node.ip) || '';
|
|
|
|
|
+ var tail = String(nm).split(/[-\s]+/).filter(Boolean).slice(-1)[0] || '';
|
|
|
|
|
+ var short = (tail ? tail + ' · ' : '') + ip;
|
|
|
|
|
+ els.push({group: 'nodes', data: {id: g.id, type: 'ghost', label: short}, position: {x: g.x, y: g.y}, selectable: false, grabbable: false});
|
|
|
|
|
+ els.push({group: 'edges', data: {
|
|
|
|
|
+ id: 'ghe_' + g.id, type: 'uplink', source: g.id, target: g.to,
|
|
|
|
|
+ speed: g.speed, state: g.state, lbl: speedText(g.speed),
|
|
|
|
|
+ color: speedColor(g.speed, g.state), ls: (g.state && g.state !== 'up') ? 'dashed' : 'solid',
|
|
|
|
|
+ w: Math.max(2.6, speedWidth(g.speed))
|
|
|
|
|
+ }});
|
|
|
|
|
+ });
|
|
|
var switchIds = {};
|
|
var switchIds = {};
|
|
|
- (snap.nodes || []).forEach(function (n) {
|
|
|
|
|
|
|
+ D.nodes.forEach(function (n) {
|
|
|
switchIds[n.id] = true;
|
|
switchIds[n.id] = true;
|
|
|
- var p = L.pos[n.id] || {x: 0, y: 0};
|
|
|
|
|
|
|
+ var p = D.pos[n.id] || {x: 0, y: 0};
|
|
|
var tier = n.tier || 'access';
|
|
var tier = n.tier || 'access';
|
|
|
var tkey = (tier.indexOf('dist') === 0) ? 'dist' : (tier.indexOf('core') === 0 ? 'core' : 'access');
|
|
var tkey = (tier.indexOf('dist') === 0) ? 'dist' : (tier.indexOf('core') === 0 ? 'core' : 'access');
|
|
|
var cls = [];
|
|
var cls = [];
|
|
|
var bd = (dark ? TIER_BG_DARK[tkey] : TIER_BG[tkey]) || '#555';
|
|
var bd = (dark ? TIER_BG_DARK[tkey] : TIER_BG[tkey]) || '#555';
|
|
|
- if (n.maxsev != null && n.maxsev >= 0 && (n.alarms && n.alarms.length)) {
|
|
|
|
|
|
|
+ if (n.alarms && n.alarms.length) {
|
|
|
cls.push('alarm');
|
|
cls.push('alarm');
|
|
|
- bd = SEV_COLORS[Math.max(0, Math.min(5, n.maxsev))];
|
|
|
|
|
|
|
+ bd = '#E45959';
|
|
|
}
|
|
}
|
|
|
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');
|
|
@@ -260,12 +516,10 @@
|
|
|
hostid: n.hostid, oper: n.oper, maxsev: n.maxsev, alarms: n.alarms || [],
|
|
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,
|
|
term_online: n.term_online, term_offline: n.term_offline, latency_ms: n.latency_ms,
|
|
|
label: labelFor(n), bg: (dark ? TIER_BG_DARK[tkey] : TIER_BG[tkey]) || '#777',
|
|
label: labelFor(n), bg: (dark ? TIER_BG_DARK[tkey] : TIER_BG[tkey]) || '#777',
|
|
|
- bd: bd, w: NODE_W, h: NODE_H, tw: NODE_W - 16, z: 20, group: groupOf(n).label
|
|
|
|
|
|
|
+ bd: bd, w: NODE_W, h: NODE_H, group: viewGroup
|
|
|
}, position: p, classes: cls.join(' ')});
|
|
}, position: p, classes: cls.join(' ')});
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- (snap.edges || []).forEach(function (e, i) {
|
|
|
|
|
- if (!switchIds[e.source] || !switchIds[e.target]) return;
|
|
|
|
|
|
|
+ D.internal.forEach(function (e, i) {
|
|
|
els.push({group: 'edges', data: {
|
|
els.push({group: 'edges', data: {
|
|
|
id: 'e_' + i, source: e.source, target: e.target, speed: e.speed, state: e.state,
|
|
id: 'e_' + i, source: e.source, target: e.target, speed: e.speed, state: e.state,
|
|
|
a_port: e.a_port, b_port: e.b_port,
|
|
a_port: e.a_port, b_port: e.b_port,
|
|
@@ -273,8 +527,6 @@
|
|
|
w: speedWidth(e.speed)
|
|
w: speedWidth(e.speed)
|
|
|
}});
|
|
}});
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- // terminals
|
|
|
|
|
if (showTerm) {
|
|
if (showTerm) {
|
|
|
var bySw = {};
|
|
var bySw = {};
|
|
|
(snap.terminals || []).forEach(function (tm) {
|
|
(snap.terminals || []).forEach(function (tm) {
|
|
@@ -282,9 +534,8 @@
|
|
|
if (onlyOff && tm.online) return;
|
|
if (onlyOff && tm.online) return;
|
|
|
(bySw[tm.sw_ip] = bySw[tm.sw_ip] || []).push(tm);
|
|
(bySw[tm.sw_ip] = bySw[tm.sw_ip] || []).push(tm);
|
|
|
});
|
|
});
|
|
|
- var cnt = 0;
|
|
|
|
|
Object.keys(bySw).forEach(function (sw) {
|
|
Object.keys(bySw).forEach(function (sw) {
|
|
|
- var base = L.pos[sw]; if (!base) return;
|
|
|
|
|
|
|
+ var base = D.pos[sw]; if (!base) return;
|
|
|
bySw[sw].sort(function (a, b) { return (a.online === b.online) ? 0 : (a.online ? -1 : 1); });
|
|
bySw[sw].sort(function (a, b) { return (a.online === b.online) ? 0 : (a.online ? -1 : 1); });
|
|
|
bySw[sw].forEach(function (tm, k) {
|
|
bySw[sw].forEach(function (tm, k) {
|
|
|
var col = k % 9, row = Math.floor(k / 9);
|
|
var col = k % 9, row = Math.floor(k / 9);
|
|
@@ -293,32 +544,10 @@
|
|
|
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: tm.online ? '#2ECC71' : '#95a5a6', mac: tm.mac, ip: tm.ip, online: tm.online},
|
|
|
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}});
|
|
|
- cnt++;
|
|
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if (!cy) {
|
|
|
|
|
- cy = cytoscape({
|
|
|
|
|
- container: el('l2-cy'),
|
|
|
|
|
- elements: els,
|
|
|
|
|
- style: styleFor(dark),
|
|
|
|
|
- layout: {name: 'preset', fit: true, padding: 50},
|
|
|
|
|
- wheelSensitivity: 0.2,
|
|
|
|
|
- minZoom: 0.05, maxZoom: 4
|
|
|
|
|
- });
|
|
|
|
|
- bindCy();
|
|
|
|
|
- } else {
|
|
|
|
|
- cy.elements().remove();
|
|
|
|
|
- cy.add(els);
|
|
|
|
|
- cy.style(styleFor(dark));
|
|
|
|
|
- cy.layout({name: 'preset', fit: true, padding: 50}).run();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- updateStats(snap);
|
|
|
|
|
- buildLegend();
|
|
|
|
|
- buildAlarms(snap);
|
|
|
|
|
- buildOffline(snap);
|
|
|
|
|
|
|
+ applyElements(els);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* ---------------- side panels & stats ---------------- */
|
|
/* ---------------- side panels & stats ---------------- */
|
|
@@ -347,6 +576,7 @@
|
|
|
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:#95a5a6;border-radius:50%;margin-left:8px"></span>' + t('l2.term_off', 'Offline') + '</div>';
|
|
|
|
|
+ h += '<div class="lg-row" style="opacity:.72;margin-top:4px">' + t('l2.click_hint', 'Click a switch to highlight its links') + '</div>';
|
|
|
el('l2-legend').innerHTML = h;
|
|
el('l2-legend').innerHTML = h;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -359,9 +589,6 @@
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
- (snap.other_alarms || []).forEach(function (a) {
|
|
|
|
|
- items.push({sev: a.sev, title: a.name, host: a.host, ip: a.ip, id: null, hostid: null, other: true});
|
|
|
|
|
- });
|
|
|
|
|
el('l2-alarm-count').textContent = items.length;
|
|
el('l2-alarm-count').textContent = items.length;
|
|
|
if (!items.length) { el('l2-alarm-list').innerHTML = '<div class="panel-empty">' + t('l2.alarm_empty', 'No active alarms.') + '</div>'; return; }
|
|
if (!items.length) { el('l2-alarm-list').innerHTML = '<div class="panel-empty">' + t('l2.alarm_empty', 'No active alarms.') + '</div>'; return; }
|
|
|
items.sort(function (a, b) { return b.sev - a.sev; });
|
|
items.sort(function (a, b) { return b.sev - a.sev; });
|
|
@@ -371,14 +598,14 @@
|
|
|
h += '<div class="alarm-row" data-id="' + (it.id || '') + '" data-hostid="' + (it.hostid || '') + '">' +
|
|
h += '<div class="alarm-row" data-id="' + (it.id || '') + '" data-hostid="' + (it.hostid || '') + '">' +
|
|
|
'<span class="alarm-dot" style="background:' + col + '"></span>' +
|
|
'<span class="alarm-dot" style="background:' + col + '"></span>' +
|
|
|
'<div class="alarm-main"><div class="alarm-title">' + esc(it.title) + '</div>' +
|
|
'<div class="alarm-main"><div class="alarm-title">' + esc(it.title) + '</div>' +
|
|
|
- '<div class="alarm-sub">' + esc(it.host || '') + (it.ip ? ' · ' + esc(it.ip) : '') + (it.other ? ' · 拓扑外' : '') + '</div></div></div>';
|
|
|
|
|
|
|
+ '<div class="alarm-sub">' + esc(it.host || '') + (it.ip ? ' · ' + esc(it.ip) : '') + '</div></div></div>';
|
|
|
});
|
|
});
|
|
|
var box = el('l2-alarm-list');
|
|
var box = el('l2-alarm-list');
|
|
|
box.innerHTML = h;
|
|
box.innerHTML = h;
|
|
|
box.querySelectorAll('.alarm-row').forEach(function (r) {
|
|
box.querySelectorAll('.alarm-row').forEach(function (r) {
|
|
|
r.addEventListener('click', function () {
|
|
r.addEventListener('click', function () {
|
|
|
var id = r.getAttribute('data-id');
|
|
var id = r.getAttribute('data-id');
|
|
|
- if (id) focusNode(id);
|
|
|
|
|
|
|
+ if (id) revealById(id);
|
|
|
else { var hid = r.getAttribute('data-hostid'); if (hid) window.open(META.zabbixHostUrl.replace('__HOSTID__', hid), '_blank'); }
|
|
else { var hid = r.getAttribute('data-hostid'); if (hid) window.open(META.zabbixHostUrl.replace('__HOSTID__', hid), '_blank'); }
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
@@ -399,8 +626,7 @@
|
|
|
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');
|
|
var mac = r.getAttribute('data-mac');
|
|
|
- if (!showTerm) { showTerm = true; el('l2-showterm').checked = true; render(lastSnap); }
|
|
|
|
|
- setTimeout(function () { focusNode('t_' + mac); }, 60);
|
|
|
|
|
|
|
+ revealById('t_' + mac);
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -418,6 +644,31 @@
|
|
|
setTimeout(function () { n.removeClass('hl'); }, 2600);
|
|
setTimeout(function () { n.removeClass('hl'); }, 2600);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function groupOfNodeId(id) {
|
|
|
|
|
+ var hit = (lastSnap && lastSnap.nodes || []).filter(function (n) { return n.id === id; })[0];
|
|
|
|
|
+ return hit ? groupOf(hit).label : null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Make sure a switch/terminal is visible (auto-opens its workshop from overview).
|
|
|
|
|
+ function revealById(id) {
|
|
|
|
|
+ if (!lastSnap) return;
|
|
|
|
|
+ id = String(id);
|
|
|
|
|
+ if (id.indexOf('t_') === 0) {
|
|
|
|
|
+ var tm = (lastSnap.terminals || []).filter(function (x) { return 't_' + x.mac === id; })[0];
|
|
|
|
|
+ if (!tm) return;
|
|
|
|
|
+ var g = groupOfNodeId(tm.sw_ip);
|
|
|
|
|
+ if (g) viewGroup = g;
|
|
|
|
|
+ showTerm = true;
|
|
|
|
|
+ if (el('l2-showterm')) el('l2-showterm').checked = true;
|
|
|
|
|
+ render(lastSnap);
|
|
|
|
|
+ setTimeout(function () { focusNode(id); }, 90);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ var g2 = groupOfNodeId(id);
|
|
|
|
|
+ if (g2 && viewGroup !== g2) { viewGroup = g2; render(lastSnap); }
|
|
|
|
|
+ setTimeout(function () { focusNode(id); }, 90);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function bindCy() {
|
|
function bindCy() {
|
|
|
cy.on('mouseover', 'node[type="switch"]', function (e) {
|
|
cy.on('mouseover', 'node[type="switch"]', function (e) {
|
|
|
var d = e.target.data();
|
|
var d = e.target.data();
|
|
@@ -433,6 +684,18 @@
|
|
|
showTip(e, html);
|
|
showTip(e, html);
|
|
|
});
|
|
});
|
|
|
cy.on('mouseout', 'node[type="switch"]', hideTip);
|
|
cy.on('mouseout', 'node[type="switch"]', hideTip);
|
|
|
|
|
+ cy.on('mouseover', 'node[type="tile"]', function (e) {
|
|
|
|
|
+ var d = e.target.data();
|
|
|
|
|
+ var html = '<b>' + esc(d.group) + '</b><br>' + d.n + ' ' + t('l2.unit_sw', 'sw') + '<br>' +
|
|
|
|
|
+ '↑ Core: ' + (d.up || 0) + ' · ⚠ ' + (d.alarms || 0) + ' · ◌ ' + (d.offline || 0) +
|
|
|
|
|
+ '<br><span style="opacity:.7">' + t('l2.tile_hint', 'Click to open this workshop') + '</span>';
|
|
|
|
|
+ showTip(e, html);
|
|
|
|
|
+ });
|
|
|
|
|
+ cy.on('mouseout', 'node[type="tile"]', hideTip);
|
|
|
|
|
+ cy.on('tap', 'node[type="tile"]', function (e) {
|
|
|
|
|
+ var g = e.target.data('group');
|
|
|
|
|
+ if (g) { viewGroup = g; render(lastSnap); }
|
|
|
|
|
+ });
|
|
|
cy.on('mouseover', 'edge:not([type="term"])', function (e) {
|
|
cy.on('mouseover', 'edge:not([type="term"])', function (e) {
|
|
|
var d = e.target.data();
|
|
var d = e.target.data();
|
|
|
var sp = (parseFloat(d.speed) || 0) >= 1000 ? ((parseFloat(d.speed) / 1000) + 'G') : (d.speed + 'M');
|
|
var sp = (parseFloat(d.speed) || 0) >= 1000 ? ((parseFloat(d.speed) / 1000) + 'G') : (d.speed + 'M');
|
|
@@ -440,8 +703,16 @@
|
|
|
});
|
|
});
|
|
|
cy.on('mouseout', 'edge', hideTip);
|
|
cy.on('mouseout', 'edge', hideTip);
|
|
|
cy.on('tap', 'node[type="switch"]', function (e) {
|
|
cy.on('tap', 'node[type="switch"]', function (e) {
|
|
|
- var hid = e.target.data('hostid');
|
|
|
|
|
- if (hid) { /* single click: focus only; double handled below */ }
|
|
|
|
|
|
|
+ var n = e.target;
|
|
|
|
|
+ cy.elements().removeClass('dim hl');
|
|
|
|
|
+ cy.elements().not(n).addClass('dim');
|
|
|
|
|
+ n.removeClass('dim');
|
|
|
|
|
+ var links = n.connectedEdges();
|
|
|
|
|
+ links.removeClass('dim').addClass('hl');
|
|
|
|
|
+ links.connectedNodes().removeClass('dim');
|
|
|
|
|
+ });
|
|
|
|
|
+ cy.on('tap', function (e) {
|
|
|
|
|
+ if (e.target === cy) cy.elements().removeClass('dim hl');
|
|
|
});
|
|
});
|
|
|
cy.on('dbltap', 'node[type="switch"]', function (e) {
|
|
cy.on('dbltap', 'node[type="switch"]', function (e) {
|
|
|
var hid = e.target.data('hostid');
|
|
var hid = e.target.data('hostid');
|
|
@@ -488,8 +759,7 @@
|
|
|
box.querySelectorAll('.sug-row').forEach(function (r) {
|
|
box.querySelectorAll('.sug-row').forEach(function (r) {
|
|
|
r.addEventListener('click', function () {
|
|
r.addEventListener('click', function () {
|
|
|
var id = r.getAttribute('data-id');
|
|
var id = r.getAttribute('data-id');
|
|
|
- if (id.indexOf('t_') === 0 && !showTerm) { showTerm = true; el('l2-showterm').checked = true; render(lastSnap); setTimeout(function () { focusNode(id); }, 80); }
|
|
|
|
|
- else focusNode(id);
|
|
|
|
|
|
|
+ revealById(id);
|
|
|
box.style.display = 'none';
|
|
box.style.display = 'none';
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
@@ -499,12 +769,16 @@
|
|
|
function setupControls() {
|
|
function setupControls() {
|
|
|
dark = el('l2-wrap').classList.contains('l2-dark');
|
|
dark = el('l2-wrap').classList.contains('l2-dark');
|
|
|
el('l2-dark').checked = dark;
|
|
el('l2-dark').checked = dark;
|
|
|
|
|
+ var back = el('l2-back');
|
|
|
|
|
+ if (back) {
|
|
|
|
|
+ back.textContent = t('l2.back_overview', '← Overview');
|
|
|
|
|
+ back.addEventListener('click', function () { viewGroup = null; render(lastSnap); });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
el('l2-dark').addEventListener('change', function () {
|
|
el('l2-dark').addEventListener('change', function () {
|
|
|
dark = el('l2-dark').checked;
|
|
dark = el('l2-dark').checked;
|
|
|
el('l2-wrap').classList.toggle('l2-dark', dark);
|
|
el('l2-wrap').classList.toggle('l2-dark', dark);
|
|
|
- if (cy) cy.style(styleFor(dark));
|
|
|
|
|
- buildLegend();
|
|
|
|
|
|
|
+ render(lastSnap);
|
|
|
});
|
|
});
|
|
|
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 () {
|
|
el('l2-onlyoff').addEventListener('change', function () {
|
|
@@ -563,6 +837,10 @@
|
|
|
/* ---------------- boot ---------------- */
|
|
/* ---------------- boot ---------------- */
|
|
|
function boot() {
|
|
function boot() {
|
|
|
setupControls();
|
|
setupControls();
|
|
|
|
|
+ try {
|
|
|
|
|
+ var qg = new URLSearchParams(window.location.search).get('g');
|
|
|
|
|
+ if (qg) viewGroup = qg;
|
|
|
|
|
+ } catch (e) {}
|
|
|
if (!SNAP || !SNAP.nodes) {
|
|
if (!SNAP || !SNAP.nodes) {
|
|
|
el('l2-cy').innerHTML = '<div class="l2-empty">' + t('l2.no_data', 'No data') + '</div>';
|
|
el('l2-cy').innerHTML = '<div class="l2-empty">' + t('l2.no_data', 'No data') + '</div>';
|
|
|
} else {
|
|
} else {
|