summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarin Ivanov <[email protected]>2024-08-21 00:04:40 +0300
committerMarin Ivanov <[email protected]>2024-08-21 00:04:40 +0300
commit57bebd16ff7e89bfe5e9089ac4c0a21733675f22 (patch)
tree4a3ebefba3e584d956f704f04d9d37e7f8fc87eb
parent869c09a0ed7e7c1fa6a4376cf9acf06e7fecc874 (diff)
config and setting fields
-rw-r--r--app.js160
-rw-r--r--cgi-bin/getconfig8
-rw-r--r--config.js44
-rw-r--r--index.html1
-rw-r--r--ka.js16
-rw-r--r--style.css26
6 files changed, 173 insertions, 82 deletions
diff --git a/app.js b/app.js
index ddbb66b..a74104c 100644
--- a/app.js
+++ b/app.js
@@ -1,5 +1,7 @@
(() => {
- let isLoading = 0;
+ state = {};
+ isLoading = 0;
+
const loading = (val) => {
isLoading = val;
loader.$cls(isLoading ? "" : "hidden");
@@ -7,44 +9,56 @@
loader.$click(() => loading(0));
let error = null;
- function errClose() {
- error = null;
- app.reload();
- }
- let data = {};
- function onerror(err) {
- error = err;
- console.error(err);
+ setError = (err) => {
+ error = String(err);
app.reload();
}
- function parsedata(r) {
- return Object.fromEntries(r.split("\n").map(x=>x.trim()).filter(x=>x).map(x=>x.split('=')));
+ function errClose() {
}
- function cmd(url, body) {
- return fetch(url, {method:body?'POST':'GET', body})
- .then(r => {
- if (!r.ok){
- throw new Error(r.statusText);
- }
- return r.text();
- })
- .then(r => parsedata(r));
- };
- function loadconfig() {
+ window.onerror = (message, source, lineNumber, colno, err) => {
+ setError(message);
+ };
+ window.onunhandledrejection = (event) => {
+ setError(event.reason);
+ };
+ let parseini = (data) => Object.fromEntries(data.split("\n").map(x=>x.trim()).filter(x=>x).map(x=>x.split("=")));
+ let buildini = (data, prefix="") => entries(data).map(([k,v])=>`${prefix}${k}=${v}\n`).join();
+ let resptext = r => {
+ if (!r.ok){
+ throw new Error(r.statusText);
+ }
+ return r.text();
+ };
+ getconfig = () => fetch("/cgi-bin/getconfig").then(resptext).then(x => parseini(x));
+ cmd = (body) => fetch("/cgi-bin/cmd",{method:"POST",body}).then(resptext);
+ setconfig = (data, prefix) => {
loading(1);
- cmd("/cgi-bin/getconfig")
+ cmd(buildini(data, prefix)+"save\n")
+ .finally(() => loading(0))
+ };
+ loadconfig = () => {
+ loading(1);
+ getconfig("/cgi-bin/getconfig")
.then(x => {
- data = x;
+ state = {...state, ...x};
app.reload();
})
- .catch(onerror)
- .then(() => loading(0))
- }
- function saveconfig(groups) {
- cmd("/cgi-bin/setconfig", data)
- .catch(onerror)
- .then(loadconfig)
+ .finally(() => loading(0));
}
+ reboot = (data, prefix) => {
+ var ok = 0;
+ loading(1);
+ cmd("reboot\n")
+ .then(() => {
+ ok = 1;
+ })
+ .finally(() => {
+ setTimeout(() => {
+ loading(0);
+ app.reload();
+ }, ok?10000:0);
+ })
+ };
function CErr(text) {
var el = div(
@@ -72,52 +86,64 @@
return routes[r]();
}
}
+
+ function CSettingInput(f) {
+ var inp = input("text")
+ .$attr("id","form__"+f.id)
+ .$attr("name",f.id)
+ .$attr("required",!!f.required)
+ .$value(state[f.id]??null)
+ .$change2state(state, f.id);
+ if (f.pattern) {
+ inp.$attr("pattern", f.pattern??null);
+ }
+ 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 form_ = form(
+ table(...s.fields.map(x => CSettingInput(x))).$cls("settings"),
+ input("submit").$value("Save")
+ );
+ form_.onsubmit = (e) => {e.preventDefault(); setconfig({});}
+ return div(
+ h1(s.title),
+ form_,
+ )
+ }
+
const navbar = CNav([
["#", "Home"],
- ["#/network", "Network"],
- ["#/config", "Configuration"],
- ["#/tools", "Tools"],
+ ...entries(config.sections).map(([id, x]) => [`#/${id}`, x.title]),
]);
const router = CRouter({
"/": () => div(
- h2("Lorem Ipsum"),
- p("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "),
- p("Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."),
- ),
- "/network": () => div(
- h1("Network settings"),
+ h1("Information"),
table(
- tr(
- td("IP"),
- td(input("text").$value(data["cfg.ip"])),
- ),
- tr(
- td("MAC"),
- td(
- input("text").$value(data["cfg.mac"])
- .$change2state(data, "cfg.mac")
- ),
- )
- ).$cls("network"),
- input("button").$value("Save").$click(saveconfig)
+ 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),
+ ).$cls("center"),
),
- "/config": () => {
- return div();
- },
- "/waiting": () => {
- loading(1);
- setTimeout(() => {
- loading(0);
- }, 3000);
- return div("");
- },
+ ...Object.fromEntries(entries(config.sections).map(([id, _]) => [`/${id}`, () => CSettings(id)])),
"": () => div(
h1("Not Found"),
p("The requested page is not available.")
),
});
-
- mount(app, (h) => {
+ CApp = (h) => {
loading(isLoading);
return [
div(error && CErr(String(error))),
@@ -126,6 +152,8 @@
section(router(h))
),
];
- });
+ };
+
+ mount(app, CApp);
loadconfig();
})()
diff --git a/cgi-bin/getconfig b/cgi-bin/getconfig
index 21128a9..51de5b1 100644
--- a/cgi-bin/getconfig
+++ b/cgi-bin/getconfig
@@ -1,6 +1,6 @@
-cfg.ip=127.0.0.1
-cfg.mac=11:22:33:44:55:66
-
-cfg.gateway=0.0.0.0
+macaddr=11:22:33:44:55:66
+ipaddr=127.0.0.1
+netmask=255.255.255.0
+gateway=0.0.0.0
diff --git a/config.js b/config.js
new file mode 100644
index 0000000..814371e
--- /dev/null
+++ b/config.js
@@ -0,0 +1,44 @@
+config = {
+ device: "Device Name",
+ swVersion: "1.0",
+ hwVersion: "1.0",
+ sections: {
+ network: {
+ title: "Network",
+ fields: [
+ {
+ id: "macaddr",
+ type: "mac",
+ title: "MAC address",
+ pattern: "[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}",
+ invalidmsg: "Enter MAC address. E.g. 00:11:22:33:44:55",
+ required: true,
+ },
+ {
+ id: "ipaddr",
+ type: "ip",
+ title: "IP address",
+ pattern: "\\d+.\\d+.\\d+.\\d+",
+ hint: "192.168.4.2",
+ invalidmsg: "Enter IP address. E.g. 192.168.4.2",
+ required: true,
+ },
+ {
+ id: "netmask",
+ type: "netmask",
+ title: "Netmask",
+ pattern: "\\d+.\\d+.\\d+.\\d+",
+ hint: "255.255.255.0",
+ invalidmsg: "Enter network mask. E.g. 255.255.255.0",
+ required: true,
+ },
+ {
+ id: "gateway",
+ type: "ip",
+ title: "Gateway",
+ pattern: "\\d+.\\d+.\\d+.\\d+",
+ },
+ ],
+ },
+ },
+}
diff --git a/index.html b/index.html
index 6f1cfb8..3cc012b 100644
--- a/index.html
+++ b/index.html
@@ -24,6 +24,7 @@
</div>
</div>
<script src="ka.js"></script>
+ <script src="config.js"></script>
<script src="app.js"></script>
</body>
</html>
diff --git a/ka.js b/ka.js
index ea75b2a..d2ae4bc 100644
--- a/ka.js
+++ b/ka.js
@@ -1,5 +1,6 @@
(()=>{
doc = document;
+ entries = Object.entries;
var ep = Element.prototype;
ep.attr = function(name){
@@ -10,7 +11,7 @@
return this;
};
ep.$attrs = function(attrs) {
- Object.entries(attrs).forEach(([attr, value]) => this.$attr(attr, value));
+ entries(attrs).forEach(([attr, value]) => this.$attr(attr, value));
return this;
}
ep.$cls = function(cls) {
@@ -26,14 +27,19 @@
}
const getvalue = x => x.value;
ep.$change2state = function(state, field, valgetter=getvalue) {
- this.onchange = () => {
- state[field] = valgetter(this);
+ this.oninput = () => {
+ this.setCustomValidity('');
+ if (this.validity.valid) {
+ state[field] = valgetter(this);
+ } else {
+ this.reportValidity();
+ }
};
return this;
}
tag = (name, attrs, ...children) => {
const el = doc.createElement(name);
- el.$attrs(attrs||{});
+ attrs && el.$attrs(attrs);
for (const child of children.filter(x=>x)) {
el.appendChild((typeof(child) === 'string') ? doc.createTextNode(child) : child);
}
@@ -43,7 +49,7 @@
labelfor = (for_, ...children) => tag("label", {"for":for_}, ...children);
img = (src) => tag("img", {src});
input = (type) => tag("input", {type});
- const TRIVIAL = "main,section,nav,h1,h2,h3,p,,div,span,select,table,tr,td";
+ const TRIVIAL = "main,section,nav,h1,h2,h3,p,b,div,span,form,select,button,table,tr,td";
for (let name of TRIVIAL.split(",")) {
window[name] = (...children) => tag(name, null, ...children);
}
diff --git a/style.css b/style.css
index 334a765..c17bb9e 100644
--- a/style.css
+++ b/style.css
@@ -4,7 +4,9 @@ body {
}
h1, h2, h3 {
- margin: 0;
+ margin: 0 0 0.5em 0;
+ padding: 0.5em 0 0.5em 0;
+ border-bottom: 1px solid #ccc;
}
.container {
@@ -19,7 +21,7 @@ header {
padding: 1em;
}
section {
- padding: 0.5em;
+ padding: 0 1em 0 1em;
}
main {
display: flex;
@@ -34,7 +36,7 @@ section > div {
nav {
display: inline-block;
- /*background-color: #f1f1f1;*/
+ background-color: #f1f1f1;
width: 250px;
}
@@ -73,19 +75,26 @@ nav a:hover:not(.active) {
color: white;
}
-input[type=button] {
+input[type=submit], button {
+ display: inline-block;
background-color: #07a2a0;
- width: 200px;
+ width: 175px;
height: 40px;
- display: block;
border:0;
color:#fff;
}
-input[type=button]:active {
+input[type=submit]:active, button:active {
background-color: #079290;
}
+.settings {
+ margin: 0 0 1em 0;
+}
+table td.title {
+ min-width: 150px;
+}
+
.err {
color: #fff;
background-color: #f00;
@@ -157,3 +166,6 @@ input[type=button]:active {
}
}
+.center {
+ text-align: center;
+}