(() => { let state = {}; let loading = 0; let error = null; let setLoading = (val) => { loading = val; loader.$cls(loading ? "" : "hide"); } loader.$click(() => setLoading(0)); let setError = (err) => { error = err&&String(err); app.reload(); } window.onerror = (message) => { setError(message); }; window.onunhandledrejection = (e) => { setError(e.reason); }; let parsetxt = (data) => fromEntries(data.split("\n").map(x=>x.trim()).filter(x=>x).map(x=>x.split("="))); let buildtxt = (data) => entries(data).map(([k,v])=>`${k}=${v}\n`).join(""); let resptext = r => { if (!r.ok){ throw new Error(r.statusText); } return r.text(); }; let getconfig = () => fetch("/cgi-bin/getconfig").then(resptext).then(x => parsetxt(x)); let cmd = (body) => fetch("/cgi-bin/cmd",{method:"POST",body}).then(resptext); let setconfig = (data) => { setLoading(1); cmd(buildtxt(data)+"save\n") .finally(() => setLoading(0)) }; let loadconfig = () => { setLoading(1); setError(); getconfig() .then(x => { state = {...state, ...x}; app.reload(); }) .finally(() => setLoading(0)); } let reboot = (data) => { let ok = 0; setLoading(1); cmd("reboot\n") .then(() => { ok = 1; }) .finally(() => { setTimeout(() => { setLoading(0); app.reload(); }, ok?10000:0); }) }; let setdefaults = (data) => { setLoading(1); cmd("defaults\nsave\n") .finally(() => {setLoading(0);}) }; function CErr(text) { let el = div( div("✖").$cls("close").$click(errClose), span("⚠ "), text, ).$cls("err"); function errClose() { error = null; el.remove(); }; return el; } function CNav(navs) { return (h) => nav( labelfor("showmenu", span("☰"), " Menu"), input("checkbox").$attr("id", "showmenu"), ...navs.map(([href,txt]) => ahref(href,txt).$cls(href.substring(1)===h?"active":"")) ); } function CRouter(routes) { return (h) => { let p = h.substr(1) || "/"; let r = p in routes ? p : ""; return routes[r](); } } function CSettingInput(f) { let inp = input("text") .$attr("id","form__"+f.id) .$attr("name",f.id) .$value(state[f.id]??null) .$change2state(state, f.id); if (f.required) { inp.$attr("required","required"); } if (f.pattern) { inp.$attr("pattern", f.pattern); } if (f.invalidmsg) { inp.oninvalid = () => inp.setCustomValidity(f.invalidmsg); } inp.$attr("placeholder", f.hint??""); return tr( td(labelfor(inp.id, f.title)).$cls("title"), td(inp), ); } function CSettings(section) { let s = config.sections[section]; let f = form( table(...s.fields.map(x => CSettingInput(x))), input("submit").$value("Save") ); f.onsubmit = (e) => { e.preventDefault(); setconfig(fromEntries(new FormData(f))); } return div( h1(s.title), f, ) } let navbar = CNav([ ["/#", "Home"], ...entries(config.sections).map(([id, x]) => [`/#/${id}`, x.title]), ]); let router = CRouter({ "/": () => div( h1("Information"), table( tr(td("Device:").$cls("title"), td(config.device)), tr(td("Software Version:").$cls("title"), td(config.swVersion)), tr(td("Hardware Version:").$cls("title"), td(config.hwVersion)), ), h1("Controls"), p( button("Reboot").$click(reboot), button("Reset Configuration").$click(setdefaults), ).$cls("center"), ), ...fromEntries(entries(config.sections).map(([id, _]) => [`/${id}`, () => CSettings(id)])), "": () => div( h1("Not Found"), p("The requested page is not available.") ), }); let CApp = (h) => { setLoading(loading); return [ div(error && CErr(String(error))), main( navbar(h), section(router(h)) ), ]; }; mount(app, CApp); loadconfig(); })();