"use strict"; /* Basic editor for switches, and possibly networks and whatever. * This is the first real use of both the nmsBox and nmsType, so * expect changes as the need(s) arise. * * The general idea is simple, though: Editing and adding is to be treated * as similar as possible, and there should be no hard-coding anywhere. If * we need a "one-off" for whatever, we should find a genric way of solving * it to avoid complicating things. * */ class nmsNewSwitch extends nmsPanel { constructor() { super("Add new switch"); this.add(new nmsModSwitch(undefined)); this.nav.add( new nmsString( "The only required field for adding a switch is the sysname, everything else will be filled in by the backend if you do not provide it. However, you should PROBABLY fill in managemnt IP and a few other fields." ) ); } } class nmsNewNet extends nmsPanel { constructor() { super("Add new network"); this.add(new nmsModNet(undefined)); this.nav.add( new nmsString( "Only the name is required, but you should probably fill in more." ) ); } } class nmsModThing extends nmsBox { constructor(data) { super("div"); this.identifier = data.identifier; this.invalidate = data.invalidate; this.api = data.api; this.item = data.item; this.generateBaseTemplate(); this.populate(); } commit(data) { $.ajax({ type: "POST", url: this.api, dataType: "text", nmsBox: this, data: JSON.stringify(data), success: function (data, textStatus, jqXHR) { var msg = new nmsString("Changed..."); msg.attach(this.nmsBox._root); msg.show(); this.nmsBox.destroy(); //nmsInfoBox.hide(); for (var x of this.nmsBox.invalidate) { nmsData.invalidate(x); } }, }); } del(e) { if ( confirm( "This will delete the " + this.typeName + ":" + this.nmsBox.panel.item ) ) { this.nmsBox.panel.commit([ { sysname: this.nmsBox.panel.item, deleted: true }, ]); } } save(e) { var diff = this.nmsBox.panel.diff(); if (diff != undefined) { this.nmsBox.panel.commit([diff]); } } /* Pretty sure that the type-thing is OK, but what I want is to * generate a nmsTemplate or something that can be used to get/set * variables generically, and replaces nmsEditRow. Since right now, * both the template and the row is fiddling with values, luckily * all through the same actual object, but still.... * This is because I wrote nmsEditRow before I added a type-system. * * The fundamental problem is that the row-rendering is obviously * affected by the type, and the overall "template"/parent * (nmsModSwitch right now) is also obviously affected by changes to * the individual rows. * * Right now a change in a value means nmsEditRow will get the * event, it will use the nmsType to validate, and ultimately set, * but it ALSO has to store a text-representation of the value if it * changes from other sources (e.g.: auto-complete), and we need to * alert nmsModSwitch that a change has occurred so it can act * approrpiately (e.g.: Enabling/disabling a save button). * * This means that nmsType instances, nmsEditRow instances and * nmsModSwitch instance is tightly coupled in non-obvious ways. * * Which means bugs. */ populate() { if (this.item != undefined) { this._populateTemplate(this.item); } this.table = new nmsTable(); this.rows = {}; for (var v in this._template) { this.rows[v] = new nmsEditRow(v, this._template[v]); this.rows[v].parent = this; this.table.add(this.rows[v]); } this.add(this.table); } changed(row) { this.title = "saw row change on " + row.name + " to " + row.value; } get value() { var ret = {}; for (var idx in this.rows) { ret[idx] = this.rows[idx].value; } return ret; } diff() { var ret = {}; var changed = 0; for (var idx in this.rows) { if (this.rows[idx].value.toString() != this.rows[idx].original) { ret[idx] = this.rows[idx].value.value; changed++; } } if (!changed) { return undefined; } ret[this.identifier] = this.rows[this.identifier].value.value; return ret; } } class nmsEditRow extends nmsBox { constructor(text, value) { super("tr"); // This should/could be smarter in the future. console.assert(value instanceof nmsType); this.name = text; this._value = value; this.original = value.toString(); var td1 = new nmsBox("td"); var name = new nmsString(text + " "); name.html.title = value.description; td1.add(name); this.add(td1); td1.html.width = "50%"; this._state = new nmsBox("span", { html: { className: "label label-default", textContent: "Original" }, }); this._valid = new nmsBox("span", { html: { className: "label label-default", textContent: "Not verified" }, }); name.add(this._state); name.add(this._valid); this._valid.hide(); this.changed(false); var content = new nmsBox("td"); var input = new nmsBox("input"); input.html.value = value.toString(); input.html.className = "form-control"; input.html.type = "text"; input.row = this; input.html.disabled = true; if (value instanceof nmsTypeSecret) { input.html.type = "password"; input.html.autocomplete = "off"; input.html.onfocus = function f() { this.type = "text"; }; input.html.oninput = function f() { this.type = "text"; }; input.html.onblur = function f() { this.type = "password"; }; } input.html.onchange = function () { this.nmsBox.row.value = this.value; }; input.html.oninput = function () { this.nmsBox.row.value = this.value; }; this._input = input; this._content = content; content.add(input); this.add(content); } get value() { return this._value; } changed(val) { if (val) { this._state.show(); this._state.html.textContent = "Changed"; this._state.html.classList.remove("label-default"); this._state.html.classList.add("label-warning"); } else { this._state.hide(); } } valid(val) { this._valid.html.classList.remove("label-default"); this._valid.show(); if (val) { this._valid.html.textContent = "Valid"; this._valid.html.classList.remove("label-danger"); this._valid.html.classList.add("label-success"); } else { this._valid.html.textContent = "Invalid"; this._valid.html.classList.add("label-danger"); this._valid.html.classList.remove("label-success"); } } /* THIS IS A MESS */ set value(value) { if (!this._value.validate(value)) { this.valid(false); this._content.html.classList.add("has-error"); return; } else { this.valid(true); this._content.html.classList.remove("has-error"); this._value.fromString(value); } if (this._input.html.value != this._value.toString()) { this._input.html.value = this._value.toString(); } if (this._value.toString() != this.original) { this.changed(true); this._content.html.classList.add("has-success"); } else { this.changed(false); this._content.html.classList.remove("has-success"); } this.parent.changed(this); } } class nmsModSwitch extends nmsModThing { constructor(sw) { super({ item: sw, identifier: "sysname", invalidate: ["switches", "smanagement"], api: "/api/write/switches", }); } generateBaseTemplate() { this._template = { sysname: new nmsTypeSysname( "Unique systemname/switch name. Only required field. Read/only on existing equipment." ), mgmt_v4_addr: new nmsTypeIP("Management address IPv4"), mgmt_v6_addr: new nmsTypeIP("Management address IPv6"), mgmt_vlan: new nmsTypeNetwork("Management VLAN"), traffic_vlan: new nmsTypeNetwork("Traffic VLAN"), distro_name: new nmsTypeSysnameReference( "Distro switch upstream of this system. Required for provisioning." ), distro_phy_port: new nmsTypePort( "Name of port we connect to at the distro switch. Used for provisioning, among other things." ), placement: new nmsTypePlace( "Map placement (If following a regular naming scheme, the backend will place it poperly, otherwise a random place will be chose)" ), tags: new nmsTypeTags( "Additional tags in JSON text array format. Can be anything. Used to provide a simple escape hatch mechanism to tag systems." ), }; } _populateTemplate(sw) { var swi = []; var swm = []; try { swi = nmsData.switches["switches"][sw]; swm = nmsData.smanagement.switches[sw]; } catch (e) {} var template = {}; for (var v in swi) { console.assert(this._template[v] instanceof nmsType); if (swi[v] != null) { this._template[v].initial(swi[v]); } } for (var v in swm) { if (v == "last_updated") { continue; } console.assert(this._template[v] instanceof nmsType); if (swm[v] != null && this._template[v]) { this._template[v].initial(swm[v]); } } } } class nmsModNet extends nmsModThing { constructor(net) { super({ item: net, identifier: "name", invalidate: ["networks", "smanagement"], api: "/api/write/networks", }); } generateBaseTemplate() { this._template = { name: new nmsType( "Unique networkname. Only required field. Read/only on existing nets." ), vlan: new nmsType("VLAN ID"), gw4: new nmsTypeIP("Gateway address, IPv4"), gw6: new nmsTypeIP("Gateway address, IPv6"), subnet4: new nmsTypeCIDR("Subnet, IPv4"), subnet6: new nmsTypeCIDR("Subnet, IPv6"), router: new nmsTypeSysnameReference( "Router where net is terminated. E.g.: r1.noc for floor traffic nets" ), tags: new nmsTypeTags( "Additional tags in JSON text array format. Can be anything. Used to provide a simple escape hatch mechanism to tag systems." ), }; } _populateTemplate(net) { var nets = []; try { nets = nmsData.networks["networks"][net]; } catch (e) {} var template = {}; for (var v in nets) { console.assert(this._template[v] instanceof nmsType); if (nets[v] != null) { this._template[v].initial(nets[v]); } } } }