Total back-office control, RBAC-gated end to end
A Vue 3 + Composition API console: one shared axios client, one envelope-unwrapping layer, one Pinia store per resource, and a native WebSocket client for live events. Backend plumbing (API modules, stores) exists for several modules — NAQD treasury, cards, partners, FX, adjustments — ahead of any view being built for them yet.
Two frontend teams may still be actively adding screens. What follows describes the architecture and what's demonstrably wired to real data as of this snapshot — treat the module-status table as current-state, not a roadmap.
Module map
| Module | View | Store + API | Status |
|---|---|---|---|
| Dashboard | Dashboard.vue | dashboard.ts | live, real data |
| Users | users/UserList.vue, UserDetail.vue | user.ts | real — wallets/transactions/sessions/force-logout tabs |
| KYC review | kyc/KYCList.vue, KYCDetail.vue | kyc.ts | real |
| Transactions | transactions/TransactionList.vue, TransactionDetail.vue | transactions.ts | real — deposits/withdrawals tabs, approve action |
| Reports | Reports.vue | reports.ts | real — daily report |
| NAQD treasury | — none yet | naqd.ts store + API module fully built | backend-ready, no view |
| Cards | — none yet | cards.ts store + API module fully built | backend-ready, no view |
| Partners | — none yet | partners.ts store + API module fully built | backend-ready, no view |
| FX board | — none yet | fx.ts store + API module fully built | backend-ready, no view |
| Wallet adjustments | — none yet | adjustments.ts store + API module fully built, incl. dual-approval 403 handling | backend-ready, no view |
| Audit log | — none yet | audit.ts store + API module fully built | backend-ready, no view |
| Analytics / Settings / System | Analytics.vue, Settings.vue, System.vue | — | honest placeholders — labeled "arrives in the next wave," not disguised as real |
API client architecture
get/post/put/del<T> in src/api/http.ts.RBAC-driven UI
AdminRole = 'support' | 'finance' | 'compliance' | 'admin' | 'super_admin',
ordered exactly like the backend's hierarchy (a code comment in src/types/admin.ts
states this mirrors internal/api/middleware/admin_auth_fiber.go deliberately).
One function, hasMinRole, gates both the router guard and the sidebar:
- Router guard — redirects to
/403(Forbidden.vue) if the target route'smeta.minRoleisn't met. - Sidebar — filters nav items by the same check, so a support-role admin never even sees a link to a finance-gated page.
- No component-level permission gating beyond routes/nav was found — action buttons gate on data state (e.g. "pending" status, not-your-own-request) rather than an additional in-page role check.
| Route | Min role |
|---|---|
| Dashboard, Analytics, Settings, System | support |
| Users, KYC | compliance |
| Transactions | finance |
| Reports | admin |
WebSocket live layer
The transport is solid: src/services/ws.ts connects to
GET /api/v1/admin/ws?token=<jwt>, reconnects with exponential backoff
(1s → 30s + jitter), and force-closes on a 20-second heartbeat timeout if nothing (not even
a stats.tick) arrives. realtime.ts owns the single connection
lifecycle, started/stopped from App.vue watching auth state.
| Event type | What actually happens in the UI |
|---|---|
system.alert | Routed to useToast() (error/warning/info by severity) — fully wired |
stats.tick | Captured into a ref; dashboardStore.applyLiveTick() exists to consume it but is never called — live dashboard numbers don't update from WS ticks today |
txn.created/completed/failed, withdrawal.requested | transactionsStore.prependLive() exists to splice these into the list but is never invoked |
partner.operation | partnersStore.refreshOperationsQuiet() exists, same story — not wired |
| everything else (13 more types) | Pushed into a generic 60-item feed array only — no dedicated list/badge renders it anywhere |
The hard part — a correct, resilient WebSocket client — is done. What's missing is
wiring three already-written consumer functions to the realtime store's event stream (a
watch() each). See Data Flows → Admin WebSocket
event flow for the full path from publisher to browser.
Shared components
Every list view (Users, Transactions, KYC) is built from the same primitives:
DataTable.vue | Sortable columns, loading skeleton rows, empty state, pagination, per-column slot overrides — the standard list table |
Modal.vue / ConfirmDialog.vue | Teleported dialog + a thin confirm/cancel wrapper used for every approve/reject action |
StatCard.vue | KPI tile with loading skeleton and optional delta — Dashboard and Reports |
Badge.vue | Status pill (success/error/brand/neutral) |
charts/ChartCanvas.vue | Theme-aware Chart.js wrapper via a useChart composable — no vue-chartjs dependency |
layout/{AdminLayout, Header, Sidebar}.vue | Shell chrome; Sidebar filters nav by hasMinRole |