feat(repeaters): My Repeaters panel (stage 1)

New full-screen panel (main menu / FAB) for repeater administration:
- repeaters table (saved per-pubkey admin password, login metadata);
  plaintext per observer_brokers precedent (single-user LAN app)
- REST /api/repeaters: list merged with device contact truth, add
  (device REP contacts only), set/clear password, remove, login
- device_manager: _repeater_lock serializes all repeater ops (companion
  firmware has a single pending-request slot); repeater_login now
  filters LOGIN_SUCCESS by pubkey_prefix and captures is_admin/
  permissions into an in-memory session store
- login timeout UX: wrong password and unreachable repeater are
  indistinguishable (firmware stays silent) - error message names both
- panel UI: add-picker, password modal (remembered password), per-
  repeater path editor reusing /api/contacts/<pk>/paths + Leaflet
  repeater map picker (adapted from the DM panel)
- meshcore pin bumped to >=2.3.7 (send_login_sync era API, fixed
  LOGIN_SUCCESS/LOGIN_FAILED parsing; container already ships 2.3.7)

Stage 2 (management panel with Status/Telemetry/Neighbors/CLI/
Settings/Actions tools) follows after user review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-07-17 22:55:44 +02:00
parent 87b30f63b3
commit ca2a6bacf9
13 changed files with 2004 additions and 79 deletions
+10
View File
@@ -223,6 +223,16 @@ CREATE TABLE IF NOT EXISTS app_settings (
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- My Repeaters: repeaters selected for administration, with saved login.
-- No FK to contacts: a saved password must survive contact cleanup/re-add.
CREATE TABLE IF NOT EXISTS repeaters (
public_key TEXT PRIMARY KEY, -- hex, lowercase (64 chars)
password TEXT NOT NULL DEFAULT '', -- plaintext (single-user LAN app)
added_at TEXT NOT NULL DEFAULT (datetime('now')),
last_login_at TEXT,
last_login_role TEXT -- 'admin' | 'guest'
);
-- ============================================================
-- Indexes
-- ============================================================