- TypeScript 81%
- Dart 12.9%
- Shell 3.5%
- CSS 0.7%
- HTML 0.7%
- Other 1.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Claude-Session: https://claude.ai/code/session_01BVkTzd8XTc2JBQLS3K2xaQ Co-authored-by: Claude <noreply@anthropic.com> |
||
| .devin/skills | ||
| .github | ||
| backend | ||
| deploy | ||
| docs | ||
| frontend | ||
| mobile | ||
| packages/shared-types | ||
| scripts | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| LICENSE | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
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 andgoogle-services.jsoncome from repository secrets; APKs are published to theapk-latestrelease) - 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
- Buka Admin → Pengaturan → WhatsApp → Penyedia
- Klik + Tambah Provider, pilih tipe Baileys
- Klik QR Code → scan dengan HP (WhatsApp → Linked Devices)
- Setelah scan berhasil, modal menampilkan centang hijau konfirmasi
- 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
- Pastikan MikroTik sudah dikonfigurasi sebagai RADIUS client — jalankan "Setup RADIUS" di halaman Router untuk generate script RouterOS
- Klik tombol "Migrate to RADIUS" (ikon panah kuning) di router card — hanya muncul saat
authMode = local - 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)
- Ubah
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/jsonheader - Throw
ApiErroruntuk non-2xx responses - Multipart & blob download support
- Generic typed —
apiAdmin<T = unknown>(Phase 6A: defaultunknownforces explicit typing) - 3 auth modes:
apiAdmin(cookies),apiCustomer(Bearer),apiAgent(Bearer)
Verification (per batch)
Setiap batch diverifikasi dengan:
- ✅ Local build (
npm run build) — exit code 0 - ✅ Deploy via
pscp+ remote build +pm2 restart salfanet-frontend - ✅ Production page test via Playwright
- ✅ Browser console errors check (0 errors expected)
- ✅ Update
FRONTEND_AUDIT.md - ✅ 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()keuseApiQuery/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:
refetchIntervalmenggantikansetInterval - Auth pages (login, 2FA) tidak dimigrasi (intentional)
Verification
npx tsc --noEmit: 0 errorsnpx 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
- Install @tanstack/react-query v5 +
QueryProviderdi root layout - Create hooks —
useApiQuery,useApiMutation,useAdminQuery,useCustomerQuery,useAgentQuery - 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
- Dashboard — 30s/5min polling via
- Remove dead code —
lib/utils/export.ts(unused, had eager jsPDF/exceljs imports) - Add
loading="lazy"to 27<img>tags across 16 files - 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
- alert() → toast — 23 occurrences di 7 files, diganti dengan
showError/showSuccess/showInfo - confirm() → showConfirm() — 15 occurrences di 12 files, diganti dengan
await showConfirm() - Swal.fire() → CyberToast bridge — 14 occurrences di 3 files (AddNodePanel, NetworkNodePanel, unified-map)
- Silent catch blocks — 22 occurrences di 10 files, ditambah
console.error/console.warn - Error/loading/not-found boundaries — root, customer, technician portals
- Shared feedback components —
EmptyState,LoadingSpinner,ErrorStatedicomponents/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
- API Client Content-Type fix (CRITICAL) —
apiAdmin()tidak lagi memaksakanContent-Type: application/jsonuntuk FormData/Blob/binary. Hanya JSON string body yang dapat Content-Type. - Error handling — 401/403/404/405/429/500+ messages, 204 No Content handling,
ApiErrorResponseinterface - SplitterDiagram —
metadata?: any→PortMetadata/SplitterNodeMetadatainterfaces - AddNodePanel — 20
: any→ 8 new interfaces - Recharts — 4
: any→TooltipPayloadEntry,TooltipValueTypefrom recharts - Admin pages — 20+ files,
as any→ typedapiAdmin<T>()calls - Hooks —
useTranslation.ts,useSSE.ts—any→unknown/MessageEvent as unknown asaudit — all 24 documented with inline comments (Leaflet, jsPDF, API type boundaries, SplitterNode, custom DOM)- Type Safety Exceptions —
docs/TYPE_SAFETY_EXCEPTIONS.mdcreated
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
- 257
catch (e: any)→catch (e: unknown)di 88 files dengan safe narrowing (e instanceof Error ? e.message : String(e)) - 272
(data as any)casts removed di 40 files — setiapapiAdmin()call sekarang memiliki explicit type argument - 6
Record<string, any>→Record<string, unknown>— semua fixed Promise<any>→Promise<unknown>di rateLimiter.tsuseState<any>→ proper interfaces — EntityFormData, OnuDetailResponse, dll.- 102 files modified — type-only changes, no business logic changes
Sisa any yang Legitimate
Promise<any>dimidtrans-client.d.ts(third-party declaration)<any>di Leaflet map refs dan React.ComponentType untuk dynamic icons: anydi Recharts callbacks dan SplitterDiagram metadataas anydi 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
- Type definitions updated — semua type di
frontend/src/types/api/di-validasi terhadap actual backend route responses - 2 new type files —
types/api/customer.ts(Customer portal) dantypes/api/agent.ts(Agent portal) - API client generic fix —
apiAdmin<T = any>→apiAdmin<T = unknown>(force explicit typing) - Endpoint fixes — manual payment approve/reject (POST→PATCH), transactions path, settings endpoint, agent endpoints
- 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 - API contract documentation —
docs/FRONTEND_API_CONTRACT.mddengan 34 endpoint documented
Backend Issues Found (documented, NOT fixed)
7 backend issues ditemukan dan didocument di docs/FRONTEND_API_CONTRACT.md:
- Missing:
DELETE /api/pppoe/users/bulk-delete - Missing:
GET /api/invoices/[id]/pdf - Missing: generic
/api/settingsendpoint - Missing:
/api/agent/meand/api/agent/vouchersendpoints - Inconsistent response wrappers (
ok(data)vs{ success: true, data }) - Inconsistent error shapes (
{ error }vs{ success: false, error }) - 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_identifierdiradcheck,radreply,radusergroup - Auto-sync dari
pppoeUser.routerIdsaat 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(FreeRADIUScuimodule) - 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.viewdankeuangan.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
Metode 1 — Git Clone (Recommended)
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:
Public:2020 -> VM:22(SSH)Public:80 -> VM:80(HTTP)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
- Change default admin password on first login
- Change MySQL passwords in
.env - Configure SSL (Let's Encrypt or Cloudflare)
- 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:
- Frontend polling
GET /api/pppoe/users/online-statussetiap 10 detik - Backend cek
radacct(RADIUS users) + MikroTik/ppp/active(local users) - Frontend update
isOnlinefield hanya jika ada perubahan (cegah unnecessary re-render) - 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:
- Update status user →
active - Restore RADIUS
radcheck(password) +radusergroup(profile group) dengannas_identifier - Hapus entry isolir dari
radreply - Restore
Framed-IP-Addressjika user punya IP static - Restore MikroTik PPP secret: enable + set profile ke group user (bukan
isolir) - Kick session lama via MikroTik + RADIUS CoA disconnect
- User reconnect otomatis dengan profile yang benar
FreeRADIUS config penting:
restmodule →connect_uri = "http://localhost:3001"(backend, bukan frontend)sqlippool,sql,cuisqldi 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
nameyang tidak ada di data ONU, sehingga Nama dan Deskripsi hilang. Kini satu baris Nama / Deskripsi daridescription, N/A bila kosong
Warna dan tombol mengikuti tema (terang dan gelap)
- [CHANGE] Komponen
Buttonbersama tidak lagi bergaya cyan/neon (gradien, teks hitam, huruf kapital); kini memakai token tema primary, destructive, success, warning, outline, secondary, dan ghost.CyberButtondi 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. TrackSwitchnonaktif 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/-700untuk 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, laluno shutdownpada interface-nya, sama seperti NMS, karena merek lain tidak bereaksi padareboot - [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
yespada ZTE berjalan lewatexpect, 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> resethanya 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
onuTypepada tabel ONU danwebPortpada 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.aspmemberi 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 berbasisexpectmengirim 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.mddan 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.