| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 | 
							- const PassiveListener = { passive: true };
 
- const LocalGet        = { method: 'GET', mode: 'same-origin',
 
- 			  redirect: 'follow' };
 
- // Get an element by id or an Element object
 
- function getelem(id) {
 
-     return (id instanceof Element) ? id : document.getElementById(id);
 
- }
 
- // Find a child with a specific tag
 
- function chi(me,tag) { return me.getElementsByTagName(tag)[0]; }
 
- // Find a sibling element with a specific tag
 
- function sib(me,tag) { return chi(me.parentElement, tag); }
 
- // Add/remove class entries in bulk; tags is an Array each containing
 
- // an Array of arguments to toggle. On return the second element of
 
- // each Array will be updated to the current value.
 
- function classmod(elem,tags) {
 
-     for (var i = 0; i < tags.length; i++)
 
- 	tags[i][1] = elem.classList.toggle(...tags[i]);
 
-     return tags;
 
- }
 
- // Read a key=value text file and return it as a Promise of a Map
 
- function fetchconfig(url) {
 
-     return fetch(url, LocalGet)
 
- 	.then(res => {
 
- 	    if (!res.ok) {
 
- 		throw new Error('HTTP error '+response.status);
 
- 	    } else {
 
- 		return res.text();
 
- 	    }
 
- 	})
 
- 	.then(text => {
 
- 	    var map = new Map();
 
- 	    for (const c of text.split(/[\r\n]+/)) {
 
- 		var m = c.match(/^\s*([\;\/]?)((?:"[^"]*"|[^"])*?)\s*=(.*)$/);
 
- 		if (m && m[1] == "") {
 
- 		    var k = m[2].replaceAll(/(^"|"$|(")")/g, "$2");
 
- 		    map.set(k, m[3]);
 
- 		}
 
- 	    }
 
- 	    return map;
 
- 	});
 
- }
 
- // Get the value from an input field if valid, otherwise its default
 
- function valval(ie) {
 
-     return ie.checkValidity() ? ie.value : ie.defaultValue;
 
- }
 
- // Parse a string for a valid boolean truth value
 
- function cfgbool(str) {
 
-     return str && !str.match(/^(0*|[fnd].*|of.*)$/i);
 
- }
 
- // Initialize a form from a map. Checkboxes take a cfgbool string;
 
- // if in the HTML their value is set to a false cfgbool string then
 
- // the checkbox is inverted logic.
 
- function initform(form,map,ro = false) {
 
-     form = getelem(form);
 
-     for (var e of form.elements) {
 
- 	if (e.classList.contains('noinit') ||
 
- 	    e instanceof HTMLFieldSetElement)
 
- 	    continue;
 
- 	if (ro && e.disabled != undefined && !e.classList.contains('noro'))
 
- 	    e.disabled = true;
 
- 	if (e instanceof HTMLInputElement ||
 
- 	    e instanceof HTMLSelectElement) {
 
- 	    const val = map.get(e.name);
 
- 	    if (val == null)
 
- 		continue;
 
- 	    if (e.type == 'checkbox') {
 
- 		e.checked = cfgbool(val) == cfgbool(e.value);
 
- 	    } else if (e.type == 'radio') {
 
- 		e.checked = (val == e.value);
 
- 	    } else {
 
- 		e.value = val;
 
- 	    }
 
- 	} else if (e instanceof HTMLButtonElement) {
 
- 	    e.disabled = ro;
 
- 	}
 
-     }
 
- }
 
- // Load form initialization data
 
- function loadform(form,url,ro = false) {
 
-     fetchconfig(url)
 
- 	.then((map) => {initform(form,map,ro); })
 
- 	.catch(() => {});
 
- }
 
- // Replace the contents of selected HTML elements based on a map with
 
- // selectors.
 
- function fillin(map,html = false,top = document) {
 
-     for (const [key,val] of map) {
 
- 	try {
 
- 	    const m = key.match(/(.*?)(?:\;([\w.-]*)(\??))?$/);
 
- 	    for (var e of top.querySelectorAll(m[1])) {
 
- 		try {
 
- 		    if (!html) e.textContents = val;
 
- 		    else if (!m[2]) e.innerHTML = val;
 
- 		    else if (!m[3]) e.setAttribute(m[2],val);
 
- 		    else if (!cfgbool(val)) e.removeAttribute(m[2]);
 
- 		    else e.setAttribute(m[2],'');
 
- 		} catch(e) { };
 
- 	    }
 
- 	} catch(e) { };
 
-     }
 
- }
 
- // Load status or HTML data
 
- function load(url,html = false)
 
- {
 
-     fetchconfig(url)
 
- 	.then(map => fillin(map,html))
 
- 	.catch(() => {});
 
- }
 
- // POST upload of data from within a form, with (optional)
 
- // progress and response text, and redirect after success
 
