gigafibre-fsm/docs/ARCHITECTURE.md
louispaulb 838f8dcd8d docs: complete architecture — service map, dependencies, data flows
Full system documentation: Docker containers, request flows,
targo-hub endpoints, GenieACS integration, ops app structure,
external service dependencies, and device diagnostics data flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 21:29:05 -04:00

293 lines
13 KiB
Markdown

# Gigafibre FSM — Architecture
## Service Map
```
┌──────────────────┐
│ Authentik SSO │
│ id.gigafibre.ca │
└────────┬─────────┘
│ OIDC / Proxy Auth
┌──────────────────┐
│ Traefik │
│ Reverse Proxy │
│ + Let's Encrypt │
└──┬───┬───┬───┬───┘
│ │ │ │
┌───────────────────┘ │ │ └──────────────────┐
▼ ▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Ops App │ │ ERPNext v16 │ │ Targo-Hub │
│ erp.../ops/ │ │ erp.gigafibre.ca│ │ msg.gigafibre.ca │
│ (nginx+Quasar) │ │ (Frappe/Python) │ │ (Node.js) │
└───────┬─────────┘ └───────┬──────────┘ └──┬──────┬───────┘
│ │ │ │
│ /api/* proxy │ │ │
│ (token injected) │ │ │
└─────────────────────┘ │ │
│ │
┌──────────────────────────────────────┘ │
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ GenieACS NBI │ │ Twilio API │
│ 10.5.2.115:7557 │ │ SMS + Voice │
│ (TR-069 ACS) │ └──────────────────┘
└───────┬──────────┘
│ CWMP (TR-069)
┌──────────────────┐ ┌──────────────────┐
│ CPE / ONT │ │ Oktopus (USP) │
│ TP-Link XX230v │ │ oss.gigafibre.ca│
│ Raisecom HT803G │ │ TR-369 (future) │
└──────────────────┘ └──────────────────┘
```
## Host: 96.125.196.67 (hubdocker)
All services run on a single Docker host. DNS records `erp.gigafibre.ca`,
`oss.gigafibre.ca`, `msg.gigafibre.ca` all resolve to this IP.
### Docker Containers
| Container | Image | Port | Network | Purpose |
|-----------|-------|------|---------|---------|
| ops-frontend | nginx:alpine | 80 | proxy | Ops SPA + ERPNext API proxy |
| targo-hub | node:20-alpine | 3300 | proxy, erpnext | SSE relay, SMS, GenieACS proxy |
| erpnext-frontend | frappe/erpnext | 8080 | erpnext | ERPNext web + API |
| erpnext-backend | frappe/erpnext | 8000 | erpnext | Frappe worker |
| erpnext-db-1 | postgres:16 | 5432 | erpnext | ERPNext database |
| oktopus-acs-1 | oktopusp/acs | 9292 | oktopus | USP/TR-369 controller |
| oktopus-mongo-1 | mongo:7 | 27017 | oktopus | Oktopus datastore |
| fn-routr | fonoster/routr-one | — | fonoster | VoIP SIP routing |
| fn-asterisk | fonoster/asterisk | — | fonoster | PBX media server |
| fn-postgres | postgres:16 | — | fonoster | Fonoster DB |
| apps-targo-db-1 | postgres | — | apps | Targo-hub database |
| authentik-* | goauthentik | — | authentik | SSO provider |
---
## Ops App (Quasar v2 + Vite)
**Served from:** `/opt/ops-app/` via `ops-frontend` (nginx)
**URL:** `https://erp.gigafibre.ca/ops/`
### Request Flow
```
Browser Traefik ops-frontend (nginx) ERPNext
│ │ │ │
│── GET /ops/... ──────▶ strip /ops ────────────▶ try_files ──▶ SPA │
│ │ │ │
│── GET /ops/api/... ──▶ strip /ops ────────────▶ /api/* proxy ─────▶
│ │ │ + Auth token │
│ │ │ injected │
```
### Directory Structure
```
apps/ops/src/
├── api/ # API clients
│ ├── auth.js # Token auth + session check
│ ├── erp.js # ERPNext CRUD (listDocs, getDoc, updateDoc...)
│ ├── dispatch.js # Dispatch jobs/techs/tags CRUD
│ ├── sms.js # SMS via n8n webhook
│ ├── traccar.js # GPS tracking
│ ├── ocr.js # Ollama Vision OCR
│ └── service-request.js # Service booking (future)
├── components/
│ ├── shared/
│ │ ├── DetailModal.vue # Right-panel detail viewer
│ │ ├── InlineField.vue # Odoo-style inline editing
│ │ ├── TagEditor.vue # Tag selector with levels
│ │ └── detail-sections/ # Per-doctype detail views
│ │ ├── EquipmentDetail # ONT diagnostics + mesh topology
│ │ ├── IssueDetail # Ticket view
│ │ ├── InvoiceDetail # Invoice + payment status
│ │ ├── PaymentDetail # Payment entry
│ │ └── SubscriptionDetail# Subscription management
│ └── customer/
│ ├── CustomerHeader.vue # Name, status, actions
│ ├── ContactCard.vue # Phone/email
│ ├── CustomerInfoCard.vue # Flags, notes, tax category
│ ├── ChatterPanel.vue # SMS/call thread
│ ├── ComposeBar.vue # Message input
│ ├── SmsThread.vue # SMS history
│ └── PhoneModal.vue # Twilio voice/SIP
├── composables/ # 24 Vue composables
│ ├── useDeviceStatus.js # GenieACS device lookup + cache
│ ├── useSSE.js # Server-sent events (targo-hub)
│ ├── useInlineEdit.js # Inline field save logic
│ ├── usePhone.js # 3CX/Twilio voice config
│ └── ... # Dispatch, map, formatting, etc.
├── modules/
│ └── dispatch/components/ # Dispatch-specific UI (timeline, calendar, map)
├── pages/ # 10 routed pages
│ ├── DashboardPage # KPI overview
│ ├── ClientsPage # Search-first customer list
│ ├── ClientDetailPage # Customer detail (subs, invoices, tickets)
│ ├── TicketsPage # Issue/ticket list
│ ├── DispatchPage # Scheduling + timeline (~1600 LOC)
│ ├── EquipePage # Equipment fleet
│ ├── OcrPage # Invoice OCR
│ ├── TelephonyPage # VoIP management
│ ├── RapportsPage # Reports (stub)
│ └── SettingsPage # Config: API, SMS, 3CX
├── stores/ # Pinia: dispatch, auth
├── config/ # erpnext.js, nav.js, ticket-config.js
└── router/index.js
```
---
## Targo-Hub (Node.js)
**Container:** `targo-hub` | **URL:** `msg.gigafibre.ca` | **Port:** 3300
### Endpoints
| Method | Path | Purpose |
|--------|------|---------|
| GET | `/sse?topics=customer:X` | Server-Sent Events stream |
| POST | `/broadcast` | Push event to SSE clients |
| POST | `/send/sms` | Send SMS via Twilio |
| POST | `/webhook/twilio/sms-incoming` | Receive inbound SMS |
| POST | `/webhook/twilio/sms-status` | SMS delivery status |
| GET | `/voice/token` | Twilio voice JWT |
| GET | `/devices/lookup?serial=X` | Find CPE in GenieACS |
| GET | `/devices/summary` | Fleet statistics |
| GET | `/devices/:id/hosts` | Connected clients + mesh mapping |
| POST | `/devices/:id/tasks` | Send task (reboot, refresh) |
| GET | `/health` | Health check |
### GenieACS Device Lookup (3 fallbacks)
```
serial = "TPLGC4160688"
1. Exact: DeviceID.SerialNumber._value == serial → match?
2. GPON: Device.Optical...GponSn._value =~ /C4160688$/ → match?
3. ID: _id =~ /TPLGC4160688/ → match?
```
### Hosts Endpoint Flow
```
GET /devices/:id/hosts?refresh
├── Task 1: getParameterValues → Device.Hosts.Host.{1-20}.*
│ (connection_request, timeout=15s)
├── Task 2: getParameterValues → Device.WiFi.MultiAP.APDevice.{1-3}
│ .Radio.{1-2}.AP.{1-4}.AssociatedDevice.{1-8}.*
│ (timeout=10s)
├── Read cached data from GenieACS MongoDB
├── Build clientNodeMap: MAC → {nodeName, band, signal, speed}
└── Return { total, hosts[{name, ip, mac, band, signal, attachedNode, lease}] }
```
---
## GenieACS Provision (XX230v)
**Provision:** `xx230v_inform` / `XX230v_inform_TpLink_fix`
TP-Link requires `clear()` + `commit()` before re-reading to avoid error 9805/9806:
```javascript
clear("Device.Hosts.Host", Date.now());
clear("Device.WiFi.MultiAP.APDevice.*.Radio.*.AP.*.AssociatedDevice", Date.now());
commit();
// Re-read in same provision cycle
declare("Device.Hosts.Host.*.HostName", {value: now});
declare("Device.WiFi.MultiAP.APDevice.*.Radio.*.AP.*.AssociatedDevice.*.MACAddress", {value: now});
```
Configures: TR-069 credentials, remote access (HTTPS on 443), VoIP digit map,
NTP, superadmin password. Reads ~100 diagnostic parameters per inform.
---
## Data Model (ERPNext Doctypes)
```
Customer (ERPNext native)
└─ Service Location (LOC-#####)
├─ Address + GPS coordinates
├─ Connection type (FTTH/FTTB/Cable/DSL)
├─ OLT port, VLAN, network config
├─ Service Equipment (EQP-#####)
│ ├─ Type: ONT / Router / Switch / AP / Decodeur
│ ├─ Serial number + MAC address
│ ├─ Status: Active / Inactive / En stock / Defectueux / Retourne
│ ├─ Network config (IP, firmware, credentials)
│ └─ Move history (Equipment Move Log)
└─ Service Subscription (SUB-#####)
├─ Plan: Internet / IPTV / VoIP / Bundle
├─ Billing: price, frequency, Stripe integration
└─ Status: pending → active → suspended → cancelled
Dispatch Job
├─ Customer + Service Location
├─ Assigned tech + assistants + tags
├─ Schedule: date, time, duration
├─ Equipment Items / Materials Used
└─ GPS position (Traccar)
```
---
## External Services
| Service | URL | Used By | Purpose |
|---------|-----|---------|---------|
| ERPNext | erp.gigafibre.ca | Ops App, targo-hub | Business data |
| GenieACS | 10.5.2.115:7557 | targo-hub | CPE management (TR-069) |
| Twilio | api.twilio.com | targo-hub | SMS + Voice |
| Traccar | tracker.targointernet.com:8082 | Ops App | GPS fleet tracking |
| n8n | n8n.gigafibre.ca | Ops App | SMS workflow |
| Authentik | id.gigafibre.ca | Traefik | SSO (staff) |
| Authentik Client | auth.targo.ca | Portal | SSO (customers) |
| Mapbox | api.mapbox.com | Ops App | Maps + routing |
| 3CX PBX | targopbx.3cx.ca | targo-hub | Call logging |
---
## Data Flow: Customer → Device Diagnostics
```
User clicks equipment chip in ClientDetailPage
EquipmentDetail.vue
├── fetchStatus([{serial_number: "TPLGC4160688"}])
│ → GET msg.gigafibre.ca/devices/lookup?serial=TPLGC4160688
│ → targo-hub → GenieACS NBI → summarizeDevice()
│ → {interfaces[], mesh[], wifi{}, opticalStatus, ethernet{}}
├── fetchHosts(serial, refresh=true)
│ → GET msg.gigafibre.ca/devices/:id/hosts?refresh
│ → 2 inline tasks to CPE → read cache → clientNodeMap
│ → {total, hosts[{name, ip, mac, band, signal, attachedNode}]}
UI renders:
┌─ Fibre [Up] Rx dBm (when available)
├─ IP Addresses (Internet, Gestion [clickable → /superadmin/], Service, LAN)
├─ WiFi (per-radio + total clients: direct + mesh)
├─ Ethernet ports
├─ General (firmware, SSID, uptime)
└─ Connected Clients (collapsible, grouped by mesh node)
├─ basement (5) — signal %, name, IP, MAC, band, lease
├─ hallway (2)
├─ living_room (4)
└─ Filaire / Autre (wired clients)
```