After honest acknowledgment that easy-email-standard is abandoned and
limited (Chrome-only, no responsive preview, no AMP, no Unsplash, no
file manager), pivoted to Unlayer's vue-email-editor — a Vue 3 native
component giving all the features the user listed for free (internal
use; a small "Powered by Unlayer" badge shows in the sidebar but NOT
in sent emails).
Why drop MJML alongside:
• MJML was our SERVER-SIDE compilation step because we hand-wrote
templates. With a visual editor that outputs email-safe HTML
directly (responsive media queries, Outlook MSO fallbacks, AMP
where used), the compilation step is redundant.
• One fewer dependency on the hub (mjml package no longer needed).
• One fewer file format to persist (.mjml dropped, only .html
canonical + .json design).
Storage simplification:
Before: .mjml (source) + .html (compiled) + .json (editor state)
After: .html (canonical) + .json (Unlayer design tree)
The hub's send-worker reads .html as before — no changes to send
logic.
Architecture wins:
• Vue 3 native — zero iframe friction, no postMessage choreography
• No separate microservice — easy-email container decommissioned
(docker compose down, code kept under /opt/email-editor/ in case
of rollback)
• DNS editor.gigafibre.ca retained but unused — can be removed via
Cloudflare API cleanup later
• The editor's mergeTags option exposes our {{firstname}}, {{amount}},
{{gift_url}}, etc. in Unlayer's native "Merge tags" panel — same
pattern, more polished UI
• Features now native: responsive preview (mobile/tablet/desktop
breakpoints), Unsplash search, file manager, dark mode, design
history, undo/redo, layers panel, content blocks library
Frontend (TemplateEditorPage.vue):
• Imports EmailEditor from vue-email-editor
• onReady() callback: fetch template + loadDesign() to restore canvas
• saveTemplate(): exportHtml() → PUT { html, design } to hub
• Top bar kept: template selector, saved chip, preview, test-send,
save button
• Removed: iframe-related glue (postMessage listener, iframeKey,
EDITOR_BASE constant, Cmd-S handling that lived in the iframe)
API client (apps/ops/src/api/campaigns.js):
• saveTemplate() now accepts opts.design (Unlayer JSON tree) alongside
content. Legacy opts.format='mjml' still works for backward compat.
Hub (services/targo-hub/lib/campaigns.js):
• GET /campaigns/templates/:name unconditionally returns
{ name, format, html, design } (+ mjml when format=mjml for
legacy templates). The design field is null when no .json file
exists yet.
• PUT /campaigns/templates/:name HTML save path now accepts
body.design alongside body.html and persists both with backups.
• MJML save path (legacy) preserved for any callers using the old
contract.
Container decommissioned on prod: email-editor container stopped +
removed. The Vue editor lives inside the ops SPA, served from
erp.gigafibre.ca/ops as a normal route.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|---|---|---|
| apps | ||
| docs | ||
| erpnext | ||
| patches | ||
| scripts | ||
| services | ||
| .gitignore | ||
| README.md | ||
Gigafibre FSM
Gigafibre FSM is the operations platform for Gigafibre (consumer brand of TARGO Internet), a fiber ISP in Quebec. It replaces a legacy PHP/MariaDB billing system with ERPNext v16 + Vue 3/Quasar apps for ops, dispatch, field service, and customer self-service.
Repository Structure
gigafibre-fsm/
apps/
ops/ Targo Ops -- main operations PWA (Vue 3 / Quasar v2)
field/ Targo Field -- mobile app for technicians
client/ Gigafibre Portal -- customer self-service
website/ www.gigafibre.ca -- marketing site (React / Vite / Tailwind)
portal/ Customer portal deploy configs
services/
targo-hub/ Node.js API gateway (ERPNext, GenieACS, Twilio, Traccar)
modem-bridge/ SNMP/TR-069 bridge for CPE diagnostics
legacy-db/ Legacy MariaDB read-only access
docuseal/ Document signing service
erpnext/ Custom doctype setup scripts (setup_fsm_doctypes.py)
scripts/
migration/ 51 Python scripts for legacy-to-ERPNext data migration
bulk_submit.py, fix_ple_*.py/sh -- PostgreSQL patches, bulk ops
docs/ Architecture, infrastructure, migration, strategy docs
docker/ Docker compose fragments
patches/ ERPNext patches
Architecture
Internet
|
96.125.196.67 (Proxmox VM, Ubuntu 24.04, Docker)
|
Traefik v2.11 (TLS via Let's Encrypt)
|
+----------+----------+----------+----------+----------+
| | | | | |
ERPNext Ops PWA Authentik n8n Website DocuSeal
erp. erp. auth. n8n. www. docs.
gigafibre gigafibre targo.ca giga giga gigafibre
.ca .ca/ops/ id.giga fibre fibre .ca
fibre.ca .ca .ca
|
targo-hub (API gateway, msg.gigafibre.ca)
|
+----------+----------+----------+----------+
| | | | |
GenieACS Twilio Mailjet Stripe Traccar
(TR-069) (SMS) (email) ($) (GPS)
|
modem-bridge (SNMP / TR-069 deep-dive for TP-Link)
Services & Dependencies
| Service | URL | Stack | Purpose |
|---|---|---|---|
| ERPNext | erp.gigafibre.ca | Frappe v16, PostgreSQL | ERP / billing / ticketing — Source of Truth |
| Ops SPA | erp.gigafibre.ca/ops/ | Vue 3, Quasar v2, Pinia | Internal operations app (dispatch, clients, settings) |
| targo-hub | msg.gigafibre.ca | Node.js 20, Express | API gateway: SMS, SSE, AI, OAuth admin, Stripe webhooks, Traccar proxy |
| modem-bridge | internal | Node.js, Playwright | TR-181 deep-dive for TP-Link XX230v / Deco mesh |
| Authentik (staff) | auth.targo.ca | Python, PostgreSQL | SSO for ops + n8n + Gitea + ERPNext OAuth provider |
| Authentik (clients) | id.gigafibre.ca | Python, PostgreSQL | SSO for customer-facing portals |
| n8n | n8n.gigafibre.ca | Node.js | Workflow automation (campaigns, SMS, email) |
| Traefik | — | Go | Reverse proxy, TLS, forwardAuth middleware |
| Website | www.gigafibre.ca | React, Vite, Tailwind | Marketing site + address API |
| DocuSeal | sign.gigafibre.ca | Ruby on Rails | Contract e-signature |
| GenieACS | (external) | Node.js, MongoDB | TR-069 ACS for ONT/router fleet |
| Traccar | (external) | Java | GPS tracking for techs |
ERPNext Custom Doctypes
| Doctype | ID Pattern | Purpose |
|---|---|---|
| Service Location | LOC-##### | Customer premises (address, GPS, OLT port, network config) |
| Service Equipment | EQP-##### | Deployed hardware (ONT, router, TV box -- serial, MAC, IP) |
| Service Subscription | SUB-##### | Active service plans (speed, price, billing, RADIUS) |
| Dispatch Job | DJ-##### | Work orders with equipment, materials, checklist, photos, signature |
| Dispatch Technician | DT-##### | Tech profiles with GPS (Traccar), skills, color coding |
| Dispatch Tag | -- | Skill/service/region tags with levels (Fibre, TV, Telephonie, etc.) |
Key Custom Fields
| Doctype | Custom Fields |
|---|---|
| Customer | legacy_account_id, legacy_customer_id, ppa_enabled, stripe_id |
| Item | legacy_product_id, download_speed, upload_speed, olt_profile |
| Subscription | radius_user, radius_pwd, legacy_service_id |
| Issue | legacy_ticket_id, assigned_staff, issue_type, is_important, service_location |
Tech Stack
Frontend: Vue 3, Quasar v2, Pinia, Vite, Mapbox GL JS Backend: ERPNext v16 / Frappe (Python), PostgreSQL, Node.js (targo-hub) Infra: Docker, Traefik v2.11, Authentik SSO, Proxmox Integrations: Twilio (SMS), Mailjet (email), Stripe (payments), Traccar (GPS), GenieACS (TR-069), Gemini 2.5 Flash via targo-hub (vision/OCR — see docs/features/vision-ocr.md)
Data Volumes (migrated from legacy)
| Entity | Volume |
|---|---|
| Customers | 6,667 (active + terminated) |
| Subscriptions | 21,876 (with RADIUS credentials) |
| Sales Invoices | 115,000+ |
| Payments | 99,000+ (with invoice references) |
| Tickets (Issues) | 242,000+ (parent/child hierarchy) |
| Ticket Messages | 784,000+ |
| Devices | 7,600+ (ONT, router, TV box) |
| Service Locations | ~17,000 |
Development
# Ops app
cd apps/ops && npm install && npx quasar dev
# Website
cd apps/website && npm install && npm run dev
# targo-hub
cd services/targo-hub && npm install && npm run dev
# Deploy ops to production
cd apps/ops && bash deploy.sh
Auth Pattern
Two parallel Authentik instances — not a migration in progress:
auth.targo.ca— staff-facing. Protects/ops/, n8n, Gitea, the hub admin endpoints. Also acts as the OAuth provider for ERPNext sign-in. Lives on the prod box at/opt/authentik(managed by Targo's IT team).id.gigafibre.ca— customer-facing. Protects the client portal. Lives at/opt/authentik-clienton the prod box.
Staff apps go through Traefik forwardAuth → Authentik outpost → cookie. The ops SPA reads X-Authentik-Email from the proxied header to identify the user, then maps Authentik groups to the in-app capability set (useUserGroups.js). All ERPNext API calls from targo-hub use Authorization: token <ERP_SERVICE_TOKEN> (Bearer from .env).
New users are added via ops Settings → Utilisateurs → "Inviter" which hits POST /auth/users on the hub. The hub creates the Authentik user, sets a temporary password, emails it via Mailjet, and creates the matching ERPNext System User in one round-trip. See docs/SETUP.md §6.
Documentation
Start at docs/README.md — it indexes every doc with a "I want to…" intent table. Quick map:
| Area | Entry point |
|---|---|
| Plan & live module URLs | docs/roadmap.md |
| System architecture (services, Docker, SSO) | docs/architecture/overview.md |
| ERPNext data model + customer flows | docs/architecture/data-model.md |
| Frontend patterns (Vue/Quasar/Pinia) | docs/architecture/app-design.md |
| Billing, Stripe, invoices | docs/features/billing-payments.md |
| CPE / modems / ONTs / TR-069 | docs/features/cpe-management.md |
| Scanner / OCR / Gemini pipeline | docs/features/vision-ocr.md |
| Agent flows (Flow Template) | docs/features/flow-editor.md |
| Wizard SKU vs legacy audit | docs/reference/erpnext-item-diff.md |
| Historical snapshots & migration logs | docs/archive/ |