- function upload(form,data) {
 
-     var xhr = new XMLHttpRequest();
 
-     var progress = chi(form,'progress');
 
-     if (progress) {
 
- 	progress.value = 0;
 
- 	xhr.upload.addEventListener('progress', (e) => {
 
- 	    if (!e.lengthComputable)
 
- 		return;
 
- 	    progress.max   = e.total * 1.05;
 
- 	    progress.value = e.loaded;
 
- 	}, PassiveListener);
 
-     }
 
-     classmod(form, [['started',1],['done',0],['ok',0],['err',0],['running',1]]);
 
-     xhr.addEventListener('loadend', (e) => {
 
- 	const ok = xhr.status >= 200 && xhr.status < 400;
 
- 	if (progress && ok)
 
- 	    progress.value = progress.max;
 
- 	var result = chi(form,'output');
 
- 	if (result) {
 
- 	    var msg = xhr.responseText.trimEnd();
 
- 	    if (!msg)
 
- 		msg = xhr.status + ' ' + xhr.statusText;
 
- 	    result.textContent = msg;
 
- 	}
 
- 	classmod(form, [['ok',ok],['err',!ok],['running',0],['done',1]]);
 
- 	// Optionally redirect elsewhere after success
 
- 	const reft = parseFloat(form.dataset.ref) * 1000;
 
- 	if (ok && reft >= 0.0) {
 
- 	    const refh = form.dataset.refUrl || window.location.href;
 
- 	    setTimeout(() => { window.location.href = refh; }, reft);
 
- 	}
 
-     }, PassiveListener);
 
-     xhr.open(form.method, form.action);
 
-     xhr.responseType = 'text';
 
-     xhr.send(data);
 
-     return xhr;
 
- }
 
- // key=value formatting of form data; including inverted checkboxes
 
- function textformdata(form) {
 
-     var data = '';
 
-     for (var e of form.elements) {
 
- 	var val = e.value;
 
- 	if (val == undefined || !e.name || e instanceof HTMLButtonElement) {
 
- 	    continue;
 
- 	} else if (e instanceof HTMLInputElement) {
 
- 	    if (e.type == 'checkbox')
 
- 		val = e.checked == cfgbool(val) ? '1' : '0';
 
- 	    else if (e.type == 'radio' && !e.checked)
 
- 		continue;
 
- 	}
 
- 	data += e.name + '=' + val + "\r\n";
 
-     }
 
-     return data;
 
- }
 
- // POST form contents upload with response text
 
- function uploadform() {
 
-     event.preventDefault();
 
-     const form = event.target.form || event.target;
 
-     var files = form.elements['file'];
 
-     if (files == undefined)
 
- 	return upload(form,textformdata(form));
 
-     else if (files.files.length == 1)
 
- 	return upload(form,files.files[0]);
 
-     else
 
- 	return files.click();
 
- }
 
- // Flip the status of an INPUT element between text and password
 
- function showpwd(me = event.currentTarget) {
 
-     const now_visible = me.classList.toggle('hide');
 
-     const new_type = now_visible ? 'text' : 'password';
 
-     me.classList.toggle('show',!now_visible);
 
-     sib(me,'input').setAttribute('type', new_type);
 
- }
 
- // Insert translations as needed
 
- var translations = null;
 
- var delay_translate = false;
 
- function translate(top = document) {
 
-     delay_translate = (!translations || document.readyState != 'complete');
 
-     if (!delay_translate)
 
- 	fillin(translations, true, top);
 
- }
 
- document.addEventListener('load', (e) => translate(), PassiveListener);
 
- var lang_styleobj = document.createElement('style');
 
- document.head.append(lang_styleobj);
 
- function setlang(l = null) {
 
-     l = l || document.documentElement.lang;
 
-     if (!l.match(/^\w+(?:-\w+)*$/)) return; // Invalid language tag
 
-     var sty = lang_styleobj.sheet;
 
-     while (sty.rules.length) sty.deleteRule(0);
 
-     sty.insertRule('[lang]:not(:lang('+l+')):not(:last-child),[lang]:lang('+l+')~[lang] {display: none}', 0);
 
-     document.documentElement.lang = l;
 
- }
 
- setlang();
 
- fetchconfig('/sys/lang')
 
-     .then((map) => {
 
- 	setlang(map.get('LANG'));
 
- 	translations = map;
 
- 	if (delay_translate)
 
- 	    translate();
 
-     })
 
-     .catch(() => {});
 
- // HTML include hack
 
- class IncHTML extends HTMLElement {
 
-     constructor() { self = super(); }
 
-     connectedCallback() {
 
- 	fetch(self.getAttribute('src'), LocalGet)
 
- 	    .then ((r) => r.text())
 
- 	    .then ((text) => {
 
- 		const p = self.parentElement;
 
- 		self.outerHTML = text;
 
- 		translate(p);
 
- 	    });
 
-     }
 
- }
 
- customElements.define('x-inc', IncHTML);
 
 
  |