diff options
Diffstat (limited to 'web/js')
| -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 | 
3 files changed, 256 insertions, 45 deletions
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)   	}  | 
