Fix ssh list

This commit is contained in:
2026-07-20 17:51:45 -03:00
parent 9c5bbaf55d
commit b903775fb7
6 changed files with 123 additions and 34 deletions
+90 -13
View File
@@ -81,10 +81,33 @@ const USER_SORT_EXTRACT = {
};
// Columns that default to descending on first click (most/online first).
const USER_SORT_DEFAULT_DESC = new Set(["status", "conn", "max", "up", "down", "usage"]);
const USER_SORT_OPTIONS = [
["username", "User"], ["status", "Status"], ["auth", "Auth"], ["conn", "Connections"],
["usage", "Usage"], ["expires", "Expiry"], ["max", "Max"], ["up", "Up"], ["down", "Dn"],
["owner", "Owner"],
];
const USER_FILTER_OPTIONS = [
["all", "All"], ["online", "Online"], ["offline", "Offline"],
["active", "Active"], ["expired", "Expired"], ["quota", "Quota reached"],
];
let userSort = { key: "username", dir: "asc" };
let userFilter = "all";
let lastUsersData = [];
function userMatchesFilter(user) {
const online = Number(user.active_conns || 0) > 0;
const expired = isExpiredDate(user.expires_at);
switch (userFilter) {
case "online": return online;
case "offline": return !online;
case "active": return !expired;
case "expired": return expired;
case "quota": return !!user.quota_exceeded;
default: return true;
}
}
function sortUsers(list) {
const ext = USER_SORT_EXTRACT[userSort.key] || USER_SORT_EXTRACT.username;
const dir = userSort.dir === "desc" ? -1 : 1;
@@ -110,6 +133,44 @@ function updateSortIndicators() {
});
}
function renderSSHListControls() {
const sortLabel = document.getElementById("sshSortLabel");
const filterLabel = document.getElementById("sshFilterLabel");
const sortButtons = document.getElementById("sshSortButtons");
const filterButtons = document.getElementById("sshFilterButtons");
const count = document.getElementById("sshListCount");
if (sortLabel) sortLabel.textContent = t("Sort by");
if (filterLabel) filterLabel.textContent = t("Show");
if (sortButtons) {
const options = USER_SORT_OPTIONS.filter(([key]) => key !== "owner" || currentRole === "superadmin");
sortButtons.replaceChildren(...options.map(([key, label]) => {
const button = document.createElement("button");
button.type = "button";
button.className = "user-list-filter-btn" + (userSort.key === key ? " active" : "");
button.textContent = t(label);
button.setAttribute("aria-pressed", userSort.key === key ? "true" : "false");
if (userSort.key === key) button.dataset.direction = userSort.dir;
button.addEventListener("click", () => setUserSort(key));
return button;
}));
}
if (filterButtons) {
filterButtons.replaceChildren(...USER_FILTER_OPTIONS.map(([key, label]) => {
const button = document.createElement("button");
button.type = "button";
button.className = "user-list-filter-btn" + (userFilter === key ? " active" : "");
button.textContent = t(label);
button.setAttribute("aria-pressed", userFilter === key ? "true" : "false");
button.addEventListener("click", () => setUserFilter(key));
return button;
}));
}
if (count) {
const visible = lastUsersData.filter(userMatchesFilter).length;
count.textContent = t("{visible} of {total} users", {visible, total:lastUsersData.length});
}
}
function setUserSort(key) {
if (!USER_SORT_EXTRACT[key]) return;
if (userSort.key === key) {
@@ -118,7 +179,12 @@ function setUserSort(key) {
userSort.key = key;
userSort.dir = USER_SORT_DEFAULT_DESC.has(key) ? "desc" : "asc";
}
updateSortIndicators();
renderUsers(lastUsersData);
}
function setUserFilter(filter) {
if (!USER_FILTER_OPTIONS.some(([key]) => key === filter)) return;
userFilter = filter;
renderUsers(lastUsersData);
}
@@ -131,6 +197,8 @@ function setUserSort(key) {
updateSortIndicators();
})();
renderSSHListControls();
function sshTrafficHTML(u) {
const up = Number(u.total_uplink_bytes || 0);
const down = Number(u.total_downlink_bytes || 0);
@@ -146,19 +214,18 @@ function sshTrafficHTML(u) {
function renderUsers(users) {
// Cache the raw list so a header click can re-sort without refetching, and
// order by the active column so rows don't shuffle on each live poll.
lastUsersData = users || [];
users = sortUsers(lastUsersData);
updateDashboardFromUsers(users);
lastUsersData = Array.isArray(users) ? users : [];
users = sortUsers(lastUsersData.filter(userMatchesFilter));
updateDashboardFromUsers(lastUsersData);
renderSSHListControls();
updateSortIndicators();
const isSA = currentRole === "superadmin";
userCountChip.textContent = users.length;
if (isSA) ownerColHead.classList.remove("hidden");
ownerColHead.classList.toggle("hidden", !isSA);
usersBody.innerHTML = "";
let online = 0;
let expiredCount = 0;
const online = lastUsersData.filter(u => Number(u.active_conns || 0) > 0).length;
const expiredCount = lastUsersData.filter(u => isExpiredDate(u.expires_at)).length;
users.forEach(u => {
const on = (u.active_conns || 0) > 0;
if (on) online++;
if (isExpiredDate(u.expires_at)) expiredCount++;
const tr = document.createElement("tr");
const cells = [
u.username,
@@ -198,9 +265,19 @@ function renderUsers(users) {
tr.appendChild(tdA);
usersBody.appendChild(tr);
});
const activeCount = Math.max(0, users.length - expiredCount);
userCountChip.textContent = t("{count} total · {active} active · {online} online", {count: users.length, active: activeCount, online});
if (sshMetricTotal) sshMetricTotal.textContent = String(users.length);
if (!users.length) {
const row = document.createElement("tr");
const cell = document.createElement("td");
cell.colSpan = isSA ? 11 : 10;
cell.className = "hint";
cell.style.cssText = "padding:24px;text-align:center;";
cell.textContent = t("No SSH users match this filter.");
row.appendChild(cell);
usersBody.appendChild(row);
}
const activeCount = Math.max(0, lastUsersData.length - expiredCount);
userCountChip.textContent = t("{count} total · {active} active · {online} online", {count:lastUsersData.length, active:activeCount, online});
if (sshMetricTotal) sshMetricTotal.textContent = String(lastUsersData.length);
if (sshMetricActive) sshMetricActive.textContent = String(activeCount);
if (sshMetricOnline) sshMetricOnline.textContent = String(online);
if (sshMetricState) sshMetricState.textContent = t("Online");