diff options
-rw-r--r-- | web/index.html | 1 | ||||
-rw-r--r-- | web/js/nms-types.js | 174 | ||||
-rw-r--r-- | web/js/nms-ui-boxes.js | 2 | ||||
-rw-r--r-- | web/js/nms-ui-switch.js | 125 |
4 files changed, 257 insertions, 45 deletions
diff --git a/web/index.html b/web/index.html index 3e92dc8..acb0fbf 100644 --- a/web/index.html +++ b/web/index.html @@ -337,6 +337,7 @@ <script type="text/javascript" src="js/nms-template.js"></script> <script type="text/javascript" src="js/nms-draw-chart.js"></script> <script type="text/javascript" src="js/nms-ui-boxes.js"></script> + <script type="text/javascript" src="js/nms-types.js"></script> <script type="text/javascript" src="js/nms-ui-switch.js"></script> <script src="js/jquery.datetimepicker.full.js" type="text/javascript"></script> <script type="text/javascript"> diff --git a/web/js/nms-types.js b/web/js/nms-types.js new file mode 100644 index 0000000..254c00b --- /dev/null +++ b/web/js/nms-types.js @@ -0,0 +1,174 @@ +"use strict"; +/* + * Base type. All nmsType* classes represent a logical family of units. + * This is used to provide input validation on the class-level and + * is used to store the value and description of variables. + * This is not bound to editing in theory. It is natural to think + * that nms-map-handlers will leverage the same type-system to + * verify that things are as they should. + * + * An example of future features outside the scope of simply editing stuff: + * verify that a nmsTypeIP is contained in nmsTypeNetwork + * use nmsTypePlace for all placement-logic + * + * Stuff that should be supported within an editor: + * - Validation (duh) + * - Hints (auto-complete, parse-error-feedback) + * - Creating a new network "on the fly" when adding a switch + * - Rich editing modes (e.g.: the placement-editing should + * allow you to just drag the switch and see the values change, + * and tags should obviously be smater too. And yeah, the snmp- + * thing should be an actual secret like it is today in + * nms-info-box + */ +class nmsType { + constructor(description, priority) { + this._value = null; + this.description = description; + this.validationReason = ""; + if (priority == undefined) { + priority = this._defaultPriority; + } + this.priority = priority + } + // Override this to get free validation. Just return true or false. + validate(input) { + return true; + } + get value() { + return this._value; + } + _valueParse(input) { + return input; + } + set value(input) { + if (this.validate(input)) { + this._value = this._valueParse(input); + } else { + throw "Invalid input. Use .validate() before setting stuff please. I am " + this + " and my input is " + input + } + } + get _defaultPriority() { + return nmsPriority.optional; + } +} + +class nmsTypeInterval extends nmsType { + validate(input) { + return !!(input.match(/^\d\d:\d\d:\d\d$/)) + } +} +class nmsTypeIP extends nmsType { + get _defaultPriority() { + return nmsPriority.important; + } + _validateV4(input) { + var x = input.match(/^(\d+)\.(\d+).(\d+).(\d+)$/) + if (!x) { + this.validationReason = "Doesn't look like IPv4 address or IPv6"; + return false; + } + for (var i = 1; i < 5; i ++) { + if (x[i] < 0 || x[i] > 255) { + this.validationReason = "The " + i + "'th octet("+x[i]+") is outside of expected range (0-255)" + return false + } + } + this.validationReason = "OK" + return true; + } + /* It's so easy to check if IPv6 addresses are valid. + */ + _validateV6(input) { + if (!!input.match(/^[a-fA-F0-9:]+$/)) { + this.validationReason = "OK IPv6 address" + return true; + } else { + this.validationReason = "Doesn't parse as a IPv6 address despite :"; + return false; + } + } + validate(input) { + if (input.match(":")) { + return this._validateV6(input); + } else { + return this._validateV4(input); + } + } +} +class nmsTypeNetwork extends nmsType { + get _defaultPriority() { + return nmsPriority.important; + } + validate(input) { + var x =testTree(nmsData,["networks","networks",input]) + if (x) { + this.validationReason = "OK" + } else { + this.validationReason = "No such network: " + input + } + return x; + } +} +class nmsTypeJSON extends nmsType { + get value() { + if (this._value == null || this._value == undefined) { + return ""; + } + return JSON.stringify(this._value) + } + set value(input) { + super.value = input; + } + /* This should probably actually know the basic template + * for a placement-object... + */ + validate(input) { + try { + this._valueParse(input); + this.validationReason = "OK" + return true; + } catch(e) { + this.validationReason = e.message; + return false; + } + } + _valueParse(input) { + if (input instanceof Object) { + return input; + } else { + return JSON.parse(input); + } + } +} +class nmsTypePlace extends nmsTypeJSON { } +class nmsTypePort extends nmsType { } +class nmsTypeSysname extends nmsType { + get _defaultPriority() { + return nmsPriority.newOnly; + } +} +class nmsTypeSysnameReference extends nmsType { + get _defaultPriority() { + return nmsPriority.important; + } + validate(input) { + var x = testTree(nmsData,["switches","switches",input]) + if (x) { + this.validationReason = "OK" + } else { + this.validationReason = "No such switch: " + input + } + return x; + } +} +class nmsTypeTags extends nmsTypeJSON { } +class nmsTypeSecret extends nmsType { } + +var nmsPriority = { + readOnly: 0, + required: 1, + important: 2, + optional: 3, + newOnly: 4 +} diff --git a/web/js/nms-ui-boxes.js b/web/js/nms-ui-boxes.js index 3ed65cd..ac4445e 100644 --- a/web/js/nms-ui-boxes.js +++ b/web/js/nms-ui-boxes.js @@ -166,7 +166,7 @@ class nmsPanel extends nmsBox{ /* Mainly just to make the constructor more readable. */ makeHeading(title) { var titleObject = new nmsBox("div",{html:{classList: ["panel-heading"]}}); - this._titleText = new nmsBox("p",{html:{textContent: title}}); + this._titleText = new nmsBox("h4",{html:{textContent: title}}); var closeButton = new nmsBox("button"); closeButton.html.className = "close"; closeButton.panel = this; diff --git a/web/js/nms-ui-switch.js b/web/js/nms-ui-switch.js index fe4bbe9..3e6dd47 100644 --- a/web/js/nms-ui-switch.js +++ b/web/js/nms-ui-switch.js @@ -1,6 +1,16 @@ "use strict"; -class nmsUiSwitch extends nmsPanel { +/* 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 nmsModSwitch extends nmsPanel { constructor(sw) { var title; if (sw == undefined) { @@ -10,23 +20,50 @@ class nmsUiSwitch extends nmsPanel { } super(title) this._sw = sw; + this.nav.add(new nmsString("Adding and editing stuff has immediate effects and blah blah blah, insert sensible help-text here.")); + this.generateBaseTemplate() this.populate() } - /* - * We really should base this on a backend-API exposing relevant fields... + /* 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. */ - getTemplate(sw) { - if (sw == undefined) { - return { - mgmt_v4_addr: null, - mgmt_v6_addr: null, - community: null, - placement: null, - mgmt_vlan: null, - poll_frequency: null, - tags: null - }; - } + generateBaseTemplate() { + this._template = { + sysname: new nmsTypeSysname("Unique systemname/switch name. Only required field." ), + 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."), + poll_frequency: new nmsTypeInterval("Poll frequency for SNMP (will use default from backend)"), + community: new nmsTypeSecret("SNMP community (will use default from backend)"), + 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 { @@ -38,32 +75,25 @@ class nmsUiSwitch extends nmsPanel { var template = {} for (var v in swi) { - template[v] = swi[v]; + console.assert(this._template[v] instanceof nmsType) + this._template[v].value = swi[v]; } for (var v in swm) { if (v == "last_updated") { continue; } - template[v] = swm[v]; + console.assert(this._template[v] instanceof nmsType) + this._template[v].value = swm[v]; } - return template; } populate() { - var template = this.getTemplate(this._sw); - this.table = new nmsTable(); - var first = new Array("sysname","distro_name","distro_phy_port","traffic_vlan") - var sorted = new Array(); - for (var v in template) { - if (!first.includes(v)) { - sorted.push(v); - } + if (this._sw != undefined) { + this._populateTemplate(this._sw); } - sorted.sort(); - var finals = first.concat(sorted); + this.table = new nmsTable(); this.rows = {} - for (var i in finals) { - var v = finals[i]; - this.rows[v] = new nmsEditRow(v, nmsInfoBox._nullBlank(template[v])); + 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]); } @@ -81,19 +111,19 @@ class nmsEditRow extends nmsBox { constructor(text,value) { super("tr") // This should/could be smarter in the future. - if (value instanceof Object) { - value = JSON.stringify(value); - } + console.assert(value instanceof nmsType) this.name = text; this._value = value; - this.original = value; + this.original = value.value; var td1 = new nmsBox("td") - td1.add(new nmsString(text)) + var name = new nmsString(text); + name.html.title = value.description; + td1.add(name) this.add(td1); var td2 = new nmsBox("td") var input = new nmsBox("input") - input.html.value = value; + input.html.value = value.value; input.html.className = "form-control"; input.html.type = "text"; input.row = this; @@ -109,17 +139,24 @@ class nmsEditRow extends nmsBox { this.add(td2) } get value() { - return this._value; + return this._value.value; } + /* THIS IS A MESS */ set value(value) { - this._value = value; - if (this._input.html.value != value) { - this._input.html.value = value + if (!this._value.validate(value)) { + this._td2.html.classList.add("has-error"); + return; + } else { + this._td2.html.classList.remove("has-error"); + this._value.value = value; + } + if (this._input.html.value != this._value.value) { + this._input.html.value = this._value.value } - if (this._value != this.original) { - this._td2.html.classList.add("has-warning"); + if (this._value.value != this.original) { + this._td2.html.classList.add("has-success"); } else { - this._td2.html.classList.remove("has-warning"); + this._td2.html.classList.remove("has-success"); } this.parent.changed(this) } |