Disable Done button when invalid dialog content (#1562)

* Disable Done button if entries are invalid.

* Generalize Done validation
This commit is contained in:
Tim Wilkinson 2024-09-22 16:58:30 -07:00 committed by GitHub
parent 5b80ae8adc
commit 360b558b94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 303 additions and 241 deletions

View File

@ -92,6 +92,25 @@
e.stopPropagation();
}
}, true);
function dialogDone()
{
const d = htmx.find("#dialog-done");
if (d) {
setTimeout(function() {
let invalid = false;
const f = htmx.findAll(m, "form");
for (let i = 0; i < f.length; i++) {
if (!f[i].checkValidity()) {
invalid = true;
break;
}
}
d.disabled = invalid;
}, 10);
}
}
m.addEventListener("input", dialogDone);
m.addEventListener("click", dialogDone);
})();
</script>
{% } %}

View File

@ -239,6 +239,7 @@ const dhcpOptionTypes = {
</div>
{{_H("Creates a permenant mapping between a device MAC address and an IP address on the LAN network.
The given hostname is available to everyone on the mesh unless the entry is marked as <b>do not propagate</b>")}}
<form>
<div id="dhcp-reservations">
<div class="cols reservation-label">
<div style="white-space:nowrap">
@ -268,6 +269,7 @@ const dhcpOptionTypes = {
}
} %}
</div>
</form>
<hr>
<div class="o">Active Leases</div>
<div class="m">Addresses currently in use</div>

View File

@ -204,6 +204,7 @@ if (f) {
{{_H("Create a service by selecting from the templates above and hitting +. The two most generic templates are
<b>Generic URL</b> and <b>Simple text</b> and can be used for any services. You can also use the other templates to
help create services for specific cameras, mail servers, phones, etc. and these will pre-populate various service fields.")}}
<form>
<div id="local-services">{%
for (let i = 0; i < length(services); i++) {
const s = services[i];
@ -260,6 +261,7 @@ if (f) {
</div>
</div>
{% } %}</div>
</form>
<hr>
<div id="host-aliases-add" class="cols">
<div>
@ -270,6 +272,7 @@ if (f) {
</div>
{{_H("Assign a hostname to an LAN IP address on this node. Multiple hostnames can be assigned to the same IP address. You are
encouraged to prefix your hostnames with your callsign so it will be unique on the network.")}}
<form>
<div id="host-aliases">{%
for (let i = 0; i < length(aliases.map); i++) {
%}
@ -291,6 +294,7 @@ if (f) {
{%
}
%}</div>
</form>
<hr>
<div id="port-forward-add" class="cols">
<div>
@ -320,6 +324,7 @@ if (f) {
<div>enabled</div>
</div>
</div>
<form>
<div id="port-forwards">{%
for (let i = 0; i < length(ports); i++) {
const p = ports[i];
@ -362,6 +367,7 @@ if (f) {
</div>{%
}
%}</div>
</form>
</div>
{{_R("dialog-footer")}}
<script>
@ -369,8 +375,10 @@ if (f) {
{{_R("open")}}
const templates = {{templates}};
const hosts = {{hosts}};
function updateServices() {
function updateServices()
{
const services = [];
let invalid = 0;
const svc = htmx.findAll("#local-services .service");
for (let i = 0; i < svc.length; i++) {
const s = svc[i];
@ -384,21 +392,31 @@ if (f) {
if (name.validity.valid && protocol.validity.valid && port.validity.valid && path.validity.valid) {
services.push(`${name.value}${type.value ? " [" + type.value + "]" : ""}|1|${protocol.value}|${host.value}|${port.value}|${path.value}`);
}
else {
invalid++;
}
}
else {
if (name.validity.valid) {
services.push(`${name.value}${type.value ? " [" + type.value + "]" : ""}|0||${host.value}||`);
}
else {
invalid++;
}
}
}
if (invalid === 0) {
htmx.ajax("PUT", "{{request.env.REQUEST_URI}}", {
swap: "none",
values: { services: JSON.stringify(services) }
});
htmx.find("#dialog-done").disabled = false;
}
}
function updateAliases()
{
const aliases = [];
let invalid = 0;
const al = htmx.findAll("#host-aliases .alias");
for (let i = 0; i < al.length; i++) {
const s = al[i];
@ -407,15 +425,21 @@ if (f) {
if (hostname.validity.valid) {
aliases.push(`${address.value} ${hostname.value}`);
}
else {
invalid++;
}
}
if (invalid === 0) {
htmx.ajax("PUT", "{{request.env.REQUEST_URI}}", {
swap: "none",
values: { aliases: JSON.stringify(aliases) }
});
}
}
function updatePortForwards()
{
const ports = [];
let invalid = 0;
const rows = htmx.findAll("#port-forwards .cols .row");
for (let i = 0; i < rows.length; i++) {
const r = rows[i];
@ -428,12 +452,17 @@ if (f) {
if (sports.validity.valid && dport.validity.valid) {
ports.push(`${src.value}:${type.value}:${sports.value}:${dst.value}:${dport.value}:${enabled.checked ? "1" : "0"}`);
}
else {
invalid++;
}
}
if (invalid === 0) {
htmx.ajax("PUT", "{{request.env.REQUEST_URI}}", {
swap: "none",
values: { ports: JSON.stringify(ports) }
});
}
}
function refreshHostSelectors()
{
const selectors = htmx.findAll("#local-services .service .cols:last-child select");
@ -644,10 +673,11 @@ if (f) {
newForwardEntry();
}
});
htmx.find("#dialog-done").disabled = true;
}
htmx.on("#port-forward-add button", "click", newForwardEntry);
htmx.on("#port-forwards", "change", _ => updatePortForwards());
htmx.on("#port-forwards", "select", _ => updatePortForwards());
htmx.on("#port-forwards", "change", updatePortForwards);
htmx.on("#port-forwards", "select", updatePortForwards);
})();
</script>
</div>

View File

@ -311,6 +311,7 @@ uciMesh.foreach("xlink", "interface", x => {
<button class="add">+</button>
</div>
</div>
<form>
<div id="xlink-list">
<div class="cols xlink-label">
<div style="white-space:nowrap">
@ -368,6 +369,7 @@ uciMesh.foreach("xlink", "interface", x => {
</div>
{% } %}
</div>
</form>
</div>
{{_H("<p>XLinks provide a way of routing AREDN traffic across external non-AREDN networks. Each is created with a specific VLAN,
IP address for both ends, a weight of how likely to link is to be used, and an optional network size and port (on multi-port

View File

@ -299,6 +299,7 @@ function t2t(type)
information as possible. For tunnel clients all the fields can be easily populated by copy-n-pasting the entire server configuration
from another node into any field.")}}
<br>
<form>
<div id="tunnels">{%
for (let i = 0; i < length(tunnels); i++) {
const t = tunnels[i];
@ -330,6 +331,7 @@ function t2t(type)
</div>{%
}
%}</div>
</form>
{{_R("dialog-advanced")}}
<div>
{% if (includeAdvanced) { %}
@ -379,6 +381,7 @@ This value is used by default, but each tunnel may overide it.
{
const tunnels = [];
let servercount = 0;
let invalid = 0;
const tuns = htmx.findAll("#tunnels .tunnel");
for (let i = 0; i < tuns.length; i++) {
const t = tuns[i];
@ -404,14 +407,19 @@ This value is used by default, but each tunnel may overide it.
weight: weight.value
});
}
else {
invalid++;
}
if (type === "ls" || type === "ws") {
servercount++;
}
}
if (invalid === 0) {
htmx.ajax("PUT", "{{request.env.REQUEST_URI}}", {
swap: "none",
values: { tunnels: JSON.stringify(tunnels) }
});
}
const start = htmx.find("input[name=tunnel_range_start]");
if (start) {
start.disabled = servercount > 0 ? true : false;
@ -547,6 +555,7 @@ This value is used by default, but each tunnel may overide it.
if (start && !client) {
start.disabled = true;
}
htmx.find("#dialog-done").disabled = true;
}
htmx.on("#tunnel-templates button", "click", newTunnelEntry);
htmx.on("#tunnels", "change", updateTunnels);