feat(webui): sync host clock and timezone from browser
Adds POST /api/system/time-sync so the dashboard's "Sync with this browser" button can set the host's date, time, and IANA timezone from the client's browser — useful for appliances with no NTP/network access to keep their own clock straight. Disables NTP before writing the manual time so it doesn't get immediately overwritten. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b2b3f86c8d
commit
3ec7ca08da
@@ -1483,6 +1483,61 @@ func (h *handler) handleAPISystemShutdown(w http.ResponseWriter, r *http.Request
|
||||
writeJSON(w, map[string]string{"status": "shutting down"})
|
||||
}
|
||||
|
||||
// timezoneNameRE matches IANA timezone identifiers like "Europe/Moscow" or "UTC".
|
||||
var timezoneNameRE = regexp.MustCompile(`^[A-Za-z0-9_+\-]+(/[A-Za-z0-9_+\-]+)*$`)
|
||||
|
||||
func validTimezoneName(tz string) bool {
|
||||
if tz == "" || !timezoneNameRE.MatchString(tz) {
|
||||
return false
|
||||
}
|
||||
_, err := os.Stat(filepath.Join("/usr/share/zoneinfo", tz))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// handleAPISystemTimeSync sets the host's timezone and wall-clock time from
|
||||
// values supplied by the client's browser (used when the appliance has no
|
||||
// network/NTP access to keep its own clock in sync).
|
||||
func (h *handler) handleAPISystemTimeSync(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Timezone string `json:"timezone"`
|
||||
EpochMS int64 `json:"epoch_ms"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
if req.EpochMS <= 0 {
|
||||
writeError(w, http.StatusBadRequest, "epoch_ms required")
|
||||
return
|
||||
}
|
||||
|
||||
var out strings.Builder
|
||||
|
||||
if req.Timezone != "" {
|
||||
if !validTimezoneName(req.Timezone) {
|
||||
writeError(w, http.StatusBadRequest, "invalid timezone")
|
||||
return
|
||||
}
|
||||
if b, err := exec.Command("timedatectl", "set-timezone", req.Timezone).CombinedOutput(); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "set-timezone failed: "+strings.TrimSpace(string(b)))
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(&out, "timezone set to %s\n", req.Timezone)
|
||||
}
|
||||
|
||||
// Manual time only sticks if NTP sync is off.
|
||||
_ = exec.Command("timedatectl", "set-ntp", "false").Run()
|
||||
|
||||
sec := req.EpochMS / 1000
|
||||
if b, err := exec.Command("date", "-u", "-s", "@"+strconv.FormatInt(sec, 10)).CombinedOutput(); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "set-time failed: "+strings.TrimSpace(string(b)))
|
||||
return
|
||||
}
|
||||
out.WriteString("system clock synced\n")
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok", "output": out.String()})
|
||||
}
|
||||
|
||||
// ── Tools ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
var standardTools = []string{
|
||||
|
||||
Reference in New Issue
Block a user