No description
  • TypeScript 93.7%
  • Shell 4.1%
  • CSS 0.9%
  • HTML 0.8%
  • JavaScript 0.4%
  • Other 0.1%
Find a file
s4lfanet 5902b91d3a feat(frontend): bulk cleanup — remove AI-look patterns across 100 files
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>
2026-09-13 20:24:50 +07:00
backend chore: remove AI agent configs, debug scripts, and non-production files from repo 2026-09-13 19:51:28 +07:00
deploy chore: remove AI agent configs, debug scripts, and non-production files from repo 2026-09-13 19:51:28 +07:00
docs docs: update isolation docs to match current code - fix file paths, status casing, firewall rules, endpoints, radgroupreply attributes 2026-08-16 22:02:23 +07:00
frontend feat(frontend): bulk cleanup — remove AI-look patterns across 100 files 2026-09-13 20:24:50 +07:00
packages/shared-types refactor: restructure to pnpm monorepo -- frontend/ + backend/ + packages/ (v2.35.0) 2026-08-11 21:34:48 +07:00
scripts chore: remove AI agent configs, debug scripts, and non-production files from repo 2026-09-13 19:51:28 +07:00
.gitattributes chore: enforce lf line endings for shell scripts 2026-03-15 11:09:30 +07:00
.gitignore chore: remove AI agent configs, debug scripts, and non-production files from repo 2026-09-13 19:51:28 +07:00
CHANGELOG.md feat: migrate local to RADIUS auth + bulk sync RADIUS + bulk edit PPPoE + UI icons 2026-09-07 22:23:55 +07:00
DESIGN_SYSTEM.md feat(frontend): Phase 1-3 design system + admin shell redesign 2026-09-13 20:16:10 +07:00
FRONTEND_AUDIT.md feat(frontend): Phase 1-3 design system + admin shell redesign 2026-09-13 20:16:10 +07:00
package.json feat: migrate local to RADIUS auth + bulk sync RADIUS + bulk edit PPPoE + UI icons 2026-09-07 22:23:55 +07:00
pnpm-lock.yaml refactor: replace hardcoded neon colors with semantic Tailwind tokens + add API proxy rewrites for local dev 2026-09-05 03:05:25 +07:00
pnpm-workspace.yaml refactor: replace hardcoded neon colors with semantic Tailwind tokens + add API proxy rewrites for local dev 2026-09-05 03:05:25 +07:00
README.md docs: sync README changelog 2026-09-07 15:24:13 +00:00

SALFANET RADIUS - Billing System for ISP/RTRW.NET

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

Architecture: pnpm monorepo — Two Next.js apps (frontend UI + backend API) + Baileys WhatsApp service Version: 5.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, 05 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

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

PM2 Processes

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

Auth Session

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


Redis Cache (v4.2.0)

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

Yang Di-Cache (TTL 5 menit)

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

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

Yang TIDAK Di-Cache (Realtime)

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

Graceful Degradation

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

Setup

Redis harus terinstall dan running di VPS:

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

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


📡 RADIUS Enhancements (v3.1.0)

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

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

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

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

Cara Migrasi Local → RADIUS

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

Re-sync RADIUS (Tanpa Ubah AuthMode)

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

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

Bulk Edit Pelanggan

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

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

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

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

Dokumentasi lengkap: FRONTEND_AUDIT.md · CHANGELOG.md

Phase 1 — Architectural Cleanup (Prasyarat)

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

Phase 2 — Centralized API Client Migration (Batch 152)

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

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

Centralized API Client (@/lib/api)

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

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

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

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

Fitur:

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

Verification (per batch)

Setiap batch diverifikasi dengan:

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

Phase 2 Status

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

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

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

Yang Dikerjakan

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

Verification

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

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

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

Dokumentasi lengkap: FRONTEND_AUDIT.md

Yang Dikerjakan

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

Performance Improvements

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

Verification

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

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

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

Dokumentasi lengkap: FRONTEND_AUDIT.md

Hasil

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

Yang Dikerjakan

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

Verification

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

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

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

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

Hasil

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

Yang Dikerjakan

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

Sisa any (2 — all third-party)

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

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

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

Dokumentasi lengkap: FRONTEND_AUDIT.md

Hasil

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

Yang Dikerjakan

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

Sisa any yang Legitimate

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

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

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

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

Hasil Audit

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

Yang Dikerjakan

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

Backend Issues Found (documented, NOT fixed)

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

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

Deployment

  • Commit: 95bfa7e8
  • VPS: 192.168.54.129https://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 radacctdata_usage_by_period table.

Multi-NAS Isolation

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

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

