Improve admin and mesh page refresh (#1509)

* Improve page refresh when browser tab is redisplayed

* Improve mesh search query persitance

* Improve portable theme messaging
This commit is contained in:
Tim Wilkinson 2024-09-14 20:08:54 -07:00 committed by GitHub
parent 60be6b812a
commit 84824e81c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 129 additions and 37 deletions

View File

@ -69,7 +69,7 @@
{{_R("selection")}}
</div>
<div id="main">
<div id="main-container">
<div id="main-container" hx-get="u-{{request.page}}" hx-trigger="visibilitychange[document.visibilityState === 'visible'] from:document">
{{_R(request.page)}}
</div>
</div>
@ -105,7 +105,7 @@
{{_R("selection")}}
</div>
<div id="main">
<div id="main-container">
<div id="main-container" hx-get="u-{{request.page}}" hx-trigger="visibilitychange[document.visibilityState === 'visible'] from:document">
{{_R(request.page)}}
</div>
</div>

View File

@ -178,7 +178,7 @@ if (request.env.REQUEST_METHOD === "DELETE") {
<div id="select-theme" class="cols">
<div>
<div class="o">Theme</div>
<div class="m">Display theme and colors</div>
<div class="m">Display theme and colors<p></div>
</div>
<div style="flex:0">
{%
@ -355,7 +355,7 @@ if (request.env.REQUEST_METHOD === "DELETE") {
htmx.find("#portable-theme").style.display = "";
}
else if (j.portableTheme === true) {
htmx.find("#select-theme .m").innerHTML = "Display theme and colors seen by others. You see your portable theme.";
htmx.find("#select-theme .m").innerHTML = "Display theme and colors seen by others. You see your portable theme when possible.";
}
});
}

View File

@ -426,7 +426,7 @@ const po = getPackageOptions();
});
});
htmx.on("#dialog-done", "click", _ => {
htmx.ajax("GET", "/a/packages", {
htmx.ajax("GET", "/a/u-packages", {
swap: "none"
});
});

35
files/app/main/u-mesh.ut Executable file
View File

@ -0,0 +1,35 @@
{%
/*
* Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
* Copyright (C) 2024 Tim Wilkinson
* See Contributors file for additional contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms:
*
* Additional use restrictions exist on the AREDN® trademark and logo.
* See AREDNLicense.txt for more info.
*
* Attributions to the AREDN® Project must be retained in the source code.
* If importing this code into a new or existing project attribution
* to the AREDN® project must be added to the source code.
*
* You must not misrepresent the origin of the material contained within.
*
* Modified versions must be modified to attribute to the original source
* and be marked in reasonable ways as differentiate it from the original
* version
*/
%}
{{_R("mesh")}}

View File

@ -32,6 +32,6 @@
* version
*/
%}
div id="packages" hx-swap-oob="innerHTML">
<div id="packages" hx-swap-oob="innerHTML">
{{_R("packages", "oob")}}
</div>

35
files/app/main/u-status.ut Executable file
View File

@ -0,0 +1,35 @@
{%
/*
* Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
* Copyright (C) 2024 Tim Wilkinson
* See Contributors file for additional contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms:
*
* Additional use restrictions exist on the AREDN® trademark and logo.
* See AREDNLicense.txt for more info.
*
* Attributions to the AREDN® Project must be retained in the source code.
* If importing this code into a new or existing project attribution
* to the AREDN® project must be added to the source code.
*
* You must not misrepresent the origin of the material contained within.
*
* Modified versions must be modified to attribute to the original source
* and be marked in reasonable ways as differentiate it from the original
* version
*/
%}
{{_R("status")}}

View File

@ -41,7 +41,7 @@
else if (request.args.commit) {
pwchanged = configuration.isPasswordChanged();
configuration.commitChanges();
print(`<div id="main-container" hx-swap-oob="true">${_R(request.page)}</div>`);
print(`<div id="main-container" hx-get="u-${request.page}" hx-trigger="visibilitychange[document.visibilityState === 'visible'] from:document" hx-swap-oob="true">${_R(request.page)}</div>`);
}
}
%}

View File

@ -47,7 +47,7 @@
</div>
{% } %}
</div>
<div id="health" hx-get="health" hx-trigger="every 30s [document.visibilityState === 'visible'], visibilitychange[document.visibilityState === 'visible'] from:document">
<div id="health" hx-get="u-health" hx-trigger="every 30s [document.visibilityState === 'visible']">
{{_R("health")}}
</div>
{{_R("firmware")}}

View File

