/**
* @module tunnels
* @description Handles automatic refresh for the console tunnels page and
* enables persistent toggling of tunnel IDs and tunnel table row visibility
* via localStorage. Updates tunnel in/out counts on refresh.
* @author dr|z3d
* @license AGPL3 or later
*/
import { refreshElements } from './refreshElements.js';
const bodyTag = document.querySelector("body");
const container = document.querySelector("#tunnelsContainer");
const nav = document.querySelector(".confignav");
const toggleIds = document.getElementById("toggleTunnelIds");
const toggleTunnels = document.getElementById("toggleTunnels");
const tunnelIdsHidden = document.querySelector(".idsHidden");
const tunnelsHidden = document.querySelector(".tunnelsHidden");
const isAdvancedMode = document.documentElement.classList.contains("advmode");
let tunnelTableVisibility = localStorage.getItem("tunnelTableVisibility");
let tunnelIdVisibility = localStorage.getItem("tunnelIdVisibility");
nav.addEventListener("click", function(event) {
if (event.target.id === "toggleTunnels") {
const isHidden = document.querySelector("body").classList.contains("tunnelsHidden");
if (isHidden) {
bodyTag.classList.remove("tunnelsHidden");
if (toggleTunnels.classList.contains("off")) {toggleTunnels.classList.remove("off");}
localStorage.removeItem("tunnelTableVisibility");
} else {
bodyTag.classList.add("tunnelsHidden");
if (!toggleTunnels.classList.contains("off")) {toggleTunnels.classList.add("off");}
localStorage.setItem("tunnelTableVisibility", "hidden");
}
}
if (!isAdvancedMode) {
toggleIds.remove();
return;
}
if (event.target.id === "toggleTunnelIds") {
const isHidden = document.querySelector("body").classList.contains("idsHidden");
if (isHidden) {
bodyTag.classList.remove("idsHidden");
if (toggleIds.classList.contains("off")) {toggleIds.classList.remove("off");}
localStorage.removeItem("tunnelIdVisibility");
} else {
bodyTag.classList.add("idsHidden");
if (!toggleIds.classList.contains("off")) {toggleIds.classList.add("off");}
localStorage.setItem("tunnelIdVisibility", "hidden");
}
}
});
/**
* Restores tunnel table visibility state from localStorage on page load.
* @function persistTunnelTableVisibility
* @returns {void}
*/
function persistTunnelTableVisibility() {
if (tunnelTableVisibility) {
if (!tunnelsHidden) {
bodyTag.classList.add("tunnelsHidden");
if (!toggleTunnels.classList.contains("off")) {toggleTunnels.classList.add("off");}
}
} else {
bodyTag.classList.remove("tunnelsHidden");
if (toggleTunnels.classList.contains("off")) {toggleTunnels.classList.remove("off");}
localStorage.removeItem("tunnelTableVisibility");
}
}
/**
* Restores tunnel ID visibility state from localStorage on page load.
* @function persistTunnelIdVisibility
* @returns {void}
*/
function persistTunnelIdVisibility() {
if (!isAdvancedMode) {return;}
if (tunnelIdVisibility) {
if (!tunnelIdsHidden) {
document.querySelector("body").classList.add("idsHidden");
if (!toggleIds.classList.contains("off")) {toggleIds.classList.add("off");}
}
} else {
document.querySelector("body").classList.remove("idsHidden");
if (toggleIds.classList.contains("off")) {toggleIds.classList.remove("off");}
localStorage.removeItem("tunnelIdVisibility");
}
}
/**
* Recalculates and updates tunnel in/out count displays for each pool.
* @function updateTunnelCounts
* @returns {void}
*/
function updateTunnelCounts() {
const pools = container.querySelectorAll(".tablewrap");
pools.forEach(pool => {
const summary = pool.querySelector("table.poolsummary");
const tunnelTable = pool.querySelector("table.tunnels_client");
if (!summary || !tunnelTable) { return; }
const inCount = tunnelTable.querySelectorAll('td.direction[data-sort="in"]').length;
const outCount = tunnelTable.querySelectorAll('td.direction[data-sort="out"]').length;
const inCell = summary.querySelector("th.inCount");
const outCell = summary.querySelector("th.outCount");
if (inCell) {
const parts = inCell.textContent.split(" / ");
if (parts.length === 2) {inCell.textContent = inCount + " / " + parts[1];}
}
if (outCell) {
const parts = outCell.textContent.split(" / ");
if (parts.length === 2) {outCell.textContent = outCount + " / " + parts[1];}
}
});
}
document.addEventListener("DOMContentLoaded", function() {
persistTunnelTableVisibility();
persistTunnelIdVisibility();
bodyTag.classList.add("js");
let tunnelRefreshPending = false;
document.addEventListener("elementsRefreshed", function(event) {
if (tunnelRefreshPending) return;
if (!event.detail.selectors.some(sel => sel.includes("tunnelsContainer"))) return;
tunnelRefreshPending = true;
const currentTables = container.querySelectorAll("table").length;
const currentRows = container.querySelectorAll("tr").length;
fetch("/tunnels")
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const fetchedTables = doc.querySelectorAll("#tunnelsContainer table").length;
const fetchedRows = doc.querySelectorAll("#tunnelsContainer tr").length;
if (fetchedTables !== currentTables || fetchedRows !== currentRows) {
const newContainer = doc.querySelector("#tunnelsContainer");
if (newContainer) {
container.innerHTML = newContainer.innerHTML;
}
}
updateTunnelCounts();
})
.catch(error => {})
.finally(() => { tunnelRefreshPending = false; });
});
// Steady-state refresh morphs only the volatile cells (test status,
// expiry, latency, data, footer bandwidth) so morphdom never recombines
// tbody/tfoot table sections; structural changes trigger the full
// #tunnelsContainer replace above.
refreshElements(
"#tunnelsContainer td.status, #tunnelsContainer td.expiry, #tunnelsContainer td.latency, #tunnelsContainer td.datatransfer, #tunnelsContainer tfoot td",
"/tunnels", 10000
);
});