- TypeScript 93.7%
- Shell 4.1%
- CSS 0.9%
- HTML 0.8%
- JavaScript 0.4%
- Other 0.1%
Remove the most recognizable AI-generated visual patterns across the entire frontend: - Gradient text (dark:bg-clip-text dark:bg-gradient-to-r) replaced with clean text-foreground - font-black → font-semibold (less heavy) - tracking-wider removed (no excessive letter spacing) - uppercase tracking-[0.15em]/[0.2em]/widest removed (no uppercase eyebrow labels) - shadow-theme-sm removed (no TailAdmin shadows) 100 files cleaned across all portals and pages. Build succeeds. No backend/API/logic changes. Generated with Devin Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|---|---|---|
| backend | ||
| deploy | ||
| docs | ||
| frontend | ||
| packages/shared-types | ||
| scripts | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| DESIGN_SYSTEM.md | ||
| FRONTEND_AUDIT.md | ||
| 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.19.0 — Payment Webhook Fixes & HOTSPOT Isolation/Reactivation Support + Notification & Push Notification System Audit + Bulk Import Fixes, Null-Safe Profile Access & Auto-Refresh + Import Audit Fixes & Optional Profile + Mobile Scroll Fix (All Portals) + Semantic Color Token Migration & Responsive Layout Improvements + Collector Portal + APK Download Audit + Backend Security/Validation Fixes + MikroTik Local-Only Voucher Sync + QRIS Mandiri Payment + Auto-Update System + Phase 7 (React Query) + Phase 6D (UI State & Error Handling) + Phase 6C (API Client Correctness) + Phase 6B (Type-Safety) + Phase 6A (API Contract Audit) + Phase 5 (frontend audit) + Phase 2 (111 batches, ~510 fetch calls migrated) + Phase 3 architecture improvements
🤖 AI Development Assistant
READ FIRST: docs/AI_PROJECT_MEMORY.md — contains full architecture, VPS details, DB schema, known issues, and proven solutions.
🎯 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 |
| IP Pool | RADIUS ippool module — dynamic IP allocation per speed tier, pool create/expand/delete, Pool-Name → group mapping, utilization stats |
| Data Usage Reporting | Per-user bandwidth tracking (daily aggregation via cron), monthly summary, top consumers, GB upload/download per period |
| 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 |
| Payment | Manual upload (bukti transfer), Midtrans/Xendit/Duitku gateway, approval workflow, 0–5 bank accounts |
| Notifications | WhatsApp (Fonnte/WAHA/GOWA/MPWA/Wablas/WABlast/Kirimi.id/Baileys native), Email SMTP, broadcast (outage/invoice/payment), webhook pesan masuk |
| Agent/Reseller | Balance-based voucher generation, commission tracking, sales stats |
| Financial | Income/expense tracking with categories, keuangan reconciliation |
| Network (FTTH) | OLT/ODC/ODP management, customer port assignment, network map, distance calculation |
| 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 | 17 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, subscribe/unsubscribe toggle per portal (admin, agent, technician, 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 notifications |
| Collector Portal | Portal kolektor dengan dashboard, billing (mark-paid + upload bukti transfer), isolir list, ONT removal workflow, my-collections, settlement/setoran harian, admin verification (approve/reject), area-based access control |
| Mobile App | Flutter customer portal (WiFi control, invoice, payment) |
| WhatsApp Baileys | Native WhatsApp gateway built-in VPS via @whiskeysockets/baileys, PM2 proses terpisah, scan QR langsung di admin panel, auto-reconnect |
📱 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/v1/ippool)
Dynamic IP allocation via FreeRADIUS ippool module — tidak perlu IP static per user.
| Endpoint | Method | Fungsi |
|---|---|---|
/api/v1/ippool |
GET | List semua pool dengan summary |
/api/v1/ippool/stats |
GET | Statistik global (total, allocated, free, utilization) |
/api/v1/ippool/:name |
GET | Detail pool + recent allocations |
/api/v1/ippool |
POST | Create pool (pool_name, network, start, end) |
/api/v1/ippool/expand |
PUT | Expand pool dengan IP tambahan |
/api/v1/ippool |
DELETE | Hapus pool (hanya jika tidak ada allocation) |
/api/v1/ippool/mappings/list |
GET | List Pool-Name → group mappings |
/api/v1/ippool/mappings |
POST | Map pool ke RADIUS group |
/api/v1/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
Data Usage Reporting (/api/v1/data-usage)
Bandwidth tracking per user per period — diadopsi dari FreeRADIUS process-radacct.sql.
| Endpoint | Method | Fungsi |
|---|---|---|
/api/v1/data-usage |
GET | Bandwidth per user untuk date range |
/api/v1/data-usage/monthly |
GET | Monthly summary per user (sorted by usage) |
/api/v1/data-usage/top |
GET | Top bandwidth consumers |
/api/v1/data-usage/aggregate |
POST | Manual trigger aggregation |
Cron: Daily at 00:05 — aggregate radacct → data_usage_by_period table.
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.14.0)
Portal khusus untuk kolektor (petugas tagihan) dengan akses berbasis area (PPPoE area assignment).
Fitur Collector Portal
| Halaman | Fungsi |
|---|---|
| Dashboard | Summary invoice area (unpaid, paid, isolir count), total tagihan belum dibayar |
| Billing | List invoice area, mark-paid dengan pilihan metode pembayaran (cash/transfer), upload bukti transfer (auto-compress ke base64) |
| Isolir | List pelanggan isolir/suspended di area dengan jumlah tagihan unpaid |
| ONT Removal | Workflow cabut-ONT: list task, create removal record, update status |
| My Collections | Riwayat pembayaran yang dikumpulkan kolektor |
| Settlements | Setoran harian: summary cash vs transfer, konfirmasi setoran |
| Proofs | Bukti transfer yang diupload, admin bisa verify (approve/reject) |
Admin: Kelola Kolektor
- Admin → Collectors — CRUD kolektor: username, nama, email, telepon, area assignment, aktif/non-aktif
- Admin → Collector Settlements — Verifikasi setoran harian: breakdown cash vs transfer, approve/reject dengan invoice detail, expand per kolektor
API Endpoints
| Endpoint | Method | Fungsi |
|---|---|---|
/api/collector/auth/login |
POST | Login kolektor (Bearer token) |
/api/collector/dashboard |
GET | Summary invoice area |
/api/collector/users |
GET | List pelanggan di area (filter: unpaid/all/paid) |
/api/collector/isolir |
GET | List pelanggan isolir di area |
/api/collector/billing |
GET | List invoice di area untuk mark-paid |
/api/collector/mark-paid |
POST | Mark invoice paid + upload bukti transfer |
/api/collector/ont-removals |
GET/POST | ONT removal records |
/api/collector/proofs |
GET | Bukti transfer kolektor |
/api/collector/my-collections |
GET | Riwayat koleksi kolektor |
/api/collector/my-settlements |
GET/POST | Setoran harian kolektor |
/api/collector/setoran |
GET | Summary setoran harian |
/api/collector/history |
GET | Riwayat aktivitas kolektor |
/api/collector/list |
GET | List semua kolektor (admin) |
/api/collector/confirm-settlement |
POST | Admin konfirmasi setoran |
Keamanan
- Kolektor hanya bisa akses data di area yang di-assign
- Bearer token auth (terpisah dari session admin)
- Validasi
areaIddi setiap query - Bukti transfer disimpan sebagai base64 (MediumText) di DB
- Admin verify bukti transfer sebelum approve setoran
🚀 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/
├── 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!
🔌 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 (16 automated)
| Job | Schedule | Function |
|---|---|---|
| Voucher Sync | Every 5 min | Sync voucher status with RADIUS |
| Disconnect Sessions | Every 5 min | CoA disconnect expired vouchers |
| Auto Isolir (PPPoE) | Every hour | Suspend overdue customers |
| FreeRADIUS Health | Every 5 min | Auto-restart if down |
| PPPoE Session Sync | Every 10 min | Sync radacct sessions |
| Agent Sales | Daily 1 AM | Update sales statistics |
| Invoice Generate | Daily 2 AM | Generate monthly invoices |
| Activity Log Cleanup | Daily 2 AM | Delete logs >30 days |
| Invoice Reminder | Daily 8 AM | Send payment reminders |
| Invoice Status | Daily 9 AM | Mark overdue invoices |
| Notification Check | Every 10 min | Process notification queue |
| Auto Renewal | Daily 8 AM | Prepaid auto-renew from balance |
| Webhook Log Cleanup | Daily 3 AM | Delete webhook logs >30 days |
| Session Monitor | Every 5 min | Security session monitoring |
| Cron History Cleanup | Daily 4 AM | Keep last 50 per job type |
| Suspend Check | Every hour | Activate/restore suspend requests |
All jobs can be triggered manually from Settings → Cron in the admin panel.
📱 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).
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)
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 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 |
⏱️ 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 · Hotspot · Agent · Invoice · Payment · Keuangan · Sessions · WhatsApp · Network (OLT/ODC/ODP) · GenieACS · Settings
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.20.0 — 2026-09-07 — Bulk Edit PPPoE + Migrasi Local→RADIUS + UI Icon Audit
Summary
Tiga fitur besar: (1) Bulk edit pelanggan PPPoE untuk Router/Hari Tagihan/Auto Isolasi, (2) Migrasi satu-klik dari auth mode LOCAL ke RADIUS dengan sync otomatis semua data pelanggan, dan (3) Audit UI menambahkan ikon Lucide ke empty states, page headers, status badges, dan modal sections di semua role.
Bulk Edit Pelanggan PPPoE
- [FEATURE] Tombol "Edit" di bulk action bar pada halaman Data Pelanggan — ubah Router, Hari Tagihan (1-28), dan Auto Isolasi untuk multiple pelanggan sekaligus
- [FEATURE] Endpoint
PUT /api/pppoe/users/bulk-update— update massalrouterId,billingDay,autoIsolationEnabled - [FEATURE] Saat router berubah, backend otomatis: hapus RADIUS entries di NAS lama, buat di NAS baru, manage MikroTik PPP secret (disable di router lama, enable di router baru), CoA disconnect semua sesi aktif
- [UI] Modal bulk edit dengan opsi "— Tidak berubah —" per field, konfirmasi sebelum apply, summary hasil
Migrasi Local → RADIUS Auth
- [FEATURE] Tombol "Migrate to RADIUS" di halaman Router (hanya muncul saat
authMode = local) — migrasi satu-klik - [FEATURE] Endpoint
POST /api/pppoe/users/bulk-migrate-radius— migrasi router dari local ke RADIUS:- Ubah
router.authModedarilocal→radius - Re-sync SEMUA pelanggan ke RADIUS tables (
radcheck,radusergroup,radreply) - Disable PPP secrets di MikroTik (sebagai backup, RADIUS jadi primary)
- Reload FreeRADIUS
- CoA disconnect semua sesi aktif untuk re-auth via RADIUS
- Ubah
- [FEATURE] Tombol "Re-sync RADIUS" di halaman Router — perbaiki data RADIUS yang tidak sinkron tanpa ubah authMode
- [FEATURE] Endpoint
POST /api/pppoe/users/bulk-sync-radius— re-sync semua user ke RADIUS tables untuk router tertentu atau semua router - [DOCS] Penjelasan arsitektur auth mode (local vs RADIUS) di README
UI Icon Audit (Lucide)
- [UI] Tambah ikon Lucide ke 12 empty states yang sebelumnya text-only (radcheck, manual-payments, keuangan, stopped PPPoE, balance, bank-accounts, hotspot templates/profiles/vouchers/evouchers/agents, pay-manual)
- [UI] Tambah ikon ke page headers admin (invoices, pppoe users, tickets, whatsapp send/providers/notifications, settings/security)
- [UI] Tambah ikon ke customer invoice status badges (Lunas/Jatuh Tempo/Menunggu/Ditolak/Belum Bayar)
- [UI] Tambah ikon ke UserDetailModal section headers (KTP, Foto Instalasi, Layanan Tambahan, Janji Bayar) + empty states + action buttons
- [UI] Tambah AlertCircle ke error alerts di customer/technician/collector login pages
- [UI] Tambah Wallet icon ke agent balance card label
- [UI] Tambah ikon ke collector my-collections stat cards (Receipt/Wallet/Banknote/ArrowRightLeft)
Other
- [CHORE] Bump service worker cache ke v28
- [CHORE] Bump root & frontend package.json version ke 5.20.0
v5.19.1 — 2026-09-06 — Rekap Voucher Rombak + PPPoE Profile Sync + CSP Fix
Summary
Rombak total laporan Rekap Voucher agar support penjualan harian/mingguan/bulanan berdasarkan tanggal terjual (firstLoginAt), bukan tanggal batch dibuat (createdAt). Fix PPPoE user stuck di profile isolir setelah restore. Fix Cloudflare Web Analytics CSP error. Cleanup file debug/temp dari repo.
Rekap Voucher — Rombak Laporan Penjualan
- [FEATURE] Filter periode (Harian/Mingguan/Bulanan) sekarang filter by
firstLoginAt(tanggal voucher terjual/digunakan), bukancreatedAt(tanggal batch dibuat) - [FEATURE] Mode "Semua Data" tetap filter by
createdAtdengan full batch counts (qty, stock, sold, active, expired) - [FEATURE] Tabel "Rincian Penjualan per Hari" — rincian penjualan per tanggal dalam periode (sold, active, expired, revenue)
- [FEATURE] Tabel "Pendapatan per Agent" — summary terjual & profit per agent
- [FEATURE] Export Excel ikut rombak — konsisten dengan API
- [FEATURE] Support both RADIUS NAS (
post-authhook setfirstLoginAt) dan Local NAS (sync dari MikroTik/system/scriptformatuser/price/sales/date/time/phone/seller) - [UI] Rombak tampilan lebih simple & clean — hilangkan efek neon/glow/blur berlebihan, compact stat pills, responsive card/table
- [FIX] colSpan tfoot mode "Semua" (8 → 6) agar baris Total sejajar dengan kolom
- [FIX]
whitespace-nowrap+min-wdi semua tabel agar kolom tidak wrap/truncate
PPPoE Profile Synchronization
- [CRITICAL] User active di DB tapi MikroTik masih pakai profile
isolirsetelah restore — session tidak di-kick, RouterOS retain profile lama - [FIX] Kick active PPPoE session saat restore dari isolated/blocked/stop ke active (
status/route.ts,bulk-status/route.ts) - [FIX] CoA handler: gunakan router user yang sebenarnya untuk MikroTik API fallback kick (
coa-handler.service.ts) - [FIX]
runSuspendCheck: sync RADIUS + MikroTik saat manual suspend - [FEATURE] Reconciliation script: cek MikroTik PPP secret langsung, repair RADIUS group + MikroTik secret profile, kick affected sessions (
profile-sync-repair.service.ts)
Cloudflare Web Analytics CSP Fix
- [FIX]
VM... startTimeerror dari Cloudflare beacon.min.js yang injectreportAllChanges - [FIX] Remove
https://static.cloudflareinsights.comdanhttps://cloudflareinsights.comdari CSPscript-srcdanconnect-srcdifrontend/next.config.ts
Project Cleanup
- [CHORE] Remove 18 file debug/temp dari git tracking (
.check-*,.debug-*,.test-*,.commit-msg-*,.trigger-*) - [CHORE] Update
.gitignoreuntuk mencegah file debug/temp ter-commit lagi - [CHORE] Update README — tambah info Rekap Voucher di feature table
v5.19.0 — 2026-09-01 — Payment Webhook Fixes & HOTSPOT Isolation/Reactivation Support
Summary
Audit dan perbaikan menyeluruh untuk payment webhook (Midtrans), payment success page, dan isolasi/reactivation user untuk kedua mode autentikasi (RADIUS & local MikroTik). Perbaikan bug kritis pada orderId parsing, variable shadowing yang mencegah RADIUS reactivation, serta penambahan dukungan HOTSPOT connectionType untuk isolasi dan reaktivasi di semua route.
Bug Fixes
- [CRITICAL] Webhook
orderIdparsing tidak strip trailing hex segment — invoice tidak ditemukan, status tetap PENDING. Fix: strip semua trailing segments (timestamp + hex) - [CRITICAL]
wasDisabledvariable shadowing dihandleInvoicePayment—const wasDisableddi dalam block shadowing outerlet wasDisabled, menyebabkan RADIUS reactivation tidak pernah berjalan untuk user isolated/suspended. Fix: gunakan assignmentwasDisabled =bukanconst wasDisabled - [CRITICAL] HOTSPOT connectionType tidak ditangani di semua route reaktivasi (webhook, mark-paid, status, bulk-status, extend, invoices PUT) — user Hotspot local mode yang diisolir tidak bisa di-reactivate. Fix: cek
connectionType, HOTSPOT →manageHotspotUser(disabled=false)+kickHotspotSession - [FIX] Invoice DELETE gagal karena foreign key constraints. Fix: hapus semua related records (manualPayment, paymentProof, invoiceAddon, paymentAttempt, registrationRequest, qrisPending) sebelum hapus invoice
- [FIX] Missing translation keys di payment pages. Fix: tambah top-level
paymentsection diid.json - [FIX] Payment success page tidak menampilkan data invoice/user lengkap. Fix: tambah user relation & customerUsername di invoice check response
- [FIX] Race condition antara webhook dan frontend polling. Fix: tambah polling retry (5x, 2s delay) di payment success page
- [FIX] Type error:
user.profileId(string | null) tidak assignable keprofileId: string. Fix: coerce dengan?? ''
Features
- [FEATURE] ConnectionType-aware isolation & reactivation — semua route sekarang mengecek
user.connectionTypedan menggunakanmanageHotspotUser/kickHotspotSessionuntuk HOTSPOT ataumanagePppSecret/kickPppoeSessionuntuk PPPoE/STATIC_IP
Files Changed
backend/src/app/api/payment/webhook/route.ts— fix orderId parsing, wasDisabled shadowing, HOTSPOT reactivation, type fixbackend/src/app/api/payment/check-order/route.ts— fix parseInvoiceNumberFromOrderbackend/src/app/api/invoices/check/route.ts— add user relation & customerUsernamebackend/src/app/api/invoices/route.ts— fix DELETE foreign keys, HOTSPOT reactivation in PUTbackend/src/app/api/pppoe/users/[id]/mark-paid/route.ts— HOTSPOT reactivationbackend/src/app/api/pppoe/users/status/route.ts— HOTSPOT enable/disable + kickbackend/src/app/api/pppoe/users/bulk-status/route.ts— HOTSPOT enable/disable + kickbackend/src/app/api/pppoe/users/[id]/extend/route.ts— HOTSPOT re-enable + kickfrontend/src/app/payment/success/page.tsx— polling retryfrontend/src/locales/id.json— payment translation keys
v5.18.0 — 2026-08-31 — Notification & Push Notification System Audit
Summary
Audit menyeluruh dan perbaikan sistem notifikasi dan push notification untuk semua portal (Admin, Agent, Customer, Technician, Collector). Perbaikan bug kritis dispatch push, vulnerability keamanan pada route agent, integrasi push ke cron jobs, serta penambahan push toggle dan in-app notification untuk collector.
Security
- [CRITICAL] Agent push subscribe/unsubscribe route menerima
agentIddari request body tanpa verifikasi — sekarang menggunakanrequireAgentAuthdan mengambilagentIddari JWT token yang terverifikasi
Bug Fixes
- [CRITICAL] Admin push subscriptions dikirim saat
recipientRole=technician— push sekarang hanya dikirim ke admin subs ketikarecipientRoleadalahadminatauall - [FIX] Service worker notification click redirect hardcoded ke
/customer— sekarang menggunakandata.url/data.linkdari payload, fallback ke/
Features
- [FEATURE] Admin push toggle di admin panel — komponen
AdminPushToggle.tsxdi user menu dropdown, route/api/push/admin-subscribe&/api/push/admin-unsubscribedengancheckAuth - [FEATURE] Admin sebagai recipient role di broadcast push notifications page — type notification, quick templates, dan template content khusus admin
- [FEATURE] Agent push toggle di sidebar — komponen
AgentPushToggle.tsxdengan subscribe/unsubscribe viaapiAgent - [FEATURE] Push notification terintegrasi di cron jobs: invoice reminder (overdue + reminder), auto-isolir (isolation-notice), auto-renewal (auto-renewal-success)
- [FEATURE]
createAgentNotificationAndPushhelper — centralizes agent notification creation + web push sending, menggantikan semuaprisma.agentNotification.createcalls di 8 file - [FEATURE] Customer notification read state server-side — model
customerNotificationReaddi Prisma, PATCH endpoint untuk mark-as-read,unreadCount&isReaddari server - [FEATURE] Collector notification system — API
/api/collector/notifications(derived dari payments, ONT tasks, tickets), komponenCollectorNotificationBell.tsxdengan push toggle + notification dropdown - [FEATURE] Technician notifications API —
/api/technician/notificationsdengan structured data dari tickets dan ONT tasks - [FEATURE] Service worker cache bumped to v17 untuk force update
Files Changed
backend/src/server/services/push-notification.service.ts— fix admin push dispatch logic, add 'admin' to recipientRole typebackend/src/app/api/push/agent-subscribe/route.ts— addrequireAgentAuthbackend/src/app/api/push/agent-unsubscribe/route.ts— addrequireAgentAuthbackend/src/app/api/push/admin-subscribe/route.ts— new routebackend/src/app/api/push/admin-unsubscribe/route.ts— new routebackend/src/server/services/agent-notification.service.ts— new helperbackend/src/server/cron/invoice-jobs.ts— add push for invoice reminders + auto-renewalbackend/src/server/cron/auto-isolir.ts— add push for isolation noticesbackend/src/app/api/admin/agent-deposits/route.ts— usecreateAgentNotificationAndPushbackend/src/app/api/agent/deposit/webhook/route.ts— usecreateAgentNotificationAndPushbackend/src/app/api/agent/generate-voucher/route.ts— usecreateAgentNotificationAndPushbackend/src/app/api/agent/deposit/manual-request/route.ts— usecreateAgentNotificationAndPushbackend/src/app/api/hotspot/agents/balance/route.ts— usecreateAgentNotificationAndPushbackend/src/server/services/hotspot.service.ts— usecreateAgentNotificationAndPushbackend/src/app/api/hotspot/voucher/bulk-delete/route.ts— usecreateAgentNotificationAndPushbackend/src/app/api/hotspot/voucher/delete-multiple/route.ts— usecreateAgentNotificationAndPushbackend/src/app/api/hotspot/voucher/[id]/route.ts— usecreateAgentNotificationAndPushbackend/src/app/api/customer/notifications/route.ts— add PATCH mark-as-read, server-side read statebackend/src/app/api/technician/notifications/route.ts— new routebackend/src/app/api/collector/notifications/route.ts— new routebackend/prisma/schema.prisma— addcustomerNotificationReadmodelfrontend/src/components/admin/AdminPushToggle.tsx— new componentfrontend/src/components/agent/AgentPushToggle.tsx— new componentfrontend/src/components/collector/CollectorNotificationBell.tsx— new componentfrontend/src/app/admin/AdminClientLayout.tsx— addAdminPushToggleto user menufrontend/src/app/agent/AgentLayoutClient.tsx— addAgentPushToggleto sidebarfrontend/src/app/collector/CollectorPortalLayout.tsx— addCollectorNotificationBellto headerfrontend/src/app/customer/CustomerClientLayout.tsx— server-side read state,markAllAsReadon bell openfrontend/src/app/admin/push-notifications/page.tsx— add 'admin' recipient rolefrontend/public/sw.js— role-based redirect, cache v17package.json— version bump to 5.18.0frontend/package.json— version bump to 5.18.0README.md— version updateCHANGELOG.md— this entry
Schema Changes
- New model
customerNotificationRead— tablecustomer_notification_readswithuserId,eventKey,readAt, unique constraint on[userId, eventKey] - Auto-applied via
prisma db pushon VPS update
Deployment
cd /var/www/salfanet-radius
git pull origin master
# updater.sh handles: pnpm install, prisma generate, prisma db push, build, PM2 restart
bash frontend/vps-install/updater.sh --branch master
v5.17.0 — 2026-08-30 — Bulk Import Fixes, Null-Safe Profile Access & Auto-Refresh
Summary
Perbaikan komprehensif untuk fitur import pelanggan PPPoE: error 400 saat import, crash frontend saat menampilkan/mengedit user tanpa profile, data tidak auto-refresh setelah import, serta cleanup project dari file temporary.
Fixes
- [CRITICAL]
TypeError: Cannot read properties of null (reading 'name')— frontend crash saat menampilkan user tanpa profile di card view, table view, dan CSV export. Ditambahkan optional chaining (?.) dan fallback values - [CRITICAL]
TypeError: Cannot read properties of null (reading 'id')— frontend crash saat membuka modal edit pelanggan (UserDetailModal) dan extend modal untuk user tanpa profile.user.profile.iddiubah keuser.profile?.id || '' - [FIX] Bulk import 400 Bad Request — ditambahkan detailed error logging di setiap validation point di backend route, frontend error parsing diperbaiki untuk menampilkan pesan spesifik dari backend
- [FIX] Data tidak auto-refresh setelah import —
invalidateQueriesdenganstaleTime: 30000tidak memaksa refetch. Diubah kerefetchQueriesyang memaksa immediate refetch regardless of staleTime - [FIX] Backend env variables truncated saat PM2 restart —
awkmemotongDATABASE_URLdanNEXTAUTH_SECRETdi karakter&. Diganti denganseduntuk extraction yang reliable - [FIX] Excel parsing debug logs — ditambahkan logging untuk file name, size, row count, dan sample data untuk diagnosing import issues
- [FIX] PM2 frontend NEXTAUTH_SECRET kosong setelah restart — fix dengan script bash yang extract env dari
.envfile menggunakanseddan restart PM2 dengandelete+start(bukanrestart --update-env)
Features
- [FEATURE] Import dialog file preview — parse CSV/Excel client-side, tampilkan file name, row count summary (valid/without profile/skipped), dan preview table dengan status indicators
- [FEATURE] Collapsible import column guide dengan 3-tier status (Wajib/Disarankan/Opsional), MAC Address & Komentar columns, legend, descriptions dan tips
- [FEATURE] PPPoE sync audit — compare DB vs MikroTik PPP secrets (username, password, profile, status) dengan fix actions
- [FEATURE] Cloudflare 524 timeout fix — web update berjalan sebagai detached background process dengan status polling
Cleanup
- Removed
check-encoding.ps1— temporary PowerShell script untuk check BOM/encoding - Removed
fix-encoding.ps1— temporary PowerShell script untuk fix BOM/encoding - Removed
AUTOCHANGELOG.md— auto-generated changelog, redundant dengan CHANGELOG.md - Updated
.gitignore— patterndeploy-*.sh,restart-*.sh,fix-fe-env.sh,check-encoding.ps1,fix-encoding.ps1,AUTOCHANGELOG.md
Files Changed
frontend/src/app/admin/pppoe/users/page.tsx— null-safeprofile?.name,profile?.id,profile?.groupNamedi card/table/extend/CSV;refetchQueriesmenggantikaninvalidateQueriesfrontend/src/components/UserDetailModal.tsx—profiletype nullable,profile?.iddi form initfrontend/src/lib/api/pppoe.ts— improved error parsing untuk bulk uploadbackend/src/app/api/pppoe/users/bulk/route.ts— detailed error logging di semua 400 responses, Excel parsing debug logs.gitignore— temp script patternspackage.json— version bump to 5.17.0README.md— version updateCHANGELOG.md— this entry
Deployment
cd /var/www/salfanet-radius
git pull origin master
cd frontend && pnpm install --no-frozen-lockfile && pnpm build
# Restart frontend dengan env yang benar (delete + start, bukan restart)
export NEXTAUTH_SECRET=$(sed -n 's/^NEXTAUTH_SECRET=//p' frontend/.env | tr -d '"' | tr -d "'")
export NEXTAUTH_URL=$(sed -n 's/^NEXTAUTH_URL=//p' frontend/.env | tr -d '"' | tr -d "'")
pm2 delete salfanet-frontend && pm2 start ecosystem.config.js --only salfanet-frontend
pm2 save
See full changelog: CHANGELOG.md
📚 Documentation
| File | Description |
|---|---|
| docs/INSTALLATION-GUIDE.md | Complete VPS installation |
| docs/GENIEACS-GUIDE.md | GenieACS TR-069 setup & WiFi management |
| docs/AGENT_DEPOSIT_SYSTEM.md | Agent balance & deposit |
| docs/RADIUS-CONNECTIVITY.md | RADIUS architecture |
| docs/FREERADIUS-SETUP.md | FreeRADIUS configuration guide |
📝 License
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.