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.
This commit is contained in:
Daniel Redetzke
2026-10-03 19:21:10 +03:00
parent 066d669f3a
commit 242377fdef
15 changed files with 707 additions and 21 deletions
+39
View File
@@ -61,6 +61,38 @@ nonisolated struct PeerStats: Decodable, Hashable {
let lastHandshake: Date?
let endpoint: String
let down24h, up24h, down30d, up30d, downTotal, upTotal: Int64
let location: GeoInfo?
}
/// Country and network of an address, from the server's DB-IP lookup.
nonisolated struct GeoInfo: Decodable, Hashable {
let country: String?
let countryName: String?
let asn: Int?
let network: String?
/// "Germany · Deutsche Telekom AG" or "Local network".
var label: String {
[countryName ?? country, network].compactMap { $0 }.filter { !$0.isEmpty }.joined(separator: " · ")
}
}
/// One row of a peer's connection history.
nonisolated struct ConnSession: Decodable, Identifiable, Hashable {
let start: Date
let end: Date
let open: Bool
let seconds: Int64
let endpoint: String
let ip: String
let geo: GeoInfo?
let down: Int64
let up: Int64
var id: String { "\(start.timeIntervalSince1970)-\(ip)" }
}
nonisolated struct SessionsResponse: Decodable {
let sessions: [ConnSession]
}
nonisolated struct Peer: Decodable, Identifiable, Hashable {
@@ -166,6 +198,12 @@ nonisolated struct LogSettings: Codable, Equatable {
nonisolated struct StatsSettings: Codable, Equatable {
var hourlyHours: Int
var dailyDays: Int
var geoip: Bool?
}
nonisolated struct GeoStatus: Decodable {
let enabled: Bool
let updated: Date?
}
nonisolated struct AppSettings: Decodable {
@@ -175,6 +213,7 @@ nonisolated struct AppSettings: Decodable {
var adminUsername: String
var fingerprint: String
var logPath: String
var geo: GeoStatus?
}
nonisolated struct SettingsResult: Decodable {