No description
  • TypeScript 81%
  • Dart 12.9%
  • Shell 3.5%
  • CSS 0.7%
  • HTML 0.7%
  • Other 1.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-09 21:34:37 +07:00
.devin/skills chore(skills): pasang superpowers skill pack untuk agent 2026-10-29 09:44:10 +07:00
.github ci: ulangi build APK saat unduhan Gradle gagal sementara (#24) 2026-11-08 17:10:41 +07:00
backend fix(notif): teknisi berakun admin ikut dinotifikasi; pelanggan diberi tahu saat pendaftaran ditolak (#32) 2026-11-09 21:34:37 +07:00
deploy docs: sync README with codebase — 27 cron jobs, Tripay/QRIS, FTTH alerts, broken links 2026-10-25 18:45:21 +07:00
docs feat(olt): tipe ONU, kipas ZTE, modul optik per port, ganti nama dan reboot ONU Hioso, reboot ZTE menjawab konfirmasi, merek Dasan, port web (v5.45.0) 2026-11-07 19:37:38 +07:00
frontend docs: changelog 5.46.0 dan README; naikkan versi ke 5.46.0 (#17) 2026-11-08 07:10:10 +07:00
mobile fix(notif): beri tahu teknisi saat pendaftarannya disetujui/ditolak; ikon notifikasi approval teknisi (#31) 2026-11-09 20:57:54 +07:00
packages/shared-types refactor: restructure to pnpm monorepo -- frontend/ + backend/ + packages/ (v2.35.0) 2026-08-11 21:34:48 +07:00
scripts fix(updater): git fetch + reset --hard agar tahan history rewrite 2026-10-31 12:45:28 +07:00
.gitattributes chore: enforce lf line endings for shell scripts 2026-03-15 11:09:30 +07:00
.gitignore chore: sertakan FUNDING.yml yang tertahan gitignore 2026-11-06 14:46:18 +07:00
CHANGELOG.md docs: changelog 5.46.0 dan README; naikkan versi ke 5.46.0 (#17) 2026-11-08 07:10:10 +07:00
LICENSE docs: tambah lisensi MIT dan perbarui README (v5.39.4) 2026-11-06 14:45:51 +07:00
package.json docs: changelog 5.46.0 dan README; naikkan versi ke 5.46.0 (#17) 2026-11-08 07:10:10 +07:00
pnpm-lock.yaml feat(backend): add FCM push infrastructure for customer Flutter app 2026-09-23 10:09:26 +07:00
pnpm-workspace.yaml feat(backend): add FCM push infrastructure for customer Flutter app 2026-09-23 10:09:26 +07:00
README.md docs: sync README changelog 2026-11-08 00:10:20 +00:00

SALFANET RADIUS - Billing System for ISP/RTRW.NET

Modern, full-stack billing & RADIUS management system for ISP/RTRW.NET with FreeRADIUS integration supporting PPPoE and Hotspot authentication.

Architecture: pnpm monorepo — Two Next.js apps (frontend UI + backend API) + Baileys WhatsApp service Version: 5.46.0 - Tampilan Mobile OLT, Nama ONU N/A, dan Tema Terang/Gelap Seragam


🎯 Features

Category Key Capabilities
RADIUS / Auth FreeRADIUS 3.0.26, PAP/CHAP/MS-CHAP, VPN L2TP/IPSec, PPPoE & Hotspot, CoA real-time speed/disconnect, IP Pool management, Multi-NAS isolation
VPN Management MikroTik CHR via API, VPS built-in WireGuard & L2TP/IPsec peer management, configurable IP pool & gateway per protocol, auto-generated RouterOS scripts
PPPoE Management Customer accounts, profile-based bandwidth, isolation, IP assignment, MikroTik auto-sync, foto KTP+instalasi via kamera HP, GPS otomatis, realtime online/offline status (polling 10s), realtime status isolir/aktif (polling 10s), PSB wizard 3-step (adopt dari home.pmynet.id), true optimistic update (reactivate/delete instant), placeholder MAC rejection, Instalatur (kelompokkan pelanggan, tagihan, transaksi, dan export per pemasang; riwayat tidak pernah dihapus), halaman Pelanggan Berhenti dengan alat pemulihan auto stop
IP Pool RADIUS ippool module — dynamic IP allocation per speed tier, pool create/expand/delete, Pool-Name → group mapping, utilization stats
Hotspot Voucher 8 code types, batch up to 25,000, agent distribution, auto-sync with RADIUS, print templates, MikroTik local-only mode (voucher sync via RouterOS API, salfanet: comment marker, batch cleanup, cron status sync), Rekap Voucher (laporan penjualan per hari/minggu/bulan, filter by agent & profile, export Excel, rincian per hari, pendapatan per agent — support both RADIUS & local NAS via firstLoginAt)
Billing Postpaid/prepaid invoices, auto-generation, payment reminders, balance/deposit, auto-renewal, postpaid isolir date per customer or for all at once (same as the billing date, or following the payment date, with the due date fixed), one expiry calculation shared by every payment path
Payment Manual upload (bukti transfer), QRIS Mandiri (QR dinamis + listener), Midtrans/Xendit/Duitku/Tripay gateway, approval workflow, 0–5 bank accounts, halaman bayar publik /pay/[token]
Notifications WhatsApp (Fonnte/WAHA/GOWA/MPWA/Wablas/WABlast/Kirimi.id/Baileys native), Email SMTP, broadcast (outage/invoice/payment), webhook pesan masuk, Telegram (backup DB terjadwal + bot 2-arah untuk teknisi: cek redaman/pelanggan)
Agent/Reseller Balance-based voucher generation, commission tracking, sales stats
Financial Income/expense tracking with categories, keuangan reconciliation, Kewajiban BHP & USO for licensed ISPs (revenue base without PPN, write-offs and interconnection deductions, monthly recap, Excel/PDF, April reminder), DPP and PPN per invoice in Export Data, Riwayat Transaksi memuat semua pembayaran termasuk tunai kolektor
Network (FTTH) OLT multi-vendor (ZTE/Huawei/FiberHome/BDCOM/Hioso/Raisecom/C-Data/HSGQ/V-SOL, plus mode Otomatis yang mengenali merek dari OLT) via SNMP+Telnet+SSH, ONU discovery/register/reboot/config (dry-run preview + commit), ODC/ODP/fiber inventory & trace, network map, OLT poll + low-signal & mass-outage alert per PON, ACS RX/offline alert via GenieACS, daftar ONU berbentuk kartu di HP, nama ONU kosong tampil N/A
GenieACS TR-069 CPE/ONT management, WiFi config (SSID/password), device status & uptime
Isolation Auto-isolate expired customers, customizable WhatsApp/Email/HTML landing page templates, fallback MikroTik API kick saat radacct kosong, HOTSPOT local mode isolation/reactivation (manageHotspotUser enable/disable + kick), connectionType-aware reactivation (PPPoE & Hotspot) di semua route (webhook, mark-paid, status, bulk-status, extend)
Cron Jobs 27 automated background jobs (tsx runner via PM2 fork), history, distributed locking, manual trigger, auto-close orphaned/stale sessions
Roles & Permissions 53 permissions, 6 portals (Admin/Customer/Agent/Technician/Collector + SuperAdmin)
Activity Log Audit trail with auto-cleanup (30 days)
Security Session timeout 30 min, idle warning, RBAC, HTTPS/SSL
Performance Redis cache untuk data non-realtime (profiles, areas, routers), graceful degradation jika Redis unavailable
Auth Modes local (MikroTik primary) dan radius (FreeRADIUS primary, PPP secret backup disabled). Auto-migrate radius → local: create PPP secrets from existing customer data + disconnect RADIUS sessions. Migrate local → radius: one-click bulk sync all users to RADIUS tables + disable PPP secrets + CoA kick + reload FreeRADIUS. Bulk re-sync RADIUS (fix out-of-sync data tanpa ubah authMode). hybrid mode obsolete
RADIUS Setup Auto-generated RouterOS script pakai IP asli VPS (bukan domain/Cloudflare proxy), VPN-specific address selection
Bahasa Bahasa Indonesia (full)
PWA Installable di semua portal (admin, customer, agent, technician), offline fallback, service worker cache
Web Push VAPID-based browser push notifications, one shared sidebar subscribe/unsubscribe toggle across all portals (admin, agent, technician, customer, collector), admin broadcast with role targeting (customer/agent/technician/admin/all), cron-triggered push (invoice reminder, auto-isolir, auto-renewal), server-side read state for customer and collector notifications. Collector subscriptions have their own table, apart from the admin one
Collector Portal Portal kolektor berbasis wilayah tugas (area dan/atau router/NAS): ringkasan per bulan, tagihan (tandai lunas tunai/transfer + bukti), pelanggan dengan status pembayaran, peta dan detail GPS, Telepon/WhatsApp, isolir, setoran, koleksi, bukti transfer, cabut ONT, lonceng notifikasi dengan status baca dan web push. Admin: Setoran Kolektor sebagai daftar pembayaran dengan konfirmasi per baris atau massal
Mobile App Native Flutter customer app (mobile/customer_app/) — WiFi control, invoice, payment, tickets with photos, push via FCM — and staff app (mobile/admin_app/). Built per deployment, see docs/BUILD-APK.md
WhatsApp Baileys Native WhatsApp gateway built-in VPS via @whiskeysockets/baileys, PM2 proses terpisah, scan QR langsung di admin panel, auto-reconnect

📱 Mobile App (Flutter, native Android)

Source: mobile/customer_app/. Same customer-facing feature set as the web portal, talking to the same backend/src/app/api/customer/* API. Every install runs on its own domain (no hard-coded host — configurable in-app), and every install needs its own Firebase project for push notifications.

A second native app for staff lives in mobile/admin_app/ (customers, invoices, Kasir QRIS, network map, in-app guides, quick actions; menus follow each account's role). Both apps can update themselves from the APK uploaded in the web panel (Build APK).

  • Build a release APK for your own deployment: docs/BUILD-APK.md — your own Firebase project, signing keystore, flutter build apk --split-per-abi, or the manual GitHub Actions workflow Build Native APKs (signing and google-services.json come from repository secrets; APKs are published to the apk-latest release)
  • Run it in development: mobile/customer_app/README.md
  • Enable push notifications (FCM): mobile/customer_app/FIREBASE_SETUP.md — creating the Firebase project, google-services.json, the backend's service account key, and how to verify each side actually works. Neither credential is ever committed to this repo.

📱 WhatsApp Baileys (Native Gateway)

Provider WhatsApp bawaan tanpa layanan pihak ketiga. Berjalan sebagai proses PM2 terpisah (salfanet-wa) di VPS.

Setup

Provider Baileys otomatis di-setup saat menjalankan updater.sh. Tidak ada konfigurasi tambahan.

# Cek status wa-service
pm2 status
pm2 logs salfanet-wa --lines 20

Cara Pakai

  1. Buka Admin → Pengaturan → WhatsApp → Penyedia
  2. Klik + Tambah Provider, pilih tipe Baileys
  3. Klik QR Code → scan dengan HP (WhatsApp → Linked Devices)
  4. Setelah scan berhasil, modal menampilkan centang hijau konfirmasi
  5. Provider siap digunakan untuk kirim notifikasi

PM2 Processes

Process Mode Port Purpose
salfanet-frontend cluster 3000 Next.js standalone (UI + NextAuth routes)
salfanet-backend fork 3001 Next.js standalone (API routes + Prisma + services)
salfanet-cron fork — Cron runner (calls backend APIs on schedule)
salfanet-wa fork 4000 (internal) Baileys WA service

Auth Session

Session WhatsApp tersimpan di /var/data/salfanet/baileys_auth/ dan persist meski PM2 restart. Untuk logout/scan ulang, klik Restart Session di admin panel.


⚡ Redis Cache (v4.2.0)

Redis digunakan untuk cache data non-realtime agar load halaman lebih cepat. Data realtime (online/offline, sessions, invoices) tidak di-cache.

Yang Di-Cache (TTL 5 menit)

Endpoint Cache Key TTL
GET /api/pppoe/profiles pppoe:profiles 5 menit
GET /api/pppoe/areas pppoe:areas 5 menit
GET /api/network/routers network:routers 5 menit

Cache di-invalidate otomatis saat create/update/delete pada data terkait.

Yang TIDAK Di-Cache (Realtime)

  • /api/pppoe/users — online/offline status harus realtime
  • /api/pppoe/users/[id] — detail user harus realtime
  • /api/invoices — status pembayaran harus realtime
  • /api/sessions/* — session data harus realtime

Graceful Degradation

Jika Redis unavailable, semua function cache tetap jalan dengan fallback ke database langsung (return null, caller query DB).

Setup

Redis harus terinstall dan running di VPS:

sudo apt install redis-server
sudo systemctl enable redis-server
sudo systemctl start redis-server
redis-cli ping  # harus return PONG

File: backend/src/server/cache/redis.ts


📡 RADIUS Enhancements (v3.1.0)

Diadopsi dari FreeRADIUS 3.2.8 schema (home.pmynet.id-main project).

<EFBFBD> Auth Modes: Local vs RADIUS (v5.20.0)

Setiap router/NAS punya dua mode autentikasi yang bisa dipilih di halaman Router settings:

Mode authMode Sumber Auth PPP Secret di MikroTik RADIUS Tables
Local local MikroTik PPP secret (primary) Aktif Backup (selalu di-sync tapi tidak dipakai untuk auth)
RADIUS radius FreeRADIUS (primary) Disabled (backup) Aktif (primary auth source)

Cara Migrasi Local → RADIUS

  1. Pastikan MikroTik sudah dikonfigurasi sebagai RADIUS client — jalankan "Setup RADIUS" di halaman Router untuk generate script RouterOS
  2. Klik tombol "Migrate to RADIUS" (ikon panah kuning) di router card — hanya muncul saat authMode = local
  3. Sistem otomatis:
    • Ubah router.authMode → radius
    • Re-sync SEMUA pelanggan ke RADIUS tables (radcheck, radusergroup, radreply)
    • Disable PPP secrets di MikroTik (sebagai backup)
    • Reload FreeRADIUS
    • CoA disconnect semua sesi aktif (pelanggan re-auth via RADIUS)

Re-sync RADIUS (Tanpa Ubah AuthMode)

Jika data RADIUS tidak sinkron dengan database (misalnya setelah restore atau manual edit):

  • Klik tombol "Re-sync RADIUS" (ikon refresh cyan) di router card
  • Sistem akan re-sync semua pelanggan ke RADIUS tables tanpa mengubah authMode

Bulk Edit Pelanggan

Di halaman Data Pelanggan, select multiple pelanggan via checkbox, lalu klik tombol "Edit" (biru) di bulk action bar untuk mengubah massal:

  • Router/NAS — pindah router (otomatis sync RADIUS + MikroTik secret + CoA)
  • Hari Tagihan — tanggal jatuh tempo bulanan (1-28) untuk POSTPAID
  • Auto Isolasi — aktif/nonaktifkan auto-isolasi saat jatuh tempo

<EFBFBD>📋 Frontend Audit & Centralized API Migration (v4.4.0)

Migrasi frontend dari inline fetch() ke centralized API client (@/lib/api) untuk semua halaman admin. Frontend sekarang UI-only — tidak ada direct Prisma/DB/MikroTik/SSH/FreeRADIUS access.

Dokumentasi lengkap: FRONTEND_AUDIT.md · CHANGELOG.md

Phase 1 — Architectural Cleanup (Prasyarat)

Phase Deskripsi Status
1A Dead code removal (import tidak terpakai, komponen yatim) ✅ Done
1B NextAuth refactor & Prisma removal dari frontend ✅ Done
1C Uploads serving dipindahkan ke Nginx (/uploads/) ✅ Done

Phase 2 — Centralized API Client Migration (Batch 1–52)

Total: 52 batch, 361 inline fetch() calls di-migrasi

Batch Range Halaman Calls Tanggal
1–8b API client + pppoe/profiles, areas, users, invoices, dashboard, keuangan, ippool 80 13 Aug
9 hotspot/voucher 15 13 Aug
10–11 vpn-server + vpn-client 31 13 Aug
12 genieacs/devices 13 13 Aug
13 network/diagrams 10 13 Aug
14 network/map 9 13 Aug
15 network/olts 8 13 Aug
16 hotspot/agent 9 13 Aug
17 network/infrastruktur 8 13 Aug
18 whatsapp/providers 7 13 Aug
19 network/routers 8 13 Aug
20 download-apk 7 13 Aug
21 genieacs/parameter-config 7 13 Aug
22 pppoe/registrations 7 13 Aug
23 genieacs/vp-scripts 6 13 Aug
24 management 6 13 Aug
25 network/trace 4 13 Aug
26 settings/email 6 13 Aug
27 sessions 4 13 Aug
28 notifications 6 13 Aug
29 settings/database 6 13 Aug
30 settings/company 5 13 Aug
31 settings/telegram 5 13 Aug
32 tickets 5 13 Aug
33 inventory/items 5 13 Aug
34 settings/cron 5 13 Aug
35 sessions/pppoe 4 13 Aug
36 network/unified-map 5 13 Aug
37 network/customers 5 13 Aug
38 network/splice-points 5 13 Aug
39 freeradius/backup 5 13 Aug
40 tickets/[id] 5 13 Aug
41 network/odps 5 14 Aug
42 pppoe/users/[id] 5 14 Aug
43 manual-payments 4 14 Aug
44 settings/security (2FA) 4 14 Aug
45 settings/isolation/templates 4 14 Aug
46 settings/genieacs 4 14 Aug
47 pppoe/addons 4 14 Aug
48 data-usage 4 14 Aug
49 whatsapp/send 4 14 Aug
50 push-notifications 4 14 Aug
51 referrals + settings/referral 4 14 Aug
52 whatsapp/templates 2 14 Aug

Centralized API Client (@/lib/api)

import { apiAdmin } from '@/lib/api';
import type { PppoeUserListResponse } from '@/types/api';

// GET — typed response
const data = await apiAdmin<PppoeUserListResponse>('/api/pppoe/users');
// data.users, data.count — fully typed

// POST/PUT/DELETE
const result = await apiAdmin<{ success: boolean; message?: string }>('/api/pppoe/users', {
  method: 'POST',
  body: JSON.stringify(payload),
});

// Error handling otomatis via ApiError
try {
  const data = await apiAdmin<PppoeUserListResponse>('/api/invoices');
} catch (error) {
  // error instanceof ApiError — non-2xx response
}

Fitur:

  • Auth-aware (mengirim session cookie otomatis)
  • Auto JSON parsing
  • Auto Content-Type: application/json header
  • Throw ApiError untuk non-2xx responses
  • Multipart & blob download support
  • Generic typed — apiAdmin<T = unknown> (Phase 6A: default unknown forces explicit typing)
  • 3 auth modes: apiAdmin (cookies), apiCustomer (Bearer), apiAgent (Bearer)

Verification (per batch)

Setiap batch diverifikasi dengan:

  1. ✅ Local build (npm run build) — exit code 0
  2. ✅ Deploy via pscp + remote build + pm2 restart salfanet-frontend
  3. ✅ Production page test via Playwright
  4. ✅ Browser console errors check (0 errors expected)
  5. ✅ Update FRONTEND_AUDIT.md
  6. ✅ Git commit + push

Phase 2 Status

  • Migrated: 52 batch, 361 fetch calls
  • Remaining: ~176 fetch calls di halaman admin lainnya
  • Phase 3 (pending): middleware improvements, error boundaries, theme improvements

Phase 8 — Complete React Query Migration (v5.1.0)

Melanjutkan migrasi React Query dari Phase 7 untuk semua halaman admin tersisa. 80 file diubah, net reduction 875 baris kode.

Yang Dikerjakan

  • Migrasi 65+ halaman admin dari useEffect + apiAdmin + load() ke useApiQuery/useQueryClient
  • Settings (15), GenieACS (6), Network (11), PPPoE+Hotspot (10), FreeRADIUS (6), other admin (23)
  • Mutations menggunakan queryClient.invalidateQueries() bukan manual reload
  • Filter/pagination params masuk ke query key
  • Reference data: staleTime: 300000 (5 menit)
  • Polling: refetchInterval menggantikan setInterval
  • Auth pages (login, 2FA) tidak dimigrasi (intentional)

Verification

  • npx tsc --noEmit: 0 errors
  • npx next build: sukses (local + VPS)
  • PM2: 4 processes online
  • Smoke: health 200, login 200, 404, protected 401, upload 401
  • Commit: 9f03f93f

<EFBFBD> Phase 7 — React Query + Performance Optimizations (v5.0.0)

Implementasi @tanstack/react-query v5 untuk caching, deduplication, dan background refetching.

Dokumentasi lengkap: FRONTEND_AUDIT.md

Yang Dikerjakan

  1. Install @tanstack/react-query v5 + QueryProvider di root layout
  2. Create hooks — useApiQuery, useApiMutation, useAdminQuery, useCustomerQuery, useAgentQuery
  3. Migrate 15 pages to React Query:
    • Dashboard — 30s/5min polling via refetchInterval
    • PPPoE users — 10s online status polling, optimistic updates
    • Hotspot voucher — eliminates 3x duplicate loadVouchers() on mount
    • PPPoE sessions — 10s polling, disconnect mutation
    • Hotspot sessions — 10s polling, sync/disconnect mutations
    • Network: routers, OLTs, ODPs, trace, infrastruktur
    • GenieACS: presets, provisions, files, config, faults
    • Invoices — mark-as-paid, send-reminder, broadcast, generate mutations
    • Keuangan — transactions, categories with 5min staleTime
  4. Remove dead code — lib/utils/export.ts (unused, had eager jsPDF/exceljs imports)
  5. Add loading="lazy" to 27 <img> tags across 16 files
  6. Remove { cache: 'no-store' } from GenieACS pages (RQ handles caching)

Performance Improvements

Metric Before After
Duplicate API calls on mount 5+ instances 0 (RQ dedup)
Pages with no caching ~100 15 migrated (RQ caching)
setInterval polling 8 files 0 (refetchInterval)
Dead code (eager imports) 1 file 0 (removed)
Images without loading="lazy" 27 0
Net code reduction — -1132 lines

Verification

  • TypeScript: 0 errors
  • Build: success
  • No business logic, API endpoint, or HTTP method changes
  • React Query implemented as planned based on profiling data

<EFBFBD>🔒 Phase 6D — UI State & Error Handling Audit (v4.9.0)

Standardisasi error handling, loading states, dan confirmation dialogs di seluruh frontend.

Dokumentasi lengkap: FRONTEND_AUDIT.md

Hasil

Metric Before After
alert() calls 23 0
Swal.fire() direct 14 0
bare confirm() 15 0
silent catch blocks 22 0
error.tsx boundaries 1 4
loading.tsx boundaries 1 4
not-found.tsx 0 1
shared feedback components 0 3

Yang Dikerjakan

  1. alert() → toast — 23 occurrences di 7 files, diganti dengan showError/showSuccess/showInfo
  2. confirm() → showConfirm() — 15 occurrences di 12 files, diganti dengan await showConfirm()
  3. Swal.fire() → CyberToast bridge — 14 occurrences di 3 files (AddNodePanel, NetworkNodePanel, unified-map)
  4. Silent catch blocks — 22 occurrences di 10 files, ditambah console.error/console.warn
  5. Error/loading/not-found boundaries — root, customer, technician portals
  6. Shared feedback components — EmptyState, LoadingSpinner, ErrorState di components/feedback/

Verification

  • TypeScript: 0 errors
  • Build: success
  • No business logic, API endpoint, or HTTP method changes
  • React Query not implemented (deferred to Phase 7)

🔒 Phase 6C — API Client Correctness & Type-Safety Hardening (v4.8.0)

Perbaikan critical API client + pengurangan any ke minimum + dokumentasi semua exceptions.

Dokumentasi lengkap: FRONTEND_AUDIT.md · docs/TYPE_SAFETY_EXCEPTIONS.md

Hasil

Metric Before (6B) After (6C) Reduction
: any 70 2 97%
as any 37 0 100%
<any> 9 5 44%
(data as any) 0 0 —
catch (e: any) 0 0 —
as unknown as 24 24 — (all documented)
TypeScript errors 0 0 —
Build ✅ ✅ —

Yang Dikerjakan

  1. API Client Content-Type fix (CRITICAL) — apiAdmin() tidak lagi memaksakan Content-Type: application/json untuk FormData/Blob/binary. Hanya JSON string body yang dapat Content-Type.
  2. Error handling — 401/403/404/405/429/500+ messages, 204 No Content handling, ApiErrorResponse interface
  3. SplitterDiagram — metadata?: any → PortMetadata / SplitterNodeMetadata interfaces
  4. AddNodePanel — 20 : any → 8 new interfaces
  5. Recharts — 4 : any → TooltipPayloadEntry, TooltipValueType from recharts
  6. Admin pages — 20+ files, as any → typed apiAdmin<T>() calls
  7. Hooks — useTranslation.ts, useSSE.ts — any → unknown / MessageEvent
  8. as unknown as audit — all 24 documented with inline comments (Leaflet, jsPDF, API type boundaries, SplitterNode, custom DOM)
  9. Type Safety Exceptions — docs/TYPE_SAFETY_EXCEPTIONS.md created

Sisa any (2 — all third-party)

  • midtrans-client.d.ts — Midtrans API declaration (no types available)

🔒 Phase 6B — Frontend Type-Safety Hardening (v4.7.0)

Pengurangan any secara signifikan tanpa memaksa any = 0. Membangun safe type system dengan explicit types, safe narrowing, dan response interfaces.

Dokumentasi lengkap: FRONTEND_AUDIT.md

Hasil

Metric Before (6A) After (6B) Reduction
: any 418 70 83%
as any 483 37 92%
(data as any) 272 0 100%
catch (e: any) 257 0 100%
Record<string, any> 6 0 100%
TypeScript errors 0 0 —
Build ✅ ✅ —

Yang Dikerjakan

  1. 257 catch (e: any) → catch (e: unknown) di 88 files dengan safe narrowing (e instanceof Error ? e.message : String(e))
  2. 272 (data as any) casts removed di 40 files — setiap apiAdmin() call sekarang memiliki explicit type argument
  3. 6 Record<string, any> → Record<string, unknown> — semua fixed
  4. Promise<any> → Promise<unknown> di rateLimiter.ts
  5. useState<any> → proper interfaces — EntityFormData, OnuDetailResponse, dll.
  6. 102 files modified — type-only changes, no business logic changes

Sisa any yang Legitimate

  • Promise<any> di midtrans-client.d.ts (third-party declaration)
  • <any> di Leaflet map refs dan React.ComponentType untuk dynamic icons
  • : any di Recharts callbacks dan SplitterDiagram metadata
  • as any di network diagrams SplitterNode conversions

🔒 Phase 6A — Full API Contract & Type-Safety Audit (v4.6.0)

Verifikasi bahwa Phase 5B API types benar-benar digunakan through complete data flow: Backend API → API Response → apiAdmin() → Domain API module → Hook/Page → Component

Dokumentasi lengkap: docs/FRONTEND_API_CONTRACT.md · FRONTEND_AUDIT.md

Hasil Audit

Metric Before After Reduction
Total any patterns 1369 950 31%
(data as any) casts 616 318 48%
TypeScript errors 0 0 —
Lint errors 0 0 —

Yang Dikerjakan

  1. Type definitions updated — semua type di frontend/src/types/api/ di-validasi terhadap actual backend route responses
  2. 2 new type files — types/api/customer.ts (Customer portal) dan types/api/agent.ts (Agent portal)
  3. API client generic fix — apiAdmin<T = any> → apiAdmin<T = unknown> (force explicit typing)
  4. Endpoint fixes — manual payment approve/reject (POST→PATCH), transactions path, settings endpoint, agent endpoints
  5. Top 20 pages fixed — (data as any) casts removed dari dashboard, PPPoE, VPN, GenieACS, invoices, company, OLT, routers, keuangan, voucher, profiles, FreeRADIUS, payment-gateway, email settings, technicians
  6. API contract documentation — docs/FRONTEND_API_CONTRACT.md dengan 34 endpoint documented

Backend Issues Found (documented, NOT fixed)

7 backend issues ditemukan dan didocument di docs/FRONTEND_API_CONTRACT.md:

  1. Missing: DELETE /api/pppoe/users/bulk-delete
  2. Missing: GET /api/invoices/[id]/pdf
  3. Missing: generic /api/settings endpoint
  4. Missing: /api/agent/me and /api/agent/vouchers endpoints
  5. Inconsistent response wrappers (ok(data) vs { success: true, data })
  6. Inconsistent error shapes ({ error } vs { success: false, error })
  7. Inconsistent pagination patterns (3 different shapes)

Deployment

  • Commit: 95bfa7e8
  • VPS: 192.168.54.129 — https://radius.salfa.my.id
  • PM2: All 4 processes online (frontend, backend, cron, wa)
  • Health check: Backend OK, Frontend 200, API 200 via domain

IP Pool Management (/api/admin/ippool)

Dynamic IP allocation via FreeRADIUS ippool module — tidak perlu IP static per user.

Endpoint Method Fungsi
/api/admin/ippool GET List semua pool dengan summary
/api/admin/ippool/stats GET Statistik global (total, allocated, free, utilization)
/api/admin/ippool/:name GET Detail pool + recent allocations
/api/admin/ippool POST Create pool (pool_name, network, start, end)
/api/admin/ippool/expand PUT Expand pool dengan IP tambahan
/api/admin/ippool DELETE Hapus pool (hanya jika tidak ada allocation)
/api/admin/ippool/mappings/list GET List Pool-Name → group mappings
/api/admin/ippool/mappings POST Map pool ke RADIUS group
/api/admin/ippool/mappings/:id DELETE Hapus mapping

Seed IP Pool per speed tier:

cd frontend && npx prisma db seed -- --ippool
# Creates: 10Mbps-Pool, 20Mbps-Pool, 30Mbps-Pool, 50Mbps-Pool
# Each: 1022 IPs (/22 subnet) + auto-mapped to RADIUS groups

Multi-NAS Isolation

Username isolation per-NAS untuk ISP dengan multiple router/MikroTik:

  • Column nas_identifier di radcheck, radreply, radusergroup
  • Auto-sync dari pppoeUser.routerId saat sync ke RADIUS
  • Memungkinkan username yang sama di router berbeda tanpa konflik

CUI (Chargeable User Identity)

Persistent user tracking across sessions/NAS untuk billing & audit:

  • Table cui (FreeRADIUS cui module)
  • Unique identifier per user+IP+MAC combination

🏦 Collector Portal (v5.24.0)

Portal untuk kolektor (petugas tagihan lapangan). Aksesnya mengikuti wilayah tugas yang ditetapkan admin di Kelola Kolektor: satu area, satu atau beberapa router/NAS, atau keduanya. Kolektor tanpa area dan NAS tidak melihat data apa pun.

Halaman

Halaman Fungsi
Ringkasan Tagihan per bulan (bulan lalu dan bulan depan bisa dibuka), total belum dibayar dan sudah lunas, setoran bulan ini, filter area dan router/NAS
Tagihan Tabel pelanggan dengan invoice yang bisa dibuka. Tandai lunas tunai atau transfer (bukti transfer dikompres otomatis), janji bayar, kirim bukti lunas lewat WhatsApp, cetak ke printer Bluetooth
Pelanggan Semua pelanggan di wilayah tugas: tabel yang bisa diurutkan, status pembayaran (Lunas, Belum lunas, Terlambat) dengan filter, tampilan Daftar atau Peta, detail pelanggan dengan GPS, pratinjau peta, dan navigasi Google Maps, tombol Telepon dan WhatsApp (pesan pengingat tagihan sudah terisi)
Pelanggan Isolir Pelanggan terisolir dengan jumlah tagihan, aksi tagih dan catat cabut ONT
Setoran Saya Pembayaran yang dicatat per hari atau per rentang tanggal, tunai dan transfer, dan status konfirmasi admin
Koleksi Saya Riwayat 6 bulan terakhir per bulan dan transaksi terbaru
Bukti Transfer Bukti transfer pelanggan di wilayah tugas dan hasil pemeriksaan admin
Cabut ONT Riwayat perangkat yang dicabut dari pelanggan isolir
Lonceng Notifikasi dengan status baca (klik untuk membaca, Tandai semua dibaca) dan web push

Notifikasi: setoran dikonfirmasi atau dibatalkan, bukti transfer disetujui atau ditolak, pembayaran dicatat admin ke kolektor, tiket ditugaskan. Web push butuh HTTPS dan kunci VAPID di .env backend; tombol Aktifkan ada di banner halaman, sidebar, dan panel lonceng, dan menjelaskan alasannya bila perangkat tidak bisa push.

Admin

  • Kelola Kolektor: tambah, edit, hapus (kolektor yang pernah menagih hanya bisa dinonaktifkan), area opsional, pilihan beberapa router/NAS, alamat login dengan tombol salin
  • Setoran Kolektor: satu daftar semua pembayaran kolektor (pelanggan, kolektor, invoice, tunai atau transfer, bukti, status). Konfirmasi per baris, atau centang beberapa dan konfirmasi sekaligus; mode harian dan rentang tanggal
  • Tagihan, Tandai Lunas: bisa memilih kolektor yang menerima uang dan metodenya, supaya masuk ke setorannya. Invoice lunas yang tidak dimiliki kolektor tampil di Setoran Kolektor dan bisa dicatat ke kolektor

API Endpoints

Endpoint Method Fungsi
/api/collector/auth/login POST Login kolektor (cookie collector-token)
/api/collector/auth/session GET Sesi, wilayah tugas, pilihan filter
/api/collector/dashboard GET Ringkasan per bulan (period, areaId, routerId)
/api/collector/users GET Pelanggan di wilayah tugas dengan status pembayaran (filter unpaid/all/paid)
/api/collector/isolir GET Pelanggan isolir di wilayah tugas
/api/collector/mark-paid POST Tandai invoice lunas, tunai atau transfer dengan bukti
/api/collector/ont-removals GET/POST Catatan cabut ONT
/api/collector/proofs GET Bukti transfer di wilayah tugas
/api/collector/my-collections GET Riwayat koleksi kolektor
/api/collector/my-settlements GET Setoran harian, dan /range untuk rentang tanggal
/api/collector/notifications GET/POST Daftar notifikasi, tandai dibaca (ids atau all)
/api/push/collector-subscribe POST Daftarkan web push kolektor (collector-unsubscribe untuk mencabut)
/api/collector/list GET Daftar kolektor (admin)
/api/collector/setoran GET Setoran semua kolektor per hari (date) atau rentang (from, to) (admin)
/api/collector/setoran/confirm POST Konfirmasi atau batalkan beberapa pembayaran (invoiceIds, confirm) (admin)
/api/collector/setoran/unassigned GET/POST Invoice lunas tanpa kolektor, dan mencatatnya ke kolektor (admin)
/api/collector/setoran/collectors GET Kolektor aktif untuk pilihan penerima uang (admin)
/api/collector/confirm-settlement POST/DELETE Konfirmasi atau batalkan setoran satu hari (dipakai aplikasi admin)

Keamanan

  • Sesi kolektor memakai cookie JWT terpisah dari sesi admin, dan akun dicek ke database di setiap permintaan (aktif dan role COLLECTOR)
  • Setiap rute memeriksa wilayah tugas (area dan router/NAS) di sisi server, termasuk tandai lunas, janji bayar, kirim invoice, dan PDF invoice
  • Setoran dan konfirmasi di admin memakai izin keuangan.view dan keuangan.edit
  • Langganan web push kolektor disimpan di tabel sendiri, terpisah dari langganan admin
  • Bukti transfer disimpan sebagai base64 (MediumText) di DB dan diperiksa admin di Verifikasi Bukti Transfer

🚀 Tech Stack

Component Technology
Frontend Next.js 16 (App Router, standalone output) — UI + NextAuth
Backend Next.js 16 (App Router, standalone output) — API routes + Prisma + services
Cron Separate tsx runner (PM2 fork, calls backend APIs)
Language TypeScript (shared types via @salfanet/shared-types)
Styling Tailwind CSS
Database MySQL 8.0 + Prisma ORM
RADIUS FreeRADIUS 3.0.26
Process Manager PM2 (4 processes)
Reverse Proxy Nginx (/api/auth/* → frontend, /api/* → backend, / → frontend)
Session Tracking FreeRADIUS radacct + MikroTik /ppp/active (realtime polling)
Maps Leaflet / OpenStreetMap
Package Manager pnpm (monorepo workspaces)

📁 Project Structure

salfanet-radius/                  # pnpm monorepo root
├── frontend/                     # Next.js — UI + NextAuth (port 3000)
│   ├── src/
│   │   ├── app/                  # Admin, agent, customer, technician, collector portals
│   │   │   └── api/auth/         # NextAuth routes only
│   │   ├── components/           # Shared React components
│   │   ├── features/             # Vertical slices (queries, schemas)
│   │   ├── lib/api-client.ts     # Centralized API client (→ backend port 3001)
│   │   ├── locales/              # i18n (id, en)
│   │   └── hooks/                # React hooks (usePermissions, useTranslation, etc.)
│   ├── scripts/postbuild.js      # Copy static assets to standalone (monorepo)
│   └── package.json
├── backend/                      # Next.js — API + Prisma + services (port 3001)
│   ├── src/
│   │   ├── app/api/              # API route handlers (/api/pppoe, /api/invoices, etc.)
│   │   ├── server/               # Services (mikrotik, radius, cron, pppoe, billing)
│   │   │   ├── services/mikrotik/  # MikroTik API integration
│   │   │   ├── cron/               # Cron job logic
│   │   │   └── db/client.ts        # Prisma client
│   │   └── lib/api-response.ts    # Standard API response helpers
│   ├── prisma/                   # Prisma schema + migrations + seeds
│   ├── freeradius-config/        # FreeRADIUS config templates
│   ├── scripts/postbuild.js      # Copy static assets to standalone (monorepo)
│   ├── cron-runner.ts            # Standalone cron runner entry point
│   └── package.json
├── packages/                     # Shared TypeScript types
│   └── shared-types/
├── mobile/                       # Native Flutter apps
│   ├── customer_app/             # Customer portal app (payment, WiFi, tickets, FCM)
│   └── admin_app/                # Staff app (customers, invoices, Kasir QRIS, map)
├── deploy/                       # Deployment configuration
│   ├── ecosystem.config.js       # PM2 config (4 processes: frontend, backend, cron, wa)
│   ├── nginx-salfanet.conf       # Nginx reverse proxy (2-app routing)
│   └── README.md
├── docs/                         # Documentation
├── frontend/vps-install/         # VPS installer scripts (installer, updater, uninstaller)
├── frontend/production/          # Production deployment configs
└── pnpm-workspace.yaml

⚙️ Installation

ssh root@YOUR_VPS_IP

git clone https://github.com/s4lfanet/salfanet-radius.git /root/salfanet-radius
cd /root/salfanet-radius
bash frontend/vps-install/vps-installer.sh

Installer akan berjalan interaktif — mendeteksi environment otomatis, memandu konfigurasi, lalu menjalankan semua step.


Metode 2 — Upload Manual via SCP (Tanpa Akses Internet di Server)

# Jalankan di terminal LOKAL (bukan di server)
scp -r ./salfanet-radius root@YOUR_VPS_IP:/root/salfanet-radius

# SSH ke server, lalu jalankan installer
ssh root@YOUR_VPS_IP
cd /root/salfanet-radius
bash frontend/vps-install/vps-installer.sh

Environment yang Didukung

Environment Flag Akses
Public VPS (DigitalOcean, Vultr, Hetzner, AWS) --env vps Internet
Proxmox LXC --env lxc LAN/VLAN
Proxmox VM / VirtualBox --env vm LAN
Bare Metal / Server Fisik --env bare LAN
# Contoh: paksa environment + IP
bash frontend/vps-install/vps-installer.sh --env lxc --ip 192.168.1.50

Updating Existing Installation

Cara paling aman. Semua data upload (logo, foto KTP pelanggan, bukti bayar) otomatis dipreservasi.

bash /var/www/salfanet-radius/frontend/vps-install/updater.sh

Atau update dari branch terbaru secara manual:

cd /var/www/salfanet-radius
git pull origin master
pnpm install
cd backend && npx prisma generate && npx prisma db push && cd ..
cd frontend && npx prisma generate && cd ..
# Build both apps
cd backend && NODE_OPTIONS='--max-old-space-size=1536' npx next build && node scripts/postbuild.js && cd ..
cd frontend && NODE_OPTIONS='--max-old-space-size=1536' npx next build && node scripts/postbuild.js && cd ..
# Restart PM2
pm2 restart salfanet-frontend salfanet-backend salfanet-cron --update-env

Lihat detail lengkap di vps-install/README.md.


Data yang Aman Saat Update

Data Status
Logo perusahaan (public/uploads/logos/) ✅ Dipreservasi
Foto KTP & dokumen pelanggan ✅ Dipreservasi
Bukti pembayaran ✅ Dipreservasi
File .env (database, secrets) ✅ Tidak disentuh
Database MySQL (semua data pelanggan) ✅ Tidak disentuh

Default Credentials

Admin URL http://YOUR_VPS_IP/admin/login
Username superadmin
Password admin123

⚠️ Ganti password segera setelah login pertama!

APK Android di Instalasi Baru

Instalasi baru belum bisa membuat APK. APK WebView portal dan QRIS Listener di-build di server setelah Java 17 + Android SDK dipasang sekali (perintahnya tampil di Admin → Download APK). Aplikasi native admin dan pelanggan di-build di PC/laptop atau GitHub Actions dengan Firebase dan keystore milik sendiri. Langkah lengkap: docs/BUILD-APK.md.


🔌 FreeRADIUS

Key config files at /etc/freeradius/3.0/:

File Purpose
mods-enabled/sql MySQL connection for user auth
mods-enabled/rest REST API for voucher management
sites-enabled/default Main auth logic (PPPoE realm support)
clients.conf NAS/router clients (+ $INCLUDE clients.d/)
sites-enabled/coa CoA/Disconnect-Request virtual server

Config backup in freeradius-config/ is auto-deployed by the installer.

Auth Flow

PPPoE: MikroTik → FreeRADIUS → MySQL (radcheck/radusergroup/radgroupreply) → Access-Accept with Mikrotik-Rate-Limit

Hotspot Voucher: Same RADIUS path + REST /api/radius/post-auth → sets firstLoginAt, expiresAt, syncs keuangan

RADIUS Tables

Table Purpose
radcheck User credentials
radreply User-specific reply attrs
radusergroup User → Group mapping
radgroupreply Group reply (bandwidth, session timeout)
radacct Session accounting
nas NAS/Router clients (dynamic)

⏰ Cron Jobs (27 automated)

Dijalankan oleh cron-runner.ts (PM2 salfanet-cron). Jadwal bawaan bisa diubah, job bisa diaktif/nonaktifkan, dan semua job bisa dipicu manual dari Settings → Cron di panel admin. Detail lengkap: docs/features/CRON-SYSTEM.md

Job Schedule Function
Hotspot Sync Every minute Sync voucher hotspot aktif/kedaluwarsa
External Task Processor Every minute Antrean tugas keluar (MikroTik, WhatsApp, email, CoA)
Agent Sales Every 5 min Catat penjualan voucher agent
Disconnect Sessions Every 5 min CoA disconnect pelanggan isolir/stop
PPPoE Session Sync Every 5 min Sync sesi PPPoE MikroTik ke radacct
RADIUS Sync Retry Every 5 min Ulangi sync FreeRADIUS yang gagal (backoff)
FreeRADIUS Health Every 5 min Auto-restart if down
Hotspot Voucher Sync Every 5 min Status voucher dari router local-only
OLT Poll Every 5 min Polling semua OLT (status, ONU, RX power, alert)
Telegram Backup Every 5 min Backup DB ke Telegram sesuai jadwal pengaturan
ACS Alert Every 3 min RX degradation & ONU offline via GenieACS
Session Monitor Every 15 min Security session monitoring
Auto Isolir (PPPoE) Every hour Isolir pelanggan prepaid kedaluwarsa dan postpaid sesuai pilihan Tanggal Isolir
Invoice Reminder Every hour Reminder invoice (kirim sesuai jam di pengaturan)
Invoice Status Every hour PENDING → OVERDUE setelah jatuh tempo
Suspend Check Every hour Activate/restore suspend requests
Notification Check Every 6 hours Notifikasi admin (invoice telat, expired, pendaftaran)
Activity Log Cleanup Daily 2 AM Log >30 hari + notifikasi kolektor lama
Webhook Log Cleanup Daily 3 AM Delete webhook logs >7 days
RADIUS Auth Log Cleanup Daily 3:30 AM Hapus radpostauth >30 hari (per batch)
Cron History Cleanup Daily 4 AM Hapus cron history >30 hari
Financial Reconciliation Daily 5 AM Kecocokan invoice–payment, deteksi anomali
Auto Stop Daily 5 AM Stop pelanggan isolir >30 hari (nonaktif bawaan)
Invoice Auto-Cancel Daily 6 AM Batalkan invoice basi (pelanggan sudah perpanjang)
RADIUS Reconciliation Daily 6 AM Selisih data SalfaNet ↔ FreeRADIUS
Invoice Generate Daily 7 AM Generate invoice bulanan (postpaid)
Auto Renewal Daily 8 AM Prepaid auto-renew dari saldo

📱 Android APK Builder

Buat APK Android (WebView wrapper) untuk 4 portal langsung di server VPS — tanpa GitHub Actions, tanpa Android Studio. Logo perusahaan yang diupload di halaman Download APK otomatis digunakan sebagai ikon APK (server build maupun ZIP project).

APK QRIS Listener (standalone, untuk HP kasir) di-build dengan cara yang sama dari Admin → Payment Gateway → tab QRIS Mandiri; device key dan webhook server sudah terisi di APK.

Aplikasi native Flutter (admin dan pelanggan) tidak di-build di sini — butuh Flutter SDK dan RAM besar. Pakai GitHub Actions atau build lokal, lihat docs/BUILD-APK.md.

1) Setup Android SDK (satu kali via SSH)

apt-get update && apt-get install -y openjdk-17-jdk wget unzip && \
mkdir -p /opt/android/cmdline-tools && \
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdtools.zip && \
unzip -q /tmp/cmdtools.zip -d /opt/android/cmdline-tools && \
mv /opt/android/cmdline-tools/cmdline-tools /opt/android/cmdline-tools/latest && \
yes | /opt/android/cmdline-tools/latest/bin/sdkmanager --licenses && \
/opt/android/cmdline-tools/latest/bin/sdkmanager "platforms;android-34" "build-tools;34.0.0" && \
echo 'export ANDROID_HOME=/opt/android' >> /etc/environment && \
echo 'Selesai!'

Perkiraan waktu: ~5–10 menit (download ~500MB). Disk yang dibutuhkan: ~2GB.

2) Build APK via Admin Panel

Buka Admin → Download Aplikasi Android → klik Build APK pada role yang diinginkan.

  • Build berjalan di background (tidak timeout meski butuh beberapa menit)
  • Status diperbarui otomatis setiap 3 detik
  • Setelah selesai, tombol Download APK muncul

3) Build via API (opsional)

# Cek environment
curl http://YOUR_VPS/api/admin/apk/trigger

# Mulai build (role: admin | customer | technician | agent | qris_listener)
curl -X POST http://YOUR_VPS/api/admin/apk/trigger?role=customer \
  -H "Cookie: next-auth.session-token=..."

# Cek status
curl http://YOUR_VPS/api/admin/apk/status?role=customer

# Download APK
curl -OJ http://YOUR_VPS/api/admin/apk/file?role=customer \
  -H "Cookie: next-auth.session-token=..."

Storage APK

Path Keterangan
/var/data/salfanet/apk/{role}/app.apk File APK hasil build
/var/data/salfanet/apk/{role}/status.json Status & metadata build
/var/data/salfanet/apk/{role}/build.log Log Gradle
/var/data/salfanet/gradle-cache Cache Gradle (mempercepat build berikutnya)

Paket Aplikasi

Role Package ID Warna
Admin net.salfanet.admin Biru
Customer net.salfanet.customer Cyan
Technician net.salfanet.technician Hijau
Agent net.salfanet.agent Ungu

🛠️ Common Commands

# PM2
pm2 status
pm2 logs salfanet-frontend --lines 50
pm2 logs salfanet-backend --lines 50
pm2 logs salfanet-cron --lines 50
pm2 restart salfanet-frontend salfanet-backend salfanet-cron --update-env

# FreeRADIUS
systemctl restart freeradius
freeradius -XC    # Test config
radtest 'user@realm' password 127.0.0.1 0 testing123

# Database
mysql -u salfanet_user -psalfanetradius123 salfanet_radius
mysqldump -u salfanet_user -psalfanetradius123 salfanet_radius > backup.sql

# Build (manual)
cd /var/www/salfanet-radius
cd backend && npx prisma generate && NODE_OPTIONS='--max-old-space-size=1536' npx next build && node scripts/postbuild.js && cd ..
cd frontend && NODE_OPTIONS='--max-old-space-size=1536' npx next build && node scripts/postbuild.js && cd ..
pm2 restart salfanet-frontend salfanet-backend --update-env

🧯 Troubleshooting Cepat

1) Website tidak bisa diakses dari IP VPS

Jika Nginx dan app sudah jalan di server tapi dari internet tetap tidak bisa akses, biasanya masalah ada di layer jaringan (NAT/forwarding/firewall external), bukan di aplikasi.

# Di VM/VPS guest
ss -tulpn | grep -E ':80|:443|:3000|:3001'
curl -I http://127.0.0.1:3000   # frontend
curl -I http://127.0.0.1:3001   # backend
curl http://127.0.0.1:3001/api/health  # backend health check
curl -I http://127.0.0.1
systemctl status nginx --no-pager
pm2 status

Jika semua check local di atas OK, cek mapping di host Proxmox/router/cloud firewall:

  1. Public:2020 -> VM:22 (SSH)
  2. Public:80 -> VM:80 (HTTP)
  3. Public:443 -> VM:443 (HTTPS)

Catatan: IP:2020 adalah port SSH, bukan URL web aplikasi.

2) PM2 jalan tapi web tetap blank/error

pm2 status
pm2 logs salfanet-frontend --lines 100
pm2 logs salfanet-backend --lines 100
cd /var/www/salfanet-radius
# Rebuild both apps
cd backend && NODE_OPTIONS='--max-old-space-size=1536' npx next build && node scripts/postbuild.js && cd ..
cd frontend && NODE_OPTIONS='--max-old-space-size=1536' npx next build && node scripts/postbuild.js && cd ..
pm2 restart salfanet-frontend salfanet-backend --update-env

3) API returns 404 (route not found)

Pastikan nginx routing benar — /api/auth/* → frontend (3000), /api/* → backend (3001):

nginx -T 2>&1 | grep -A2 'location.*api'
# Should show:
#   location /api/auth/  → proxy_pass http://127.0.0.1:3000
#   location /api/       → proxy_pass http://127.0.0.1:3001

4) Jalankan diagnosa Nginx otomatis dari installer

Installer Nginx terbaru menambahkan self-check internal (127.0.0.1:3000, 127.0.0.1) dan best-effort check publik (HTTP/HTTPS).

cd /var/www/salfanet-radius
bash frontend/vps-install/install-nginx.sh

Jika warning menunjukkan HTTP publik tidak reachable, fokus perbaikan di NAT/port-forward/security-group, bukan di Next.js.


🔐 Security

# Firewall
ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw allow 8080/tcp
ufw allow 1812/udp && ufw allow 1813/udp && ufw allow 3799/udp
  1. Change default admin password on first login
  2. Change MySQL passwords in .env
  3. Configure SSL (Let's Encrypt or Cloudflare)
  4. Enable UFW

📡 CoA (Change of Authorization)

Sends real-time speed/disconnect commands to MikroTik without dropping PPPoE connections.

MikroTik requirement: /radius incoming set accept=yes port=3799

API: POST /api/radius/coa — actions: disconnect, update, sync-profile, test

Auto-triggered when: PPPoE profile speed is edited (syncs all active sessions).


📲 WhatsApp Providers

Provider Base URL Auth
Fonnte https://api.fonnte.com/send Token
WAHA http://IP:PORT API Key
GOWA http://IP:PORT user:pass
MPWA http://IP:PORT API Key
Wablas https://pati.wablas.com Token
WABlast http://IP:PORT API Key
Kirimi.id https://api.kirimi.id Token/Secret
Baileys (native) PM2 salfanet-wa (port 4000) QR scan di admin panel

⏱️ Timezone

Layer Timezone Note
Database (Prisma) UTC Prisma default
FreeRADIUS WIB (UTC+7) Server local time
PM2 env WIB TZ: 'Asia/Jakarta' in ecosystem.config.js
API / Frontend WIB Auto-converts UTC ↔ WIB

For WITA (UTC+8) or WIT (UTC+9): change TZ in .env, ecosystem.config.js, and src/lib/timezone.ts.


📋 Admin Modules

Dashboard · PPPoE (users, customers, profiles, areas, addons) · Hotspot (voucher, e-voucher, agent, template, rekap) · Invoice · Payment (gateway, proofs, bank accounts, manual) · Keuangan · Laporan (invoice, payment, customer, per-paket) · Sessions · Tickets · Network (OLT/ODC/ODP, fiber, map, routers) · GenieACS · Agent · Collector (kelola & setoran) · Technician · WhatsApp · Notifications & Push · IP Pool · Referral · Settings (cron, telegram, email, security, isolation, Cloudflare tunnel) · System (update, logs, APK)

Roles: SUPER_ADMIN · FINANCE · CUSTOMER_SERVICE · TECHNICIAN · MARKETING · VIEWER


🟢 Realtime Online/Offline Status

Status online/offline pelanggan PPPoE di admin page (/admin/pppoe/users) diperbarui otomatis setiap 10 detik tanpa reload halaman.

Cara kerja:

  1. Frontend polling GET /api/pppoe/users/online-status setiap 10 detik
  2. Backend cek radacct (RADIUS users) + MikroTik /ppp/active (local users)
  3. Frontend update isOnline field hanya jika ada perubahan (cegah unnecessary re-render)
  4. Badge "Live" dengan indikator pulse di filter Sesi

Endpoint: GET /api/pppoe/users/online-status?usernames=user1,user2,...

Response:

{ "online": ["user1", "user3"], "onlineCount": 2, "total": 5, "timestamp": "..." }

🔧 PPPoE Reconnect Setelah Payment

Saat pelanggan isolir dilunaskan (manual atau auto-renewal), sistem otomatis:

  1. Update status user → active
  2. Restore RADIUS radcheck (password) + radusergroup (profile group) dengan nas_identifier
  3. Hapus entry isolir dari radreply
  4. Restore Framed-IP-Address jika user punya IP static
  5. Restore MikroTik PPP secret: enable + set profile ke group user (bukan isolir)
  6. Kick session lama via MikroTik + RADIUS CoA disconnect
  7. User reconnect otomatis dengan profile yang benar

FreeRADIUS config penting:

  • rest module → connect_uri = "http://localhost:3001" (backend, bukan frontend)
  • sqlippool, sql, cuisql di post-auth → non-fatal (- prefix) agar auth tetap berhasil jika pool gagal

📝 Changelog

Bagian ini otomatis sinkron dari CHANGELOG.md saat file changelog berubah di GitHub.

v5.46.0 — 2026-11-08 - Tampilan Mobile OLT, Nama ONU N/A, dan Tema Terang/Gelap Seragam

Tampilan mobile OLT

  • [FIX] Detail OLT: judul dan tombol Export/Sync tidak lagi saling menimpa di layar kecil, lima tab bisa digeser ke samping, dan kartu status ONU tidak menggantung sendirian
  • [NEW] Daftar ONU di Detail OLT tampil sebagai kartu per ONU di HP (tabel 11 kolom tetap dipakai di layar tablet ke atas), lengkap dengan Detail, Assign/Register, Config, Reboot dengan konfirmasi, Hapus, dan Pilih semua untuk reboot massal
  • [FIX] OLT Monitoring: tombol dan filter selebar layar, info perangkat 2 kolom, teks 9px dinaikkan, tombol aksi 36px, dan tombol di dalam tautan dihapus
  • [FIX] OLT Management: status kartu berlabel Online/Offline (sebelumnya hanya emoji), jumlah ONU/ODC/ODP sejajar, tombol Detail/Edit/Hapus berukuran sentuh

Nama dan deskripsi ONU

  • [FIX] ONU yang namanya belum diisi tampil N/A di OLT Hioso dan ZTE secara seragam. Nilai kosong Hioso (NA, N/A, NULL, -, nol-biner) kini dinormalkan di jalur web, CLI, dan SNMP
  • [FIX] Baris Nama di OLT dan Deskripsi di detail ONU selalu tampil, berisi N/A bila kosong. Tabel rack ikut N/A, bukan -
  • [FIX] Aplikasi admin membaca field name yang tidak ada di data ONU, sehingga Nama dan Deskripsi hilang. Kini satu baris Nama / Deskripsi dari description, N/A bila kosong

Warna dan tombol mengikuti tema (terang dan gelap)

  • [CHANGE] Komponen Button bersama tidak lagi bergaya cyan/neon (gradien, teks hitam, huruf kapital); kini memakai token tema primary, destructive, success, warning, outline, secondary, dan ghost. CyberButton di portal ikut memakai token
  • [CHANGE] Warna teal dan warna solid acak (biru, indigo, cyan, oranye, ungu, hijau) di PPPoE, Tagihan, Bukti Bayar, Suspend, ONT Removal, Payment Gateway, Deposit Agen, Jaringan, GenieACS, Pengaturan, dan portal customer, agen, teknisi, kolektor diganti token tema
  • [CHANGE] Tombol aksi ONU dibedakan menurut fungsi: Detail biru brand, Assign biru lembut, Config netral, Reboot amber, Hapus merah
  • [NEW] ToggleRow (label di kiri, switch di kanan) dipakai untuk monitoring, SNMP, SSH, dan Telnet di Pengaturan OLT, serta SSH, Telnet, dan Follow road di form tambah/ubah OLT. Track Switch nonaktif lebih jelas, checkbox kecil diperbesar
  • [FIX] Sekitar 350 teks dan badge berwarna pucat (-300/-400) yang tidak terbaca di latar putih kini punya varian -600/-700 untuk mode terang. Mode gelap tidak berubah
  • [FIX] Aplikasi admin: warna status ONT dan titik lokasi di peta jaringan memakai tone tema

Hal yang perlu diketahui

  • Belum dilihat langsung di layar. Perubahan tampilan hanya diperiksa tipenya dan lint, belum dibandingkan di HP dan browser pada mode terang dan gelap. Dart di aplikasi admin belum dianalisis di mesin pengembangan; build CI yang memverifikasi
  • Peta unified-map dan chip kategori Push Notifications sengaja tidak diubah karena warnanya fungsional
  • Aplikasi collector Flutter tidak termasuk dalam audit tema

Catatan Pembaruan

  • Tidak ada perubahan skema. Aplikasi admin 1.0.39+40. Naikkan versi: root, backend, frontend ke 5.46.0

v5.45.0 — 2026-11-07 - Tipe ONU, Kipas ZTE, Modul Optik, Ganti Nama dan Reboot ONU, Dasan

Reboot ONU

  • [FIX] Reboot ONU ZTE kini menjawab pertanyaan Confirm to reboot? [yes/no]. Sebelumnya tidak dijawab, sehingga perintah habis waktu dan ONU tidak pernah reboot. Temuan ini dari NMS, yang mengujinya di OLT produksi
  • [CHANGE] ONU bukan merek ZTE di OLT ZTE di-reboot dengan shutdown, jeda 2 detik, lalu no shutdown pada interface-nya, sama seperti NMS, karena merek lain tidak bereaksi pada reboot
  • [FIX] Reboot ONU Hioso tidak lagi mengirim perintah Huawei (ont reset). Kini lewat Telnet, dan berhenti bila OLT menolak salah satu perintah
  • [FIX] Batch reboot ZTE lewat SSH salah satu nomor port: nomor port di database dihitung dari 0, di CLI dari 1

Ganti nama ONU

  • [NEW] Tombol Config kini berlaku untuk Hioso: lewat form web OLT, dibaca ulang untuk memastikan, lalu Telnet bila belum berubah. Nama baru langsung tersimpan

Data baru

  • [NEW] Tipe ONU tampil di daftar ONU dan Port Map, untuk Hioso, ZTE C320 V2.1, ZTE C300, C-Data GPON, dan HSGQ tabel lama
  • [NEW] Kolom FAN diagram ZTE membaca show fan: jumlah aktif dan RPM. Sebelumnya hanya mengikuti status OLT. Tanpa Telnet tertulis "Tidak terbaca"
  • [NEW] Tombol Baca modul optik per port PON untuk ZTE dan Hioso. Klik port pada diagram ZTE atau Port Map Hioso
  • [NEW] Merek Dasan di form, impor Excel, deteksi otomatis, dan aplikasi admin, lewat SNMP
  • [NEW] Isian Port Web untuk Hioso di form OLT, pengaturan detail OLT, dan aplikasi admin

Hal yang perlu diketahui

  • Belum diuji pada OLT sungguhan. Sesi Telnet Hioso dan parser diuji dengan server dan data tiruan. Jawaban yes pada ZTE berjalan lewat expect, yang tidak bisa dijalankan di mesin pengembangan, jadi belum pernah dijalankan sama sekali. Halaman baru hanya diperiksa tipenya
  • Reboot ONU Hioso paling tidak pasti. Sintaks epon onu <id> reset hanya templat di NMS dan tidak pernah dipakai di sana. Coba pada satu ONU uji dulu. Bila ditolak, pesan OLT tampil dan tidak ada yang berubah
  • Reboot ONU non-ZTE memutus ONU sebentar, dan cara ini berlaku juga bila serial ONU tersimpan tanpa awalan ZTE. Coba pada satu ONU dulu
  • Kolom diagram ZTE kini memakai satu perintah Telnet tambahan, show fan, jadi diagram bisa sedikit lebih lambat terbuka. Pembacaan ZTE C320 juga bertambah satu walk SNMP per port untuk tipe ONU
  • Tipe ONU terisi setelah poll berikutnya, tidak mundur untuk data lama

Catatan Pembaruan

  • Menambah kolom onuType pada tabel ONU dan webPort pada tabel OLT, diterapkan otomatis saat pembaruan. Aplikasi admin 1.0.37. Naikkan versi: root, backend, frontend ke 5.45.0

v5.44.0 — 2026-11-07 - Peta Port Semua Merek OLT dan Alur Baca dari NMS

Peta port

  • [NEW] Tab Port Map kini berlaku untuk semua merek. Selain ZTE, tampil port PON dengan jumlah ONU online, termasuk port kosong, dan port uplink dari IF-MIB. Klik port PON untuk melihat ONU-nya: nama, serial atau MAC, status, Rx, dan jarak. Sebelumnya semua merek digambar sebagai sasis ZTE C320 dan OLT merek lain dikirimi perintah show card
  • [CHANGE] Bila status uplink tidak terbaca, G1 sampai G4 tampil "Tidak terbaca" dengan keterangan. NMS menampilkannya Up mengikuti status OLT; itu sengaja tidak ditiru karena bukan data
  • [CHANGE] Judul diagram ZTE mengikuti model OLT, dan kartu uplink C300 (SCXN, SCXM, SCXO, HUVQ) tidak lagi dianggap kartu layanan

Hioso mengikuti NMS

  • [CHANGE] Urutan baca sama dengan NMS: halaman web menjadi daftar ONU utama, SNMP cadangan. Sebelumnya SNMP dasar dan halaman hanya pelengkap
  • [FIX] OLT Hioso yang SNMP-nya tidak menjawab tidak lagi tampil offline bila web atau Telnet hidup. Sebelumnya poll memakai tes Telnet bawaan yang tidak cocok dengan Hioso
  • [NEW] Telnet membaca suhu modul PON, memori, dan CPU dalam satu sesi bersama daftar ONU. /system.asp memberi CPU, memori, model, dan firmware; model dan firmware hanya mengisi kolom yang kosong
  • [FIX] Status SNMP 2 kini LOS, bukan offline, sesuai NMS
  • [NEW] Jarak dari SNMP diisi dengan rumus NMS, untuk EPON, GPON, dan clone BDCOM. Serial memakai format MAC yang sama dengan halaman web
  • [NEW] Uji Koneksi menampilkan baris Web untuk Hioso beserta jumlah ONU atau alasan gagal

ZTE C300

  • [NEW] Model yang memuat C300 dibaca dari tabel C300 milik NMS, dengan status 4 sebagai online dan rumus daya C300. Sebelumnya tabel ini tidak dipakai untuk C300. Bila kosong, jalur lama tetap berjalan. C320 tidak berubah

Merek lain

  • [NEW] Tabel NMS untuk V-SOL, Huawei, BDCOM, FiberHome, dan Raisecom ditambahkan sebagai cadangan. Selain V-SOL, tabel ini hanya dibaca bila Telnet dan SSH tidak menemukan ONU, jadi instalasi yang sudah berjalan tidak berubah

Angka offline

  • [FIX] Jumlah ONU offline dan aturan peringatan kini menghitung LOS dan dying gasp. Sebelumnya hanya status offline, sehingga ONU LOS tidak terhitung. Angka offline untuk merek apa pun, termasuk ZTE, bisa terlihat naik setelah pembaruan

Hal yang perlu diketahui

  • Belum diuji pada OLT sungguhan. Logikanya diuji dengan data SNMP dan server Telnet tiruan, dan halaman Port Map baru diperiksa tipenya, belum dibuka di browser
  • Baris ONU Hioso bisa berganti setelah pembaruan bila sumbernya berganti dari SNMP ke web. Baris lama yang tidak terbaca lagi dihapus otomatis saat poll, seperti biasa
  • Jarak Hioso dari SNMP mengikuti NMS, sedangkan KusumaVision NMS menyebut nilainya sudah meter. Bandingkan satu ONU dengan halaman web OLT
  • Jarak C300 disimpan sebagai meter sesuai komentar OID di NMS, walau kode NMS mengalikan 0,112. Periksa satu ONU
  • Port web Hioso tetap 80, karena belum ada isiannya
  • Belum diambil dari NMS: tipe ONU (belum ada kolomnya), ganti nama ONU dan reboot (perintah tulis ke OLT), status kipas ZTE, dan SFP per port. Kolom FAN pada diagram ZTE masih mengikuti status OLT, bukan bacaan kipas
  • Aplikasi admin tidak perlu diperbarui. Tombol Info Chassis di sana menerima data port untuk merek selain ZTE

Catatan Pembaruan

  • Tidak ada perubahan skema. Naikkan versi: root, backend, frontend ke 5.44.0

v5.43.1 — 2026-11-07 - Uji Koneksi Telnet untuk OLT Hioso

Data OLT

  • [FIX] Uji Koneksi Telnet untuk OLT bermerek Hioso kini memakai sesi Telnet khusus: jawaban negosiasi opsi Telnet, baris berakhir CRLF, dan login berlapis sampai prompt EPON#. Tes bawaan berbasis expect mengirim CR saja dan menyerah setelah 15 detik, sehingga berakhir seperti timeout padahal jaringan dan port 23 baik-baik saja
  • [NEW] Tes itu menyebut alasan bila gagal: username atau password ditolak OLT, tidak ada prompt Username, atau tidak bisa tersambung. Sebelumnya hanya "Failed"

Hal yang perlu diketahui

  • Penyebab timeout di OLT yang dicek belum terbukti pasti: jaringan, port 23, banner, dan prompt Username sudah diperiksa dan semuanya baik, tetapi login dengan kredensial tidak dicoba. Dugaan terkuat adalah CR yang tidak dianggap Enter oleh OLT. Bila Uji Koneksi tetap gagal setelah pembaruan, alasan yang tampil menunjukkan tahap mana
  • Merek lain tetap memakai tes lama

Catatan Pembaruan

  • Tidak ada perubahan skema, dan aplikasi tidak perlu diperbarui. Naikkan versi: root, backend, frontend ke 5.43.1

v5.43.0 — 2026-11-06 - Community SNMP Baca dan Tulis di Data OLT

Data OLT

  • [NEW] Form tambah dan ubah OLT punya Community (Baca) dan Community (Tulis) terpisah, begitu pula pengaturan SNMP di halaman detail OLT, impor Excel (kolom snmpWriteCommunity, dengan templat yang diperbarui), dan aplikasi admin. Sebelumnya hanya ada satu isian
  • [CHANGE] Community tulis tidak pernah dikirim ke browser. Klien database menyembunyikan kolomnya dari semua hasil, termasuk baris OLT yang terbawa lewat relasi seperti ODP, uplink router, dan penugasan pelanggan. Form hanya menerima penanda sudah tersimpan atau belum. Kosong berarti tidak diubah, dan ada kotak centang untuk menghapus
  • [FIX] Impor Excel mengubah merek C-Data, HSGQ, V-SOL, Hioso, dan Otomatis menjadi ZTE secara diam-diam, karena daftar merek yang diterima hanya lima. Kini semuanya dikenali
  • [FIX] Pilihan merek di aplikasi admin kini sama dengan web: Otomatis, C-Data, HSGQ, V-SOL
  • [DOCS] Penjelasan community baca dan tulis di docs/features/OLT-SNMP-REFERENCE.md dan Pusat Panduan

Hal yang perlu diketahui

  • Belum ada fitur yang memakai community tulis. Tidak ada kode yang melakukan SNMP SET; pemantauan tetap memakai community baca. Isian ini disiapkan untuk fitur berikutnya, misalnya mengganti nama ONU Hioso HA7302
  • Penyembunyian kolom diuji pada tipe dan konfigurasi klien, belum terhadap database sungguhan

Catatan Pembaruan

  • Menambah kolom snmpWriteCommunity (boleh kosong) pada tabel OLT, diterapkan otomatis saat pembaruan. Aplikasi admin 1.0.36. Naikkan versi: root, backend, frontend ke 5.43.0

See full changelog: CHANGELOG.md

📚 Documentation

File Description
docs/DOCS_INDEX.md Index of all documentation
frontend/vps-install/README.md VPS installer: steps, environments, update, uninstall
docs/ARCHITECTURE.md Architecture: apps, API, cron jobs, processes
docs/DEVELOPMENT.md Development guide
docs/features/CRON-SYSTEM.md Cron jobs: runner, job list, history, manual runs
docs/setup/FREERADIUS-SETUP.md FreeRADIUS configuration guide
docs/features/GENIEACS-GUIDE.md GenieACS TR-069 setup & WiFi management
docs/features/OLT-SNMP-REFERENCE.md OID SNMP OLT C-Data, HSGQ, V-SOL dan status verifikasinya
docs/billing/AGENT_DEPOSIT_SYSTEM.md Agent balance & deposit

☕ Dukung Proyek

Salfanet Radius gratis dipakai dan akan tetap gratis. Kalau proyek ini membantu ISP Anda, dukungan sukarela lewat Saweria membantu pengembangannya terus berjalan.

📝 License

Released under the MIT License. Free for commercial and personal use.

👨‍💻 Development

Built with ❤️ for Indonesian ISPs

Important: Always use formatWIB() and toWIB() functions when displaying dates to users.