/**
* @module lsCompact
* @description Compacts non-debug Leaseset tables, adds signature and encryption
* type counters, sorts leasesets by priority, and implements periodic auto-refresh.
* Handles both single and summary leaseset views.
* @author dr|z3d
* @license AGPL3 or later
*/
import { lsDebug } from "/js/lsDebug.js"; // NOSONAR S1128
import { searchLS } from "/js/searchLS.js";
const isSingleLS = document.getElementById("singleLS") !== null;
document.addEventListener("DOMContentLoaded", () => {
let container = document.querySelector(".leasesets_container");
let refreshInterval = null;
let isRefreshing = false;
let lsCount = 0;
const debug = document.getElementById("leasesetdebug");
/**
* Compacts leaseset tables by repositioning encryption keys after expiry cells.
* @function compact
* @returns {void}
*/
function compact() {
if (!container || document.getElementById("leasesetdebug")) { return; }
document.querySelectorAll("table.leaseset").forEach(table => {
const expiry = table.querySelector(".expiry");
const ekeys = Array.from(table.querySelectorAll(".ekey"));
if (expiry && ekeys.length > 0) {
ekeys.forEach(el => el.remove());
ekeys.forEach(ekey => expiry.insertAdjacentElement("afterend", ekey));
const oldTr = table.querySelector("tr.ekeys");
if (oldTr) { oldTr.remove(); }
}
});
}
/**
* Counts signature and encryption types across all leasesets and displays
* summary counters in the summary table.
* @function countTypes
* @returns {void}
*/
function countTypes() {
const summary = document.getElementById("leasesetsummary") || document.getElementById("leasesetdebug");
if (!summary) { return; }
const signatureCounts = {};
document.querySelectorAll("span.nowrap.stype").forEach(span => {
if (!span.classList.contains("bullet")) {
const boldElement = span.querySelector("b");
const signatureSpan = boldElement?.nextElementSibling;
if (signatureSpan) {
const signatureType = signatureSpan.textContent.trim().split(/\s+/)[0];
signatureCounts[signatureType] = (signatureCounts[signatureType] || 0) + 1;
}
}
});
const encryptionCounts = {};
document.querySelectorAll("span.nowrap.ekey").forEach(span => {
if (!span.classList.contains("bullet")) {
const boldElement = span.querySelector("b");
const encryptionSpan = boldElement?.nextElementSibling;
if (encryptionSpan) {
const encryptionType = encryptionSpan.textContent.trim().split(/\s+/)[0];
encryptionCounts[encryptionType] = (encryptionCounts[encryptionType] || 0) + 1;
}
}
});
const tbody = summary.querySelector("tbody") || summary;
const existingRow = tbody.querySelector("#sigEncCount");
if (existingRow) { existingRow.remove(); }
const row = document.createElement("tr");
row.id = "sigEncCount";
const sigCell = document.createElement("td");
sigCell.innerHTML = `<b>${translate_sigTypes}</b>`;
const sigText = Object.entries(signatureCounts)
.sort()
.map(([type, count]) => `<span class=counterLS><span class="lsLabel sigType" title="${translate_sigType}">${type} <span class=lsCounter>${count}</span></span></span>`)
.join(" ");
const sigValueCell = document.createElement("td");
sigValueCell.innerHTML = sigText;
const encCell = document.createElement("td");
encCell.innerHTML = `<b>${translate_encTypes}</b>`;
const encText = Object.entries(encryptionCounts)
.sort()
.map(([type, count]) => `<span class=counterLS><span class="lsLabel encType" title="${translate_encType}">${type} <span class=lsCounter>${count}</span></span></span>`)
.join(" ");
const encValueCell = document.createElement("td");
encValueCell.innerHTML = encText;
if (!debug) {
const mergedCell = document.createElement("td");
mergedCell.colSpan = 4;
mergedCell.style.textAlign = "center";
mergedCell.innerHTML = `
${sigValueCell.innerHTML}
<span class=vsep></span>
${encValueCell.innerHTML}
`;
row.appendChild(mergedCell);
} else {
row.appendChild(sigCell);
row.appendChild(sigValueCell);
row.appendChild(encCell);
row.appendChild(encValueCell);
}
tbody.appendChild(row);
const lsLocalCount = document.getElementById("lsLocalCount");
if (lsLocalCount) {
const leaseCountsRow = document.getElementById("leasesetCounts");
if (leaseCountsRow) {
const counters = leaseCountsRow.querySelectorAll(".lsCounter.sets");
if (counters.length > 0) {
const totalCount = Array.from(counters).reduce((sum, el) => {
const val = parseInt(el.textContent, 10);
return val ? sum + val : sum;
}, 0);
lsLocalCount.textContent = totalCount;
}
}
}
summary.removeAttribute("hidden");
}
/**
* Injects CSS styles for leaseset counter labels and badges.
* @function styleLabels
* @returns {void}
*/
function styleLabels() {
const style = document.createElement("style");
style.type = "text/css";
style.id = "lsLabels";
style.textContent = `
.counterLS{padding:2px;display:inline-block;vertical-align:middle;border:var(--border_soft);box-shadow:var(--highlight);background:var(--badge)}
.counterLS .published{background:var(--globe) no-repeat 4px center/14px}
.counterLS .unpublished{background:var(--hardhat) no-repeat 4px center/14px}
.counterLS .clientB32{background:var(--ping) no-repeat 4px center/14px}
.counterLS .clientHostname{background:var(--link) no-repeat 4px center/14px}
.lsCounter,.lsBadge{margin:-3px -1px -3px 0;padding:2px 6px;min-width:28px;display:inline-block;text-align:center;font-weight:700}
.lsLabel{padding:1px 4px 1px 22px;display:inline-block;font-weight:500}
.lsLabel.encType,.lsLabel.sigType{margin-right:-4px}
.lsLabel.encType .lsCounter,.lsLabel.sigType .lsCounter{margin-left:4px}
.lsLabel.encType{background:var(--crypto) no-repeat 4px center/14px}
.lsLabel.sigType{background:var(--lock) no-repeat 4px center/14px}`;
document.head.appendChild(style);
}
/**
* Sorts leaseset tables by priority (published > unpublished > known client > client b32)
* and adds count rows for each category.
* @function sortLeasesets
* @returns {void}
*/
function sortLeasesets() {
document.querySelectorAll("table.leaseset").forEach(table => {
const lastTh = table.querySelector("th:last-child");
let sortPriority = 4;
let sortText = "";
if (lastTh) {
sortText = lastTh.textContent.trim().toLowerCase();
const span = lastTh.querySelector("span.lsdest");
if (span) {
sortPriority = span.classList.contains("published") ? 0 : 1;
} else {
const a = lastTh.querySelector("a");
if (a) {
sortPriority = a.classList.contains("destlink") ? 2 : 3;
}
}
}
table.dataset.sortPriority = sortPriority;
table.dataset.sortText = sortText;
});
const tables = Array.from(document.querySelectorAll("table.leaseset"));
tables.sort((a, b) => {
const prioA = parseInt(a.dataset.sortPriority, 10);
const prioB = parseInt(b.dataset.sortPriority, 10);
if (prioA !== prioB) { return prioA - prioB; }
return a.dataset.sortText.localeCompare(b.dataset.sortText);
});
tables.forEach(table => {
if (table.parentNode) {
table.parentNode.appendChild(table);
}
});
if (window.location.search.includes("l=3")) {
const summaryTable = document.getElementById("leasesetsummary");
const publishedCount = document.querySelectorAll("table.leaseset th:last-child span.lsdest.published").length;
const unpublishedCount = document.querySelectorAll("table.leaseset th:last-child span.lsdest:not(.published)").length;
const knownClientCount = document.querySelectorAll("table.leaseset th:last-child a.destlink").length;
const clientCount = document.querySelectorAll("table.leaseset th:last-child a:not(.destlink)").length;
if (summaryTable) {
const tbody = summaryTable.querySelector("tbody") || summaryTable;
const existingRow = tbody.querySelector("#leasesetCounts");
if (existingRow) { existingRow.remove(); }
const row = document.createElement("tr");
row.id = "leasesetCounts";
const cell = document.createElement("td");
const cell_r = document.createElement("td");
cell.colSpan = 4;
cell.style.textAlign = "center";
cell.innerHTML = `
<span class=counterLS title="${translate_localPublic}"><span class="lsLabel published">Published</span> <span class="lsCounter sets">${publishedCount}</span></span>
<span class=counterLS title="${translate_localPrivate}"><span class="lsLabel unpublished">Unpublished</span> <span class="lsCounter sets">${unpublishedCount}</span></span>
<span class=counterLS title="${translate_requestedLS}"><span class="lsLabel clientHostname">Client (hostname)</span> <span class="lsCounter sets">${knownClientCount}</span></span>
<span class=counterLS title="${translate_requestedLS}"><span class="lsLabel clientB32">Client (b32)</span> <span class="lsCounter sets">${clientCount}</span></span>
`;
row.appendChild(cell);
tbody.appendChild(row);
lsCount = Array.from(document.querySelectorAll("#leasesetCounts span.lsCounter")).reduce((sum, el) => sum + parseInt(el.textContent, 10) || 0, 0);
lsLocalCount.textContent = lsCount;
}
}
}
/**
* Fetches updated leaseset content and replaces the current container.
* @function refreshLeasesets
* @returns {void}
*/
function refreshLeasesets() {
if (!container) { return; }
progressx.show(theme); progressx.progress(.7);
const url = window.location.href;
fetch(url)
.then(response => {
if (!response.ok) { throw new Error("Network response was not ok"); }
progressx.progress(.8);
return response.text();
})
.then(html => {
const lsLabels = document.getElementById("lsLabels");
const temp = document.createElement("div");
temp.innerHTML = html;
const newContainer = temp.querySelector(".leasesets_container");
if (!newContainer) { return; }
const oldContainer = document.querySelector(".leasesets_container");
if (!oldContainer) { return; }
oldContainer.parentNode.replaceChild(newContainer, oldContainer);
container = newContainer;
progressx.progress(.9)
compact();
lsDebug();
countTypes();
if (!lsLabels) { styleLabels(); }
if (!debug) { sortLeasesets(); }
if (isSingleLS) { searchLS(); }
progressx.progress(1);
setTimeout(() => { progressx.hide(); }, 100);
})
.catch(() => { });
}
/**
* Starts the periodic leaseset refresh interval.
* @function startRefresh
* @returns {void}
*/
function startRefresh() {
if (isRefreshing) { return; }
isRefreshing = true;
refreshInterval = setInterval(() => { requestAnimationFrame(refreshLeasesets); }, 15000);
}
/**
* Stops the periodic leaseset refresh interval.
* @function stopRefresh
* @returns {void}
*/
function stopRefresh() {
if (!isRefreshing) { return; }
isRefreshing = false;
clearInterval(refreshInterval);
refreshInterval = null;
progressx.hide();
}
/**
* Initializes the leaseset compact view with refresh, styling, sorting, and debug.
* @function initLSCompact
* @returns {void}
*/
function initLSCompact() {
startRefresh();
progressx.show(theme);
compact();
lsDebug();
countTypes();
styleLabels();
if (!debug) { sortLeasesets(); }
if (isSingleLS) { searchLS(); }
progressx.hide();
}
initLSCompact();
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") { stopRefresh(); }
else { initLSCompact(); }
});
});