diff --git a/admin-web/static/app.js b/admin-web/static/app.js
index f45be2d..0b1e79d 100644
--- a/admin-web/static/app.js
+++ b/admin-web/static/app.js
@@ -978,6 +978,26 @@ function ensureUserTrafficSelection() {
state.userTrafficUser = state.users[0]?.name || "";
}
+async function selectUserTraffic(name, options = {}) {
+ const next = String(name || "");
+ if (!next || !state.users.some((user) => user.name === next)) return;
+ const changed = state.userTrafficUser !== next;
+ state.userTrafficUser = next;
+ if (changed) {
+ state.userTraffic = null;
+ }
+ renderUsers();
+ renderUserTraffic();
+ if (options.scroll) {
+ $("#userTrafficPanel")?.scrollIntoView({ behavior: "smooth", block: "start" });
+ }
+ try {
+ await refreshUserTraffic({ showLoading: true });
+ } catch (err) {
+ toast(err.message);
+ }
+}
+
function userTrafficRows() {
return state.userTraffic?.history || [];
}
@@ -1114,11 +1134,12 @@ function renderUsers() {
}
tbody.innerHTML = state.users.map((user) => {
const pending = state.pendingUsers.has(user.name);
+ const selected = user.name === state.userTrafficUser;
const traffic = user.traffic || {};
const trafficTotal = Number(traffic.total_octets) ? fmtBytes(traffic.total_octets) : "--";
const activeIps = Number(traffic.active_unique_ips) || 0;
return `
-
+
|
${escapeHtml(user.name)}${user.main ? ` ${escapeHtml(t("main"))}` : ""}
|
@@ -1227,6 +1248,7 @@ async function refreshAll() {
state.backupSchedule = state.overview.backup_schedule || state.backupSchedule;
updateLanguageFromOverview(state.overview);
state.users = await api("/api/users");
+ ensureUserTrafficSelection();
if (!state.stats) {
state.stats = {
current: state.overview.stats_current || {},
@@ -1532,41 +1554,45 @@ document.addEventListener("click", async (eventObj) => {
}
const button = eventObj.target.closest("button");
- if (!button) return;
-
- if (button.id === "themeToggle") {
- setTheme(state.theme === "dark" ? "light" : "dark");
- } else if (button.id === "menuBtn") {
- $("#sidebar").classList.toggle("open");
- } else if (button.dataset.trafficRange) {
- changeTrafficRange(button.dataset.trafficRange);
- } else if (button.dataset.trafficView) {
- state.trafficView = button.dataset.trafficView === "table" ? "table" : "chart";
- renderStats();
- } else if (button.dataset.userTraffic) {
- state.userTrafficUser = button.dataset.userTraffic;
- refreshUserTraffic({ showLoading: true }).catch((err) => toast(err.message));
- } else if (button.dataset.userQr) {
- showUserQr(button.dataset.userQr);
- } else if (button.dataset.userTrafficRange) {
- changeUserTrafficRange(button.dataset.userTrafficRange);
- } else if (button.dataset.userTrafficView) {
- state.userTrafficView = button.dataset.userTrafficView === "table" ? "table" : "chart";
- renderUserTraffic();
- } else if (button.dataset.backupSchedule) {
- setBackupSchedule(button.dataset.backupSchedule);
- } else if (button.dataset.restoreBackup) {
- const name = button.dataset.restoreBackup;
- if (confirm(`${t("confirmRestoreBackup")} ${name}?`)) restoreBackup(name).catch((err) => toast(err.message));
- } else if (button.dataset.copy) {
- await copyText(button.dataset.copy);
- } else if (button.dataset.delete) {
- const name = button.dataset.delete;
- if (confirm(`${t("confirmDelete")} ${name}?`)) deleteUser(name).catch((err) => toast(err.message));
- } else if (button.dataset.restart) {
- const name = button.dataset.restart;
- if (confirm(`${t("confirmRestart")} ${name}?`)) restartService(name).catch((err) => toast(err.message));
+ if (button) {
+ if (button.id === "themeToggle") {
+ setTheme(state.theme === "dark" ? "light" : "dark");
+ } else if (button.id === "menuBtn") {
+ $("#sidebar").classList.toggle("open");
+ } else if (button.dataset.trafficRange) {
+ changeTrafficRange(button.dataset.trafficRange);
+ } else if (button.dataset.trafficView) {
+ state.trafficView = button.dataset.trafficView === "table" ? "table" : "chart";
+ renderStats();
+ } else if (button.dataset.userTraffic) {
+ selectUserTraffic(button.dataset.userTraffic, { scroll: true });
+ } else if (button.dataset.userQr) {
+ showUserQr(button.dataset.userQr);
+ } else if (button.dataset.userTrafficRange) {
+ changeUserTrafficRange(button.dataset.userTrafficRange);
+ } else if (button.dataset.userTrafficView) {
+ state.userTrafficView = button.dataset.userTrafficView === "table" ? "table" : "chart";
+ renderUserTraffic();
+ } else if (button.dataset.backupSchedule) {
+ setBackupSchedule(button.dataset.backupSchedule);
+ } else if (button.dataset.restoreBackup) {
+ const name = button.dataset.restoreBackup;
+ if (confirm(`${t("confirmRestoreBackup")} ${name}?`)) restoreBackup(name).catch((err) => toast(err.message));
+ } else if (button.dataset.copy) {
+ await copyText(button.dataset.copy);
+ } else if (button.dataset.delete) {
+ const name = button.dataset.delete;
+ if (confirm(`${t("confirmDelete")} ${name}?`)) deleteUser(name).catch((err) => toast(err.message));
+ } else if (button.dataset.restart) {
+ const name = button.dataset.restart;
+ if (confirm(`${t("confirmRestart")} ${name}?`)) restartService(name).catch((err) => toast(err.message));
+ }
+ return;
}
+
+ const row = eventObj.target.closest("[data-select-user-traffic]");
+ if (!row) return;
+ selectUserTraffic(row.dataset.selectUserTraffic, { scroll: true });
});
document.addEventListener("change", (eventObj) => {
diff --git a/admin-web/static/index.html b/admin-web/static/index.html
index 12f9e5c..02ed21b 100644
--- a/admin-web/static/index.html
+++ b/admin-web/static/index.html
@@ -400,6 +400,6 @@
-
+