mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 23:46:04 +00:00
v2.5.0: add admin auto refresh toggle
This commit is contained in:
@@ -11,6 +11,10 @@ const i18n = {
|
|||||||
navLogs: "Logs",
|
navLogs: "Logs",
|
||||||
navSettings: "Settings",
|
navSettings: "Settings",
|
||||||
refresh: "Refresh",
|
refresh: "Refresh",
|
||||||
|
autoRefresh: "Auto refresh every 5 seconds",
|
||||||
|
autoRefreshOn: "Auto refresh is on",
|
||||||
|
autoRefreshOff: "Auto refresh is off",
|
||||||
|
autoRefreshOffShort: "off",
|
||||||
themeDark: "Dark",
|
themeDark: "Dark",
|
||||||
themeLight: "Light",
|
themeLight: "Light",
|
||||||
metricMode: "Mode",
|
metricMode: "Mode",
|
||||||
@@ -234,6 +238,10 @@ const i18n = {
|
|||||||
navLogs: "Логи",
|
navLogs: "Логи",
|
||||||
navSettings: "Настройки",
|
navSettings: "Настройки",
|
||||||
refresh: "Обновить",
|
refresh: "Обновить",
|
||||||
|
autoRefresh: "Автообновление каждые 5 секунд",
|
||||||
|
autoRefreshOn: "Автообновление включено",
|
||||||
|
autoRefreshOff: "Автообновление выключено",
|
||||||
|
autoRefreshOffShort: "выкл",
|
||||||
themeDark: "Тёмная",
|
themeDark: "Тёмная",
|
||||||
themeLight: "Светлая",
|
themeLight: "Светлая",
|
||||||
metricMode: "Режим",
|
metricMode: "Режим",
|
||||||
@@ -469,11 +477,15 @@ const state = {
|
|||||||
backupSchedule: null,
|
backupSchedule: null,
|
||||||
qrLink: "",
|
qrLink: "",
|
||||||
pendingUsers: new Set(),
|
pendingUsers: new Set(),
|
||||||
|
refreshingAll: false,
|
||||||
|
autoRefreshEnabled: localStorage.getItem("gotelegram-auto-refresh") !== "0",
|
||||||
};
|
};
|
||||||
|
|
||||||
const t = (key) => (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key;
|
const t = (key) => (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key;
|
||||||
|
|
||||||
const trafficRanges = ["15m", "1h", "24h", "month"];
|
const trafficRanges = ["15m", "1h", "24h", "month"];
|
||||||
|
const AUTO_REFRESH_MS = 5000;
|
||||||
|
let autoRefreshTimer = null;
|
||||||
|
|
||||||
const fmtBytes = (value = 0) => {
|
const fmtBytes = (value = 0) => {
|
||||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||||
@@ -527,6 +539,34 @@ const addEvent = (title, detail = "") => {
|
|||||||
renderEvents();
|
renderEvents();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function updateAutoRefreshToggle() {
|
||||||
|
const button = $("#autoRefreshToggle");
|
||||||
|
if (!button) return;
|
||||||
|
button.classList.toggle("active", state.autoRefreshEnabled);
|
||||||
|
button.setAttribute("aria-pressed", String(state.autoRefreshEnabled));
|
||||||
|
button.title = state.autoRefreshEnabled ? t("autoRefreshOn") : t("autoRefreshOff");
|
||||||
|
const label = button.querySelector(".auto-refresh-state");
|
||||||
|
if (label) label.textContent = state.autoRefreshEnabled ? "5s" : t("autoRefreshOffShort");
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncAutoRefreshTimer() {
|
||||||
|
if (autoRefreshTimer) {
|
||||||
|
clearInterval(autoRefreshTimer);
|
||||||
|
autoRefreshTimer = null;
|
||||||
|
}
|
||||||
|
if (!state.autoRefreshEnabled) return;
|
||||||
|
autoRefreshTimer = setInterval(() => {
|
||||||
|
refreshAll().catch((err) => toast(err.message));
|
||||||
|
}, AUTO_REFRESH_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAutoRefresh(enabled) {
|
||||||
|
state.autoRefreshEnabled = Boolean(enabled);
|
||||||
|
localStorage.setItem("gotelegram-auto-refresh", state.autoRefreshEnabled ? "1" : "0");
|
||||||
|
updateAutoRefreshToggle();
|
||||||
|
syncAutoRefreshTimer();
|
||||||
|
}
|
||||||
|
|
||||||
async function api(path, options = {}) {
|
async function api(path, options = {}) {
|
||||||
const headers = {
|
const headers = {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
@@ -564,6 +604,7 @@ function applyI18n() {
|
|||||||
updateUserTrafficControls();
|
updateUserTrafficControls();
|
||||||
renderBackupSchedule();
|
renderBackupSchedule();
|
||||||
updatePageTitle();
|
updatePageTitle();
|
||||||
|
updateAutoRefreshToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTheme(theme) {
|
function setTheme(theme) {
|
||||||
@@ -1271,6 +1312,8 @@ function renderConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refreshAll() {
|
async function refreshAll() {
|
||||||
|
if (state.refreshingAll) return;
|
||||||
|
state.refreshingAll = true;
|
||||||
const btn = $("#refreshBtn");
|
const btn = $("#refreshBtn");
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
try {
|
try {
|
||||||
@@ -1305,6 +1348,8 @@ async function refreshAll() {
|
|||||||
toast(err.message);
|
toast(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
|
state.refreshingAll = false;
|
||||||
|
updateAutoRefreshToggle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1684,6 +1729,7 @@ document.addEventListener("submit", (eventObj) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#refreshBtn").addEventListener("click", refreshAll);
|
$("#refreshBtn").addEventListener("click", refreshAll);
|
||||||
|
$("#autoRefreshToggle").addEventListener("click", () => setAutoRefresh(!state.autoRefreshEnabled));
|
||||||
$("#languageSelect").addEventListener("change", (eventObj) => setLanguage(eventObj.target.value));
|
$("#languageSelect").addEventListener("change", (eventObj) => setLanguage(eventObj.target.value));
|
||||||
$("#promoClose").addEventListener("click", () => {
|
$("#promoClose").addEventListener("click", () => {
|
||||||
$("#promoModal").hidden = true;
|
$("#promoModal").hidden = true;
|
||||||
@@ -1703,6 +1749,7 @@ window.addEventListener("hashchange", () => setPage((location.hash || "#dashboar
|
|||||||
setPage((location.hash || "#dashboard").slice(1), false);
|
setPage((location.hash || "#dashboard").slice(1), false);
|
||||||
setTheme(state.theme);
|
setTheme(state.theme);
|
||||||
renderEvents();
|
renderEvents();
|
||||||
|
syncAutoRefreshTimer();
|
||||||
refreshAll();
|
refreshAll();
|
||||||
loadLogs();
|
loadLogs();
|
||||||
maybeShowPromo();
|
maybeShowPromo();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
}());
|
}());
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/styles.css?v=2.5.0-admin8">
|
<link rel="stylesheet" href="/styles.css?v=2.5.0-admin18">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-shell">
|
<div class="app-shell">
|
||||||
@@ -54,6 +54,11 @@
|
|||||||
</select>
|
</select>
|
||||||
<button id="themeToggle" class="ghost" type="button">Theme</button>
|
<button id="themeToggle" class="ghost" type="button">Theme</button>
|
||||||
<button id="refreshBtn" type="button" data-i18n="refresh">Refresh</button>
|
<button id="refreshBtn" type="button" data-i18n="refresh">Refresh</button>
|
||||||
|
<button id="autoRefreshToggle" class="auto-refresh-toggle" type="button" aria-pressed="true" data-i18n-title="autoRefresh" title="Auto refresh">
|
||||||
|
<span class="auto-refresh-icon" aria-hidden="true">↻</span>
|
||||||
|
<span class="auto-refresh-track" aria-hidden="true"><span></span></span>
|
||||||
|
<span class="auto-refresh-state">5s</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -388,6 +393,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/app.js?v=2.5.0-admin17" type="module"></script>
|
<script src="/app.js?v=2.5.0-admin18" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -228,6 +228,65 @@ h2 {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auto-refresh-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: var(--panel-strong);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-toggle:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-icon {
|
||||||
|
color: var(--blue);
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-track {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 38px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: color-mix(in srgb, var(--muted) 28%, transparent);
|
||||||
|
transition: background .16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-track span {
|
||||||
|
position: absolute;
|
||||||
|
left: 3px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--panel);
|
||||||
|
box-shadow: 0 2px 8px rgba(15, 23, 42, .18);
|
||||||
|
transition: transform .16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-toggle.active .auto-refresh-track {
|
||||||
|
background: color-mix(in srgb, var(--green) 58%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-toggle.active .auto-refresh-track span {
|
||||||
|
transform: translateX(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-refresh-state {
|
||||||
|
min-width: 22px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
.language-select {
|
.language-select {
|
||||||
width: 78px;
|
width: 78px;
|
||||||
min-width: 78px;
|
min-width: 78px;
|
||||||
|
|||||||
@@ -199,6 +199,19 @@ class AdminFeatureTests(unittest.TestCase):
|
|||||||
self.assertIn('class="keys-list" id="usersTable"', index)
|
self.assertIn('class="keys-list" id="usersTable"', index)
|
||||||
self.assertNotIn('class="keys-table"', index)
|
self.assertNotIn('class="keys-table"', index)
|
||||||
|
|
||||||
|
def test_topbar_has_five_second_auto_refresh_toggle(self):
|
||||||
|
app_js = (ROOT / "admin-web" / "static" / "app.js").read_text(encoding="utf-8")
|
||||||
|
styles = (ROOT / "admin-web" / "static" / "styles.css").read_text(encoding="utf-8")
|
||||||
|
index = (ROOT / "admin-web" / "static" / "index.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
self.assertIn('id="autoRefreshToggle"', index)
|
||||||
|
self.assertIn('data-i18n-title="autoRefresh"', index)
|
||||||
|
self.assertIn("gotelegram-auto-refresh", app_js)
|
||||||
|
self.assertIn("AUTO_REFRESH_MS = 5000", app_js)
|
||||||
|
self.assertIn("setInterval", app_js)
|
||||||
|
self.assertIn("clearInterval", app_js)
|
||||||
|
self.assertIn(".auto-refresh-toggle", styles)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user