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,35 +239,37 @@ 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>")}}
<div id="dhcp-reservations">
<div class="cols reservation-label">
<div style="white-space:nowrap">
<div>hostname</div>
<div>ip address</div>
<div>mac a&zwnj;ddress</div>
<div>do not propagate</div>
</div>
<div></div>
</div>
{% if (reservations > 0) {
for (let i = 0; i < length(options); i++) {
const o = options[i];
if (o.reserved) {
%}
<div class="cols reservation" data-ip="{{o.ip}}">
<div style="white-space:nowrap">
<input name="hostname" type="text" required placeholder="hostname" value="{{o.name}}">
<select class="dhcp-addresses">
</select>
<input name="mac" type="text" required placeholder="mac a&zwnj;ddress" pattern="([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F][0-9a-fA-F]" value="{{o.mac}}">
<label><input type="checkbox" {{o.noprop ? "checked" : ""}}></label>
</div>
<button>-</button>
<form>
<div id="dhcp-reservations">
<div class="cols reservation-label">
<div style="white-space:nowrap">
<div>hostname</div>
<div>ip address</div>
<div>mac a&zwnj;ddress</div>
<div>do not propagate</div>
</div>
{% }
}
} %}
</div>
<div></div>
</div>
{% if (reservations > 0) {
for (let i = 0; i < length(options); i++) {
const o = options[i];
if (o.reserved) {
%}
<div class="cols reservation" data-ip="{{o.ip}}">
<div style="white-space:nowrap">
<input name="hostname" type="text" required placeholder="hostname" value="{{o.name}}">
<select class="dhcp-addresses">
</select>
<input name="mac" type="text" required placeholder="mac a&zwnj;ddress" pattern="([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F][0-9a-fA-F]" value="{{o.mac}}">
<label><input type="checkbox" {{o.noprop ? "checked" : ""}}></label>
</div>
<button>-</button>
</div>
{% }
}
} %}
</div>
</form>
<hr>
<div class="o">Active Leases</div>
<div class="m">Addresses currently in use</div>

View File

