fix(ui): show server clock in browser TZ; sync button refetches

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Андрей Бобырев
2026-05-14 16:29:44 +03:00
parent 1c22f89d5a
commit 6fbe314f99
3 changed files with 68 additions and 8 deletions

View File

@@ -8,6 +8,7 @@ const logoutBtn = document.querySelector("#logout");
const refreshBtn = document.querySelector("#refresh"); const refreshBtn = document.querySelector("#refresh");
const clockServerEl = document.querySelector("#clock-server"); const clockServerEl = document.querySelector("#clock-server");
const clockLocalEl = document.querySelector("#clock-local"); const clockLocalEl = document.querySelector("#clock-local");
const clockSyncBtn = document.querySelector("#clock-sync");
const rowsEl = document.querySelector("#rows"); const rowsEl = document.querySelector("#rows");
const statusEl = document.querySelector("#status"); const statusEl = document.querySelector("#status");
const peerCountEl = document.querySelector("#peer-count"); const peerCountEl = document.querySelector("#peer-count");
@@ -216,6 +217,10 @@ refreshBtn.addEventListener("click", () => {
loadClients(); loadClients();
}); });
clockSyncBtn.addEventListener("click", () => {
void refreshServerClock();
});
const clockFmt = new Intl.DateTimeFormat("ru-RU", { const clockFmt = new Intl.DateTimeFormat("ru-RU", {
dateStyle: "medium", dateStyle: "medium",
timeStyle: "medium", timeStyle: "medium",
@@ -226,16 +231,45 @@ let clockTickId = null;
/** @type {ReturnType<typeof setInterval> | null} */ /** @type {ReturnType<typeof setInterval> | null} */
let clockServerPollId = null; let clockServerPollId = null;
/** Метка UTC сервера (мс) по последнему ответу API */
let serverAnchorUtcMs = /** @type {number | null} */ (null);
/** Date.now() в момент установки якоря (компенсация сети только между опросами) */
let serverAnchorWallMs = 0;
function browserTimeZoneLabel() {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone || "";
} catch {
return "";
}
}
function tickServerClockDisplay() {
if (serverAnchorUtcMs === null) {
clockServerEl.dateTime = "";
clockServerEl.textContent = "—";
return;
}
const estimatedUtcMs = serverAnchorUtcMs + (Date.now() - serverAnchorWallMs);
const d = new Date(estimatedUtcMs);
clockServerEl.dateTime = d.toISOString();
const tz = browserTimeZoneLabel();
clockServerEl.textContent = tz ? `${clockFmt.format(d)} · ${tz}` : clockFmt.format(d);
}
async function refreshServerClock() { async function refreshServerClock() {
try { try {
const t = await api("/api/server-time"); const t = await api("/api/server-time");
const iso = typeof t.iso === "string" ? t.iso : ""; const iso = typeof t.iso === "string" ? t.iso : "";
clockServerEl.dateTime = iso; const parsed = new Date(iso).getTime();
const tz = typeof t.timeZone === "string" ? t.timeZone : ""; if (!iso || Number.isNaN(parsed)) {
const formatted = typeof t.formatted === "string" ? t.formatted : ""; throw new Error("нет времени");
clockServerEl.textContent = }
formatted.trim() ? `${formatted}${tz ? ` · ${tz}` : ""}` : "—"; serverAnchorUtcMs = parsed;
serverAnchorWallMs = Date.now();
tickServerClockDisplay();
} catch { } catch {
serverAnchorUtcMs = null;
clockServerEl.dateTime = ""; clockServerEl.dateTime = "";
clockServerEl.textContent = "—"; clockServerEl.textContent = "—";
} }
@@ -244,7 +278,13 @@ async function refreshServerClock() {
function tickLocalClock() { function tickLocalClock() {
const n = new Date(); const n = new Date();
clockLocalEl.dateTime = n.toISOString(); clockLocalEl.dateTime = n.toISOString();
clockLocalEl.textContent = clockFmt.format(n); const tz = browserTimeZoneLabel();
clockLocalEl.textContent = tz ? `${clockFmt.format(n)} · ${tz}` : clockFmt.format(n);
}
function tickClocks() {
tickLocalClock();
tickServerClockDisplay();
} }
function stopClocks() { function stopClocks() {
@@ -256,6 +296,8 @@ function stopClocks() {
clearInterval(clockServerPollId); clearInterval(clockServerPollId);
clockServerPollId = null; clockServerPollId = null;
} }
serverAnchorUtcMs = null;
serverAnchorWallMs = 0;
clockServerEl.dateTime = ""; clockServerEl.dateTime = "";
clockLocalEl.dateTime = ""; clockLocalEl.dateTime = "";
clockServerEl.textContent = "—"; clockServerEl.textContent = "—";
@@ -264,9 +306,9 @@ function stopClocks() {
function startClocks() { function startClocks() {
stopClocks(); stopClocks();
tickLocalClock(); tickClocks();
void refreshServerClock(); void refreshServerClock();
clockTickId = setInterval(tickLocalClock, 1000); clockTickId = setInterval(tickClocks, 1000);
clockServerPollId = setInterval(() => void refreshServerClock(), 30_000); clockServerPollId = setInterval(() => void refreshServerClock(), 30_000);
} }

View File

@@ -62,6 +62,12 @@
<span class="muted clock-kind">Браузер</span> <span class="muted clock-kind">Браузер</span>
<time id="clock-local" class="clock-time"></time> <time id="clock-local" class="clock-time"></time>
</div> </div>
<p class="clock-hint muted">
Сервер показывается в вашем часовом поясе (как браузер). «Сверить» — заново запросить момент с VPS.
</p>
<button type="button" id="clock-sync" class="btn small ghost clock-sync-btn">
Сверить время
</button>
</div> </div>
<button type="button" id="refresh" class="btn primary">Обновить</button> <button type="button" id="refresh" class="btn primary">Обновить</button>
</section> </section>

View File

@@ -234,6 +234,18 @@ h1 {
color: var(--text); color: var(--text);
} }
.clock-hint {
margin: 0.35rem 0 0;
font-size: 0.74rem;
line-height: 1.35;
max-width: 22rem;
}
.clock-sync-btn {
margin-top: 0.35rem;
align-self: flex-start;
}
.pill { .pill {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;