aboutsummaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/nms.gathering.org/index.html2
-rw-r--r--web/nms.gathering.org/js/nms-info-box.js1283
2 files changed, 788 insertions, 497 deletions
diff --git a/web/nms.gathering.org/index.html b/web/nms.gathering.org/index.html
index d3928bb..4077e4e 100644
--- a/web/nms.gathering.org/index.html
+++ b/web/nms.gathering.org/index.html
@@ -295,7 +295,7 @@
</div>
</div>
</div>
- <div id="info-panel-container" class="col-md-5" style="display: none; position: absolute; z-index: 120;">
+ <div id="info-box-container" class="col-md-5 hidden" style="position: absolute; z-index: 120;">
</div>
</div>
diff --git a/web/nms.gathering.org/js/nms-info-box.js b/web/nms.gathering.org/js/nms-info-box.js
index 8ef2611..f4dcb49 100644
--- a/web/nms.gathering.org/js/nms-info-box.js
+++ b/web/nms.gathering.org/js/nms-info-box.js
@@ -1,469 +1,502 @@
"use strict";
/*
- * NMS info window controller
+ * NMS info-window controller
*
- * Interface: nmsInfoBox.showWindow(windowType,optionalParameter), nmsInfoBox.hide(), nmsInfoBox.refresh()
+ * Interface: nmsInfoBox.showWindow(windowType,optionalParameter), nmsInfoBox.hide()
*
- * Any windowTypes should at a minimum implement load, update, unload, getTitle, getContent, getChildContent
+ * Uses a basic hierarchy of window > views > panels, where each panel can work
+ * independently, but gets loaded/unloaded when needed by window or view.
*
*/
+/*
+ *
+ * Currently broken or needs reimplementing:
+ * - Possibly: Comment CRUD, did not want to test writes on our "historic db copy"
+ * - Comments popover
+ * - Handler unloading is not working correctly, and many are never removed
+ * - SSH-management link, this should propably be a custom "view" of sorts
+ * - inventoryListing window is partially broken when first opened. Since it's
+ * both a window type and a panel with different modes it has some conflicts.
+ *
+ * General TODO:
+ * - Fix broken stuff
+ * - Test comments
+ * - Add external windows (timetravel, etc)
+ * - Take a critical look at what methods/variables should be marked as "_"
+ * - Currently argument is assumed to be a switch, this should not be the case
+ * - Add some basic styling to separate panels visually when in the same view
+ * - Add some movement/placement/size options to info window
+ *
+ */
+
+/*
+ * Basic configuration
+ */
var nmsInfoBox = nmsInfoBox || {
stats: {},
_container: false, //Container window
- _window: false, //Active window (reference to _windowTypes object or false)
- _windowTypes: [] //List of all avaliable window types
+ _windowHandler: false, //Window handler
+ _sw: false, //Name of last switch opened, used for toggle-click
+ _windowTypes: [
+ {
+ 'id': 'switchInfo',
+ 'title': 'Switch info',
+ 'views': {
+ 'initial': {
+ 'name': 'Switch summary',
+ 'panels': ['switchComments','switchSummary']
+ },
+ 'details': {
+ 'name': 'Switch details',
+ 'panels': ['switchDetails']
+ },
+ 'ports': {
+ 'name': 'SNMP - Ports',
+ 'panels': ['switchSNMP:ports']
+ },
+ 'misc': {
+ 'name': 'SNMP - Misc',
+ 'panels': ['switchSNMP:misc']
+ },
+ 'comments': {
+ 'name': 'Comments',
+ 'panels': ['switchComments']
+ },
+ 'edit': {
+ 'name': 'Edit',
+ 'panels': ['switchEdit']
+ }
+ }
+ },
+ {
+ 'id': 'addSwitch',
+ 'title': 'Add new switch',
+ 'views': {
+ 'initial': {
+ 'name': 'Add switch',
+ 'panels': ['switchAdd']
+ }
+ }
+ },
+ {
+ 'id': 'inventoryListing',
+ 'title': 'Inventory listing',
+ 'views': {
+ 'initial': {
+ 'name': 'Distro names',
+ 'panels': ['inventoryListing:distro_name']
+ },
+ 'sysDescr': {
+ 'name': 'System description',
+ 'panels': ['inventoryListing:sysDescr']
+ },
+ 'jnxBoxSerialNo': {
+ 'name': 'Serial numbers',
+ 'panels': ['inventoryListing:jnxBoxSerialNo']
+ }
+ }
+ }
+ ],
+ _panelTypes: {} //Populate by using the nmsInfoBox.addPanelType method
};
/*
- * Shows a window from the _windowTypes list
+ * Shows a window, and triggers initial load if needed
*/
nmsInfoBox.showWindow = function (windowName,argument) {
- if(windowName == "switchInfo" && argument != '' && argument == this._window.sw) {
+ if(windowName == "switchInfo" && argument != '' && argument == this._sw) {
nmsInfoBox.hide();
return;
}
- nmsInfoBox.hide();
- for(var win in this._windowTypes) {
- if(windowName == win) {
- this._window = this._windowTypes[win];
- this._show(argument);
- return;
- }
- }
-};
-/*
- * Refresh the active window
- */
-nmsInfoBox.refresh = function(argument) {
- if(!nmsInfoBox._window)
- return;
- nmsInfoBox._show(argument);
+ if(!this._container)
+ this._load();
+ if(!windowName)
+ windowName = 'switchInfo';
+
+ this._sw = argument;
+
+ this._windowHandler.showWindow(windowName,argument)
+ this._container.style.display = "block";
};
-nmsInfoBox.update = function(argument) {
- if(!nmsInfoBox._window)
- return;
- nmsInfoBox._window.update(argument);
-}
/*
- * Internal function to show the active _window and pass along any arguments
+ * Internal function to load and register the initial html objects
*/
-nmsInfoBox._show = function(argument) {
- nmsData.addHandler("comments","switchshower",nmsInfoBox.update,'comments');
- nmsData.addHandler("switches","switchshower",nmsInfoBox.update,'switches');
- nmsData.addHandler("smanagement","switchshower",nmsInfoBox.update,'smanagement');
- nmsData.addHandler("snmp","switchshower",nmsInfoBox.update,'snmp');
-
- if(argument != "soft")
- this._window.load(argument);
-
- this._container = document.getElementById("info-panel-container");
- var panel = document.createElement("div");
- panel.classList.add("panel", "panel-default");
+nmsInfoBox._load = function() {
+ var infoBox = document.createElement("div");
+ infoBox.classList.add("panel", "panel-default");
var title = document.createElement("div");
+ title.id = "info-box-title";
title.classList.add("panel-heading");
+ var nav = document.createElement("div");
+ nav.id = "info-box-nav";
+ nav.classList.add("panel-body");
var body = document.createElement("div");
+ body.id = "info-box-body";
body.classList.add("panel-body");
- title.innerHTML = this._window.getTitle() + '<button type="button" class="close" aria-label="Close" onclick="nmsInfoBox.hide();" style="float: right;"><span aria-hidden="true">&times;</span></button>';
- var content = this._window.getContent();
- if(!content.nodeName) {
- body.innerHTML = this._window.content;
- } else {
- body.appendChild(content);
- }
- var childContent = this._window.getChildContent();
- if(childContent != false) {
- body.appendChild(childContent);
- }
+ infoBox.appendChild(title);
+ infoBox.appendChild(nav);
+ infoBox.appendChild(body);
- panel.appendChild(title);
- panel.appendChild(body);
- while(this._container.firstChild) {
- this._container.removeChild(this._container.firstChild);
- }
- this._container.appendChild(panel);
- this._container.style.display = "block";
- $('[data-toggle="popover"]').popover({placement:"top",container:'body'});
- $(".collapse-controller").on("click", function(e) {
- $(e.target.dataset.target).collapse('toggle');
- });
+ this._container = document.getElementById("info-box-container");
+ this._container.appendChild(infoBox);
+
+ this._windowHandler = new windowHandler();
+ this._windowHandler.setContainerObj(document.getElementById("info-box-container"));
+ this._windowHandler.setTitleObj(document.getElementById("info-box-title"));
+ this._windowHandler.setBodyObj(document.getElementById("info-box-body"));
+ this._windowHandler.setNavObj(document.getElementById("info-box-nav"));
+ this._windowHandler.setPanelTypes(this._panelTypes);
+ this._windowHandler.setWindowTypes(this._windowTypes);
+};
+
+/*
+ * Adds a panel type to _panelTypes for usage in windows and views
+ */
+nmsInfoBox.addPanelType = function (id, obj) {
+ this._panelTypes[id] = obj;
};
+
/*
* Hide the active window and tell it to unload
*/
nmsInfoBox.hide = function() {
- if(!this._container || !this._window)
- return;
- this._container.style.display = "none";
- this._window.unload();
- this._window = false;
- nmsData.unregisterHandler("comments","switchshower");
- nmsData.unregisterHandler("switches","switchshower");
- nmsData.unregisterHandler("smanagement","switchshower");
- nmsData.unregisterHandler("snmp","switchshower");
+ this._sw = false;
+ this._windowHandler.hide();
+ this._windowHandler.unloadWindow();
};
/*
- * Window type: Add Switch
- *
- * Basic window that lets you create a new switch
- *
+ * Click a switch and display it.
*/
-nmsInfoBox._windowTypes.addSwitch = {
- title: 'Add new switch',
- content: '<input type="text" class="form-control" id="create-sysname" placeholder="Sysname id"><button class="btn btn-default" onclick="nmsInfoBox._windowTypes.addSwitch.save();">Add switch</button>',
- childContent: false,
- getTitle: function() {
- return this.title;
- },
- getContent: function() {
- return this.content;
- },
- getChildContent: function() {
- return this.childContent;
- },
- load: function(argument) {
- },
- update: function(type) {
- },
- unload: function() {
- },
- save: function() {
- var sysname = document.getElementById('create-sysname').value;
- var myData = JSON.stringify([{'sysname':sysname}]);
- $.ajax({
- type: "POST",
- url: "/api/write/switch-add",
- dataType: "text",
- data:myData,
- success: function (data, textStatus, jqXHR) {
- var result = JSON.parse(data);
- if(result.switches_addded.length > 0) { // FIXME unresolved variable switches_addded
- nmsInfoBox.hide();
- }
- nmsData.invalidate("switches");
- nmsData.invalidate("smanagement");
- }
- });
- }
+nmsInfoBox.click = function(sw)
+{
+ this.showWindow("switchInfo",sw);
};
/*
- * Window type: Switch info
+ * Window handler
*
- * Advanced window with information about a specific switch, and basic editing options
+ * Is based on a hierarchy of objects: Window (itself) > Views > Panels. Where
+ * any object should not interact with any other directly. Panels are special
+ * nmsInfoPanel-objects that handle their own rendering, refreshing, etc. The
+ * window handler only makes shure these panels are loaded and unloaded when
+ * needed in a window or view.
*
- * Custom interfaces: showSummary, showInfoTable, showComments, showSNMP, showEdit, save
+ * Does primarily rely on an imported list of panel types and window types to
+ * display stuff, but has methods for manual overrides if needed.
+ *
+ * Panels can use the doInPanel(panelId,functionName,arguments) method to pass
+ * actions to themselves if needed.
*
*/
-nmsInfoBox._windowTypes.switchInfo = {
- title: '',
- content: '',
- childContent: false,
- sw: '',
- swi: '',
- swm: '',
- commentsHash: false,
- activeView: '',
- load: function(sw) {
- if(sw) {
- this.sw = sw;
- }
- if(!this.swi) {
- try {
- this.swi = nmsData.switches["switches"][this.sw];
- } catch(e) {
- this.swi = [];
- }
+var windowHandler = function () {
+ this.containerObj = false;
+ this.titleObj = false;
+ this.navObj = false;
+ this.bodyObj = false;
+ this._panels = {};
+ this._view = "";
+ this._window = {};
+ this.windowTypes = false;
+ this.panelTypes = false;
+ this.argument = false;
+ this.show = function () {
+ this.containerObj.classList.remove("hidden");
+ };
+ this.hide = function () {
+ this.containerObj.classList.add("hidden");
+ };
+ this.setContainerObj = function (containerObj) {
+ this.containerObj = containerObj;
+ };
+ this.setTitleObj = function (titleObj) {
+ this.titleObj = titleObj;
+ };
+ this.setNavObj = function (navObj) {
+ this.navObj = navObj;
+ };
+ this.setBodyObj = function (bodyObj) {
+ this.bodyObj = bodyObj;
+ };
+ this.setPanelTypes = function (panelTypes) {
+ this.panelTypes = panelTypes;
+ };
+ this.setWindowTypes = function (windowTypes) {
+ this.windowTypes = {};
+ for(var i in windowTypes) {
+ this.windowTypes[windowTypes[i].id] = windowTypes[i];
}
- if(!this.swm) {
- try {
- this.swm = nmsData.smanagement.switches[this.sw];
- } catch(e) {
- this.swm = [];
- }
+ };
+ this.setArgument = function (argument) {
+ if(this.argument != argument) {
+ this.argument = argument;
+ this.showView(this._view);
}
- nmsInfoBox._windowTypes.switchInfo.showSummary();
- nmsData.addHandler("ticker","switchInfo",nmsInfoBox._windowTypes.switchInfo.update,"tick");
- },
- update: function(type) {
- switch (type) {
- case 'comments':
- if(this.activeView == "summary" && this.commentsHash != nmsData.comments.hash) {
- nmsInfoBox._windowTypes.switchInfo.showComments();
- }
- break;
+ };
+ this.showPanel = function (panelName) {
+ var panelArray = panelName.split(":");
+ var panelName = panelArray[0];
+ var panelMode = panelArray[1];
+ if(this.panelTypes[panelName]) {
+ var id = (Math.random().toString(36)+'00000000000000000').slice(2, 10+2);
+ var panel = new this.panelTypes[panelName];
+ panel.setId(id);
+ if(!!panelMode)
+ panel.setMode(panelMode);
+ panel.load(this.bodyObj,this.argument);
+ this._panels[id] = panel;
}
- },
- getTitle: function() {
- var sshButton = '';
- try {
- var mgmt = nmsInfoBox._window.swm.mgmt_v4_addr;
- sshButton = mgmt.split("/")[0];
- } catch(e) {}
- if(sshButton != null && sshButton != undefined && sshButton != '') {
- sshButton = ' <button type="button" class="ssh btn btn-xs btn-default"><a href="ssh://' + sshButton + '">SSH</a></button>';
+ };
+ this.showTitle = function (title) {
+ this.titleObj.innerHTML = '<button type="button" class="close" aria-label="Close" onclick="nmsInfoBox.hide();" style="float: right;"><span aria-hidden="true">&times;</span></button><h4>' + title + '</h4>';
+ };
+ this.showNav = function () {
+ if(!this._window.views)
+ this.navObj.innerHTML = '';
+ var output = '<ul class="nav nav-pills small">';
+ var i = 0;
+ for(var view in this._window.views) {
+ var viewObj = this._window.views[view];
+ var active = '';
+ if(this._view == view)
+ active = ' class="active" ';
+ output += '<li' + active + '><a class="' + view + '" aria-label="' + viewObj.name + '" onclick="nmsInfoBox._windowHandler.showView(\'' + view + '\');">' + viewObj.name + '</a></li> ';
+ i++;
}
- return '<h4>' + this.sw + '</h4><button type="button" class="edit btn btn-xs btn-warning" onclick="nmsInfoBox._windowTypes.switchInfo.showEdit();">Edit</button> <button type="button" class="summary btn btn-xs btn-default" onclick="nmsInfoBox._windowTypes.switchInfo.showSummary();">Summary</button> <button type="button" class="details btn btn-xs btn-default" onclick="nmsInfoBox._windowTypes.switchInfo.showInfoTable();">Details</button> <button type="button" class="edit btn btn-xs btn-default" onclick="nmsInfoBox._windowTypes.switchInfo.showSNMP(\'ports\');">Ports</button> <button type="button" class="edit btn btn-xs btn-default" onclick="nmsInfoBox._windowTypes.switchInfo.showSNMP(\'misc\');">Misc</button>' + sshButton;
- },
- getContent: function() {
- return this.content;
- },
- getChildContent: function() {
- return this.childContent;
- },
- showSummary: function(argument) {
- this.activeView = "summary";
- var content = [];
-
- //Get DHCP info
- var lastDhcp = undefined;
- try {
- var tempDhcp = nmsData.dhcp.dhcp[this.sw];
- var now = Date.now();
- now = Math.floor(now / 1000);
- tempDhcp = now - parseInt(tempDhcp);
- tempDhcp = tempDhcp + " s";
- } catch(e) {}
-
- //Get SNMP status
- var snmpStatus = undefined;
- try {
- if (nmsData.snmp.snmp[this.sw].misc.sysName[0] != sw) {
- snmpStatus = "Sysname mismatch";
- } else {
- snmpStatus = "OK";
- }
- } catch(e) {}
-
- //Get CPU usage
- var cpuUsage = undefined;
- try {
- var cpu = 0;
- for (var u in nmsData.snmp.snmp[this.sw].misc.jnxOperatingCPU) {
- var local = nmsData.snmp.snmp[this.sw].misc['jnxOperatingCPU'][u];
- cpu = Math.max(nmsData.snmp.snmp[this.sw].misc.jnxOperatingCPU[u],cpu);
- }
- cpuUsage = cpu + " %";
- } catch (e) {}
-
- //Get traffic data
- var uplinkTraffic = undefined;
- try {
- var speed = 0;
- var t = parseInt(nmsData.switchstate.then[this.sw].uplinks.ifHCOutOctets);
- var n = parseInt(nmsData.switchstate.switches[this.sw].uplinks.ifHCOutOctets);
- var tt = parseInt(nmsData.switchstate.then[this.sw].time);
- var nt = parseInt(nmsData.switchstate.switches[this.sw].time);
- var tdiff = nt - tt;
- var diff = n - t;
- speed = diff / tdiff;
- if(!isNaN(speed)) {
- uplinkTraffic = byteCount(speed*8,0);
- }
- } catch (e) {};
-
- //Get uptime data
- var uptime = "";
- try {
- uptime = nmsData.snmp.snmp[this.sw]["misc"]["sysUpTimeInstance"][""] / 60 / 60 / 100;
- uptime = Math.floor(uptime) + " t";
- } catch(e) {}
-
- //Get temperature data
- var temp = "";
- try {
- temp = nmsData.switchstate.switches[this.sw].temp + " °C";
- } catch(e) {}
-
- content.push(["Ping latency:",(nmsData.ping.switches[this.sw].latency + " ms" || undefined)]);
- content.push(["Last DHCP lease:",lastDhcp]);
- content.push(["SNMP status:",snmpStatus]);
- content.push(["CPU usage:",cpuUsage]);
- content.push(["Uplink traffic:",uplinkTraffic]);
- content.push(["System uptime:",uptime]);
- content.push(["Temperature",temp]);
- content.push(["Management (v4):",(this.swm.mgmt_v4_addr || '')]);
- content.push(["Management (v6):",(this.swm.mgmt_v6_addr || '')]);
- content.push(["Subnet (v4):",(this.swm.subnet4 || '')]);
- content.push(["Subnet (v6):",(this.swm.subnet6 || '')]);
-
- var contentCleaned = [];
- for(var i in content) {
- if(content[i][1] == '' || content[i][1] == null)
- continue;
- if(content[i][1] == undefined || content[i][1])
- content[i][1] == "No data";
- contentCleaned.push(content[i]);
+ output += '</ul>';
+ if(i < 2) {
+ this.navObj.innerHTML = '';
+ } else {
+ this.navObj.innerHTML = output;
}
-
- var table = nmsInfoBox._makeTable(contentCleaned);
- table.id = this.sw + "-summary";
-
- this.content = table;
-
- if(argument == "tick") {
- var myObj = document.getElementById(this.sw + "-summary");
- var oldObj = myObj.parentNode.replaceChild(this.content,myObj);
+ };
+ this.addWindow = function (windowObj) {
+ this.windowTypes[windowObj.id] = windowObj;
+ };
+ this.showWindow = function (windowName,argument) {
+ if(!this.windowTypes[windowName])
return;
+ this.unloadWindow();
+ this.argument = argument;
+ this._window = this.windowTypes[windowName];
+ this.showTitle(this._window.title);
+ this.showView();
+ this.show();
+ };
+ this.showView = function (viewId) {
+ if(!viewId || viewId == '')
+ viewId = "initial";
+ if(!this._window.views || !this._window.views[viewId])
+ return;
+ this.unloadView();
+ for(var panel in this._window.views[viewId].panels) {
+ this.showPanel(this._window.views[viewId].panels[panel]);
}
-
- this.childContent = '';
- nmsInfoBox._windowTypes.switchInfo.showComments();
- nmsInfoBox.refresh("soft");
- },
- showInfoTable: function() {
- this.activeView = "infotable";
- var content = [];
-
- for (var v in this.swi) {
- if (v == "placement") {
- var place = JSON.stringify(this.swi[v]);
- content.push([v,place]);
- continue;
+ this._view = viewId;
+ this.showNav();
+ };
+ this.removePanel = function (panelId) {
+ if(!!panelId)
+ this.unloadPanel(panelId);
+ };
+ this.unloadView = function () {
+ this.unloadPanel();
+ };
+ this.unloadWindow = function() {
+ this.hide();
+ this.unloadPanel();
+ };
+ this.unloadPanel = function (panelId) {
+ if(!panelId) {
+ for(var i in this._panels) {
+ this._panels[i].unload();
}
- content.push([v, this.swi[v]]);
- }
-
- for (var v in this.swm) {
- content.push([v, this.swm[v]]);
- }
- content.sort();
-
- var infotable = nmsInfoBox._makeTable(content);
- infotable.id = "info-switch-table";
-
- this.content = infotable;
- this.childContent = '';
- nmsInfoBox.refresh("soft");
- },
- update: function(type) {
- switch (type) {
- case 'comments':
- if(nmsInfoBox._windowTypes.switchInfo.activeView == "summary" && this.commentsHash != nmsData.comments.hash) {
- nmsInfoBox._windowTypes.switchInfo.showComments();
- }
- break;
- case 'tick':
- if(nmsInfoBox._windowTypes.switchInfo.activeView == "summary")
- nmsInfoBox._windowTypes.switchInfo.showSummary("tick");
- break;
+ this._panels = {};
+ } else {
+ try {
+ this._panels[panelId].unload();
+ } catch (e) {}
+ delete this._panels[panelId];
}
- },
- showComments: function() {
- var domObj = document.createElement("div");
- var comments = [];
-
- var commentbox = document.createElement("div");
- commentbox.id = "commentbox";
- commentbox.className = "panel-body";
- commentbox.style.width = "100%";
- commentbox.innerHTML = '<div class="input-group"><input type="text" class="form-control" placeholder="Comment" id="' + this.sw + '-comment"><span class=\"input-group-btn\"><button class="btn btn-default" onclick="addComment(\'' + this.sw + '\',document.getElementById(\'' + this.sw + '-comment\').value); document.getElementById(\'' + this.sw + '-comment\').value = \'\'; document.getElementById(\'' + this.sw + '-comment\').placeholder = \'Comment added. Wait for next refresh.\';">Add comment</button></span></div>';
-
- // If we have no switch data, so just show comment form
- if(!nmsData.comments || !nmsData.comments.comments) {
- this.commentsHash = false;
-
- // We have data, refresh
- } else if(nmsData.comments.comments[this.sw]) {
- this.commentsHash = nmsData.comments.hash;
- for (var c in nmsData.comments.comments[this.sw]["comments"]) {
- var comment = nmsData.comments.comments[this.sw]["comments"][c];
- if (comment["state"] == "active" || comment["state"] == "persist" || comment["state"] == "inactive") {
- comments.push(comment);
- }
- }
-
- if (comments.length > 0) {
- var commenttable = nmsInfoBox._makeCommentTable(comments);
- commenttable.id = "info-switch-comments-table";
- domObj.appendChild(commenttable);
+ };
+ //Method for triggering a function in an active panel
+ this.doInPanel = function (panelId, action, argument) {
+ if(!this._panels[panelId])
+ return;
+ if(typeof this._panels[panelId][action] === "function") {
+ if(!argument) {
+ this._panels[panelId][action]();
+ } else {
+ this._panels[panelId][action](argument);
}
-
- // We have no data for this switch, but its still correct
- } else {
- this.commentsHash = nmsData.comments.hash;
}
+ };
+};
- domObj.appendChild(commentbox);
- this.childContent = domObj;
- nmsInfoBox.refresh("soft");
- },
- showEdit: function() {
- this.activeView = "edit";
- var domObj = document.createElement("div");
- var template = {};
-
- nmsInfoBox._editValues = {};
- var place;
- for (var v in this.swi) {
- if (v == "placement") {
- place = JSON.stringify(this.swi[v]);
- template[v] = place;
- continue;
- }
- template[v] = nmsInfoBox._nullBlank(this.swi[v]);
+/*
+ * Basic info-panel object
+ *
+ * Has a basic model of a stand alone panel which is intended to be extended
+ * upon to implement specific panels:
+ *
+ * var myNewPanel = function () {
+ * nmsInfoPanel.call(this,"myNewPanelId");
+ * this.refresh = function (reason) {
+ * //My custom window function
+ * this._render(htmlObj);
+ * };
+ * };
+ *
+ */
+var nmsInfoPanel = function nmsInfoPanel(name,id) {
+ this.name = name;
+ this.id = id;
+ this.sw = false;
+ this.container = false;
+ this.classList = ['info-panel'];
+ this.me = false;
+ this.handlers = [];
+ this.mode = "initial";
+
+ if(!this.id)
+ this.id = (Math.random().toString(36)+'00000000000000000').slice(2, 10+2);
+
+ //Method for loading the general panel properties
+ this.load = function (container,argument) {
+ this.container = container;
+ this.sw = argument;
+ this.me = document.createElement("div");
+ this.me.id = this.id;
+ for(var i in this.classList)
+ this.me.classList.add(this.classList[i]);
+ this.container.appendChild(this.me);
+ this.init(argument);
+ };
+
+ //Method for making this specific panel-instance ready for first refresh
+ //Override in children when any custom init-functionality is needed
+ this.init = function (argument) {
+ this.refresh("init");
+ };
+
+ //Methods for getting and setting panel id
+ this.setId = function (id) {
+ this.id = id;
+ };
+ this.getId = function () {
+ return this.id;
+ };
+
+ //Methods for setting and getting mode (default "initial")
+ this.setMode = function (mode) {
+ this.mode = mode;
+ };
+ this.getMode = function () {
+ return this.mode;
+ };
+
+ //Internal method for rendering content
+ this._render = function (newContent) {
+ if(!newContent || !this.me)
+ return;
+ this.me.innerHTML = newContent.outerHTML;
+ };
+
+ //Helper method for rendering error messages in a unified way
+ this._renderError = function (message) {
+ var error = document.createElement("div");
+ error.className = "alert alert-info";
+ error.innerHTML = message;
+ this._render(error);
+ };
+
+ //Method for unloading any local data
+ this.unload = function () {
+ if(!this.me)
+ return;
+ this.me.remove();
+ this.sw = false;
+ this.container = false;
+ this.me = false;
+ this.id = false;
+ this.removeHandlers();
+ };
+
+ //Method for loading new data and triggering a _render if needed
+ //Implemented in children only
+ this.refresh = function (reason) {};
+
+ //Methods for adding and removing classes
+ this.addClass = function (className) {
+ if(this.classList.indexOf(className) == -1) {
+ this.classList.push(className);
+ this.me.classList.add(className);
}
- for (var v in this.swm) {
- template[v] = nmsInfoBox._nullBlank(this.swm[v]);
+ };
+ this.removeClass = function (className) {
+ var index = this.classList.indexOf(className);
+ if(index != -1) {
+ this.classList.splice(index,1);
+ this.me.classList.remove(className);
}
- var content = [];
- for (v in template) {
- var tmpsw = '\'' + this.sw + '\'';
- var tmpv = '\'' + v + '\'';
- var tmphandler = '"nmsInfoBox._editChange(' + tmpsw + ',' + tmpv + ');"';
- var html = "<input type=\"text\" class=\"form-control\" value='" + template[v] + "' id=\"edit-"+ this.sw + "-" + v + '" onchange=' + tmphandler + ' oninput=' + tmphandler + '/>';
- content.push([v, html]);
+ };
+
+ //Method for registering a data handler (lets us clean them up later)
+ this.addHandler = function (dataType,targetFunction) {
+ if(!targetFunction)
+ targetFunction = "refresh";
+ nmsData.addHandler(dataType,this.id,(this[targetFunction]).bind(this),"handler-"+dataType);
+ this.handlers.push(dataType);
+ };
+
+ //Method for removing all handlers we have registered
+ this.removeHandlers = function () {
+ for(var i in this.handlers) {
+ nmsData.unregisterHandler(this.handlers[i],this.id);
}
+ };
- content.sort();
-
- var table = nmsInfoBox._makeTable(content, "edit");
- domObj.appendChild(table);
-
- var submit = document.createElement("button");
- submit.innerHTML = "Save changes";
- submit.classList.add("btn", "btn-primary");
- submit.id = "edit-submit-" + this.sw;
- submit.onclick = function(e) { nmsInfoBox._windowTypes.switchInfo.save(); };
- domObj.appendChild(submit);
-
- var output = document.createElement("output");
- output.id = "edit-output";
- domObj.appendChild(output);
-
- if (place) {
- var pval = document.getElementById("edit-" + this.sw + "-placement");
- if (pval) {
- pval.value = place;
- }
+ //Method for checking if we have handlers registered
+ this.hasHandler = function (dataType) {
+ for(var i in this.handlers) {
+ if(this.handlers[i] == dataType)
+ return true;
}
+ return false;
+ };
+};
- this.content = domObj;
- this.childContent = '';
- nmsInfoBox.refresh("soft");
- },
- showSNMP: function(tree) {
- this.activeView = "snmp";
+/*
+ * Panel type: Switch SNMP information
+ *
+ * Displays a list of available SNMP data from a set SNMP-group (Ex. "Ports", "Misc")
+ *
+ * TODO: Clean up html-generator code.
+ *
+ */
+var switchSNMPPanel = function () {
+ nmsInfoPanel.call(this,"switchSNMP");
+ this.init = function() {
+ this.addHandler("snmp");
+ this.refresh();
+ };
+ this.refresh = function(reason) {
var domObj = document.createElement("div");
domObj.classList.add("panel-group");
try {
- var snmpJson = nmsData.snmp.snmp[this.sw][tree];
+ var snmpJson = nmsData.snmp.snmp[this.sw][this.mode];
} catch(e) {
- this.content = "(no recent data (yet)?)";
+ this._renderError("Waiting for data.");
return;
}
- /*
- * This html-generation code seems unnecessary complex. Must be a
- * cleaner way to do this. But not today.
- */
for(var obj in snmpJson) {
var cleanObj = obj.replace(/\W+/g, "");
@@ -497,127 +530,151 @@ nmsInfoBox._windowTypes.switchInfo = {
domObj.appendChild(groupObj);
}
- this.content = domObj;
- this.childContent = '';
- nmsInfoBox.refresh("soft");
- },
- unload: function() {
- this.title = '';
- this.content = '';
- this.childContent = false;
- this.sw = '';
- this.swi = '';
- this.swm = '';
- this.commentsHash = false;
- this.activeView = '';
- nmsData.unregisterHandler("ticker","switchInfo");
- },
- save: function() {
- var myData = nmsInfoBox._editStringify(this.sw);
+ this._render(domObj);
+ };
+};
+nmsInfoBox.addPanelType("switchSNMP",switchSNMPPanel);
+
+/*
+ * Panel type: Switch details
+ *
+ * Displays a table of switch information
+ *
+ */
+var switchDetailsPanel = function() {
+ nmsInfoPanel.call(this,"switchDetails");
+ this.refresh = function(reason) {
+ var swi = [];
+ var swm = [];
+ try {
+ swi = nmsData.switches["switches"][this.sw];
+ } catch(e) {}
+ try {
+ swm = nmsData.smanagement.switches[this.sw];
+ } catch(e) {}
+
+ var content = [];
+
+ for (var v in swi) {
+ if (v == "placement") {
+ var place = JSON.stringify(swi[v]);
+ content.push([v,place]);
+ continue;
+ }
+ content.push([v, swi[v]]);
+ }
+
+ for (var v in swm) {
+ content.push([v, swm[v]]);
+ }
+ content.sort();
+
+ var infotable = nmsInfoBox._makeTable(content);
+
+ this._render(infotable);
+ };
+};
+nmsInfoBox.addPanelType("switchDetails",switchDetailsPanel);
+
+/*
+ * Panel type: Add switch
+ *
+ * Lets you add a new switch using the switch-add api
+ *
+ */
+var switchAddPanel = function() {
+ nmsInfoPanel.call(this,"switchAdd");
+ this.refresh = function(reason) {
+ var domObj = document.createElement("div");
+ domObj.innerHTML = '<input type="text" class="form-control" id="create-sysname" placeholder="Sysname id"><button class="btn btn-default" onclick="nmsInfoBox._windowHandler.doInPanel(\'' + this.id +'\',\'save\');">Add switch</button>'
+ this._render(domObj);
+ };
+ this.save = function () {
+ var sysname = document.getElementById('create-sysname').value;
+ var myData = JSON.stringify([{'sysname':sysname}]);
$.ajax({
type: "POST",
- url: "/api/write/switch-update",
+ url: "/api/write/switch-add",
dataType: "text",
data:myData,
success: function (data, textStatus, jqXHR) {
var result = JSON.parse(data);
- if(result.switches_updated.length > 0) { // FIXME unresolved variable switches_addded
+ if(result.switches_addded.length > 0) { // FIXME unresolved variable switches_addded
nmsInfoBox.hide();
}
nmsData.invalidate("switches");
nmsData.invalidate("smanagement");
}
});
- }
+ };
};
+nmsInfoBox.addPanelType("switchAdd",switchAddPanel);
/*
- * Window type: Show inventory listing
+ * Panel type: Inventory listing
*
- * Basic window that displays a list of all devices with simple summary information
+ * Displays a filterable table with switch data, based on a selected mode
*
- * TODO: Set up more complex views with more columns, sorting, etc.
+ * TODO:
+ * - Add support for multiple columns with data
+ * - Add sorting
+ * - Add live filtering
+ * - Add export options?
*
*/
-nmsInfoBox._windowTypes.inventoryListing = {
- content: '',
- childContent: false,
- activeView: '',
- activeFilter: '',
- getTitle: function() {
- return '<h4>Inventory listing</h4><button type="button" class="distro-name btn btn-xs btn-default" onclick="nmsInfoBox.showWindow(\'inventoryListing\',\'distro_name\');">Distro name</button> <button type="button" class="distro-name btn btn-xs btn-default" onclick="nmsInfoBox.showWindow(\'inventoryListing\',\'sysDescr\');">System Description</button><button type="button" class="distro-name btn btn-xs btn-default" onclick="nmsInfoBox.showWindow(\'inventoryListing\',\'jnxBoxSerialNo\');">Serial Numbers</button>';
- },
- getContent: function() {
- return this.content;
- },
- getChildContent: function() {
- return this.childContent;
- },
- setFilter: function(filter) {
- this.activeFilter = filter.toLowerCase();
- nmsInfoBox._windowTypes.inventoryListing.load("refresh");
- },
- getFilter: function() {
- return this.activeFilter;
- },
- load: function(list) {
- var hasSnmp = false;
+var inventoryListingPanel = function() {
+ nmsInfoPanel.call(this,"inventoryListing");
+ this.filter = "";
+ this.init = function (mode) {
+ if(!nmsData.snmp || !nmsData.snmp.snmp) {
+ if(!this.hasHandler("snmp")) {
+ this.addHandler("snmp","init");
+ this._renderError("Waiting for SNMP data.");
+ }
+ return;
+ } else {
+ this.removeHandlers();
+ if(!!mode && this.mode == "initial")
+ this.setMode(mode);
+ this.refresh("init");
+ }
+ };
+ this.setFilter = function (filter) {
+ this.filter = filter;
+ this.refresh();
+ };
+ this.refresh = function (reason) {
var targetArray = [];
var listTitle = '';
- var needRefresh = false;
- var needSnmp = false;
var contentObj = document.createElement("div");
var inputObj = document.createElement("div");
- inputObj.innerHTML = '<div class="input-group"><input type="text" class="form-control" placeholder="Filter" id="inventorylisting-filter" value="' + this.activeFilter + '" onkeyup="if (event.keyCode == 13) {nmsInfoBox._windowTypes.inventoryListing.setFilter(document.getElementById(\'inventorylisting-filter\').value);}"><span class=\"input-group-btn\"><button class="btn btn-default" onclick="nmsInfoBox._windowTypes.inventoryListing.setFilter(document.getElementById(\'inventorylisting-filter\').value);">Filtrer</button></span></div>';
+ inputObj.innerHTML = '<div class="input-group"><input type="text" class="form-control" placeholder="Filter" id="inventorylisting-filter" value="' + this.filter + '" onkeyup="if (event.keyCode == 13) {nmsInfoBox._windowHandler.doInPanel(\'' + this.id + '\',\'setFilter\',document.getElementById(\'inventorylisting-filter\').value);}"><span class=\"input-group-btn\"><button class="btn btn-default" onclick="nmsInfoBox._windowHandler.doInPanel(\'' + this.id + '\',\'setFilter\',document.getElementById(\'inventorylisting-filter\').value);">Filtrer</button></span></div>';
contentObj.appendChild(inputObj);
-
- if(!nmsData.switches || !nmsData.switches.switches)
- return;
- if(!(!nmsData.snmp || !nmsData.snmp.snmp)) {
- hasSnmp = true;
- }
- if(list == "refresh") {
- list = this.activeView;
- needRefresh = true;
- }
-
- switch (list) {
+ switch (this.mode) {
case 'distro_name':
listTitle = 'Distro names';
break;
case 'sysDescr':
- if(hasSnmp)
- listTitle = 'System description';
- needSnmp = true;
+ listTitle = 'System description';
break;
case 'jnxBoxSerialNo':
- if(hasSnmp)
- listTitle = 'Serial Numbers';
- needSnmp = true;
+ listTitle = 'Serial Numbers';
break;
default:
listTitle = 'Distro names';
- list = 'distro_name';
- }
- this.activeView = list;
-
- if(needSnmp && !hasSnmp) {
- this.content = "No SNMP data loaded. Reloading shortly.";
- nmsData.addHandler("snmp","inventoryListing",nmsInfoBox._windowTypes.inventoryListing.update,"snmp-request");
- return;
}
var resultArray = [];
for(var sw in nmsData.switches.switches) {
var value = '';
- if(this.activeFilter != '') {
- if(sw.toLowerCase().indexOf(this.activeFilter) == -1 && !nmsInfoBox._searchSmart(this.activeFilter,sw))
+ if(this.filter != '') {
+ if(sw.toLowerCase().indexOf(this.filter) == -1 && !nmsInfoBox._searchSmart(this.filter,sw))
continue;
}
try {
- switch (list) {
+ switch (this.mode) {
case 'distro_name':
value = nmsData.switches.switches[sw]["distro_name"];
break;
@@ -628,9 +685,7 @@ nmsInfoBox._windowTypes.inventoryListing = {
value = nmsData.snmp.snmp[sw]["misc"]["jnxBoxSerialNo"][0];
break;
}
- } catch (e) {
- //console.log(e);
- }
+ } catch (e) {}
resultArray.push([sw, value]);
}
@@ -640,34 +695,270 @@ nmsInfoBox._windowTypes.inventoryListing = {
infotable.id = "inventory-table";
contentObj.appendChild(infotable);
- this.content = contentObj;
- if(needRefresh)
- nmsInfoBox.refresh("soft");
- },
- update: function(type) {
- if(type == "snmp-request") {
- nmsData.unregisterHandler("snmp","inventoryListing");
- nmsInfoBox._windowTypes.inventoryListing.load("refresh");
+ this._render(contentObj);
+ };
+};
+nmsInfoBox.addPanelType("inventoryListing",inventoryListingPanel);
+
+/*
+ * Panel type: Edit switch
+ *
+ * Lets you edit basic switch and switch management data through the switch-update api
+ *
+ */
+var switchEditPanel = function () {
+ nmsInfoPanel.call(this,"switchEdit");
+ this.refresh = function (reason) {
+ var swi = [];
+ var swm = [];
+ try {
+ swi = nmsData.switches["switches"][this.sw];
+ } catch(e) {}
+ try {
+ swm = nmsData.smanagement.switches[this.sw];
+ } catch(e) {}
+
+ var domObj = document.createElement("div");
+ var template = {};
+
+ nmsInfoBox._editValues = {};
+ var place;
+ for (var v in swi) {
+ if (v == "placement") {
+ place = JSON.stringify(swi[v]);
+ template[v] = place;
+ continue;
+ }
+ template[v] = nmsInfoBox._nullBlank(swi[v]);
}
- },
- unload: function() {
- nmsData.unregisterHandler("snmp","inventoryListing");
- this.content = '';
- this.activeView = '';
- this.activeFilter = '';
- },
- save: function() {
- }
+ for (var v in swm) {
+ template[v] = nmsInfoBox._nullBlank(swm[v]);
+ }
+ var content = [];
+ for (v in template) {
+ var tmpsw = '\'' + this.sw + '\'';
+ var tmpv = '\'' + v + '\'';
+ var tmphandler = '"nmsInfoBox._editChange(' + tmpsw + ',' + tmpv + ');"';
+ var html = '<input type="text" class="form-control" value="' + template[v] + '" id="edit-' + this.sw + '-' + v + '" onchange=' + tmphandler + ' oninput=' + tmphandler + '>';
+ content.push([v, html]);
+ }
+
+ content.sort();
+
+ var table = nmsInfoBox._makeTable(content, "edit");
+ domObj.appendChild(table);
+
+ var submit = document.createElement("button");
+ submit.innerHTML = "Save changes";
+ submit.classList.add("btn", "btn-primary");
+ submit.id = "edit-submit-" + this.sw;
+ submit.setAttribute("onclick","nmsInfoBox._windowHandler.doInPanel('" + this.id + "','save');");
+ domObj.appendChild(submit);
+
+ var output = document.createElement("output");
+ output.id = "edit-output";
+ domObj.appendChild(output);
+
+ if (place) {
+ var pval = document.getElementById("edit-" + this.sw + "-placement");
+ if (pval) {
+ pval.value = place;
+ }
+ }
+
+ this._render(domObj);
+ };
+ this.save = function () {
+ var myData = nmsInfoBox._editStringify(this.sw);
+ $.ajax({
+ type: "POST",
+ url: "/api/write/switch-update",
+ dataType: "text",
+ data:myData,
+ success: function (data, textStatus, jqXHR) {
+ var result = JSON.parse(data);
+ if(result.switches_updated.length > 0) { // FIXME unresolved variable switches_addded
+ nmsInfoBox.hide();
+ }
+ nmsData.invalidate("switches");
+ nmsData.invalidate("smanagement");
+ }
+ });
+ };
};
+nmsInfoBox.addPanelType("switchEdit",switchEditPanel);
/*
- * Click a switch and display it
- * it.
+ * Panel type: Switch comments
+ *
+ * Displays the current comments and lets you interact with them or add new ones
+ *
+ * TODO: Test with a dummy-db to make sure everything still works properly
+ *
*/
-nmsInfoBox.click = function(sw)
-{
- this.showWindow("switchInfo",sw);
+var switchCommentsPanel = function () {
+ nmsInfoPanel.call(this,"switchComments");
+ this.commentsHash = false;
+ this.refresh = function (reason) {
+ var domObj = document.createElement("div");
+ var comments = [];
+
+ var commentbox = document.createElement("div");
+ commentbox.id = "commentbox";
+ commentbox.className = "panel-body";
+ commentbox.style.width = "100%";
+ commentbox.innerHTML = '<div class="input-group"><input type="text" class="form-control" placeholder="Comment" id="' + this.sw + '-comment"><span class=\"input-group-btn\"><button class="btn btn-default" onclick="addComment(\'' + this.sw + '\',document.getElementById(\'' + this.sw + '-comment\').value); document.getElementById(\'' + this.sw + '-comment\').value = \'\'; document.getElementById(\'' + this.sw + '-comment\').placeholder = \'Comment added. Wait for next refresh.\';">Add comment</button></span></div>';
+
+ // We have data
+ if(!(!nmsData.comments || !nmsData.comments.comments)) {
+ this.commentsHash = nmsData.comments.hash;
+
+ // We have data for this switch
+ if(nmsData.comments.comments[this.sw]) {
+ this.commentsHash = nmsData.comments.hash;
+ for (var c in nmsData.comments.comments[this.sw]["comments"]) {
+ var comment = nmsData.comments.comments[this.sw]["comments"][c];
+ if (comment["state"] == "active" || comment["state"] == "persist" || comment["state"] == "inactive") {
+ comments.push(comment);
+ }
+ }
+
+ if (comments.length > 0) {
+ var commenttable = nmsInfoBox._makeCommentTable(comments);
+ commenttable.id = "info-switch-comments-table";
+ domObj.appendChild(commenttable);
+ }
+
+ }
+ }
+
+ domObj.appendChild(commentbox);
+ this._render(domObj);
+ };
+};
+nmsInfoBox.addPanelType("switchComments",switchCommentsPanel);
+
+/*
+ * Panel type: Switch summary
+ *
+ * Display a live summary of key metrics
+ *
+ */
+var switchSummaryPanel = function() {
+ nmsInfoPanel.call(this,"switchSummary");
+ this.init = function() {
+ this.addHandler("ticker");
+ this.refresh();
+ };
+ this.refresh = function(reason) {
+ var content = [];
+
+ //Get DHCP info
+ var lastDhcp = undefined;
+ try {
+ var tempDhcp = nmsData.dhcp.dhcp[this.sw];
+ var now = Date.now();
+ now = Math.floor(now / 1000);
+ tempDhcp = now - parseInt(tempDhcp);
+ tempDhcp = tempDhcp + " s";
+ } catch(e) {}
+
+ //Get SNMP status
+ var snmpStatus = undefined;
+ try {
+ if (nmsData.snmp.snmp[this.sw].misc.sysName[0] != sw) {
+ snmpStatus = "Sysname mismatch";
+ } else {
+ snmpStatus = "OK";
+ }
+ } catch(e) {}
+
+ //Get CPU usage
+ var cpuUsage = undefined;
+ try {
+ var cpu = 0;
+ for (var u in nmsData.snmp.snmp[this.sw].misc.jnxOperatingCPU) {
+ var local = nmsData.snmp.snmp[this.sw].misc['jnxOperatingCPU'][u];
+ cpu = Math.max(nmsData.snmp.snmp[this.sw].misc.jnxOperatingCPU[u],cpu);
+ }
+ cpuUsage = cpu + " %";
+ } catch (e) {}
+
+ //Get traffic data
+ var uplinkTraffic = undefined;
+ try {
+ var speed = 0;
+ var t = parseInt(nmsData.switchstate.then[this.sw].uplinks.ifHCOutOctets);
+ var n = parseInt(nmsData.switchstate.switches[this.sw].uplinks.ifHCOutOctets);
+ var tt = parseInt(nmsData.switchstate.then[this.sw].time);
+ var nt = parseInt(nmsData.switchstate.switches[this.sw].time);
+ var tdiff = nt - tt;
+ var diff = n - t;
+ speed = diff / tdiff;
+ if(!isNaN(speed)) {
+ uplinkTraffic = byteCount(speed*8,0);
+ }
+ } catch (e) {};
+
+ //Get uptime data
+ var uptime = "";
+ try {
+ uptime = nmsData.snmp.snmp[this.sw]["misc"]["sysUpTimeInstance"][""] / 60 / 60 / 100;
+ uptime = Math.floor(uptime) + " t";
+ } catch(e) {}
+
+ //Get temperature data
+ var temp = "";
+ try {
+ temp = nmsData.switchstate.switches[this.sw].temp + " °C";
+ } catch(e) {}
+
+ //Get management data
+ var mgmtV4 = "";
+ var mgmtV6 = "";
+ var subnetV4 = "";
+ var subnetV6 = "";
+ try {
+ mgmtV4 = nmsData.smanagement.switches[this.sw].mgmt_v4_addr;
+ mgmtV6 = nmsData.smanagement.switches[this.sw].mgmt_v6_addr;
+ subnetV4 = nmsData.smanagement.switches[this.sw].subnet4;
+ subnetV6 = nmsData.smanagement.switches[this.sw].subnet6;
+ } catch(e) {}
+
+ //Get ping data
+ var ping = undefined;
+ try {
+ nmsData.ping.switches[this.sw].latency + " ms";
+ } catch (e) {}
+
+
+ content.push(["Ping latency:",ping]);
+ content.push(["Last DHCP lease:",lastDhcp]);
+ content.push(["SNMP status:",snmpStatus]);
+ content.push(["CPU usage:",cpuUsage]);
+ content.push(["Uplink traffic:",uplinkTraffic]);
+ content.push(["System uptime:",uptime]);
+ content.push(["Temperature",temp]);
+ content.push(["Management (v4):",mgmtV4]);
+ content.push(["Management (v6):",mgmtV6]);
+ content.push(["Subnet (v4):",subnetV4]);
+ content.push(["Subnet (v6):",subnetV6]);
+
+ var contentCleaned = [];
+ for(var i in content) {
+ if(content[i][1] == '' || content[i][1] == null)
+ continue;
+ if(content[i][1] == undefined || content[i][1])
+ content[i][1] == "No data";
+ contentCleaned.push(content[i]);
+ }
+
+ var table = nmsInfoBox._makeTable(contentCleaned);
+
+ this._render(table);
+ };
};
+nmsInfoBox.addPanelType("switchSummary",switchSummaryPanel);
/*
* General-purpose table-maker?