CUI (Chargeable User Identity)

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

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

🏦 Collector Portal (v5.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 areaId di 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

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: ~510 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:

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

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

2) PM2 jalan tapi web tetap blank/error

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

3) API returns 404 (route not found)

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

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

4) Jalankan diagnosa Nginx otomatis dari installer

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

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

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


🔐 Security

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

📡 CoA (Change of Authorization)

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

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

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

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


📲 WhatsApp Providers

Provider Base URL Auth
Fonnte https://api.fonnte.com/send Token
WAHA http://IP:PORT API Key
GOWA http://IP:PORT user:pass
MPWA http://IP:PORT API Key
Wablas https://pati.wablas.com Token

⏱️ 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:

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

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

Response:

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

🔧 PPPoE Reconnect Setelah Payment

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

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

FreeRADIUS config penting:

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

📝 Changelog

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

v5.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 massal routerId, 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.authMode dari localradius
    • 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
  • [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), bukan createdAt (tanggal batch dibuat)
  • [FEATURE] Mode "Semua Data" tetap filter by createdAt dengan 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-auth hook set firstLoginAt) dan Local NAS (sync dari MikroTik /system/script format user/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-w di semua tabel agar kolom tidak wrap/truncate

PPPoE Profile Synchronization

  • [CRITICAL] User active di DB tapi MikroTik masih pakai profile isolir setelah 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... startTime error dari Cloudflare beacon.min.js yang inject reportAllChanges
  • [FIX] Remove https://static.cloudflareinsights.com dan https://cloudflareinsights.com dari CSP script-src dan connect-src di frontend/next.config.ts

Project Cleanup

  • [CHORE] Remove 18 file debug/temp dari git tracking (.check-*, .debug-*, .test-*, .commit-msg-*, .trigger-*)
  • [CHORE] Update .gitignore untuk 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 orderId parsing tidak strip trailing hex segment — invoice tidak ditemukan, status tetap PENDING. Fix: strip semua trailing segments (timestamp + hex)
  • [CRITICAL] wasDisabled variable shadowing di handleInvoicePaymentconst wasDisabled di dalam block shadowing outer let wasDisabled, menyebabkan RADIUS reactivation tidak pernah berjalan untuk user isolated/suspended. Fix: gunakan assignment wasDisabled = bukan const 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 payment section di id.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 ke profileId: string. Fix: coerce dengan ?? ''

Features

  • [FEATURE] ConnectionType-aware isolation & reactivation — semua route sekarang mengecek user.connectionType dan menggunakan manageHotspotUser/kickHotspotSession untuk HOTSPOT atau managePppSecret/kickPppoeSession untuk PPPoE/STATIC_IP

Files Changed

  • backend/src/app/api/payment/webhook/route.ts — fix orderId parsing, wasDisabled shadowing, HOTSPOT reactivation, type fix
  • backend/src/app/api/payment/check-order/route.ts — fix parseInvoiceNumberFromOrder
  • backend/src/app/api/invoices/check/route.ts — add user relation & customerUsername
  • backend/src/app/api/invoices/route.ts — fix DELETE foreign keys, HOTSPOT reactivation in PUT
  • backend/src/app/api/pppoe/users/[id]/mark-paid/route.ts — HOTSPOT reactivation
  • backend/src/app/api/pppoe/users/status/route.ts — HOTSPOT enable/disable + kick
  • backend/src/app/api/pppoe/users/bulk-status/route.ts — HOTSPOT enable/disable + kick
  • backend/src/app/api/pppoe/users/[id]/extend/route.ts — HOTSPOT re-enable + kick
  • frontend/src/app/payment/success/page.tsx — polling retry
  • frontend/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 agentId dari request body tanpa verifikasi — sekarang menggunakan requireAgentAuth dan mengambil agentId dari JWT token yang terverifikasi

Bug Fixes

  • [CRITICAL] Admin push subscriptions dikirim saat recipientRole=technician — push sekarang hanya dikirim ke admin subs ketika recipientRole adalah admin atau all
  • [FIX] Service worker notification click redirect hardcoded ke /customer — sekarang menggunakan data.url/data.link dari payload, fallback ke /

Features

  • [FEATURE] Admin push toggle di admin panel — komponen AdminPushToggle.tsx di user menu dropdown, route /api/push/admin-subscribe & /api/push/admin-unsubscribe dengan checkAuth
  • [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.tsx dengan subscribe/unsubscribe via apiAgent
  • [FEATURE] Push notification terintegrasi di cron jobs: invoice reminder (overdue + reminder), auto-isolir (isolation-notice), auto-renewal (auto-renewal-success)
  • [FEATURE] createAgentNotificationAndPush helper — centralizes agent notification creation + web push sending, menggantikan semua prisma.agentNotification.create calls di 8 file
  • [FEATURE] Customer notification read state server-side — model customerNotificationRead di Prisma, PATCH endpoint untuk mark-as-read, unreadCount & isRead dari server
  • [FEATURE] Collector notification system — API /api/collector/notifications (derived dari payments, ONT tasks, tickets), komponen CollectorNotificationBell.tsx dengan push toggle + notification dropdown
  • [FEATURE] Technician notifications API — /api/technician/notifications dengan 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 type
  • backend/src/app/api/push/agent-subscribe/route.ts — add requireAgentAuth
  • backend/src/app/api/push/agent-unsubscribe/route.ts — add requireAgentAuth
  • backend/src/app/api/push/admin-subscribe/route.ts — new route
  • backend/src/app/api/push/admin-unsubscribe/route.ts — new route
  • backend/src/server/services/agent-notification.service.ts — new helper
  • backend/src/server/cron/invoice-jobs.ts — add push for invoice reminders + auto-renewal
  • backend/src/server/cron/auto-isolir.ts — add push for isolation notices
  • backend/src/app/api/admin/agent-deposits/route.ts — use createAgentNotificationAndPush
  • backend/src/app/api/agent/deposit/webhook/route.ts — use createAgentNotificationAndPush
  • backend/src/app/api/agent/generate-voucher/route.ts — use createAgentNotificationAndPush
  • backend/src/app/api/agent/deposit/manual-request/route.ts — use createAgentNotificationAndPush
  • backend/src/app/api/hotspot/agents/balance/route.ts — use createAgentNotificationAndPush
  • backend/src/server/services/hotspot.service.ts — use createAgentNotificationAndPush
  • backend/src/app/api/hotspot/voucher/bulk-delete/route.ts — use createAgentNotificationAndPush
  • backend/src/app/api/hotspot/voucher/delete-multiple/route.ts — use createAgentNotificationAndPush
  • backend/src/app/api/hotspot/voucher/[id]/route.ts — use createAgentNotificationAndPush
  • backend/src/app/api/customer/notifications/route.ts — add PATCH mark-as-read, server-side read state
  • backend/src/app/api/technician/notifications/route.ts — new route
  • backend/src/app/api/collector/notifications/route.ts — new route
  • backend/prisma/schema.prisma — add customerNotificationRead model
  • frontend/src/components/admin/AdminPushToggle.tsx — new component
  • frontend/src/components/agent/AgentPushToggle.tsx — new component
  • frontend/src/components/collector/CollectorNotificationBell.tsx — new component
  • frontend/src/app/admin/AdminClientLayout.tsx — add AdminPushToggle to user menu
  • frontend/src/app/agent/AgentLayoutClient.tsx — add AgentPushToggle to sidebar
  • frontend/src/app/collector/CollectorPortalLayout.tsx — add CollectorNotificationBell to header
  • frontend/src/app/customer/CustomerClientLayout.tsx — server-side read state, markAllAsRead on bell open
  • frontend/src/app/admin/push-notifications/page.tsx — add 'admin' recipient role
  • frontend/public/sw.js — role-based redirect, cache v17
  • package.json — version bump to 5.18.0
  • frontend/package.json — version bump to 5.18.0
  • README.md — version update
  • CHANGELOG.md — this entry

Schema Changes

  • New model customerNotificationRead — table customer_notification_reads with userId, eventKey, readAt, unique constraint on [userId, eventKey]
  • Auto-applied via prisma db push on 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.id diubah ke user.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 — invalidateQueries dengan staleTime: 30000 tidak memaksa refetch. Diubah ke refetchQueries yang memaksa immediate refetch regardless of staleTime
  • [FIX] Backend env variables truncated saat PM2 restart — awk memotong DATABASE_URL dan NEXTAUTH_SECRET di karakter &. Diganti dengan sed untuk 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 .env file menggunakan sed dan restart PM2 dengan delete + start (bukan restart --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 — pattern deploy-*.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-safe profile?.name, profile?.id, profile?.groupName di card/table/extend/CSV; refetchQueries menggantikan invalidateQueries
  • frontend/src/components/UserDetailModal.tsxprofile type nullable, profile?.id di form init
  • frontend/src/lib/api/pppoe.ts — improved error parsing untuk bulk upload
  • backend/src/app/api/pppoe/users/bulk/route.ts — detailed error logging di semua 400 responses, Excel parsing debug logs
  • .gitignore — temp script patterns
  • package.json — version bump to 5.17.0
  • README.md — version update
  • CHANGELOG.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.