refactor: harden diagnostics and consolidate runtime code
This commit is contained in:
@@ -150,8 +150,9 @@ setInterval(function(){
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// renderTimeSyncCard shows the server's current time and a button that syncs
|
||||
// the host clock and timezone to whatever the client's browser reports.
|
||||
// renderTimeSyncCard shows the server's current clock and timezone next to the
|
||||
// browser's own, highlights any drift, and offers a button that syncs the host
|
||||
// clock and timezone to whatever the client's browser reports.
|
||||
func renderTimeSyncCard() string {
|
||||
return `<div class="card" style="margin-bottom:16px">
|
||||
<div class="card-head card-head-actions">
|
||||
@@ -161,11 +162,66 @@ func renderTimeSyncCard() string {
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table style="font-size:13px;border-collapse:collapse">
|
||||
<tr><td style="padding:2px 16px 2px 0;color:var(--muted)">Server</td>
|
||||
<td style="padding:2px 24px 2px 0" id="time-server-clock">—</td>
|
||||
<td style="padding:2px 0" id="time-server-tz">—</td></tr>
|
||||
<tr><td style="padding:2px 16px 2px 0;color:var(--muted)">Browser</td>
|
||||
<td style="padding:2px 24px 2px 0" id="time-browser-clock">—</td>
|
||||
<td style="padding:2px 0" id="time-browser-tz">—</td></tr>
|
||||
</table>
|
||||
<div id="time-drift-note" style="font-size:13px;margin-top:8px"></div>
|
||||
<span id="time-sync-status" style="font-size:13px;color:var(--muted)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function timeSyncRun() {
|
||||
(function(){
|
||||
var CRIT = 'var(--crit-fg,#9f3a38)';
|
||||
var OK = 'var(--ok-fg,#2c662d)';
|
||||
var refreshPending = false;
|
||||
|
||||
function refreshTime() {
|
||||
var browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
|
||||
document.getElementById('time-browser-clock').textContent = new Date().toLocaleString();
|
||||
document.getElementById('time-browser-tz').textContent = browserTz;
|
||||
|
||||
if (refreshPending) return Promise.resolve();
|
||||
refreshPending = true;
|
||||
return fetch('/api/system/time', {cache: 'no-store'})
|
||||
.then(function(r){ if(!r.ok) throw new Error(r.statusText); return r.json(); })
|
||||
.then(function(d){
|
||||
var serverMs = d.epoch_ms;
|
||||
var serverTz = d.timezone || '';
|
||||
var skewMs = Math.abs(Date.now() - serverMs);
|
||||
document.getElementById('time-server-clock').textContent = d.local_time || new Date(serverMs).toISOString();
|
||||
document.getElementById('time-server-tz').textContent = serverTz;
|
||||
|
||||
var clockBad = skewMs > 60000;
|
||||
var tzBad = serverTz !== browserTz;
|
||||
document.getElementById('time-server-clock').style.color = clockBad ? CRIT : '';
|
||||
document.getElementById('time-browser-clock').style.color = clockBad ? CRIT : '';
|
||||
document.getElementById('time-server-tz').style.color = tzBad ? CRIT : '';
|
||||
document.getElementById('time-browser-tz').style.color = tzBad ? CRIT : '';
|
||||
|
||||
var note = document.getElementById('time-drift-note');
|
||||
if (clockBad || tzBad) {
|
||||
var parts = [];
|
||||
if (clockBad) parts.push('clock differs by ' + Math.round(skewMs/1000) + 's');
|
||||
if (tzBad) parts.push('timezone mismatch');
|
||||
note.style.color = CRIT;
|
||||
note.textContent = '⚠ ' + parts.join(', ') + ' — click "Sync with this browser"';
|
||||
} else {
|
||||
note.style.color = OK;
|
||||
note.textContent = '✓ server clock and timezone match this browser';
|
||||
}
|
||||
})
|
||||
.catch(function(){
|
||||
document.getElementById('time-server-clock').textContent = 'unavailable';
|
||||
})
|
||||
.finally(function(){ refreshPending = false; });
|
||||
}
|
||||
|
||||
window.timeSyncRun = function() {
|
||||
var btn = document.getElementById('time-sync-btn');
|
||||
var status = document.getElementById('time-sync-status');
|
||||
btn.disabled = true;
|
||||
@@ -177,15 +233,20 @@ function timeSyncRun() {
|
||||
})
|
||||
.then(function(r) { if (!r.ok) return r.text().then(function(t){throw new Error(t || r.statusText);}); return r.json(); })
|
||||
.then(function(d) {
|
||||
status.style.color = 'var(--ok-fg,#2c662d)';
|
||||
status.style.color = OK;
|
||||
status.textContent = '✓ Synced to ' + tz + ' at ' + new Date().toLocaleString();
|
||||
refreshTime();
|
||||
})
|
||||
.catch(function(err) {
|
||||
status.style.color = 'var(--crit-fg,#9f3a38)';
|
||||
status.style.color = CRIT;
|
||||
status.textContent = '✗ Sync failed: ' + err.message;
|
||||
})
|
||||
.finally(function() { btn.disabled = false; });
|
||||
}
|
||||
};
|
||||
|
||||
refreshTime();
|
||||
setInterval(refreshTime, 5000);
|
||||
})();
|
||||
</script>`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user