System page laid out after 3CX's admin dashboard: a System information panel (version,
host name, company, public IP, LAN IPs, ports, telephony, restart needed, copy buttons,
how to restart) and an Event log panel (system_events, 90 days: trunk down/up, Asterisk
down/up, restart needed, config failed, 911, public IP changed, backups, disk) with Load
more and a CSV download.
About (/about, no sign-in, linked from the login page, My phone and System): AGPL-3.0
notice, credits, the repository link, and "Code v<version>", which downloads
selfphone.tar.gz: the api image now tars its own build context (no .env, data,
node_modules, build output or .git) before installing anything.
Task: M11-T7
auth/totp.ts per 08 §2.4: RFC 6238 (HMAC-SHA1, 30 s, 6 digits, ±1
step) with node:crypto only, checked against the RFC's test vectors.
Migration 0003: users.totp_recovery_hashes and totp_last_step.
Admins turn it on from My account (the header's "Account"): scan the
QR or type the key, confirm a code, get 10 recovery codes once. Login
asks for the code only after the password is right (401 totp_required,
no session); a wrong code is a failed login (rate limit, audit). A
step signs in once (totp_last_step updated atomically) and a recovery
code once (array_remove guarded by any()). Off and new codes need the
password; another admin can turn it off for a lost phone, which signs
that user out. Users list shows it (twoStep).
Also, found by the full e2e chain on one shared database:
- Linphone tokens: only the exact token issued is accepted. The last
base64 character can carry ignored bits, so another spelling decoded
to the same signed token.
- Test scripts no longer assume a fresh system: m3-t3 picks Dan in the
test box, m8-check dials whatever slot the park took (findslot =
next), m10-t4 uses the admin's existing extension, m11-t1 clears the
public IP on its own first (local nets are in the transports while
one is set, and transports change only on a restart).
e2e m11-t1-totp; the full chain M1-M11 (33 scripts) passes.
Task: M11-T1
Queues per 03 §6.8: queues_selfphoneng.conf (members as Local agents
following their hint), sp-queue-{id} (answer, prefix, record, Queue with
max wait, timeout destination), the number in sp-local-numbers and
destApp queue. Golden case queue; the Asterisk load test now checks
`queue show` lists each queue's members.
*45<queue> toggles login and checks PQMSTATUS/UPQMSTATUS, so a code for
a queue the caller isn't an agent of plays ss-noservice and stores
nothing (spec snippet fixed). Pauses live in queue_member_state, AstDB
SP/QPAUSE (resynced on connect) and app_queue; QueuePause is re-sent
after every `queue reload all` and rollback, which unpause everyone.
/api/queues CRUD with members [{extensionId, penalty}], admin
pause/unpause, GET /:id/stats; /api/me/queues/:id/pause|unpause for
Home. Stats come from queue_calls (new table), filled from app_queue's
AMI events plus UserEvent SpQueueExit in the fail branch: a real call
showed that when no agent rings, the Queue part of a call vanishes into
the next leg's CDR, and Asterisk 24 sends QueueCallerAbandon on
timeouts too.
Live: QueueTracker (QueueStatus on connect and after applies, queue
events); admins see every queue, users the queues they're agents of.
Web: Queues page (list, form with every column, detail page with
waiting callers, agents and pause buttons, today's stats), queue tiles
on the dashboard, queue login switches on Home, Queue in the
DestinationPicker.
CDR fixtures for queue answered/abandoned/timeout calls. e2e m8-t1 with
real phones: 3 callers wait with 2 agents, one paused by *45550 and one
in the UI, unpause connects the longest waiting caller, one times out
to the overflow phone, stats count it all. The whole e2e chain passes.
Task: M8-T1
Migration 0000 enables citext; 0001 creates all 39 tables. DB tests use a
throwaway database per file (TEST_DATABASE_URL, derived from .env in dev,
a postgres service in CI). Decision 0002: pg driver and @fastify/cookie.
Task: M1-T1