@ -204,62 +204,64 @@ 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.")}}
<div id="local-services">{%
for (let i = 0; i < length(services); i++) {
const s = services[i];
%}
<div class="service">
<div class="cols">
<form>
<div id="local-services">{%
for (let i = 0; i < length(services); i++) {
const s = services[i];
%}
<div class="service">
<div class="cols">
<input name="name" type="text" placeholder="{{s.link === false ? 'service information' : 'service name'}}" required pattern='[^-:"|<>]+' value="{{s.name}}">
<div style="flex:0">
<select name="type">
<option value="">-</option>
{%
for (t in types) {
print(`<option value="${t}" ${t === s.type ? "selected" : ""}>${t}</option>`);
}
%}
<div class="cols">
<input name="name" type="text" placeholder="{{s.link === false ? 'service information' : 'service name'}}" required pattern='[^-:"|<>]+' value="{{s.name}}">
<div style="flex:0">
<select name="type">
<option value="">-</option>
{%
for (t in types) {
print(`<option value="${t}" ${t === s.type ? "selected" : ""}>${t}</option>`);
}
%}
</select>
</div>
</div>
<button>-</button>
</div>
<div class="cols">
<div></div>
{% if (s.link === false) { %}
<div class="link">
<select name="hostname">
{%
for (j = 0; j < length(hosts); j++) {
print(`<option value="${hosts[j]}" ${hosts[j] === s.hostname ? "selected" : ""}>${hosts[j]}</option>`);
}
for (let j = 0; j < length(aliases.map); j++) {
print(`<option value="${aliases.map[j].hostname}" ${aliases.map[j].hostname === s.hostname ? "selected" : ""}>${aliases.map[j].hostname}</option>`);
}
%}
</select>
</div>
{% } else { %}
<div>
<input name="protocol" type="text" placeholder="proto" required pattern="[a-z]+" value="{{s.protocol}}">
:// <select name="hostname">
{%
for (j = 0; j < length(hosts); j++) {
print(`<option value="${hosts[j]}" ${hosts[j] === s.hostname ? "selected" : ""}>${hosts[j]}</option>`);
}
for (let j = 0; j < length(aliases.map); j++) {
print(`<option value="${aliases.map[j].hostname}" ${aliases.map[j].hostname === s.hostname ? "selected" : ""}>${aliases.map[j].hostname}</option>`);
}
%}
</select>
: <input name="port" type="text" placeholder="port" required pattern="([1-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])" value="{{s.port}}">
/ <input name="path" type="text" placeholder="path" pattern="[\-\/\?\&\._=#a-zA-Z0-9]*" value="{{s.path}}">
</div>
{% } %}
</div>
<button>-</button>
</div>
<div class="cols">
<div></div>
{% if (s.link === false) { %}
<div class="link">
<select name="hostname">
{%
for (j = 0; j < length(hosts); j++) {
print(`<option value="${hosts[j]}" ${hosts[j] === s.hostname ? "selected" : ""}>${hosts[j]}</option>`);
}
for (let j = 0; j < length(aliases.map); j++) {
print(`<option value="${aliases.map[j].hostname}" ${aliases.map[j].hostname === s.hostname ? "selected" : ""}>${aliases.map[j].hostname}</option>`);
}
%}
</select>
</div>
{% } else { %}
<div>
<input name="protocol" type="text" placeholder="proto" required pattern="[a-z]+" value="{{s.protocol}}">
:// <select name="hostname">
{%
for (j = 0; j < length(hosts); j++) {
print(`<option value="${hosts[j]}" ${hosts[j] === s.hostname ? "selected" : ""}>${hosts[j]}</option>`);
}
for (let j = 0; j < length(aliases.map); j++) {
print(`<option value="${aliases.map[j].hostname}" ${aliases.map[j].hostname === s.hostname ? "selected" : ""}>${aliases.map[j].hostname}</option>`);
}
%}
</select>
: <input name="port" type="text" placeholder="port" required pattern="([1-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])" value="{{s.port}}">
/ <input name="path" type="text" placeholder="path" pattern="[\-\/\?\&\._=#a-zA-Z0-9]*" value="{{s.path}}">
</div>
{% } %}
</div>
</div>
{% } %}</div>
{% } %}</div>
</form>
<hr>
<div id="host-aliases-add" class="cols">
<div>
@ -270,27 +272,29 @@ 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.")}}
<div id="host-aliases">{%
for (let i = 0; i < length(aliases.map); i++) {
%}
<div class="cols alias">
<input type="text" name="hostname" value="{{aliases.map[i].hostname}}" required pattern="(\*\.)?[a-zA-Z][a-zA-Z0-9_\-]*">
<div>
<select name="address">
{%
for (let j = as[3]; j <= ae[3]; j++) {
const a = `${as[0]}.${as[1]}.${as[2]}.${j}`;
const n = aliases.leases[a];
print(`<option value="${a}" ${a === aliases.map[i].address ? "selected" : ""}>${n ? n : a}</option>`);
}
%}
</select>
<form>
<div id="host-aliases">{%
for (let i = 0; i < length(aliases.map); i++) {
%}
<div class="cols alias">
<input type="text" name="hostname" value="{{aliases.map[i].hostname}}" required pattern="(\*\.)?[a-zA-Z][a-zA-Z0-9_\-]*">
<div>
<select name="address">
{%
for (let j = as[3]; j <= ae[3]; j++) {
const a = `${as[0]}.${as[1]}.${as[2]}.${j}`;
const n = aliases.leases[a];
print(`<option value="${a}" ${a === aliases.map[i].address ? "selected" : ""}>${n ? n : a}</option>`);
}
%}
</select>
</div>
<button>-</button>
</div>
<button>-</button>
</div>
{%
}
%}</div>
{%
}
%}</div>
</form>
<hr>
<div id="port-forward-add" class="cols">
<div>
@ -320,48 +324,50 @@ if (f) {
<div>enabled</div>
</div>
</div>
<div id="port-forwards">{%
for (let i = 0; i < length(ports); i++) {
const p = ports[i];
%}<div class="cols noborder forward">
<div class="row">
<div>
{% if (dhcp.mode === 0) { %}
<select name="port_src">
<option value="wifi" {{p.src == "wifi" ? "selected" : ""}}>Mesh</option>
<option value="wan" {{p.src == "wan" ? "selected" : ""}}>WAN</option>
<option value="both" {{p.src == "both" ? "selected" : ""}}>Mesh &amp; WAN</option>
</select>
{% } else { %}
<select name="port_src" disabled>
<option value="wan">WAN</option>
</select>
{% } %}
<input name="port_sports" type="text" required placeholder="Port or range" pattern="([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?" value="{{p.sports}}">
<select name="port_type">
<option value="tcp" {{p.type == "tcp" ? "selected" : ""}}>TCP</option>
<option value="udp" {{p.type == "udp" ? "selected" : ""}}>UDP</option>
<option value="both" {{p.type == "both" ? "selected" : ""}}>TCP &amp; UDP</option>
</select>
<label class="switch"><input type="checkbox" name="port_enable" {{p.enabled ? "checked" : ""}}></label>
<form>
<div id="port-forwards">{%
for (let i = 0; i < length(ports); i++) {
const p = ports[i];
%}<div class="cols noborder forward">
<div class="row">
<div>
{% if (dhcp.mode === 0) { %}
<select name="port_src">
<option value="wifi" {{p.src == "wifi" ? "selected" : ""}}>Mesh</option>
<option value="wan" {{p.src == "wan" ? "selected" : ""}}>WAN</option>
<option value="both" {{p.src == "both" ? "selected" : ""}}>Mesh &amp; WAN</option>
</select>
{% } else { %}
<select name="port_src" disabled>
<option value="wan">WAN</option>
</select>
{% } %}
<input name="port_sports" type="text" required placeholder="Port or range" pattern="([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?" value="{{p.sports}}">
<select name="port_type">
<option value="tcp" {{p.type == "tcp" ? "selected" : ""}}>TCP</option>
<option value="udp" {{p.type == "udp" ? "selected" : ""}}>UDP</option>
<option value="both" {{p.type == "both" ? "selected" : ""}}>TCP &amp; UDP</option>
</select>
<label class="switch"><input type="checkbox" name="port_enable" {{p.enabled ? "checked" : ""}}></label>
</div>
<div>
<select name="port_dst">
{%
for (let j = as[3]; j <= ae[3]; j++) {
const a = `${as[0]}.${as[1]}.${as[2]}.${j}`;
const n = aliases.leases[a];
print(`<option value="${a}" ${a === p.dst ? "selected" : ""}>${n ? n : a}</option>`);
}
%}
</select>
<input name="port_dport" type="text" required placeholder="LAN Port" pattern="{{constants.patPort}}" value="{{p.dport}}">
</div>
</div>
<div>
<select name="port_dst">
{%
for (let j = as[3]; j <= ae[3]; j++) {
const a = `${as[0]}.${as[1]}.${as[2]}.${j}`;
const n = aliases.leases[a];
print(`<option value="${a}" ${a === p.dst ? "selected" : ""}>${n ? n : a}</option>`);
}
%}
</select>
<input name="port_dport" type="text" required placeholder="LAN Port" pattern="{{constants.patPort}}" value="{{p.dport}}">
</div>
</div>
<button>-</button>
</div>{%
}
%}</div>
<button>-</button>
</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++;
}
}
}
htmx.ajax("PUT", "{{request.env.REQUEST_URI}}", {
swap: "none",
values: { services: JSON.stringify(services) }
});
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) }
});
}
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,11 +452,16 @@ 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) }
});
}
htmx.ajax("PUT", "{{request.env.REQUEST_URI}}", {
swap: "none",
values: { ports: JSON.stringify(ports) }
});
}
function refreshHostSelectors()
{
@ -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,63 +311,65 @@ uciMesh.foreach("xlink", "interface", x => {
<button class="add">+</button>
</div>
</div>
<div id="xlink-list">
<div class="cols xlink-label">
<div style="white-space:nowrap">
<div>vlan</div>
<div>ip a&zwnj;ddress</div>
<div>peer a&zwnj;ddress</div>
<div>cidr</div>
<div>weight</div>
{% if (haveports) { %}
<div>port</div>
{% } %}
</div>
<div></div>
</div>
{% for (let i = 0; i < length(xlinks); i++) {
const x = xlinks[i];
%}
<div class="cols xlink" data-name="{{x.name}}">
<div>
<input name="vlan" type="text" size="5" required pattern="([4-9]|[1-9][0-9]{1,2}|[1-3][0-9]{3}|40[0-8][0-9]|409[0-5])" placeholder="VLan" value="{{x.vlan}}">
<input name="ipaddr" type="text" size="25" required pattern="{{constants.patIP}}" placeholder="IP A&zwnj;ddress" value="{{x.ipaddr}}">
<input name="peer" type="text" size="25" pattern="{{constants.patIP}}" placeholder="IP A&zwnj;ddress" value="{{x.peer}}">
<select name="cidr">
<option value="32" {{x.cidr === 32 ? "selected" : ""}}>PtP</option>
<option value="31" {{x.cidr === 31 ? "selected" : ""}}>/ 31</option>
<option value="30" {{x.cidr === 30 ? "selected" : ""}}>/ 30</option>
<option value="29" {{x.cidr === 29 ? "selected" : ""}}>/ 29</option>
<option value="28" {{x.cidr === 28 ? "selected" : ""}}>/ 28</option>
<option value="27" {{x.cidr === 27 ? "selected" : ""}}>/ 27</option>
<option value="26" {{x.cidr === 26 ? "selected" : ""}}>/ 26</option>
<option value="25" {{x.cidr === 25 ? "selected" : ""}}>/ 25</option>
<option value="24" {{x.cidr === 24 ? "selected" : ""}}>/ 24</option>
<option value="23" {{x.cidr === 23 ? "selected" : ""}}>/ 23</option>
<option value="22" {{x.cidr === 22 ? "selected" : ""}}>/ 22</option>
<option value="21" {{x.cidr === 21 ? "selected" : ""}}>/ 21</option>
<option value="20" {{x.cidr === 20 ? "selected" : ""}}>/ 20</option>
<option value="19" {{x.cidr === 19 ? "selected" : ""}}>/ 19</option>
<option value="18" {{x.cidr === 18 ? "selected" : ""}}>/ 18</option>
<option value="17" {{x.cidr === 17 ? "selected" : ""}}>/ 17</option>
<option value="16" {{x.cidr === 16 ? "selected" : ""}}>/ 16</option>
</select>
<input name="weight" type="text" size="2" required pattern="([0-9]|[1-9][0-9]|100)" placeholder="Wt" value="{{x.weight}}">
<form>
<div id="xlink-list">
<div class="cols xlink-label">
<div style="white-space:nowrap">
<div>vlan</div>
<div>ip a&zwnj;ddress</div>
<div>peer a&zwnj;ddress</div>
<div>cidr</div>
<div>weight</div>
{% if (haveports) { %}
<select name="port">
{%
for (let i = 0; i < length(ports); i++) {
const p = ports[i];
print(`<option value="${p.name}" ${x.port === p.name ? "selected" : ""}>${p.display}</option>`);
}
%}
</select>
<div>port</div>
{% } %}
</div>
<button>-</button>
<div></div>
</div>
{% } %}
</div>
{% for (let i = 0; i < length(xlinks); i++) {
const x = xlinks[i];
%}
<div class="cols xlink" data-name="{{x.name}}">
<div>
<input name="vlan" type="text" size="5" required pattern="([4-9]|[1-9][0-9]{1,2}|[1-3][0-9]{3}|40[0-8][0-9]|409[0-5])" placeholder="VLan" value="{{x.vlan}}">
<input name="ipaddr" type="text" size="25" required pattern="{{constants.patIP}}" placeholder="IP A&zwnj;ddress" value="{{x.ipaddr}}">
<input name="peer" type="text" size="25" pattern="{{constants.patIP}}" placeholder="IP A&zwnj;ddress" value="{{x.peer}}">
<select name="cidr">
<option value="32" {{x.cidr === 32 ? "selected" : ""}}>PtP</option>
<option value="31" {{x.cidr === 31 ? "selected" : ""}}>/ 31</option>
<option value="30" {{x.cidr === 30 ? "selected" : ""}}>/ 30</option>
<option value="29" {{x.cidr === 29 ? "selected" : ""}}>/ 29</option>
<option value="28" {{x.cidr === 28 ? "selected" : ""}}>/ 28</option>
<option value="27" {{x.cidr === 27 ? "selected" : ""}}>/ 27</option>
<option value="26" {{x.cidr === 26 ? "selected" : ""}}>/ 26</option>
<option value="25" {{x.cidr === 25 ? "selected" : ""}}>/ 25</option>
<option value="24" {{x.cidr === 24 ? "selected" : ""}}>/ 24</option>
<option value="23" {{x.cidr === 23 ? "selected" : ""}}>/ 23</option>
<option value="22" {{x.cidr === 22 ? "selected" : ""}}>/ 22</option>
<option value="21" {{x.cidr === 21 ? "selected" : ""}}>/ 21</option>
<option value="20" {{x.cidr === 20 ? "selected" : ""}}>/ 20</option>
<option value="19" {{x.cidr === 19 ? "selected" : ""}}>/ 19</option>
<option value="18" {{x.cidr === 18 ? "selected" : ""}}>/ 18</option>
<option value="17" {{x.cidr === 17 ? "selected" : ""}}>/ 17</option>
<option value="16" {{x.cidr === 16 ? "selected" : ""}}>/ 16</option>
</select>
<input name="weight" type="text" size="2" required pattern="([0-9]|[1-9][0-9]|100)" placeholder="Wt" value="{{x.weight}}">
{% if (haveports) { %}
<select name="port">
{%
for (let i = 0; i < length(ports); i++) {
const p = ports[i];
print(`<option value="${p.name}" ${x.port === p.name ? "selected" : ""}>${p.display}</option>`);
}
%}
</select>
{% } %}
</div>
<button>-</button>
</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,37 +299,39 @@ 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>
<div id="tunnels">{%
for (let i = 0; i < length(tunnels); i++) {
const t = tunnels[i];
const client = t.type === "lc" || t.type === "wc" ? true : false;
const wireguard = t.type === "ws" || t.type === "wc" ? true : false;
%}<div class="tunnel cols" data-index="{{t.index}}">
<div class="cols">
<div data-type="{{t.type}}">{{t2t(t.type)}}</div>
<div>
<div class="cols">
<input type="text" name="name" required placeholder="{{client ? 'Server name' : 'Node name'}}" value="{{t.name}}">
<label class="switch {{t.up ? 'up' : ''}}"><input type="checkbox" name="enable" {{t.enabled ? "checked" : ""}}></label>
</div>
<div class="cols pwnw">
{% if (t.type === "ws") { %}
<input type="hidden" name="key" value="{{t.key}}">
{% } %}
<input type="text" name="password" required pattern="{{wireguard ? '[A-Za-z0-9+\/=]+' : '[\\w@\-]+'}}" placeholder="{{wireguard ? 'Wireguard key' : 'Password'}}" value="{{t.passwd}}" {{t.type === "ws" ? "readonly" : ""}}>
<input type="text" name="network" required pattern="{{constants.patIP}}{{wireguard ? ':' + constants.patPort : ''}}" placeholder="{{wireguard ? 'Network:Port' : 'Network'}}" value="{{t.network}}" {{client ? "" : "readonly"}}>
<input type="text" name="weight" pattern="([0-9]|[1-9][0-9])" placeholder="Wgt" value="{{t.weight}}">
</div>
<div class="cols">
<input type="text" name="notes" placeholder="Notes..." value="{{t.notes}}">
{{client ? '' : '<button class="clipboard"><div class="icon clipboard"></div></button>'}}
<form>
<div id="tunnels">{%
for (let i = 0; i < length(tunnels); i++) {
const t = tunnels[i];
const client = t.type === "lc" || t.type === "wc" ? true : false;
const wireguard = t.type === "ws" || t.type === "wc" ? true : false;
%}<div class="tunnel cols" data-index="{{t.index}}">
<div class="cols">
<div data-type="{{t.type}}">{{t2t(t.type)}}</div>
<div>
<div class="cols">
<input type="text" name="name" required placeholder="{{client ? 'Server name' : 'Node name'}}" value="{{t.name}}">
<label class="switch {{t.up ? 'up' : ''}}"><input type="checkbox" name="enable" {{t.enabled ? "checked" : ""}}></label>
</div>
<div class="cols pwnw">
{% if (t.type === "ws") { %}
<input type="hidden" name="key" value="{{t.key}}">
{% } %}
<input type="text" name="password" required pattern="{{wireguard ? '[A-Za-z0-9+\/=]+' : '[\\w@\-]+'}}" placeholder="{{wireguard ? 'Wireguard key' : 'Password'}}" value="{{t.passwd}}" {{t.type === "ws" ? "readonly" : ""}}>
<input type="text" name="network" required pattern="{{constants.patIP}}{{wireguard ? ':' + constants.patPort : ''}}" placeholder="{{wireguard ? 'Network:Port' : 'Network'}}" value="{{t.network}}" {{client ? "" : "readonly"}}>
<input type="text" name="weight" pattern="([0-9]|[1-9][0-9])" placeholder="Wgt" value="{{t.weight}}">
</div>
<div class="cols">
<input type="text" name="notes" placeholder="Notes..." value="{{t.notes}}">
{{client ? '' : '<button class="clipboard"><div class="icon clipboard"></div></button>'}}
</div>
</div>
</div>
</div>
<button class="remove">-</button>
</div>{%
}
%}</div>
<button class="remove">-</button>
</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++;
}
}
htmx.ajax("PUT", "{{request.env.REQUEST_URI}}", {
swap: "none",
values: { tunnels: JSON.stringify(tunnels) }
});
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);