VPN connection log: who connected and when

WireGuard has no connect events, only the last handshake and byte counters.
awg-ctrl now checks them every 30 s and writes conn_log in users.db: connect
at the first handshake, disconnect at the last activity (the last tick the
counters moved), with the session length and traffic, plus peer_seen, the
last time each peer was online. Open sessions are restored from the database,
so a restart adds no duplicates; a down interface is not a disconnect; a peer
removed mid-session is closed. Events keep the name, owner and VPN IP as of
that moment and live 90 days. Client public IPs are not recorded.

GET /api/connlog serves the events and the per-peer last-seen list. The panel
fetches it from the selected server at /ui/connlog and fills in master key
labels and device names for sen:// peers. A new "Подключения" tab shows the
event feed with search and paging, and "who was online when", including
those who never connected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
This commit is contained in:
Claude
2026-10-28 12:06:40 +00:00
parent fd5e2cc761
commit 1a54d05cca
13 changed files with 802 additions and 0 deletions
+20
View File
@@ -100,6 +100,26 @@ func initSchema(db *sql.DB) error {
psk_key TEXT NOT NULL,
owner TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL
)`,
// Журнал VPN-подключений (connlog.go). name/owner/ip — на момент события: пользователя
// могут удалить, а запись о нём остаётся.
`CREATE TABLE IF NOT EXISTS conn_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
at INTEGER NOT NULL,
event TEXT NOT NULL,
pub_key TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
owner TEXT NOT NULL DEFAULT '',
ip TEXT NOT NULL DEFAULT '',
duration INTEGER,
rx INTEGER,
tx INTEGER
)`,
"CREATE INDEX IF NOT EXISTS idx_conn_log_at ON conn_log (at)",
"CREATE INDEX IF NOT EXISTS idx_conn_log_pub ON conn_log (pub_key, id)",
`CREATE TABLE IF NOT EXISTS peer_seen (
pub_key TEXT PRIMARY KEY,
at INTEGER NOT NULL
)`,
} {
if _, err := db.Exec(q); err != nil {