Feature: Offline device history with delete and remote uninstall
Persist every device that has ever logged in to the Go server in a small JSON store (devices.json, overridable with YAMA_DEVICES_FILE and capped by YAMA_DEVICES_MAX, default 1000 newest). The store follows the users.json convention: atomic temp-file write with fsync and rename, a 500 ms debounce for bursts of reconnects, first_seen preserved across logins, and pruning that evicts offline records before devices that are still connected. Devices connected at shutdown are stamped offline before the final flush so last_seen stays accurate across restarts. The server advertises an offlineDevices capability and device_list now appends historical devices that are not online, marked with online:false and last_seen. The web UI shows an Offline checkbox after Total/Showing (state kept in localStorage), renders offline cards dimmed and dashed, and always lists them in the screen-wall sidebar. Older servers without the capability keep the online-only list. Grid and List cards gain a delete button after the terminal button, admin only, behind a confirmation dialog. Deleting an offline device removes its history record; deleting an online device first sends the single-byte TOKEN_UNINSTALL command (matching the MFC OnOnlineUninstall flow) and then removes the record too, so the device does not come back as a dead offline entry. The agent's own disconnect then runs the existing cleanup path: screen and terminal sub-connections are closed, the hub entry is removed, and browsers receive device_offline to release their watch sets, wall sessions, and stream references. Wall mode exposes no delete action. Tests cover history persistence/pruning/recovery, offline ordering, the device_list merge, and all three delete outcomes (offline removal, online uninstall with a single 0x3F byte, and viewer rejection). Co-Authored-By: deepseek-flash
This commit is contained in:
1 parent
1922f87dd2
commit
b1ddec1b8a
12 files changed
+1212
-27
No files matched your search
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/auth"
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/connection"
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/devicehistory"
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/hub"
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/licensing"
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/logger"
|
||||
@@ -931,6 +932,34 @@ func main() {
|
||||
// the HTTP server reads from it.
|
||||
deviceHub := hub.New()
|
||||
|
||||
// Persistent device history: every device that has ever logged in, so the
|
||||
// web UI can keep showing it after it goes offline. Same working-directory
|
||||
// convention as users.json. Retention is capped by count (newest last_seen
|
||||
// wins) so the file and the device_list payload stay bounded.
|
||||
devicesFile := os.Getenv("YAMA_DEVICES_FILE")
|
||||
if devicesFile == "" {
|
||||
devicesFile = "devices.json"
|
||||
rememberDefault("YAMA_DEVICES_FILE", devicesFile)
|
||||
}
|
||||
devicesMax := 1000
|
||||
if v := os.Getenv("YAMA_DEVICES_MAX"); v != "" {
|
||||
if n, convErr := strconv.Atoi(v); convErr == nil && n > 0 {
|
||||
devicesMax = n
|
||||
} else {
|
||||
log.Warn("Invalid YAMA_DEVICES_MAX=%q (want a positive integer); using %d", v, devicesMax)
|
||||
}
|
||||
}
|
||||
deviceHistory, histErr := devicehistory.Open(devicesFile, devicesMax,
|
||||
log.WithPrefix("History"))
|
||||
if histErr != nil {
|
||||
// Keep the store: live recording still works, and the next flush
|
||||
// replaces the unreadable file. Losing old history is preferable to
|
||||
// refusing to start the server.
|
||||
log.Warn("Failed to load %s: %v (starting with empty history)", devicesFile, histErr)
|
||||
}
|
||||
deviceHub.Subscribe(deviceHistory)
|
||||
log.Info("Device history: %s (max %d)", devicesFile, devicesMax)
|
||||
|
||||
// Build the CMD_MASTERSETTING signer based on env vars:
|
||||
// - YAMA_SIGN_PASSWORD set → LocalSigner (operator's own deployment;
|
||||
// HMAC master key lives here)
|
||||
@@ -1154,6 +1183,7 @@ func main() {
|
||||
// Start HTTP server for web UI. Hub gives it read-only access to the
|
||||
// device registry; the authenticator owns user accounts and session tokens.
|
||||
httpSrv := web.New(*httpPort, log.WithPrefix("Web"), deviceHub, webAuth).
|
||||
WithDeviceHistory(deviceHistory).
|
||||
WithConfig(webCfg)
|
||||
if err := httpSrv.Start(); err != nil {
|
||||
log.Fatal("Failed to start HTTP server: %v", err)
|
||||
@@ -1227,5 +1257,17 @@ func main() {
|
||||
for _, srv := range servers {
|
||||
srv.Stop()
|
||||
}
|
||||
// Devices still connected at shutdown never fire OnDeviceOffline (their
|
||||
// TCP handlers are stopped above), so stamp them now. Otherwise a device
|
||||
// that had been online for days would show a stale "last seen" after the
|
||||
// restart until it reconnects.
|
||||
for _, d := range deviceHub.ListDevices() {
|
||||
deviceHistory.OnDeviceOffline(d.ID)
|
||||
}
|
||||
// Last-chance flush: the debounce timer may hold the final offline
|
||||
// timestamps from devices that dropped while we were shutting down.
|
||||
if err := deviceHistory.Close(); err != nil {
|
||||
log.Warn("device history flush: %v", err)
|
||||
}
|
||||
log.Info("Server stopped")
|
||||
}
|
||||
Reference in new issue
Block a user