Add connection history per peer with country and network lookup
- The stats sampler records sessions per peer: start, end, address and
traffic. A session ends when the peer goes quiet or is disabled; a new
one starts when the device changes networks. Stored in stats.json and
kept as long as the daily traffic history (max 1000 per peer).
- Country and network operator come from the free DB-IP Lite databases
(CC BY 4.0), downloaded monthly and looked up locally, so peer
addresses never leave the server. Settings → Data retention can switch
this off, which deletes the databases.
- API: GET /peers/{id}/sessions; peer stats include the current location;
settings include the database status.
- Web UI and iOS app: connection history card, location line, country
code in the peer list (web), switch in data retention.
Claude-Session: https://claude.ai/code/session_01RAnLbyQZ5ZTA7KqwXP98nw
This commit is contained in:
@@ -26,6 +26,7 @@ type App struct {
|
||||
tls *webTLS
|
||||
logPath string
|
||||
logw *rotatingWriter // nil in tests
|
||||
geo *Geo // nil in tests
|
||||
started time.Time
|
||||
shutdown func() // graceful stop; systemd restarts the service
|
||||
}
|
||||
@@ -124,6 +125,7 @@ func (a *App) routes() http.Handler {
|
||||
g("POST /api/v1/peers/{id}/disable", a.setEnabled(false))
|
||||
g("POST /api/v1/peers/{id}/issue-config", a.issueConfig)
|
||||
g("GET /api/v1/peers/{id}/stats", a.peerStats)
|
||||
g("GET /api/v1/peers/{id}/sessions", a.peerSessions)
|
||||
|
||||
// Full-access tokens (the iOS app) may change app settings and read logs.
|
||||
// Password, tokens and backups stay with the admin account.
|
||||
@@ -806,6 +808,7 @@ func (a *App) getSettings(w http.ResponseWriter, r *http.Request) {
|
||||
"web": cfg.Web,
|
||||
"log": cfg.Log,
|
||||
"stats": cfg.Stats,
|
||||
"geo": a.geoStatus(),
|
||||
"adminUsername": cfg.Admin.Username,
|
||||
"fingerprint": a.tls.Fingerprint(),
|
||||
"logPath": a.logPath,
|
||||
@@ -980,4 +983,28 @@ func (a *App) applyRuntime(c *Config) {
|
||||
if a.logw != nil {
|
||||
a.logw.SetLimits(c.Log.MaxSizeMB, c.Log.MaxFiles)
|
||||
}
|
||||
a.geo.SetEnabled(c.Stats.geoEnabled())
|
||||
}
|
||||
|
||||
func (a *App) geoStatus() GeoStatus {
|
||||
if a.geo == nil {
|
||||
return GeoStatus{}
|
||||
}
|
||||
return a.geo.Status()
|
||||
}
|
||||
|
||||
// peerSessions returns the connection history, newest first.
|
||||
func (a *App) peerSessions(w http.ResponseWriter, r *http.Request) {
|
||||
if _, p := a.store.Get().peerByID(r.PathValue("id")); p == nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "no such peer"})
|
||||
return
|
||||
}
|
||||
limit := 100
|
||||
if _, err := fmt.Sscan(r.URL.Query().Get("limit"), &limit); err != nil || limit < 1 || limit > 1000 {
|
||||
limit = 100
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"sessions": a.stats.Sessions(r.PathValue("id"), limit),
|
||||
"attribution": "IP geolocation by DB-IP (https://db-ip.com), CC BY 4.0",
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user