feat(ui): show server and browser date/time in toolbar

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Андрей Бобырев
2026-05-14 16:14:27 +03:00
parent 73b7d104d4
commit 1c22f89d5a
4 changed files with 116 additions and 0 deletions

View File

@@ -6,6 +6,8 @@ const loginError = document.querySelector("#login-error");
const logoutBtn = document.querySelector("#logout");
const refreshBtn = document.querySelector("#refresh");
const clockServerEl = document.querySelector("#clock-server");
const clockLocalEl = document.querySelector("#clock-local");
const rowsEl = document.querySelector("#rows");
const statusEl = document.querySelector("#status");
const peerCountEl = document.querySelector("#peer-count");
@@ -129,6 +131,7 @@ dtOk.addEventListener("click", async () => {
});
function showLogin() {
stopClocks();
loginGate.classList.remove("hidden");
loginGate.setAttribute("aria-hidden", "false");
appRoot.classList.add("hidden");
@@ -138,6 +141,7 @@ function showApp() {
loginGate.classList.add("hidden");
loginGate.setAttribute("aria-hidden", "true");
appRoot.classList.remove("hidden");
startClocks();
}
function setStatus(text, isErr) {
@@ -212,6 +216,60 @@ refreshBtn.addEventListener("click", () => {
loadClients();
});
const clockFmt = new Intl.DateTimeFormat("ru-RU", {
dateStyle: "medium",
timeStyle: "medium",
});
/** @type {ReturnType<typeof setInterval> | null} */
let clockTickId = null;
/** @type {ReturnType<typeof setInterval> | null} */
let clockServerPollId = null;
async function refreshServerClock() {
try {
const t = await api("/api/server-time");
const iso = typeof t.iso === "string" ? t.iso : "";
clockServerEl.dateTime = iso;
const tz = typeof t.timeZone === "string" ? t.timeZone : "";
const formatted = typeof t.formatted === "string" ? t.formatted : "";
clockServerEl.textContent =
formatted.trim() ? `${formatted}${tz ? ` · ${tz}` : ""}` : "—";
} catch {
clockServerEl.dateTime = "";
clockServerEl.textContent = "—";
}
}
function tickLocalClock() {
const n = new Date();
clockLocalEl.dateTime = n.toISOString();
clockLocalEl.textContent = clockFmt.format(n);
}
function stopClocks() {
if (clockTickId !== null) {
clearInterval(clockTickId);
clockTickId = null;
}
if (clockServerPollId !== null) {
clearInterval(clockServerPollId);
clockServerPollId = null;
}
clockServerEl.dateTime = "";
clockLocalEl.dateTime = "";
clockServerEl.textContent = "—";
clockLocalEl.textContent = "—";
}
function startClocks() {
stopClocks();
tickLocalClock();
void refreshServerClock();
clockTickId = setInterval(tickLocalClock, 1000);
clockServerPollId = setInterval(() => void refreshServerClock(), 30_000);
}
const dtRu = new Intl.DateTimeFormat("ru-RU", {
dateStyle: "short",
timeStyle: "short",
@@ -374,6 +432,7 @@ async function loadClients() {
wgShowEl.textContent = data.wgShow || "";
renderRows(data.clients);
setStatus("", false);
void refreshServerClock();
} catch (e) {
const msg = String(e.message || e);
if (msg.includes("Unauthorized")) {

View File

@@ -53,6 +53,16 @@
<section class="toolbar">
<div class="pill"><span class="dot ok"></span><span id="proto-label">Протокол: AmneziaWG</span></div>
<div class="clock-strip" role="status" aria-live="polite">
<div class="clock-row">
<span class="muted clock-kind">Сервер</span>
<time id="clock-server" class="clock-time"></time>
</div>
<div class="clock-row">
<span class="muted clock-kind">Браузер</span>
<time id="clock-local" class="clock-time"></time>
</div>
</div>
<button type="button" id="refresh" class="btn primary">Обновить</button>
</section>

View File

@@ -205,6 +205,35 @@ h1 {
flex-wrap: wrap;
}
.clock-strip {
display: flex;
flex-direction: column;
gap: 0.2rem;
padding: 0.45rem 0.75rem;
border-radius: 12px;
border: 1px solid var(--line);
background: rgba(255, 255, 255, 0.03);
font-size: 0.82rem;
line-height: 1.35;
}
.clock-row {
display: flex;
align-items: baseline;
gap: 0.5rem;
flex-wrap: wrap;
}
.clock-kind {
flex: 0 0 4.25rem;
}
.clock-time {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.84rem;
color: var(--text);
}
.pill {
display: inline-flex;
align-items: center;