@ -49,8 +49,15 @@
For example, typing "cam" in the box will filter out everything except names containsing "cam" ... which are probably cameras.
</p>
</div>
<div id="meshpage"></div>
</div>
<script>
if (location.search) {
const q = location.search.match(/^\?q=(.+)/);
if (q) {
document.querySelector("input[type=search]").value = q[1];
}
}
</script>
{% if (!config.resourcehash) { %}
<script src="/a/js/meshpage.js"></script>
{% } else { %}

View File

@ -43,7 +43,7 @@
</div>
</div>
<div id="c2">
<div hx-get="messages" hx-trigger="every 300s [document.visibilityState === 'visible'], visibilitychange[document.visibilityState === 'visible'] from:document">
<div hx-get="u-messages" hx-trigger="every 300s [document.visibilityState === 'visible']">
{% if (messages.haveMessages() || (auth.isAdmin && messages.haveToDos())) { %}
<div id="messages">
{{_R("messages")}}
@ -58,7 +58,7 @@
</div>
<div id="local-and-neighbor-devices">
<hr>
<div hx-get="local-and-neighbor-devices" hx-trigger="every 60s [document.visibilityState === 'visible'], visibilitychange[document.visibilityState === 'visible'] from:document">
<div hx-get="u-local-and-neighbor-devices" hx-trigger="every 60s [document.visibilityState === 'visible']">
{{_R("local-and-neighbor-devices")}}
</div>
</div>
@ -69,13 +69,13 @@
</div>
<div id="mesh-summary">
<hr>
<div hx-get="mesh-summary" hx-trigger="every 120s [document.visibilityState === 'visible'], visibilitychange[document.visibilityState === 'visible'] from:document">
<div hx-get="u-mesh-summary" hx-trigger="every 120s [document.visibilityState === 'visible']">
{{_R("mesh-summary")}}
</div>
</div>
<div id="dhcp">
<hr>
<div hx-get="dhcp" hx-trigger="every 120s [document.visibilityState === 'visible'], visibilitychange[document.visibilityState === 'visible'] from:document">
<div hx-get="u-dhcp" hx-trigger="every 120s [document.visibilityState === 'visible']">
{{_R("dhcp")}}
</div>
</div>
@ -88,7 +88,7 @@
{% if (fs.access("/usr/bin/wg") || fs.access("/usr/sbin/vtund")) { %}
<div id="tunnels">
<hr>
<div hx-get="tunnels" hx-trigger="every 120s [document.visibilityState === 'visible'], visibilitychange[document.visibilityState === 'visible'] from:document">
<div hx-get="u-tunnels" hx-trigger="every 120s [document.visibilityState === 'visible']">
{{_R("tunnels")}}
</div>
</div>

View File

@ -2,41 +2,55 @@ function render()
{
const search = document.querySelector("#meshfilter input");
const page = document.getElementById("meshpage");
const help = document.getElementById("meshpage-help");
const etx = mesh.etx;
const hosts = mesh.hosts;
const services = mesh.services;
let page = document.getElementById("meshpage");
if (!page) {
page = document.createElement("div");
page.id = "meshpage";
document.getElementById("main").appendChild(page);
}
let filtering;
let cfilter;
function filter()
{
clearTimeout(filtering);
filtering = setTimeout(function() {
const filter = search.value.toLowerCase();
if (filter === cfilter) {
return;
}
cfilter = filter;
const filtered = document.querySelectorAll(".valid");
for (let i = 0; i < filtered.length; i++) {
filtered[i].classList.remove("valid");
}
if (filter === "") {
page.classList.remove("filtering");
filtering = setTimeout(doFilter, 200);
}
function doFilter() {
const filter = search.value.toLowerCase();
if (filter === cfilter) {
return;
}
cfilter = filter;
if (history) {
if (search.value) {
history.replaceState(null, "", `${location.origin}${location.pathname}?q=${search.value}`);
}
else {
page.classList.add("filtering");
const targets = document.querySelectorAll("[data-search]");
for (let i = 0; i < targets.length; i++) {
const target = targets[i];
if (target.dataset.search.indexOf(filter) !== -1) {
target.classList.add("valid");
}
history.replaceState(null, "", `${location.origin}${location.pathname}`);
}
}
const filtered = document.querySelectorAll(".valid");
for (let i = 0; i < filtered.length; i++) {
filtered[i].classList.remove("valid");
}
if (filter === "") {
page.classList.remove("filtering");
}
else {
page.classList.add("filtering");
const targets = document.querySelectorAll("[data-search]");
for (let i = 0; i < targets.length; i++) {
const target = targets[i];
if (target.dataset.search.indexOf(filter) !== -1) {
target.classList.add("valid");
}
}
}, 200);
}
}
search.addEventListener("keyup", filter);
search.addEventListener("click", filter);
@ -110,11 +124,12 @@ for (let i = 0; i < etx.length; i++) {
}
page.innerHTML = data + "</div>";
document.querySelector("input[type=search]").focus();
doFilter();
help.addEventListener("click", () => {
document.querySelector(".meshpage-help").classList.toggle("visible");
});
document.querySelector("input[type=search]").focus();
}
render();