Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91b65fc769 | ||
|
|
ed557cf899 | ||
|
|
d02c9baf7b | ||
|
|
fed81ed5f9 | ||
|
|
a68121544d | ||
|
|
cdb5046147 | ||
|
|
cb60ceec10 | ||
|
|
0f612df70f | ||
|
|
6f97430e41 | ||
|
|
f27c02309e |
No files matched your search
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
|
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
## [Unreleased] — setelah v0.5.1
|
||||||
|
|
||||||
|
### Diperbaiki
|
||||||
|
- Kolom **IP Address** di **GenieACS Pro** kini tampil saat **mode pencarian** (sebelumnya kosong).
|
||||||
|
- Menu **Update GitHub** otomatis disembunyikan pada instalasi **tanpa `.git`** (container/Docker), tetap tampil pada instalasi git.
|
||||||
|
|
||||||
|
### Ditambahkan
|
||||||
|
- **Badge kelengkapan data** di daftar pelanggan: centang hijau = **nomor HP + koordinat + ACS** lengkap; silang merah = ada yang kurang (tooltip menyebut yang belum).
|
||||||
|
- **Backend speedtest bawaan** (`/speedtest/*`) kini aktif (LibreSpeed self-hosted, same-origin).
|
||||||
|
- `isolir-redirect-server` dapat dijalankan **embedded** dalam proses app (opt-out via `ISOLIR_REDIRECT_EMBEDDED=0`).
|
||||||
|
|
||||||
## [0.5.1] — 2026-10-13 — "pickle-lime"
|
## [0.5.1] — 2026-10-13 — "pickle-lime"
|
||||||
|
|
||||||
### Diubah
|
### Diubah
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
> 🪞 **Mirror.** Repo asli & pengembangan ada di **Gitea**: https://gitea.oxpoe.qzz.io/oxpoe/open-isp
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/assets/banner.png" alt="OPEN-ISP" width="100%">
|
<img src="docs/assets/banner.png" alt="OPEN-ISP" width="100%">
|
||||||
</p>
|
</p>
|
||||||
@@ -108,7 +110,7 @@ Dukungan sukarela tetap diterima lewat menu **Dukung OPEN-ISP** di aplikasi.
|
|||||||
|
|
||||||
## Teknologi
|
## Teknologi
|
||||||
|
|
||||||
Node.js · Express · EJS · better-sqlite3 (SQLite) · Bootstrap 5 + Bootstrap Icons (vendor lokal) · Leaflet · PWA/Service Worker · sharp · net-snmp · MikroTik RouterOS API · Baileys / GoWA · Telegram Bot API.
|
Node.js · Express · EJS · better-sqlite3 (SQLite) · Bootstrap 5 + Bootstrap Icons (vendor lokal) · Leaflet · PWA/Service Worker · sharp · net-snmp · MikroTik RouterOS API · GoWA (WhatsApp gateway) · Telegram Bot API.
|
||||||
|
|
||||||
## Lisensi & Kredit
|
## Lisensi & Kredit
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -67,6 +67,7 @@ const app = express();
|
|||||||
|
|
||||||
// Kompresi gzip (performa: respons HTML/CSS/JS lebih kecil)
|
// Kompresi gzip (performa: respons HTML/CSS/JS lebih kecil)
|
||||||
app.use(compression());
|
app.use(compression());
|
||||||
|
app.use('/speedtest', require('./routes/speedtest'));
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
const cookieSecure = getSetting('cookie_secure', isProduction);
|
const cookieSecure = getSetting('cookie_secure', isProduction);
|
||||||
@@ -1106,7 +1107,7 @@ app.post('/api/webhook/v1/payment-notif', multer().any(), async (req, res) => {
|
|||||||
|
|
||||||
// Variabel global untuk modul lain yang masih membaca konfigurasi (mis. skrip utilitas)
|
// Variabel global untuk modul lain yang masih membaca konfigurasi (mis. skrip utilitas)
|
||||||
global.appSettings = {
|
global.appSettings = {
|
||||||
port: getSetting('server_port', 4555),
|
port: getSetting('server_port', 3001),
|
||||||
host: getSetting('server_host', 'localhost'),
|
host: getSetting('server_host', 'localhost'),
|
||||||
genieacsUrl: getSetting('genieacs_url', 'http://localhost:7557'),
|
genieacsUrl: getSetting('genieacs_url', 'http://localhost:7557'),
|
||||||
genieacsUsername: getSetting('genieacs_username', ''),
|
genieacsUsername: getSetting('genieacs_username', ''),
|
||||||
@@ -2210,7 +2211,7 @@ logger.info(`Attempting to start server on configured port: ${port}`);
|
|||||||
startServer(port);
|
startServer(port);
|
||||||
|
|
||||||
if (getSetting('whatsapp_enabled', false)) {
|
if (getSetting('whatsapp_enabled', false)) {
|
||||||
const gwType = getSetting('wa_gateway_type', 'baileys');
|
const gwType = getSetting('wa_gateway_type', 'gowa');
|
||||||
if (gwType === 'baileys') {
|
if (gwType === 'baileys') {
|
||||||
import('./services/whatsappBot.mjs')
|
import('./services/whatsappBot.mjs')
|
||||||
.then((mod) => mod.startWhatsAppBot())
|
.then((mod) => mod.startWhatsAppBot())
|
||||||
@@ -2242,3 +2243,8 @@ app.use(errorHandler);
|
|||||||
|
|
||||||
// Export app untuk testing
|
// Export app untuk testing
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|
||||||
|
// isolir redirect server embedded (port 8099) — untuk container/reverse-proxy; opt-out: ISOLIR_REDIRECT_EMBEDDED=0
|
||||||
|
if (String(process.env.ISOLIR_REDIRECT_EMBEDDED || '1') !== '0') {
|
||||||
|
try { require('./isolir-redirect-server.js'); } catch (e) { console.error('[isolir-redirect]', e.message); }
|
||||||
|
}
|
||||||
+1
-1
@@ -464,7 +464,7 @@ db.exec(`
|
|||||||
CREATE TABLE IF NOT EXISTS wa_chat_messages (
|
CREATE TABLE IF NOT EXISTS wa_chat_messages (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
direction TEXT NOT NULL DEFAULT 'outbound',
|
direction TEXT NOT NULL DEFAULT 'outbound',
|
||||||
gateway TEXT NOT NULL DEFAULT 'baileys',
|
gateway TEXT NOT NULL DEFAULT 'gowa',
|
||||||
sender_phone TEXT,
|
sender_phone TEXT,
|
||||||
recipient_phone TEXT,
|
recipient_phone TEXT,
|
||||||
customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL,
|
customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL,
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Lokasi file settings.json — dapat diarahkan via env SETTINGS_PATH
|
||||||
|
// (dipakai agar settings bisa disimpan di direktori yang di-mount / persisten).
|
||||||
|
const path = require('path');
|
||||||
|
const settingsPath = process.env.SETTINGS_PATH
|
||||||
|
? path.resolve(process.env.SETTINGS_PATH)
|
||||||
|
: path.join(__dirname, '..', 'settings.json');
|
||||||
|
module.exports = { settingsPath };
|
||||||
@@ -17,7 +17,7 @@ let settingsCacheTime = 0;
|
|||||||
const CACHE_DURATION = 2000; // 2 detik
|
const CACHE_DURATION = 2000; // 2 detik
|
||||||
|
|
||||||
// File system watcher untuk auto-reload settings
|
// File system watcher untuk auto-reload settings
|
||||||
const settingsPath = path.join(__dirname, '../settings.json');
|
const { settingsPath } = require('./paths');
|
||||||
let watcher = null;
|
let watcher = null;
|
||||||
|
|
||||||
// Helper untuk baca settings.json secara dinamis
|
// Helper untuk baca settings.json secara dinamis
|
||||||
@@ -99,7 +99,7 @@ function startSettingsWatcher() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const s = getSettingsWithCache();
|
const s = getSettingsWithCache();
|
||||||
const port = s.server_port ?? 4555;
|
const port = s.server_port ?? 3001;
|
||||||
const host = s.server_host || 'localhost';
|
const host = s.server_host || 'localhost';
|
||||||
const gurl = s.genieacs_url || '(tidak diatur)';
|
const gurl = s.genieacs_url || '(tidak diatur)';
|
||||||
const company = s.company_header || '(default)';
|
const company = s.company_header || '(default)';
|
||||||
|
|||||||
@@ -1,274 +0,0 @@
|
|||||||
# Analisis Komprehensif Sistem, Logika Backend, dan UI/UX — Billing RT/RW Net
|
|
||||||
|
|
||||||
> **Target Project**: `/opt/billing-rtrw` (VPS `deneva` — `116.212.74.115`)
|
|
||||||
> **Layanan**: `billing-rtrw.service` (Node.js v20+, port `3001`)
|
|
||||||
> **Repository Asal**: `https://github.com/alijayanet/billing-rtrw`
|
|
||||||
> **Repository Fork/Deploy**: `https://github.com/alijayanet/billing-rtrw`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Ringkasan Eksekutif & Gambaran Sistem
|
|
||||||
|
|
||||||
Billing RT/RW Net ini adalah sistem manajemen operasional ISP (Internet Service Provider) berbasis komunitas yang memadukan:
|
|
||||||
1. **Billing & Invoicing**: Siklus tagihan bulanan, isolir otomatis, dan pembayaran QRIS dinamis/manual.
|
|
||||||
2. **Network Automation**: Integrasi RouterOS MikroTik (PPPoE, Hotspot Voucher, Simple Queue, Address List).
|
|
||||||
3. **Hardware Management (FTTH)**: Embedded ACS TR-069 Server (CWMP) untuk manajemen ONT klien dan SNMP OLT (EPON/GPON).
|
|
||||||
4. **Customer Self-Service (PWA)**: Portal pelanggan mobile-first dengan auto-login 90 hari, ganti WiFi SSID/password mandiri, grafik pemakaian kuota/bandwidth, dan reboot modem.
|
|
||||||
5. **Multi-Channel Bot**: Bot WhatsApp (Baileys socket) dan Bot Telegram untuk notifikasi, penagihan, dan tiket operasional.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
subgraph Klien_Pelanggan["Portal Klien & Interaksi"]
|
|
||||||
CustPWA["Customer PWA (Tailwind + Lucide)"]
|
|
||||||
PublicVoucher["Portal Voucher & QRIS"]
|
|
||||||
WABot["WhatsApp Bot (Baileys)"]
|
|
||||||
TeleBot["Telegram Bot Notifikasi"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Core_Backend["Express 4 Engine (Port 3001)"]
|
|
||||||
App["app-customer.js (Main Entry)"]
|
|
||||||
ACSServer["Embedded TR-069 Server (/acs)"]
|
|
||||||
AuthSvc["Auth & Token Service (90 Hari)"]
|
|
||||||
BillSvc["Billing & Invoice Engine"]
|
|
||||||
MikroSvc["MikroTik RouterOS API"]
|
|
||||||
OLTSvc["OLT Service (SNMP / Web)"]
|
|
||||||
UsageSvc["Bandwidth Usage Tracker"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Data_Network["Infrastruktur Jaringan & Database"]
|
|
||||||
DB[("SQLite WAL (better-sqlite3)")]
|
|
||||||
MikroTik[("Router MikroTik (PPPoE / Hotspot)")]
|
|
||||||
OLT[("OLT Fiber (Hioso/HSGQ/VSOL)")]
|
|
||||||
CPE[("ONT / Modem Pelanggan (TR-069)")]
|
|
||||||
end
|
|
||||||
|
|
||||||
CustPWA -->|HTTP/REST| App
|
|
||||||
PublicVoucher -->|HTTP/REST| App
|
|
||||||
WABot <--> App
|
|
||||||
TeleBot <--> App
|
|
||||||
App --> AuthSvc
|
|
||||||
App --> BillSvc
|
|
||||||
App --> MikroSvc
|
|
||||||
App --> OLTSvc
|
|
||||||
App --> UsageSvc
|
|
||||||
CPE <-->|CWMP SOAP| ACSServer
|
|
||||||
ACSServer <--> DB
|
|
||||||
BillSvc <--> DB
|
|
||||||
MikroSvc <--> MikroTik
|
|
||||||
OLTSvc <--> OLT
|
|
||||||
UsageSvc <--> MikroTik
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Analisis Backend & Logika Alur Fungsi
|
|
||||||
|
|
||||||
### 2.1 Arsitektur Server & Runtime
|
|
||||||
* **Runtime**: Node.js `>=20.0.0` (Native ES Module + CommonJS hybrid).
|
|
||||||
* **Database Engine**: SQLite 3 melalui `better-sqlite3` dengan mode **WAL (Write-Ahead Logging)**, foreign keys aktif, sinkronisasi file cepat dan hemat resource.
|
|
||||||
* **Proses Lifecycle**: Dikelola oleh Systemd (`billing-rtrw.service`) dengan auto-restart `on-failure` (delay 5 detik), batas memori RAM `--max-old-space-size=512`.
|
|
||||||
* **Proteksi Server**:
|
|
||||||
* Penanganan `unhandledRejection` dan `uncaughtException` terpusat agar error koneksi (misal timeout MikroTik) tidak mematikan server Express.
|
|
||||||
* Fallback port otomatis (`startServer` mencoba port +1000 jika port 3001 bentrok).
|
|
||||||
* Proteksi CSRF berbasis validasi header `Origin` dan `Referer` (mengecualikan webhook dan endpoint SOAP ACS).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2.2 Modul-Modul Inti & Alur Logika
|
|
||||||
|
|
||||||
#### A. Layanan Billing, Invoice & Isolir (`services/billingService.js` & `services/cronService.js`)
|
|
||||||
* **Logika Pembuatan Tagihan**:
|
|
||||||
1. Cron berjalan setiap hari (`cronService.js`) memeriksa seluruh pelanggan aktif.
|
|
||||||
2. Tagihan dibuat otomatis setiap siklus tanggal tagihan (`billing_date`) untuk periode bulan berjalan.
|
|
||||||
3. Kode unik nominal QRIS (`qris_amount_unique`) diinjeksi 3 digit acak (1–999) agar mutasi bank/QRIS dapat dicocokkan otomatis tanpa payment gateway berbayar.
|
|
||||||
* **Logika Isolir Otomatis**:
|
|
||||||
1. Bila tanggal saat ini mencapai atau melewati `isolir_day` (default tanggal 20 tiap bulan) dan tagihan pelanggan masih `unpaid`:
|
|
||||||
2. Status pelanggan di database diubah menjadi `isolated`.
|
|
||||||
3. `mikrotikService` dipanggil untuk memindahkan secret PPPoE ke profile isolir atau memasukkan IP klien ke Address List `ISOLIR` di MikroTik Firewall.
|
|
||||||
4. Sesi aktif diputus (`disconnectPPPoE`) sehingga pelanggan ter-redirect ke halaman Walled Garden (`/isolated`).
|
|
||||||
|
|
||||||
#### B. Layanan TR-069 ACS Server (`services/acsServerService.js` & `/acs`)
|
|
||||||
* **Protokol**: CWMP (CPE WAN Management Protocol) via SOAP XML over HTTP POST.
|
|
||||||
* **Alur Inform & Handshake**:
|
|
||||||
1. Modem (ONT) mengirim HTTP POST `Inform` ke endpoint `/acs`.
|
|
||||||
2. Server memeriksa header `Authorization`. Jika belum diautentikasi, server membalas dengan **HTTP 401 Unauthorized** (Digest Challenge).
|
|
||||||
3. Modem mengirim kembali request dengan Digest Hash yang dihitung berdasarkan username (`skyfiber`) dan password acak perangkat.
|
|
||||||
4. Server membaca parameter XML (`Device.DeviceInfo.*`, `Device.WANDevice.*`, `Device.Ethernet.*`, `Device.LANDevice.*`).
|
|
||||||
5. Data kekuatan redaman optik (`RXPower`, `TXPower`), serial number, model, dan versi software disimpan ke tabel `acsdevices`.
|
|
||||||
6. Server memeriksa antrean tugas (task queue) per perangkat: jika ada perintah ganti password WiFi atau reboot, server merespons dengan `SetParameterValues` atau `Reboot`.
|
|
||||||
|
|
||||||
#### C. Pengelolaan WiFi Multi-Vendor (`services/customerDeviceService.js`)
|
|
||||||
* **Masalah Klasik**: Tiap vendor modem (Huawei, ZTE, FiberHome, Nokia) memiliki skema OID/TR-098 yang berbeda-beda. Mengirim parameter yang salah menyebabkan SOAP Fault 9007.
|
|
||||||
* **Logika Adaptif Per-Vendor**:
|
|
||||||
* **Huawei / FiberHome / Nokia**: Memprioritaskan `PreSharedKey.1.KeyPassphrase` dan menghindari pengiriman hex 64-digit bersamaan dengan plain string.
|
|
||||||
* **ZTE / ZICG / CIOT**: Memprioritaskan `KeyPassphrase` top-level.
|
|
||||||
* **Dual-Band SSID**: Mengatur `WLANConfiguration.1.SSID` (2.4 GHz) dan `WLANConfiguration.5.SSID` (5 GHz) secara independen tanpa merusak konfigurasi guest SSID.
|
|
||||||
|
|
||||||
#### D. Integrasi Jaringan MikroTik (`services/mikrotikService.js`)
|
|
||||||
* **Koneksi**: RouterOS API (port default `25722`/`8728`) menggunakan library `routeros-client`.
|
|
||||||
* **Fitur Utama**:
|
|
||||||
* Sinkronisasi PPPoE: Read active connections, update secret passwords, kick active sessions.
|
|
||||||
* Monitoring Traffic: Mengambil data byte RX/TX interface pelanggan untuk kalkulasi kuota.
|
|
||||||
* Voucher Hotspot: Direct injection user hotspot, profile rate-limit, dan validitas voucher.
|
|
||||||
|
|
||||||
#### E. Autentikasi Pelanggan & Persistent Token (`services/customerAuthTokenService.js`)
|
|
||||||
* **Alur Sesi PWA**:
|
|
||||||
1. Pelanggan memasukkan nomor HP / ID Pelanggan.
|
|
||||||
2. Saat login berhasil, backend men-generate token kriptografi acak `crypto.randomBytes(32).toString('hex')`.
|
|
||||||
3. Token di-hash dengan SHA-256 dan disimpan di tabel SQLite `customer_auth_tokens` dengan masa berlaku 90 hari.
|
|
||||||
4. Plain token dikirimkan ke browser sebagai cookie `customer_remember` (`httpOnly`, `secure`, `sameSite: lax`).
|
|
||||||
5. Ketika pengguna membuka PWA (bahkan setelah browser/HP di-restart), middleware auto-login memverifikasi cookie ini dan merekonstruksi session secara transparan.
|
|
||||||
|
|
||||||
#### F. Pelacakan Kuota & Pemakaian Bandwidth (`services/usageService.js`)
|
|
||||||
* **Logika Agregasi**:
|
|
||||||
1. Mengambil byte delta dari MikroTik queue / interface.
|
|
||||||
2. Menyimpan sampel agregat harian dan bulanan ke database.
|
|
||||||
3. Menyediakan API untuk konsumsi frontend (Chart.js di dashboard pelanggan).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Analisis UI / UX, Styling, Typografi & Layout
|
|
||||||
|
|
||||||
Pemeriksaan mendalam terhadap template EJS dan file CSS/JS di `/opt/billing-rtrw/views/` dan `/opt/billing-rtrw/public/` mengungkap sejumlah temuan penting mengenai styling, engine, dan konsistensi desain:
|
|
||||||
|
|
||||||
### 3.1 Peta Engine & Library UI per Portal
|
|
||||||
|
|
||||||
| Portal / Halaman | Engine CSS Utama | Library Icon | Font Family Utama | Style Pendekatan |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| **Customer Dashboard** (`views/dashboard.ejs`) | **Tailwind CSS 3.4.17** (via CDN) | **Lucide Icons** (v1.48.0) + FontAwesome 6.4.0 | **Plus Jakarta Sans** (Google Fonts) | Modern App Shell / Glassmorphism Mobile-first |
|
|
||||||
| **Customer Login** (`views/login.ejs`) | **Bootstrap 5.3.2** (CDN) | **Bootstrap Icons 1.11.3** | **System Font Stack** (`-apple-system`, Segoe UI, Roboto) | Centered Glass Card / Radial Background |
|
|
||||||
| **Admin Portal** (`views/admin/*`) | **Custom CSS** (`/css/admin.css`) | **Bootstrap Icons 1.11.3** + FontAwesome | **Inter** (Google Fonts) | Desktop Enterprise Sidebar & Table Layout |
|
|
||||||
| **Teknisi Portal** (`views/tech/*`) | **Bootstrap 5** + `/css/theme.css` | **Bootstrap Icons** | System Font / Inter | Mobile Bottom Navigation |
|
|
||||||
| **Halaman Peta Jaringan** (`views/admin/map.ejs`) | **Leaflet 1.9.4** + `/css/admin.css` | FontAwesome + SVG Animasi | Inter | Fullscreen Interactive Map & Vector Layer |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.2 Analisis Typografi & Hierarki Font
|
|
||||||
|
|
||||||
```
|
|
||||||
[Customer Dashboard] Plus Jakarta Sans (300, 400, 500, 600, 700, 800)
|
|
||||||
├── Title / Header : text-base sm:text-lg font-bold (Tracking tight)
|
|
||||||
├── Section Labels : text-xs font-semibold text-slate-400 uppercase (Tracking wider)
|
|
||||||
├── Body / Konten : text-sm font-normal text-slate-200 (Leading relaxed)
|
|
||||||
└── Badges & Metadata : text-[10px] sm:text-xs font-medium
|
|
||||||
|
|
||||||
[Customer Login] System Font Stack (BlinkMacSystemFont, Segoe UI, Roboto)
|
|
||||||
└── Ketidaksesuaian : Font berbeda dengan Dashboard; saat user login terjadi visual jump dari font sistem ke Plus Jakarta Sans.
|
|
||||||
|
|
||||||
[Admin Portal] Inter (300, 400, 500, 600, 700, 800)
|
|
||||||
├── Sidebar Items : font-size 13px, font-weight 500
|
|
||||||
├── Data Table Header : font-size 11px, text-transform uppercase, letter-spacing 0.5px
|
|
||||||
└── Table Data Cells : font-size 13px, color var(--text: #e6edf3)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.3 Analisis Styling, Palette Warna & Tema Gelap/Terang
|
|
||||||
|
|
||||||
#### Palette Warna Dominan
|
|
||||||
1. **Mode Gelap (Customer Dashboard)**:
|
|
||||||
* Background Utama: `#0b0f19` (Deep Blue-Black)
|
|
||||||
* Card & Panel: `#1e293b` (Slate 800) dengan border `#334155` (Slate 700)
|
|
||||||
* Aksen Primer (Brand): Gradasi Teal `#14b8a6` ke Cyan `#06b6d4`
|
|
||||||
* Aksen Sekunder: Indigo `#6366f1`
|
|
||||||
* Status Indikator: Hijau Sukses `#10b981`, Merah Warning/Error `#f43f5e`, Amber `#f59e0b`
|
|
||||||
2. **Mode Terang (Customer Dashboard & Global Theme)**:
|
|
||||||
* Background Utama: `#f1f5f9` (Slate 100)
|
|
||||||
* Card & Panel: `#ffffff` murni dengan border `rgba(15, 23, 42, 0.08)`
|
|
||||||
* Teks Utama: `#0f172a` (Slate 900), Teks Muted: `#64748b` (Slate 500)
|
|
||||||
3. **Mekanisme Tema (Theming Engine)**:
|
|
||||||
* Terdapat dua sistem tema yang berjalan berdampingan:
|
|
||||||
* `views/dashboard.ejs` menggunakan script lokal inline (`localStorage: skyfiber-customer-theme`) mengendalikan class `dark` pada `<html>` untuk Tailwind.
|
|
||||||
* Portal lainnya menggunakan `/js/theme.js` dan `/css/theme.css` (`localStorage: app-theme`) mengendalikan atribut `data-theme="light|dark"`.
|
|
||||||
* Pada rilis `v1.9.4`, kedua key ini sudah disinkronkan, namun styling mode terang di portal admin masih membutuhkan penyesuaian kontras pada beberapa tabel dan modal.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.4 Analisis Layout, Spacing, Alignment & UX
|
|
||||||
|
|
||||||
#### 1. Customer Dashboard (Mobile App Shell)
|
|
||||||
* **Alignment & Container**:
|
|
||||||
* Lebar maksimum dibatasi ke `max-w-md` atau `max-w-lg` di tengah layar (`mx-auto`), memberikan rasa native mobile application saat dibuka di smartphone maupun desktop browser.
|
|
||||||
* Padding konten konsisten (`px-4 py-3`), dengan padding bawah ekstra (`pb-28`) untuk memberi ruang bagi floating navigation dock.
|
|
||||||
* **Floating Glass Dock (5 Tab Navigasi)**:
|
|
||||||
* Terpasang fixed di bagian bawah layar: `fixed bottom-4 left-1/2 -translate-x-1/2`.
|
|
||||||
* Menggunakan efek glassmorphism (`backdrop-blur-md`, background semi-transparan, border tipis halus).
|
|
||||||
* Indikator aktif berupa **sliding pill** yang bergerak halus mengikuti tab yang diklik (`updatePillPosition`).
|
|
||||||
* Tab navigasi:
|
|
||||||
1. `tab-beranda` (Home, status paket, kuota, shortcut aksi)
|
|
||||||
2. `tab-router` (Status ONT, redaman fiber optic, ganti WiFi, reboot)
|
|
||||||
3. `tab-ppob` (Layanan isi pulsa & token listrik - status Coming Soon)
|
|
||||||
4. `tab-tagihan` (Riwayat invoice, tombol bayar QRIS)
|
|
||||||
5. `tab-profile` (Data akun, riwayat tiket kendala, tombol CS WhatsApp, logout)
|
|
||||||
* **Kerapian & Touch Target**:
|
|
||||||
* Seluruh tombol interaktif memiliki ukuran touch-target minimal `40x40px` (ramah sentuhan jari di layar HP).
|
|
||||||
* Safe-area-insets untuk iPhone (`env(safe-area-inset-bottom)`) sudah ditambahkan pada `.dock-glass`.
|
|
||||||
|
|
||||||
#### 2. Admin & Staff Portal
|
|
||||||
* **Layout**:
|
|
||||||
* Sidebar navigasi di sisi kiri (`width: 240px`, fixed).
|
|
||||||
* Konten utama di sebelah kanan dengan header fixed dan breadcrumb.
|
|
||||||
* Responsive breakpoint: Pada layar kecil (mobile), sidebar disembunyikan dan diakses lewat toggle menu hamburger.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.5 Temuan Evaluasi & Celah UI/UX (Bottlenecks)
|
|
||||||
|
|
||||||
1. **Fragmentasi Framework CSS**:
|
|
||||||
* Halaman login menggunakan **Bootstrap 5**, sedangkan dashboard pelanggan menggunakan **Tailwind CSS**. Ini membebani browser pengguna karena harus mengunduh dua framework CSS besar saat pertama kali membuka aplikasi.
|
|
||||||
2. **Ketergantungan CDN Runtime (Client-Side Tailwind)**:
|
|
||||||
* Dashboard pelanggan menggunakan `<script src="https://cdn.tailwindcss.com/3.4.17"></script>`. CDN ini mem-parse class Tailwind saat runtime di perangkat klien (JIT compiler di browser), yang dapat memperlambat rendering awal (First Contentful Paint) pada ponsel berspesifikasi rendah.
|
|
||||||
3. **Dual Icon Libraries**:
|
|
||||||
* Halaman dashboard memuat **Lucide Icons** dan **FontAwesome 6.4.0** secara bersamaan. FontAwesome (file CSS + font ~400KB) hanya digunakan untuk segelintir ikon kecil, sementara sebagian besar UI sudah menggunakan SVG Lucide.
|
|
||||||
4. **Log Error Berulang di Backend**:
|
|
||||||
* Terdapat log `Telegram Polling Error` berkala pada journal server deneva karena token bot belum dikonfigurasi / timeout koneksi ke server Telegram API.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Rencana Kerja Terperinci (Execution Plan)
|
|
||||||
|
|
||||||
Rencana perubahan ini dibagi menjadi 4 fase berurutan dengan prinsip **zero-downtime** dan keamanan data:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
timeline
|
|
||||||
title Timeline Rencana Peningkatan Billing RTRW
|
|
||||||
Fase 1 : Konsolidasi Backend & Error Fix : Fix Polling Telegram : Optimalisasi Error Handling
|
|
||||||
Fase 2 : Unifikasi Design System : Standarisasi Font (Plus Jakarta Sans) : Harmonisasi Halaman Login & Dashboard
|
|
||||||
Fase 3 : Optimasi Asset & Performa : Eliminasi Redundansi Icon : Cache Profiling & PWA Hardening
|
|
||||||
Fase 4 : Testing & Validasi : Uji Responsivitas Semua Layar : Uji Fungsi WiFi & Pembayaran Live
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fase 1: Perbaikan Stabilitas & Logika Backend
|
|
||||||
1. **Fix Polling Telegram (`services/telegramBot.js`)**:
|
|
||||||
* Tambahkan pengecekan kondisi: jika token Telegram kosong, nonaktifkan `polling: true` agar tidak membanjiri log server dengan polling error setiap 5 detik.
|
|
||||||
2. **Peningkatan Ketahanan Koneksi MikroTik (`services/mikrotikService.js`)**:
|
|
||||||
* Implementasikan retry backoff otomatis saat query queue/PPPoE timeout, menghindari uncaught exception yang tidak perlu.
|
|
||||||
3. **Audit Query Database (`config/database.js`)**:
|
|
||||||
* Periksa indeks pada tabel `pppoe_traffic_samples`, `invoices`, dan `customer_auth_tokens` untuk mempercepat query bulanan.
|
|
||||||
|
|
||||||
### Fase 2: Harmonisasi UI/UX & Standarisasi Tipografi
|
|
||||||
1. **Standarisasi Tipografi**:
|
|
||||||
* Terapkan font **Plus Jakarta Sans** ke seluruh halaman pelanggan, termasuk halaman Login (`views/login.ejs`), Register, dan Isolir (`views/isolated.ejs`), menggantikan system font default agar pengalaman visual konsisten sejak awal.
|
|
||||||
2. **Harmonisasi Desain Halaman Login (`views/login.ejs`)**:
|
|
||||||
* Rombak halaman login agar memiliki nuansa tema, sudut rounded, dan aksen warna Teal/Cyan yang identik dengan Dashboard Pelanggan.
|
|
||||||
* Ganti elemen Bootstrap pada halaman login menjadi layout yang selaras dengan tema dashboard.
|
|
||||||
3. **Penyempurnaan Form Ganti WiFi di Tab Router**:
|
|
||||||
* Perjelas instruksi frekuensi 2.4 GHz vs 5 GHz.
|
|
||||||
* Tambahkan toggle visibility (mata) untuk password WiFi dan validasi panjang password (minimal 8 karakter) di sisi klien sebelum submit.
|
|
||||||
|
|
||||||
### Fase 3: Optimasi Frontend & Efisiensi Aset
|
|
||||||
1. **Rasionalisasi Icon**:
|
|
||||||
* Migrasikan sisa ikon FontAwesome di Dashboard pelanggan ke **Lucide Icons** bawaan.
|
|
||||||
* Hapus pemanggilan CSS FontAwesome di dashboard pelanggan untuk menghemat ukuran transfer data hingga ~400 KB.
|
|
||||||
2. **Optimasi Mode Terang (Light Mode)**:
|
|
||||||
* Periksa dan rapikan kontras teks di seluruh tab dashboard agar tidak ada teks putih yang tidak terbaca saat berganti ke background terang.
|
|
||||||
* Pastikan border card dan form input terlihat tegas pada mode terang.
|
|
||||||
|
|
||||||
### Fase 4: Pengujian, Validasi & Deployment
|
|
||||||
1. **Uji Coba Lintas Perangkat**:
|
|
||||||
* Uji tampilan di mobile (Android Chrome, iOS Safari), tablet, dan desktop.
|
|
||||||
* Uji fungsi PWA Add to Home Screen dan verifikasi auto-login setelah kill aplikasi.
|
|
||||||
2. **Uji Fungsional End-to-End**:
|
|
||||||
* Uji ganti SSID/password pada modem online.
|
|
||||||
* Uji alur pembuatan invoice dan QRIS.
|
|
||||||
* Restart service `billing-rtrw.service` dan verifikasi log bersih tanpa error.
|
|
||||||
@@ -1,317 +0,0 @@
|
|||||||
# AUDIT FINDINGS SUMMARY - QUICK REFERENCE
|
|
||||||
**Status:** Complete Audit
|
|
||||||
**Date:** 14 August 2026
|
|
||||||
**Format:** Executive Summary for Management
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 AUDIT OVERVIEW
|
|
||||||
|
|
||||||
| Metric | Status | Details |
|
|
||||||
|--------|--------|---------|
|
|
||||||
| **Security Score** | 5.5/10 | ⚠️ Critical issues found |
|
|
||||||
| **Code Quality** | 7/10 | Good structure, needs hardening |
|
|
||||||
| **Database Integrity** | 8/10 | Well-designed schema |
|
|
||||||
| **Production Ready** | ❌ NO | Must fix critical issues first |
|
|
||||||
| **Audit Completed** | ✅ YES | Full report available |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔴 CRITICAL FINDINGS (MUST FIX IMMEDIATELY)
|
|
||||||
|
|
||||||
### 1. PASSWORD STORAGE (CRITICAL)
|
|
||||||
- **Issue:** Passwords stored in plaintext in database
|
|
||||||
- **Risk:** Full account compromise if database breached
|
|
||||||
- **Files:** `services/techService.js`, `settings.json`
|
|
||||||
- **Fix Time:** 4-6 hours
|
|
||||||
- **Severity:** CRITICAL
|
|
||||||
- **Status:** Not Fixed ❌
|
|
||||||
|
|
||||||
### 2. DEFAULT CREDENTIALS (CRITICAL)
|
|
||||||
- **Issue:** Default admin password `admin123` in settings.json
|
|
||||||
- **Risk:** Anyone can login to admin panel
|
|
||||||
- **Fix Time:** 1 hour
|
|
||||||
- **Severity:** CRITICAL
|
|
||||||
- **Status:** Not Fixed ❌
|
|
||||||
|
|
||||||
### 3. UNENCRYPTED CREDENTIALS (CRITICAL)
|
|
||||||
- **Issue:** API keys and passwords in plaintext in settings.json
|
|
||||||
- **Risk:** Exposure in version control, server breach
|
|
||||||
- **Files:** `settings.json`
|
|
||||||
- **Fix Time:** 2-3 hours
|
|
||||||
- **Severity:** CRITICAL
|
|
||||||
- **Status:** Not Fixed ❌
|
|
||||||
|
|
||||||
### 4. WEAK PASSWORD HASHING (HIGH)
|
|
||||||
- **Issue:** SHA256 and MD5 used instead of bcrypt/argon2
|
|
||||||
- **Risk:** Password cracking in hours/minutes
|
|
||||||
- **Files:** Multiple service files
|
|
||||||
- **Fix Time:** 6-8 hours
|
|
||||||
- **Severity:** HIGH
|
|
||||||
- **Status:** Not Fixed ❌
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🟠 HIGH PRIORITY FINDINGS
|
|
||||||
|
|
||||||
### 5. CSRF PROTECTION (LIMITED)
|
|
||||||
- **Issue:** Only Referer/Origin validation, no explicit CSRF tokens
|
|
||||||
- **Risk:** Cross-site request forgery attacks possible
|
|
||||||
- **Fix Time:** 4-6 hours
|
|
||||||
- **Severity:** HIGH
|
|
||||||
|
|
||||||
### 6. SESSION MANAGEMENT
|
|
||||||
- **Issue:** In-memory session store, no regeneration on login
|
|
||||||
- **Risk:** Session hijacking possible
|
|
||||||
- **Fix Time:** 3-4 hours
|
|
||||||
- **Severity:** HIGH
|
|
||||||
|
|
||||||
### 7. INPUT VALIDATION
|
|
||||||
- **Issue:** Incomplete input validation and sanitization
|
|
||||||
- **Risk:** XSS and injection attacks
|
|
||||||
- **Fix Time:** 8-10 hours
|
|
||||||
- **Severity:** HIGH
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🟡 MEDIUM PRIORITY FINDINGS
|
|
||||||
|
|
||||||
### 8. FILE UPLOAD SECURITY
|
|
||||||
- **Issue:** Only MIME type validation, no deep file scanning
|
|
||||||
- **Risk:** Malicious file upload
|
|
||||||
- **Fix Time:** 2-4 hours
|
|
||||||
- **Severity:** MEDIUM
|
|
||||||
|
|
||||||
### 9. BACKUP & DISASTER RECOVERY
|
|
||||||
- **Issue:** Auto-backup disabled in settings
|
|
||||||
- **Risk:** Data loss in disaster scenario
|
|
||||||
- **Fix Time:** 2-3 hours
|
|
||||||
- **Severity:** MEDIUM
|
|
||||||
|
|
||||||
### 10. ERROR LOGGING
|
|
||||||
- **Issue:** Some errors may expose sensitive info
|
|
||||||
- **Risk:** Information disclosure
|
|
||||||
- **Fix Time:** 2-3 hours
|
|
||||||
- **Severity:** MEDIUM
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ POSITIVE FINDINGS
|
|
||||||
|
|
||||||
### What's Working Well:
|
|
||||||
- ✅ Database design is solid with proper constraints
|
|
||||||
- ✅ Error handling middleware implemented
|
|
||||||
- ✅ Winston logger properly configured
|
|
||||||
- ✅ SQL queries use parameterized statements (SQL injection safe)
|
|
||||||
- ✅ File upload has MIME type validation
|
|
||||||
- ✅ Session cookies have httpOnly and sameSite flags
|
|
||||||
- ✅ CORS/Origin checking implemented
|
|
||||||
- ✅ Comprehensive feature set well-structured
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 TIMELINE TO PRODUCTION READINESS
|
|
||||||
|
|
||||||
```
|
|
||||||
Week 1 (CRITICAL):
|
|
||||||
├─ Fix password hashing → 8 hours
|
|
||||||
├─ Setup .env & remove hardcoded secrets → 4 hours
|
|
||||||
├─ Implement CSRF tokens → 4 hours
|
|
||||||
├─ Setup session store → 3 hours
|
|
||||||
└─ Testing → 4 hours
|
|
||||||
TOTAL: ~23 hours (3 days)
|
|
||||||
|
|
||||||
Week 2 (HIGH):
|
|
||||||
├─ Input validation framework → 8 hours
|
|
||||||
├─ Rate limiting → 4 hours
|
|
||||||
├─ Backup automation → 3 hours
|
|
||||||
├─ Security headers (helmet) → 2 hours
|
|
||||||
└─ Testing & QA → 8 hours
|
|
||||||
TOTAL: ~25 hours (3 days)
|
|
||||||
|
|
||||||
Week 3 (MEDIUM):
|
|
||||||
├─ File upload scanning → 6 hours
|
|
||||||
├─ Log review & cleanup → 4 hours
|
|
||||||
├─ Documentation → 4 hours
|
|
||||||
└─ Staging deployment → 4 hours
|
|
||||||
TOTAL: ~18 hours (2 days)
|
|
||||||
|
|
||||||
GRAND TOTAL: ~66 hours (~2 weeks full-time)
|
|
||||||
or ~3-4 weeks part-time (20 hrs/week)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💼 BUSINESS IMPACT
|
|
||||||
|
|
||||||
### Current State (If Deployed Now):
|
|
||||||
- 🔴 **NOT RECOMMENDED** for production
|
|
||||||
- Risk of account takeover is HIGH
|
|
||||||
- Risk of data breach is CRITICAL
|
|
||||||
- Compliance risk with regulations (OJK, PCI-DSS if payment processing)
|
|
||||||
|
|
||||||
### After Critical Fixes (1-2 weeks):
|
|
||||||
- 🟢 **PRODUCTION READY** for initial deployment
|
|
||||||
- Password security improved significantly
|
|
||||||
- Credentials properly managed
|
|
||||||
- Basic CSRF protection in place
|
|
||||||
|
|
||||||
### After All Fixes (3-4 weeks):
|
|
||||||
- 🟢 **PRODUCTION HARDENED**
|
|
||||||
- Enterprise-grade security
|
|
||||||
- Compliance-ready
|
|
||||||
- Audit-ready for external review
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 👥 RESOURCE REQUIREMENTS
|
|
||||||
|
|
||||||
### Team Needed:
|
|
||||||
- **Backend Developer(s):** 1-2 (main focus)
|
|
||||||
- **Security Reviewer:** 1 (part-time, 4-6 hours/week)
|
|
||||||
- **QA/Tester:** 1 (part-time, 2-4 hours/week)
|
|
||||||
- **DevOps/Infrastructure:** 0.5 (env setup, deployment)
|
|
||||||
|
|
||||||
### Skills Required:
|
|
||||||
- Node.js/JavaScript (Backend)
|
|
||||||
- Database migration experience
|
|
||||||
- Security best practices knowledge
|
|
||||||
- Testing & QA methodologies
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 RECOMMENDED ACTION PLAN
|
|
||||||
|
|
||||||
### Phase 1: IMMEDIATE (Days 1-2)
|
|
||||||
1. ✅ Review this audit with team
|
|
||||||
2. ✅ Create .env file with sensitive data
|
|
||||||
3. ✅ Install required packages (bcrypt, helmet, etc)
|
|
||||||
4. ✅ Backup current database
|
|
||||||
|
|
||||||
### Phase 2: CRITICAL FIXES (Days 3-5)
|
|
||||||
1. ✅ Implement password hashing with bcrypt
|
|
||||||
2. ✅ Migrate existing passwords
|
|
||||||
3. ✅ Setup environment variables
|
|
||||||
4. ✅ Remove hardcoded credentials
|
|
||||||
|
|
||||||
### Phase 3: TESTING & VALIDATION (Days 6-7)
|
|
||||||
1. ✅ Test all authentication flows
|
|
||||||
2. ✅ Verify password migration
|
|
||||||
3. ✅ Test environment variable loading
|
|
||||||
4. ✅ Deploy to staging
|
|
||||||
|
|
||||||
### Phase 4: HIGH PRIORITY FIXES (Week 2)
|
|
||||||
1. ✅ Implement CSRF protection
|
|
||||||
2. ✅ Setup session store
|
|
||||||
3. ✅ Add rate limiting
|
|
||||||
4. ✅ Input validation
|
|
||||||
|
|
||||||
### Phase 5: FINAL HARDENING (Week 3)
|
|
||||||
1. ✅ Security headers (Helmet)
|
|
||||||
2. ✅ File upload scanning
|
|
||||||
3. ✅ Backup automation
|
|
||||||
4. ✅ Final testing
|
|
||||||
|
|
||||||
### Phase 6: PRODUCTION DEPLOYMENT (End of Week 3)
|
|
||||||
1. ✅ Security review checklist
|
|
||||||
2. ✅ Final audit verification
|
|
||||||
3. ✅ Deploy to production
|
|
||||||
4. ✅ Monitoring setup
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 DEPLOYMENT CHECKLIST
|
|
||||||
|
|
||||||
Before production deployment, verify:
|
|
||||||
|
|
||||||
- [ ] **Password Security**
|
|
||||||
- [ ] Bcrypt implemented and tested
|
|
||||||
- [ ] All passwords migrated to hashes
|
|
||||||
- [ ] No plaintext passwords in code/config
|
|
||||||
- [ ] Login flows working with hashed passwords
|
|
||||||
|
|
||||||
- [ ] **Credential Management**
|
|
||||||
- [ ] .env file created and populated
|
|
||||||
- [ ] Settings.json reviewed for sensitive data
|
|
||||||
- [ ] .env added to .gitignore
|
|
||||||
- [ ] Environment variables working
|
|
||||||
|
|
||||||
- [ ] **Access Control**
|
|
||||||
- [ ] CSRF protection implemented
|
|
||||||
- [ ] Rate limiting configured
|
|
||||||
- [ ] Session management hardened
|
|
||||||
- [ ] Role-based access control verified
|
|
||||||
|
|
||||||
- [ ] **Security Headers**
|
|
||||||
- [ ] Helmet.js installed and configured
|
|
||||||
- [ ] HTTPS enforced (if applicable)
|
|
||||||
- [ ] Security headers present in responses
|
|
||||||
|
|
||||||
- [ ] **Data Protection**
|
|
||||||
- [ ] Input validation implemented
|
|
||||||
- [ ] Output sanitization verified
|
|
||||||
- [ ] File uploads scanning configured
|
|
||||||
- [ ] Database encryption (at rest if needed)
|
|
||||||
|
|
||||||
- [ ] **Logging & Monitoring**
|
|
||||||
- [ ] Audit trail logged for sensitive operations
|
|
||||||
- [ ] Error logging configured
|
|
||||||
- [ ] Monitoring alerts setup
|
|
||||||
- [ ] Log aggregation configured
|
|
||||||
|
|
||||||
- [ ] **Backup & Recovery**
|
|
||||||
- [ ] Automated backup enabled
|
|
||||||
- [ ] Backup encryption enabled
|
|
||||||
- [ ] Recovery procedure tested
|
|
||||||
- [ ] Backup retention policy set
|
|
||||||
|
|
||||||
- [ ] **Testing**
|
|
||||||
- [ ] Unit tests passing
|
|
||||||
- [ ] Integration tests passing
|
|
||||||
- [ ] Security tests completed
|
|
||||||
- [ ] Load testing completed
|
|
||||||
- [ ] Penetration testing (optional but recommended)
|
|
||||||
|
|
||||||
- [ ] **Documentation**
|
|
||||||
- [ ] Security practices documented
|
|
||||||
- [ ] Incident response plan ready
|
|
||||||
- [ ] Setup procedures documented
|
|
||||||
- [ ] Troubleshooting guide prepared
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚨 KNOWN VULNERABILITIES
|
|
||||||
|
|
||||||
| Vulnerability | CVSS Score | Status | Action |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Plaintext password storage | 9.8 | Open | CRITICAL - Fix immediately |
|
|
||||||
| Default credentials | 8.7 | Open | CRITICAL - Fix immediately |
|
|
||||||
| Unencrypted API keys | 8.9 | Open | CRITICAL - Fix immediately |
|
|
||||||
| Weak password hashing | 7.5 | Open | HIGH - Fix this week |
|
|
||||||
| Limited CSRF protection | 6.5 | Open | HIGH - Fix this week |
|
|
||||||
| In-memory session store | 6.1 | Open | HIGH - Fix this week |
|
|
||||||
| Incomplete input validation | 7.1 | Open | HIGH - Fix this week |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 CONTACT & SUPPORT
|
|
||||||
|
|
||||||
For questions or clarifications:
|
|
||||||
1. Review detailed findings in `AUDIT_SECURITY_2026.md`
|
|
||||||
2. Check implementation guide in `IMPLEMENTATION_GUIDE_SECURITY.md`
|
|
||||||
3. Consult code samples in this document
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 SIGN-OFF
|
|
||||||
|
|
||||||
**Audit Completed By:** Security & Code Quality Review Team
|
|
||||||
**Date Completed:** 14 August 2026
|
|
||||||
**Next Review:** 30 days after fixes implementation
|
|
||||||
**Status:** READY FOR ACTION ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**RECOMMENDATION: DO NOT DEPLOY TO PRODUCTION UNTIL CRITICAL FIXES ARE COMPLETED**
|
|
||||||
|
|
||||||
For production deployment, all findings must be reviewed, and critical issues must be resolved.
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,833 +0,0 @@
|
|||||||
# AUDIT KEAMANAN & KUALITAS APLIKASI BILLING RTRWNET
|
|
||||||
**Status:** ✅ AUDIT LENGKAP
|
|
||||||
**Tanggal Audit:** 14 Agustus 2026
|
|
||||||
**Auditor:** Security & Code Quality Review
|
|
||||||
**Aplikasi Status:** 🟠 PRODUCTION WITH CRITICAL ISSUES
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 RINGKASAN EKSEKUTIF
|
|
||||||
|
|
||||||
Aplikasi RTRWNET Billing System adalah sistem yang **comprehensive dan feature-rich** untuk manajemen ISP. Namun, audit menemukan **beberapa kerentanan keamanan KRITIS** yang harus segera diperbaiki sebelum production deployment atau jika sudah di production harus segera ditindaklanjuti.
|
|
||||||
|
|
||||||
### Skor Keamanan: 5.5/10 🔴
|
|
||||||
- ✅ Aspek Positif: Struktur kode baik, error handling solid, logging terimplementasi
|
|
||||||
- ⚠️ Aspek Kritis: Keamanan password, configuration management, input validation
|
|
||||||
- ❌ Aspek Negatif: Plaintext credentials, default passwords, weak hashing
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 KEAMANAN KRITIS - SEGERA PERBAIKI
|
|
||||||
|
|
||||||
### 1. PASSWORD DISIMPAN DALAM PLAINTEXT ❌ CRITICAL
|
|
||||||
|
|
||||||
**Lokasi:**
|
|
||||||
- [services/techService.js](services/techService.js#L4) - Password technician tersimpan plaintext di database
|
|
||||||
- [settings.json](settings.json#L20-L21) - Admin password `admin123` dalam plaintext
|
|
||||||
|
|
||||||
**Masalah:**
|
|
||||||
```javascript
|
|
||||||
// ❌ TIDAK AMAN - Line 4 di techService.js
|
|
||||||
const tech = db.prepare('SELECT * FROM technicians WHERE username = ? AND password = ? AND is_active = 1').get(username, password);
|
|
||||||
// Password dibandingkan langsung tanpa hashing
|
|
||||||
|
|
||||||
// ❌ TIDAK AMAN - settings.json
|
|
||||||
"admin_username": "admin",
|
|
||||||
"admin_password": "admin123", // Password plaintext!
|
|
||||||
```
|
|
||||||
|
|
||||||
**Risiko:**
|
|
||||||
- Jika database bocor, semua password terekspos
|
|
||||||
- Jika settings.json terlihat, credentials admin langsung ketahuan
|
|
||||||
- Tidak compliance dengan standar keamanan (OWASP, ISO27001)
|
|
||||||
- Database breach = total kompromis sistem
|
|
||||||
|
|
||||||
**Rekomendasi - HARUS DILAKUKAN:**
|
|
||||||
|
|
||||||
1. **Implementasi bcrypt untuk hashing password:**
|
|
||||||
```javascript
|
|
||||||
// Install bcrypt terlebih dahulu
|
|
||||||
// npm install bcrypt
|
|
||||||
|
|
||||||
// Saat register/create user:
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
const hashedPassword = await bcrypt.hash(plainPassword, 10);
|
|
||||||
// Simpan hashedPassword ke database
|
|
||||||
|
|
||||||
// Saat authenticate:
|
|
||||||
const isValid = await bcrypt.compare(plainPassword, hashedPassword);
|
|
||||||
if (isValid) {
|
|
||||||
// Login berhasil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Update database schema:**
|
|
||||||
```sql
|
|
||||||
-- Tambahkan kolom password_hash
|
|
||||||
ALTER TABLE technicians ADD COLUMN password_hash TEXT;
|
|
||||||
ALTER TABLE administrators ADD COLUMN password_hash TEXT;
|
|
||||||
|
|
||||||
-- Update existing passwords dengan hash
|
|
||||||
-- (Lakukan migration script terpisah)
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Jangan simpan password di settings.json:**
|
|
||||||
```json
|
|
||||||
// ❌ SEBELUM
|
|
||||||
{
|
|
||||||
"admin_password": "admin123"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ SESUDAH - Gunakan database atau environment variable
|
|
||||||
{
|
|
||||||
"admin_user_id": 1 // Reference ke database saja
|
|
||||||
}
|
|
||||||
|
|
||||||
// Atau gunakan .env file:
|
|
||||||
ADMIN_PASSWORD_HASH="$2b$10$..."
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** IMMEDIATE - Sebelum production
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. DEFAULT CREDENTIALS (admin/admin123) ❌ CRITICAL
|
|
||||||
|
|
||||||
**Lokasi:** [settings.json](settings.json#L19-L21)
|
|
||||||
|
|
||||||
**Masalah:**
|
|
||||||
- Default username/password yang sangat lemah
|
|
||||||
- Siapapun yang tahu dokumentasi aplikasi bisa login
|
|
||||||
- OWASP Top 10: A01:2021 - Broken Access Control
|
|
||||||
|
|
||||||
**Rekomendasi:**
|
|
||||||
```bash
|
|
||||||
# Script untuk force password change saat first-time setup
|
|
||||||
node scripts/setup-admin-password.js
|
|
||||||
|
|
||||||
# Atau implementasikan setup wizard:
|
|
||||||
- Saat pertama kali aplikasi dijalankan
|
|
||||||
- Wajib setup admin username & password baru
|
|
||||||
- Simpan di database dengan hash bcrypt
|
|
||||||
- Tandai setup selesai di flag di database
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** IMMEDIATE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. API KEY & CREDENTIALS DALAM PLAINTEXT ❌ CRITICAL
|
|
||||||
|
|
||||||
**Lokasi:** [settings.json](settings.json)
|
|
||||||
```json
|
|
||||||
"genieacs_password": "admin",
|
|
||||||
"midtrans_server_key": "",
|
|
||||||
"xendit_api_key": "",
|
|
||||||
"tripay_private_key": "",
|
|
||||||
"digiflazz_api_key": "",
|
|
||||||
"radius_secret": "secret123"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Masalah:**
|
|
||||||
- Semua credentials eksternal dalam plaintext di file JSON
|
|
||||||
- File bisa terlihat di source code management
|
|
||||||
- Risiko exposure jika server dikompromis
|
|
||||||
- Tidak praktis untuk berbagai environment (dev/staging/production)
|
|
||||||
|
|
||||||
**Rekomendasi - HARUS DILAKUKAN:**
|
|
||||||
|
|
||||||
1. **Gunakan environment variables (.env):**
|
|
||||||
```bash
|
|
||||||
# File: .env (JANGAN commit ke git!)
|
|
||||||
GENIEACS_URL=http://192.168.8.189:7557
|
|
||||||
GENIEACS_USERNAME=admin
|
|
||||||
GENIEACS_PASSWORD=SecurePassword123!
|
|
||||||
MIDTRANS_SERVER_KEY=Mid-xxxxx-xxx
|
|
||||||
XENDIT_API_KEY=xnd_development_xxxxx
|
|
||||||
TRIPAY_API_KEY=xxxxx
|
|
||||||
DIGIFLAZZ_API_KEY=xxxxx
|
|
||||||
RADIUS_SECRET=RadiusSecret123!
|
|
||||||
SESSION_SECRET=RandomSecure123!SecretHere
|
|
||||||
ADMIN_API_KEY=AdminApiKey123!Secure
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Implementasi di code:**
|
|
||||||
```javascript
|
|
||||||
// ✅ AMAN
|
|
||||||
const settings = {
|
|
||||||
genieacs_password: process.env.GENIEACS_PASSWORD || getSetting('genieacs_password'),
|
|
||||||
midtrans_server_key: process.env.MIDTRANS_SERVER_KEY,
|
|
||||||
xendit_api_key: process.env.XENDIT_API_KEY,
|
|
||||||
// ... dll
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Update .gitignore:**
|
|
||||||
```bash
|
|
||||||
# Jangan commit file ini!
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
settings.json # Alternatif: commit template saja
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Implementasi encryption untuk settings.json (jika diperlukan):**
|
|
||||||
```javascript
|
|
||||||
// Gunakan settingsEncryption.js yang sudah ada
|
|
||||||
const { encryptValue, decryptValue } = require('./config/settingsEncryption');
|
|
||||||
|
|
||||||
// Encrypt credentials:
|
|
||||||
const encrypted = encryptValue(credentialValue);
|
|
||||||
|
|
||||||
// Decrypt saat digunakan:
|
|
||||||
const plainValue = decryptValue(encryptedValue);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** IMMEDIATE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. WEAK PASSWORD HASHING ⚠️ HIGH
|
|
||||||
|
|
||||||
**Lokasi Lainnya:**
|
|
||||||
- [config/database.js](config/database.js#L627) - SHA256 untuk password
|
|
||||||
- [services/agentService.js](services/agentService.js#L27) - MD5 hash
|
|
||||||
- [routes/adminPortal.js](routes/adminPortal.js#L98) - MD5 hash
|
|
||||||
|
|
||||||
**Masalah:**
|
|
||||||
```javascript
|
|
||||||
// ❌ TIDAK AMAN - SHA256 bukan untuk password hashing
|
|
||||||
const hash = crypto.createHash('sha256').update(input).digest('hex');
|
|
||||||
|
|
||||||
// ❌ TIDAK AMAN - MD5 sudah deprecated
|
|
||||||
const sign = crypto.createHash('md5').update(username + apiKey).digest('hex');
|
|
||||||
```
|
|
||||||
|
|
||||||
- SHA256/MD5 = algoritma hashing cepat (bisa di-brute force)
|
|
||||||
- Tidak ada salt random
|
|
||||||
- Tidak ada rate limiting pada hashing
|
|
||||||
- Bisa di-crack dengan GPU dalam hitungan jam
|
|
||||||
|
|
||||||
**Rekomendasi:**
|
|
||||||
```javascript
|
|
||||||
// ✅ AMAN - Gunakan bcrypt atau Argon2
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
const hash = await bcrypt.hash(password, 10); // 10 = cost factor
|
|
||||||
const isValid = await bcrypt.compare(plainPassword, hash);
|
|
||||||
|
|
||||||
// Atau Argon2 (lebih kuat):
|
|
||||||
const argon2 = require('argon2');
|
|
||||||
const hash = await argon2.hash(password);
|
|
||||||
const isValid = await argon2.verify(hash, password);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** HIGH - Dalam 1 minggu
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 KEAMANAN SEDANG - TINGKATKAN
|
|
||||||
|
|
||||||
### 5. SESSION MANAGEMENT ⚠️ MEDIUM
|
|
||||||
|
|
||||||
**Lokasi:** [app-customer.js](app-customer.js#L79-L90)
|
|
||||||
|
|
||||||
**Status Saat Ini:** ✅ Baik
|
|
||||||
```javascript
|
|
||||||
app.use(session({
|
|
||||||
secret: getSetting('session_secret', 'default-secret'),
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: false,
|
|
||||||
cookie: {
|
|
||||||
secure: Boolean(cookieSecure), // ✅ HTTPS only (jika production)
|
|
||||||
httpOnly: true, // ✅ JavaScript tidak bisa akses
|
|
||||||
sameSite: 'lax', // ✅ CSRF protection
|
|
||||||
maxAge: 24 * 60 * 60 * 1000 // ✅ Timeout 24 jam
|
|
||||||
},
|
|
||||||
name: 'customer.sid'
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rekomendasi Improvement:**
|
|
||||||
|
|
||||||
1. **Gunakan session store eksternal:**
|
|
||||||
```javascript
|
|
||||||
// Jangan gunakan default memory store di production
|
|
||||||
const SessionStore = require('better-sqlite3-session-store')(session);
|
|
||||||
const store = new SessionStore({
|
|
||||||
client: db,
|
|
||||||
expired: {
|
|
||||||
clear: true,
|
|
||||||
intervalMs: 15 * 60 * 1000 // Clear expired sessions tiap 15 menit
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(session({
|
|
||||||
store: store,
|
|
||||||
// ... config lainnya
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Regenerate session ID saat login:**
|
|
||||||
```javascript
|
|
||||||
// Saat login berhasil
|
|
||||||
req.session.regenerate((err) => {
|
|
||||||
if (err) return res.status(500).json({ error: 'Session error' });
|
|
||||||
req.session.userId = user.id;
|
|
||||||
req.session.userRole = user.role;
|
|
||||||
// ... simpan data user lainnya
|
|
||||||
res.redirect('/dashboard');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Validasi session pada setiap request:**
|
|
||||||
```javascript
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
// Cek timeout session
|
|
||||||
const lastActivity = req.session.lastActivity || Date.now();
|
|
||||||
const inactivityTimeout = 30 * 60 * 1000; // 30 menit
|
|
||||||
|
|
||||||
if (Date.now() - lastActivity > inactivityTimeout) {
|
|
||||||
req.session.destroy();
|
|
||||||
return res.status(401).json({ error: 'Session expired' });
|
|
||||||
}
|
|
||||||
|
|
||||||
req.session.lastActivity = Date.now();
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** MEDIUM - 2 minggu
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. CSRF PROTECTION ⚠️ MEDIUM
|
|
||||||
|
|
||||||
**Status Saat Ini:** ⚠️ Terbatas
|
|
||||||
```javascript
|
|
||||||
// Hanya check Referer/Origin, tidak ada CSRF token explicit
|
|
||||||
if (origin) {
|
|
||||||
const originHost = new URL(origin).host;
|
|
||||||
if (originHost !== host) {
|
|
||||||
return res.status(403).json({ error: 'Forbidden - Invalid Origin' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Masalah:**
|
|
||||||
- Referer bisa di-spoof dalam beberapa kasus
|
|
||||||
- Browser lama atau proxy bisa hilangkan Referer header
|
|
||||||
- Tidak ada explicit CSRF token di form
|
|
||||||
|
|
||||||
**Rekomendasi:**
|
|
||||||
|
|
||||||
1. **Implementasi CSRF token library:**
|
|
||||||
```javascript
|
|
||||||
const csrf = require('csurf');
|
|
||||||
const csrfProtection = csrf({ cookie: false }); // Gunakan session, bukan cookie
|
|
||||||
|
|
||||||
app.post('/admin/action', csrfProtection, (req, res) => {
|
|
||||||
// CSRF token sudah tervalidasi otomatis
|
|
||||||
// Proses request
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Di EJS templates:**
|
|
||||||
```ejs
|
|
||||||
<form method="POST" action="/admin/billing/generate">
|
|
||||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
|
||||||
<button type="submit">Generate Invoice</button>
|
|
||||||
</form>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** MEDIUM - 2 minggu
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. INPUT VALIDATION & SANITIZATION ⚠️ MEDIUM
|
|
||||||
|
|
||||||
**Issue:**
|
|
||||||
Beberapa endpoint tidak melakukan validasi input yang ketat sebelum:
|
|
||||||
- Memasukkan ke database
|
|
||||||
- Menampilkan ke user (XSS risk)
|
|
||||||
- Digunakan dalam query
|
|
||||||
|
|
||||||
**Contoh lokasi yang perlu improvement:**
|
|
||||||
- [routes/adminPortal.js](routes/adminPortal.js) - Bulk operations
|
|
||||||
- [routes/customerPortal.js](routes/customerPortal.js) - Customer data update
|
|
||||||
- File upload handling
|
|
||||||
|
|
||||||
**Rekomendasi:**
|
|
||||||
|
|
||||||
1. **Gunakan input validation library:**
|
|
||||||
```bash
|
|
||||||
npm install joi express-validator
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Implementasi validation:**
|
|
||||||
```javascript
|
|
||||||
const { body, validationResult } = require('express-validator');
|
|
||||||
|
|
||||||
router.post('/admin/customers/add', [
|
|
||||||
body('name').trim().notEmpty().isLength({ max: 100 }),
|
|
||||||
body('phone').trim().isMobilePhone('id-ID'),
|
|
||||||
body('email').isEmail().normalizeEmail(),
|
|
||||||
body('address').trim().isLength({ max: 255 }),
|
|
||||||
body('package_id').isInt({ gt: 0 }),
|
|
||||||
], (req, res) => {
|
|
||||||
const errors = validationResult(req);
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() });
|
|
||||||
}
|
|
||||||
// Lanjutkan proses
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Sanitize output di EJS:**
|
|
||||||
```ejs
|
|
||||||
<!-- ❌ TIDAK AMAN - Bisa XSS -->
|
|
||||||
<%= customer.name %>
|
|
||||||
|
|
||||||
<!-- ✅ AMAN - Sanitized -->
|
|
||||||
<%- cleanHtml(customer.name) %>
|
|
||||||
|
|
||||||
<!-- atau gunakan escape by default di EJS config -->
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** MEDIUM - 2 minggu
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ KEAMANAN RENDAH - PERHATIAN
|
|
||||||
|
|
||||||
### 8. FILE UPLOAD SECURITY ⚠️ LOW
|
|
||||||
|
|
||||||
**Status Saat Ini:** ⚠️ Cukup
|
|
||||||
```javascript
|
|
||||||
// ✅ Ada validasi MIME type
|
|
||||||
const allowedTypes = /jpeg|jpg|png|gif|webp/;
|
|
||||||
const mimetype = allowedTypes.test(file.mimetype);
|
|
||||||
|
|
||||||
// ✅ Ada file size limit (5MB)
|
|
||||||
limits: { fileSize: 5 * 1024 * 1024 }
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rekomendasi Improvement:**
|
|
||||||
|
|
||||||
1. **Validasi file lebih ketat:**
|
|
||||||
```javascript
|
|
||||||
const FileType = require('file-type');
|
|
||||||
|
|
||||||
// Validasi magic bytes (bukan hanya extension)
|
|
||||||
const fileType = await FileType.fromBuffer(file.buffer);
|
|
||||||
if (!['image/jpeg', 'image/png', 'image/webp'].includes(fileType?.mime)) {
|
|
||||||
throw new Error('Invalid file type');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Scan file dengan antivirus:**
|
|
||||||
```javascript
|
|
||||||
const ClamScan = require('clamscan');
|
|
||||||
const clamscan = await new ClamScan().init({
|
|
||||||
clamdscan: { host: 'localhost', port: 3310 }
|
|
||||||
});
|
|
||||||
|
|
||||||
const { isInfected } = await clamscan.scanFile(filePath);
|
|
||||||
if (isInfected) {
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
throw new Error('Malicious file detected');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** LOW - Dalam 1 bulan
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9. LOGGING & MONITORING ⚠️ LOW
|
|
||||||
|
|
||||||
**Status Saat Ini:** ✅ Baik
|
|
||||||
- Winston logger terimplementasi dengan baik
|
|
||||||
- Terpisah antara error.log, combined.log, exceptions.log
|
|
||||||
- Console output di development
|
|
||||||
|
|
||||||
**Rekomendasi:**
|
|
||||||
|
|
||||||
1. **Audit trail untuk sensitive operations:**
|
|
||||||
```javascript
|
|
||||||
const auditTrailSvc = require('./services/auditTrailService');
|
|
||||||
|
|
||||||
// Log setiap action sensitive
|
|
||||||
auditTrailSvc.logAction({
|
|
||||||
user_id: req.session.adminId,
|
|
||||||
action: 'invoice_generated',
|
|
||||||
resource_type: 'invoice',
|
|
||||||
resource_id: invoiceId,
|
|
||||||
changes: { status: 'generated' },
|
|
||||||
ip_address: req.ip,
|
|
||||||
timestamp: new Date()
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Centralized log aggregation:**
|
|
||||||
```javascript
|
|
||||||
// Contoh: Kirim logs ke ELK Stack atau Datadog
|
|
||||||
const winston = require('winston');
|
|
||||||
const WinstonCloudWatch = require('winston-cloudwatch');
|
|
||||||
|
|
||||||
logger.add(new WinstonCloudWatch({
|
|
||||||
logGroupName: 'billing-rtrwnet',
|
|
||||||
logStreamName: 'production',
|
|
||||||
awsRegion: 'ap-southeast-1'
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** LOW - Dalam 1 bulan
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 KUALITAS KODE
|
|
||||||
|
|
||||||
### 10. ERROR HANDLING ✅ BAIK
|
|
||||||
|
|
||||||
**Status:** ✅ Baik
|
|
||||||
- Middleware error handler sudah terimplementasi
|
|
||||||
- Custom error classes (ValidationError, UnauthorizedError, etc)
|
|
||||||
- Proper HTTP status codes
|
|
||||||
|
|
||||||
**Minor Improvements:**
|
|
||||||
```javascript
|
|
||||||
// Pastikan semua error di-catch dan di-log
|
|
||||||
router.get('/api/data', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const data = await fetchData();
|
|
||||||
res.json(data);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`API Error: ${err.message}`, {
|
|
||||||
stack: err.stack,
|
|
||||||
endpoint: req.path,
|
|
||||||
method: req.method,
|
|
||||||
user_id: req.session?.userId
|
|
||||||
});
|
|
||||||
next(err); // Pass ke error handler middleware
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 11. DATABASE QUERIES ✅ BAIK
|
|
||||||
|
|
||||||
**Status:** ✅ Baik
|
|
||||||
- Menggunakan parameterized queries (prepared statements)
|
|
||||||
- Tidak ada string concatenation pada SQL queries
|
|
||||||
- Foreign key constraints aktif
|
|
||||||
|
|
||||||
**Contoh aman:**
|
|
||||||
```javascript
|
|
||||||
// ✅ AMAN - Parameterized query
|
|
||||||
const customer = db.prepare('SELECT * FROM customers WHERE id = ?').get(customerId);
|
|
||||||
|
|
||||||
// ✅ AMAN - Multiple parameters
|
|
||||||
const result = db.prepare('SELECT * FROM customers WHERE router_id = ? AND status = ?').all(routerId, 'active');
|
|
||||||
```
|
|
||||||
|
|
||||||
**Minor Issue - Dynamic SQL:**
|
|
||||||
```javascript
|
|
||||||
// ⚠️ PERLU REVIEW - Jika search input tidak di-sanitize
|
|
||||||
const base = `SELECT * FROM customers WHERE ...${userInput}...`;
|
|
||||||
|
|
||||||
// ✅ SOLUSI - Gunakan parameterized dengan conditions array
|
|
||||||
const whereClauses = [];
|
|
||||||
const params = [];
|
|
||||||
if (search) {
|
|
||||||
whereClauses.push('name LIKE ?');
|
|
||||||
params.push(`%${search}%`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 12. ASYNC/AWAIT HANDLING ⚠️ MEDIUM
|
|
||||||
|
|
||||||
**Issue:**
|
|
||||||
Beberapa function masih menggunakan callback style yang bisa error-prone.
|
|
||||||
|
|
||||||
**Contoh yang perlu improvement:**
|
|
||||||
```javascript
|
|
||||||
// ❌ CALLBACK STYLE
|
|
||||||
fs.readFile(path, (err, data) => {
|
|
||||||
if (err) handleError(err);
|
|
||||||
// process data
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ ASYNC/AWAIT STYLE
|
|
||||||
try {
|
|
||||||
const data = await fs.promises.readFile(path);
|
|
||||||
// process data
|
|
||||||
} catch (err) {
|
|
||||||
handleError(err);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** LOW - Refactoring gradual
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗄️ DATABASE INTEGRITY
|
|
||||||
|
|
||||||
### 13. Database Schema ✅ SOLID
|
|
||||||
|
|
||||||
**Status:** ✅ Sudah baik
|
|
||||||
- Foreign key constraints aktif
|
|
||||||
- Proper data types
|
|
||||||
- Indexes pada frequently queried fields
|
|
||||||
- WAL mode untuk reliability
|
|
||||||
|
|
||||||
**Rekomendasi:**
|
|
||||||
```sql
|
|
||||||
-- Pastikan ada indexes pada frequently queried columns
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_customers_router_id ON customers(router_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_invoices_customer_id ON invoices(customer_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_pppoe_users_username ON pppoe_users(username);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 14. Backup & Recovery ⚠️ MEDIUM
|
|
||||||
|
|
||||||
**Status Saat Ini:** ⚠️ Ada backup service tapi belum clear
|
|
||||||
- `backupService.js` sudah ada
|
|
||||||
- `auto_backup_enabled: false` di settings.json (disabled!)
|
|
||||||
|
|
||||||
**Rekomendasi:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Enable automatic backup
|
|
||||||
const settings = {
|
|
||||||
"auto_backup_enabled": true,
|
|
||||||
"auto_backup_interval": "0 2 * * *", // Jam 2 pagi setiap hari
|
|
||||||
"auto_backup_retention_days": 30,
|
|
||||||
"backup_encryption_enabled": true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Implementasi di cronService.js
|
|
||||||
const backup = require('./services/backupService');
|
|
||||||
schedule.scheduleJob('0 2 * * *', async () => {
|
|
||||||
try {
|
|
||||||
const result = await backup.createBackup();
|
|
||||||
logger.info(`Backup created: ${result.filename}`);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Backup failed: ${err.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Timeline:** MEDIUM - 2 minggu
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 ACTION ITEMS & TIMELINE
|
|
||||||
|
|
||||||
### IMMEDIATE (Hari ini - 48 jam) 🔴
|
|
||||||
- [ ] Implementasi bcrypt untuk password hashing
|
|
||||||
- [ ] Hapus default credentials dari settings.json
|
|
||||||
- [ ] Setup .env file untuk credentials
|
|
||||||
- [ ] Update .gitignore untuk .env dan settings.json
|
|
||||||
- [ ] Create setup wizard untuk first-time admin password
|
|
||||||
|
|
||||||
### HIGH PRIORITY (Minggu 1) 🟠
|
|
||||||
- [ ] Migrate semua password di database dengan bcrypt
|
|
||||||
- [ ] Implementasi CSRF token protection
|
|
||||||
- [ ] Setup session store eksternal (SQLite session)
|
|
||||||
- [ ] Enable auto backup dalam cron jobs
|
|
||||||
- [ ] Update API key management
|
|
||||||
|
|
||||||
### MEDIUM PRIORITY (Minggu 2-3) 🟡
|
|
||||||
- [ ] Input validation dengan joi/express-validator
|
|
||||||
- [ ] Enhanced file upload scanning
|
|
||||||
- [ ] Session regeneration saat login
|
|
||||||
- [ ] Centralized logging setup
|
|
||||||
- [ ] Database query audit
|
|
||||||
|
|
||||||
### LOW PRIORITY (Bulan 1) 🟢
|
|
||||||
- [ ] Antivirus integration untuk file upload
|
|
||||||
- [ ] Log aggregation (ELK/Datadog)
|
|
||||||
- [ ] Async/await refactoring
|
|
||||||
- [ ] Performance optimization
|
|
||||||
- [ ] Documentation update
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 SECURITY CHECKLIST - PRODUCTION DEPLOYMENT
|
|
||||||
|
|
||||||
Sebelum go live ke production, pastikan:
|
|
||||||
|
|
||||||
- [ ] **Authentication**
|
|
||||||
- [ ] Password menggunakan bcrypt (cost ≥ 10)
|
|
||||||
- [ ] Admin password bukan default
|
|
||||||
- [ ] Session timeout implemented
|
|
||||||
- [ ] Session regeneration saat login
|
|
||||||
|
|
||||||
- [ ] **Authorization**
|
|
||||||
- [ ] Role-based access control diterapkan
|
|
||||||
- [ ] API endpoints check permission
|
|
||||||
- [ ] Admin endpoints protected
|
|
||||||
|
|
||||||
- [ ] **Data Protection**
|
|
||||||
- [ ] Sensitive data encrypted at rest
|
|
||||||
- [ ] HTTPS enforced (secure cookie flag)
|
|
||||||
- [ ] CSRF protection aktif
|
|
||||||
- [ ] XSS protection aktif
|
|
||||||
|
|
||||||
- [ ] **Configuration**
|
|
||||||
- [ ] Semua credentials di .env
|
|
||||||
- [ ] .env tidak di-commit
|
|
||||||
- [ ] settings.json di-review untuk sensitive data
|
|
||||||
- [ ] API keys di-rotate
|
|
||||||
|
|
||||||
- [ ] **Logging & Monitoring**
|
|
||||||
- [ ] Audit trail untuk sensitive operations
|
|
||||||
- [ ] Error logging configured
|
|
||||||
- [ ] Monitoring alerts setup
|
|
||||||
|
|
||||||
- [ ] **Backup & Recovery**
|
|
||||||
- [ ] Automated backup enabled
|
|
||||||
- [ ] Backup encryption enabled
|
|
||||||
- [ ] Recovery procedure tested
|
|
||||||
- [ ] Backup storage off-site
|
|
||||||
|
|
||||||
- [ ] **Network**
|
|
||||||
- [ ] Firewall configured
|
|
||||||
- [ ] Only needed ports open
|
|
||||||
- [ ] Rate limiting implemented
|
|
||||||
- [ ] DDoS protection (jika applicable)
|
|
||||||
|
|
||||||
- [ ] **Testing**
|
|
||||||
- [ ] Security testing completed
|
|
||||||
- [ ] Penetration testing done (jika budget ada)
|
|
||||||
- [ ] Load testing completed
|
|
||||||
- [ ] Recovery testing done
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 REKOMENDASI LANJUTAN
|
|
||||||
|
|
||||||
### 1. Security Code Review
|
|
||||||
Jadwalkan code review berkala (monthly) oleh developer berpengalaman untuk:
|
|
||||||
- Review pull requests sebelum merge
|
|
||||||
- Check untuk security vulnerabilities
|
|
||||||
- Update dependencies
|
|
||||||
|
|
||||||
### 2. Dependency Management
|
|
||||||
```bash
|
|
||||||
# Regular security audit
|
|
||||||
npm audit
|
|
||||||
|
|
||||||
# Update dependencies (carefully)
|
|
||||||
npm update
|
|
||||||
|
|
||||||
# Check for outdated packages
|
|
||||||
npm outdated
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Environment Management
|
|
||||||
```bash
|
|
||||||
# Development
|
|
||||||
NODE_ENV=development npm start
|
|
||||||
|
|
||||||
# Staging
|
|
||||||
NODE_ENV=staging npm start
|
|
||||||
|
|
||||||
# Production
|
|
||||||
NODE_ENV=production npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Rate Limiting
|
|
||||||
```javascript
|
|
||||||
const rateLimit = require('express-rate-limit');
|
|
||||||
|
|
||||||
const loginLimiter = rateLimit({
|
|
||||||
windowMs: 15 * 60 * 1000, // 15 menit
|
|
||||||
max: 5, // 5 attempts
|
|
||||||
message: 'Terlalu banyak login attempts, coba lagi nanti'
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/login', loginLimiter, authController.login);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Security Headers
|
|
||||||
```javascript
|
|
||||||
const helmet = require('helmet');
|
|
||||||
app.use(helmet()); // Adds various HTTP headers for security
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 PERFORMANCE OPTIMIZATION
|
|
||||||
|
|
||||||
### Database Performance
|
|
||||||
```javascript
|
|
||||||
// Add connection pooling
|
|
||||||
const DatabasePool = require('pg-pool');
|
|
||||||
|
|
||||||
// Add query optimization
|
|
||||||
EXPLAIN ANALYZE SELECT ...; // Check query plans
|
|
||||||
```
|
|
||||||
|
|
||||||
### Caching Strategy
|
|
||||||
```javascript
|
|
||||||
// Implement Redis caching untuk frequently accessed data
|
|
||||||
const redis = require('redis');
|
|
||||||
const client = redis.createClient();
|
|
||||||
|
|
||||||
// Cache customer data
|
|
||||||
const getCachedCustomer = async (id) => {
|
|
||||||
const cached = await client.get(`customer:${id}`);
|
|
||||||
if (cached) return JSON.parse(cached);
|
|
||||||
|
|
||||||
const customer = db.prepare('SELECT * FROM customers WHERE id = ?').get(id);
|
|
||||||
await client.setex(`customer:${id}`, 3600, JSON.stringify(customer));
|
|
||||||
return customer;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Response Compression
|
|
||||||
```javascript
|
|
||||||
const compression = require('compression');
|
|
||||||
app.use(compression()); // Compress responses
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎓 DEVELOPER TRAINING
|
|
||||||
|
|
||||||
Recommended training topics untuk team:
|
|
||||||
1. OWASP Top 10 vulnerabilities
|
|
||||||
2. Secure coding practices
|
|
||||||
3. Dependency vulnerability scanning
|
|
||||||
4. Database security
|
|
||||||
5. API security
|
|
||||||
6. Authentication & Authorization best practices
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 KESIMPULAN
|
|
||||||
|
|
||||||
Aplikasi RTRWNET Billing System memiliki **foundation yang kuat** dengan:
|
|
||||||
- ✅ Arsitektur yang well-structured
|
|
||||||
- ✅ Comprehensive feature set
|
|
||||||
- ✅ Good error handling & logging
|
|
||||||
- ✅ Proper database design
|
|
||||||
|
|
||||||
Namun ada **beberapa kerentanan keamanan KRITIS** yang harus diperbaiki:
|
|
||||||
- 🔴 Password plaintext storage
|
|
||||||
- 🔴 Default credentials
|
|
||||||
- 🔴 Unencrypted credentials in config
|
|
||||||
|
|
||||||
Dengan mengikuti rekomendasi di audit ini, aplikasi dapat mencapai **production-ready security level** dalam waktu 2-3 minggu.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status Audit: COMPLETE**
|
|
||||||
**Recommendation: DO NOT DEPLOY TO PRODUCTION** sampai critical issues di-fix.
|
|
||||||
**Next Review:** 30 hari setelah fixes di-implementasi
|
|
||||||
@@ -1,458 +0,0 @@
|
|||||||
# AUDIT SUMMARY - Aplikasi Billing B481
|
|
||||||
|
|
||||||
**Tanggal Audit:** Juni 2026
|
|
||||||
**Status:** ✅ COMPLETE & DOCUMENTED
|
|
||||||
**Aplikasi Status:** 🟢 PRODUCTION READY
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📌 Executive Summary
|
|
||||||
|
|
||||||
Telah dilakukan audit **lengkap & teliti** terhadap aplikasi Billing RTRWnet (B481) yang sedang berjalan. Aplikasi ini merupakan sistem **comprehensive ISP billing & network management** dengan 15+ portal dan fitur-fitur advanced.
|
|
||||||
|
|
||||||
**Hasil:** Aplikasi dalam kondisi **BAIK & SIAP PRODUCTION** dengan beberapa rekomendasi improvement untuk keamanan & performance.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Dokumentasi yang Telah Dibuat
|
|
||||||
|
|
||||||
### 1. **AUDIT_LENGKAP.md** (Komprehensif)
|
|
||||||
- Informasi umum aplikasi
|
|
||||||
- Arsitektur sistem lengkap
|
|
||||||
- Database schema overview
|
|
||||||
- Endpoints & routes (semua portal)
|
|
||||||
- Fitur-fitur utama (15+ modul)
|
|
||||||
- Keamanan & otentikasi
|
|
||||||
- Frontend & UI
|
|
||||||
- Configurasi sistem
|
|
||||||
- Production checklist
|
|
||||||
- Performance optimization
|
|
||||||
- 📊 **Size:** ~50 KB (detailed reference)
|
|
||||||
|
|
||||||
### 2. **QUICK_REFERENCE.md** (Panduan Cepat)
|
|
||||||
- Quick start commands
|
|
||||||
- Common tasks (5 menit selesai)
|
|
||||||
- Security settings (MUST DO)
|
|
||||||
- Important endpoints
|
|
||||||
- Database commands
|
|
||||||
- Cron jobs schedule
|
|
||||||
- Troubleshooting guide
|
|
||||||
- Multi-language info
|
|
||||||
- Pre-launch checklist
|
|
||||||
- 📊 **Size:** ~10 KB (quick lookup)
|
|
||||||
|
|
||||||
### 3. **DATABASE_SCHEMA.md** (Struktur Database)
|
|
||||||
- Tabel-tabel lengkap dengan struktur
|
|
||||||
- Detailed field descriptions
|
|
||||||
- Foreign key relationships
|
|
||||||
- Query examples
|
|
||||||
- Optimization tips
|
|
||||||
- Backup & recovery procedures
|
|
||||||
- 📊 **Size:** ~30 KB (database reference)
|
|
||||||
|
|
||||||
### 4. **BACKUP_CHECKLIST.md** (Backup & Recovery)
|
|
||||||
- Pre-audit backup procedures
|
|
||||||
- Step-by-step backup guide
|
|
||||||
- Restore procedures (multiple scenarios)
|
|
||||||
- Automated backup setup (cron)
|
|
||||||
- Disaster recovery plan
|
|
||||||
- Backup verification checklist
|
|
||||||
- 📊 **Size:** ~20 KB (operational guide)
|
|
||||||
|
|
||||||
### 5. **AUDIT_SUMMARY.md** (File ini)
|
|
||||||
- Executive overview
|
|
||||||
- Quick links
|
|
||||||
- Key statistics
|
|
||||||
- Next steps
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Key Findings
|
|
||||||
|
|
||||||
### ✅ Strengths
|
|
||||||
1. **Comprehensive Feature Set** - 15+ portal dengan integrasi lengkap
|
|
||||||
2. **Well-Organized Architecture** - Services, routes, middleware pattern
|
|
||||||
3. **Multi-Integration** - MikroTik, GenieACS, OLT, Payment Gateways
|
|
||||||
4. **Automation Ready** - Cron jobs untuk billing, isolir, reminders
|
|
||||||
5. **Lightweight** - SQLite, no external database required
|
|
||||||
6. **Scalable Design** - Multi-router, multi-ACS, multi-gateway support
|
|
||||||
7. **Built-in ACS** - TR-069 server tanpa GenieACS eksternal
|
|
||||||
|
|
||||||
### ⚠️ Areas for Improvement
|
|
||||||
1. **Password Security** - Need bcrypt implementation (currently plaintext risk)
|
|
||||||
2. **API Security** - Implement JWT or token-based authentication
|
|
||||||
3. **Rate Limiting** - Prevent brute force attacks
|
|
||||||
4. **Performance** - Add Redis session store, implement caching
|
|
||||||
5. **Testing** - Add comprehensive test suite
|
|
||||||
6. **Documentation** - API documentation (Swagger/OpenAPI)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Application Statistics
|
|
||||||
|
|
||||||
### Codebase Size
|
|
||||||
```
|
|
||||||
Templates (EJS): 78 files
|
|
||||||
Services: 27 modules
|
|
||||||
Routes: 12 modules
|
|
||||||
Configuration: 10 files
|
|
||||||
Views/Partials: ~100+ components
|
|
||||||
```
|
|
||||||
|
|
||||||
### Features Count
|
|
||||||
| Category | Count |
|
|
||||||
|----------|-------|
|
|
||||||
| Portals | 6 (Customer, Admin, Tech, Agent, Collector, Finance) |
|
|
||||||
| API Endpoints | 100+ |
|
|
||||||
| Database Tables | 30+ |
|
|
||||||
| Cron Jobs | 6 |
|
|
||||||
| Payment Gateways | 4 |
|
|
||||||
| External Integrations | 6+ |
|
|
||||||
|
|
||||||
### Database Typical Size
|
|
||||||
```
|
|
||||||
Small deployment: 10-50 MB
|
|
||||||
Medium deployment: 50-150 MB
|
|
||||||
Large deployment: 200-500 MB
|
|
||||||
(Depends on data retention & transaction volume)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### 1. Review Documentation
|
|
||||||
```
|
|
||||||
Read in this order:
|
|
||||||
1. QUICK_REFERENCE.md (5 min)
|
|
||||||
2. AUDIT_LENGKAP.md (30 min)
|
|
||||||
3. DATABASE_SCHEMA.md (20 min)
|
|
||||||
4. BACKUP_CHECKLIST.md (10 min)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Initial Backup
|
|
||||||
```bash
|
|
||||||
bash scripts/fix-database.sh # Optional: fix any DB issues
|
|
||||||
node scripts/verify-database.js # Verify DB integrity
|
|
||||||
npm start # Start application
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Configuration
|
|
||||||
```bash
|
|
||||||
# Edit settings.json:
|
|
||||||
# - Change admin password
|
|
||||||
# - Configure MikroTik connection
|
|
||||||
# - Setup payment gateway
|
|
||||||
# - Configure WhatsApp (optional)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Verify All Portals
|
|
||||||
```
|
|
||||||
http://localhost:3001/admin/login ← Admin panel
|
|
||||||
http://localhost:3001/customer/login ← Customer self-service
|
|
||||||
http://localhost:3001/tech/login ← Technician portal
|
|
||||||
http://localhost:3001/agent/login ← Agent sales
|
|
||||||
http://localhost:3001/health ← Health check
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 Security Checklist (CRITICAL)
|
|
||||||
|
|
||||||
**Before Production Deployment:**
|
|
||||||
|
|
||||||
- [ ] Change `session_secret` → Random 32+ characters
|
|
||||||
- [ ] Change `admin_password` → Strong password
|
|
||||||
- [ ] Change `admin_api_key` → Random string
|
|
||||||
- [ ] Enable HTTPS (via reverse proxy: nginx)
|
|
||||||
- [ ] Restrict admin panel access (/admin via IP whitelist)
|
|
||||||
- [ ] Setup firewall rules
|
|
||||||
- [ ] Implement bcrypt for password hashing
|
|
||||||
- [ ] Setup audit logging
|
|
||||||
- [ ] Test backup & restore procedure
|
|
||||||
- [ ] Verify all external connections (MikroTik, GenieACS, OLT)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Operational Runbook
|
|
||||||
|
|
||||||
### Daily Tasks
|
|
||||||
```
|
|
||||||
✓ Monitor CPU/RAM usage
|
|
||||||
✓ Check /health endpoint
|
|
||||||
✓ Review error logs
|
|
||||||
✓ Monitor WhatsApp connection
|
|
||||||
```
|
|
||||||
|
|
||||||
### Weekly Tasks
|
|
||||||
```
|
|
||||||
✓ Verify backups completed
|
|
||||||
✓ Check database size
|
|
||||||
✓ Review payment transactions
|
|
||||||
✓ Test restore procedures (weekly)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monthly Tasks
|
|
||||||
```
|
|
||||||
✓ Cleanup old audit trails
|
|
||||||
✓ Cleanup old traffic samples
|
|
||||||
✓ Review performance metrics
|
|
||||||
✓ Update SSL certificates (if HTTPS)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ad-Hoc Tasks
|
|
||||||
```
|
|
||||||
✓ Billing generation (manual)
|
|
||||||
✓ Customer isolir (manual)
|
|
||||||
✓ Settings updates
|
|
||||||
✓ Emergency procedures
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Maintenance Procedures
|
|
||||||
|
|
||||||
### Adding New Customer
|
|
||||||
```
|
|
||||||
Admin Panel → Customers → Add New
|
|
||||||
- Enter basic info (name, phone, address)
|
|
||||||
- Assign package & MikroTik router
|
|
||||||
- Set install date
|
|
||||||
- System auto-generates invoice on billing cycle
|
|
||||||
```
|
|
||||||
|
|
||||||
### Updating Payment Gateway
|
|
||||||
```
|
|
||||||
Admin Panel → Settings → Payment Gateway
|
|
||||||
- Select gateway (Midtrans, Tripay, Xendit, Duitku)
|
|
||||||
- Enter API credentials
|
|
||||||
- Test connection
|
|
||||||
- Save & restart if needed
|
|
||||||
```
|
|
||||||
|
|
||||||
### Generating Vouchers
|
|
||||||
```
|
|
||||||
Admin Panel → MikroTik → Vouchers
|
|
||||||
- Create batch (name, quantity, nominal)
|
|
||||||
- Generate codes
|
|
||||||
- Print codes
|
|
||||||
- Distribute to agents
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monitoring System
|
|
||||||
```
|
|
||||||
Admin Panel → Monitoring
|
|
||||||
- Check CPU/RAM usage
|
|
||||||
- Check disk space
|
|
||||||
- Check database size
|
|
||||||
- Check connectivity status
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎓 Training Resources
|
|
||||||
|
|
||||||
### For Admin
|
|
||||||
1. QUICK_REFERENCE.md → Common admin tasks
|
|
||||||
2. AUDIT_LENGKAP.md section 5.1-5.5 → Billing, MikroTik, GenieACS
|
|
||||||
3. Admin portal built-in help
|
|
||||||
|
|
||||||
### For Technician
|
|
||||||
1. QUICK_REFERENCE.md → Tech portal features
|
|
||||||
2. AUDIT_LENGKAP.md section 4.3 → Tech routes
|
|
||||||
3. Ticket management system
|
|
||||||
|
|
||||||
### For Agent/Cashier
|
|
||||||
1. Portal home page → Help section
|
|
||||||
2. QUICK_REFERENCE.md → Agent features
|
|
||||||
3. Dashboard tutorial
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Support & Escalation
|
|
||||||
|
|
||||||
### Level 1: Self-Service
|
|
||||||
- Check QUICK_REFERENCE.md
|
|
||||||
- Check built-in help in portals
|
|
||||||
- Review error messages
|
|
||||||
|
|
||||||
### Level 2: Admin Support
|
|
||||||
- Check AUDIT_LENGKAP.md troubleshooting
|
|
||||||
- Review application logs: `pm2 logs`
|
|
||||||
- Check database: `node scripts/verify-database.js`
|
|
||||||
|
|
||||||
### Level 3: Developer Support
|
|
||||||
- Check GitHub: https://github.com/alijayanet/billing-rtrw
|
|
||||||
- Contact: Ali Jaya Net (081947215703)
|
|
||||||
- Email: alijayanet@gmail.com
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📁 Document File Locations
|
|
||||||
|
|
||||||
All documentation saved in application root:
|
|
||||||
|
|
||||||
```
|
|
||||||
billing-B481/
|
|
||||||
├── AUDIT_LENGKAP.md ← Comprehensive audit (50 KB)
|
|
||||||
├── QUICK_REFERENCE.md ← Quick start guide (10 KB)
|
|
||||||
├── DATABASE_SCHEMA.md ← Database reference (30 KB)
|
|
||||||
├── BACKUP_CHECKLIST.md ← Backup procedures (20 KB)
|
|
||||||
└── AUDIT_SUMMARY.md ← This file (15 KB)
|
|
||||||
|
|
||||||
Total documentation: ~125 KB
|
|
||||||
```
|
|
||||||
|
|
||||||
**Backup these files also!** Add to your backup script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tar -czf audit-docs-backup.tar.gz \
|
|
||||||
AUDIT_LENGKAP.md \
|
|
||||||
QUICK_REFERENCE.md \
|
|
||||||
DATABASE_SCHEMA.md \
|
|
||||||
BACKUP_CHECKLIST.md \
|
|
||||||
AUDIT_SUMMARY.md
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Next Steps
|
|
||||||
|
|
||||||
### Immediate (This Week)
|
|
||||||
1. ✅ Review all documentation
|
|
||||||
2. ✅ Create full backup (database + files + code)
|
|
||||||
3. ✅ Store backup in safe location
|
|
||||||
4. ✅ Test restore procedure
|
|
||||||
5. ✅ Verify all portals functional
|
|
||||||
|
|
||||||
### Short-term (This Month)
|
|
||||||
1. ⏳ Setup automated daily backup
|
|
||||||
2. ⏳ Implement monitoring & alerting
|
|
||||||
3. ⏳ Setup log rotation
|
|
||||||
4. ⏳ Document any customizations
|
|
||||||
5. ⏳ Create runbook in internal wiki
|
|
||||||
|
|
||||||
### Long-term (This Quarter)
|
|
||||||
1. ⏳ Add comprehensive test suite
|
|
||||||
2. ⏳ Implement bcrypt for passwords
|
|
||||||
3. ⏳ Setup Redis session store
|
|
||||||
4. ⏳ Create API documentation (Swagger)
|
|
||||||
5. ⏳ Performance optimization & tuning
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎖️ Audit Completion Status
|
|
||||||
|
|
||||||
| Task | Status | Notes |
|
|
||||||
|------|--------|-------|
|
|
||||||
| Application Review | ✅ Complete | All features examined |
|
|
||||||
| Code Structure | ✅ Complete | Well-organized & maintainable |
|
|
||||||
| Database Schema | ✅ Complete | Proper design & relationships |
|
|
||||||
| Security Analysis | ✅ Complete | Identified areas for improvement |
|
|
||||||
| Documentation | ✅ Complete | 125 KB comprehensive docs created |
|
|
||||||
| Backup Procedures | ✅ Complete | Ready to implement |
|
|
||||||
| Testing Guide | ✅ Complete | Quick reference provided |
|
|
||||||
| Troubleshooting | ✅ Complete | Common issues documented |
|
|
||||||
|
|
||||||
**Overall Status: 🟢 PRODUCTION READY**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Document Versions
|
|
||||||
|
|
||||||
| Document | Version | Date | Status |
|
|
||||||
|----------|---------|------|--------|
|
|
||||||
| AUDIT_LENGKAP.md | 1.0 | 2026-06-19 | ✅ Final |
|
|
||||||
| QUICK_REFERENCE.md | 1.0 | 2026-06-19 | ✅ Final |
|
|
||||||
| DATABASE_SCHEMA.md | 1.0 | 2026-06-19 | ✅ Final |
|
|
||||||
| BACKUP_CHECKLIST.md | 1.0 | 2026-06-19 | ✅ Final |
|
|
||||||
| AUDIT_SUMMARY.md | 1.0 | 2026-06-19 | ✅ Final |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 Audit Methodology
|
|
||||||
|
|
||||||
This audit was conducted using:
|
|
||||||
1. **Code Review** - All source files examined
|
|
||||||
2. **Architecture Analysis** - System design & patterns reviewed
|
|
||||||
3. **Security Assessment** - Authentication, encryption, data protection
|
|
||||||
4. **Database Analysis** - Schema, queries, relationships
|
|
||||||
5. **Feature Mapping** - All portals & endpoints documented
|
|
||||||
6. **Documentation** - Comprehensive guides created for operations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Final Checklist
|
|
||||||
|
|
||||||
Before considering audit complete:
|
|
||||||
|
|
||||||
- ✅ Application analyzed thoroughly
|
|
||||||
- ✅ All features documented
|
|
||||||
- ✅ Database schema documented
|
|
||||||
- ✅ Backup procedures defined
|
|
||||||
- ✅ Security recommendations provided
|
|
||||||
- ✅ Troubleshooting guide created
|
|
||||||
- ✅ Quick reference created
|
|
||||||
- ✅ Production readiness confirmed
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Success Criteria
|
|
||||||
|
|
||||||
**Application meets production standards when:**
|
|
||||||
|
|
||||||
- ✅ All documentation read & understood
|
|
||||||
- ✅ Full backup created & tested
|
|
||||||
- ✅ Security settings updated (passwords, secrets)
|
|
||||||
- ✅ All portals verified working
|
|
||||||
- ✅ Monitoring setup
|
|
||||||
- ✅ Escalation procedures defined
|
|
||||||
- ✅ Runbook available to operations team
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Questions or Issues?
|
|
||||||
|
|
||||||
**For questions about this audit:**
|
|
||||||
- Review relevant documentation section
|
|
||||||
- Check troubleshooting guides
|
|
||||||
- Contact application owner: Ali Jaya Net (081947215703)
|
|
||||||
|
|
||||||
**For operational issues:**
|
|
||||||
- Check QUICK_REFERENCE.md
|
|
||||||
- Review application logs
|
|
||||||
- Use health check: `GET /health`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏁 Conclusion
|
|
||||||
|
|
||||||
Aplikasi Billing B481 (RTRWnet) telah selesai di-audit secara teliti. Sistem berada dalam kondisi **BAIK & SIAP UNTUK PRODUKSI** dengan dokumentasi lengkap untuk operasional.
|
|
||||||
|
|
||||||
**Dokumentasi yang telah dibuat mencakup:**
|
|
||||||
- Comprehensive feature audit (50 KB)
|
|
||||||
- Quick reference guide (10 KB)
|
|
||||||
- Database schema documentation (30 KB)
|
|
||||||
- Backup & recovery procedures (20 KB)
|
|
||||||
- This summary (15 KB)
|
|
||||||
|
|
||||||
**Total: 125 KB documentation untuk operasional jangka panjang.**
|
|
||||||
|
|
||||||
Semua file dokumentasi tersimpan di root aplikasi dan siap untuk di-backup.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Audit Completion Date:** June 19, 2026
|
|
||||||
**Audit Version:** 1.0 - Final
|
|
||||||
**Status:** ✅ APPROVED FOR PRODUCTION
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Jangan lupa untuk:**
|
|
||||||
1. ✅ Backup semua dokumentasi ini
|
|
||||||
2. ✅ Share dengan tim operasional
|
|
||||||
3. ✅ Update dokumentasi saat ada perubahan
|
|
||||||
4. ✅ Review & test ulang secara berkala
|
|
||||||
|
|
||||||
**Terima kasih telah menggunakan dokumentasi audit lengkap ini! 🎉**
|
|
||||||
|
|
||||||
@@ -63,7 +63,6 @@
|
|||||||
- `tickets` - Tiket support pelanggan
|
- `tickets` - Tiket support pelanggan
|
||||||
|
|
||||||
### 10. Payment & Digital
|
### 10. Payment & Digital
|
||||||
- `digiflazz_transactions` - Transaksi pulsa/data Digiflazz
|
|
||||||
- `vomit_log` - Log webhook pembayaran (generic)
|
- `vomit_log` - Log webhook pembayaran (generic)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -439,23 +438,6 @@ CREATE TABLE vouchers (
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### digiflazz_transactions
|
|
||||||
```sql
|
|
||||||
CREATE TABLE digiflazz_transactions (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
agent_id INTEGER NOT NULL REFERENCES agents(id),
|
|
||||||
ref_id TEXT NOT NULL UNIQUE, -- Digiflazz reference ID
|
|
||||||
phone_or_code TEXT NOT NULL, -- Tujuan nomor / kode
|
|
||||||
product_sku TEXT NOT NULL,
|
|
||||||
amount INTEGER NOT NULL,
|
|
||||||
status TEXT DEFAULT 'pending', -- pending, success, failed
|
|
||||||
response_data TEXT, -- JSON response
|
|
||||||
created_at DATETIME DEFAULT (NOW_LOCAL())
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Query Examples
|
## Query Examples
|
||||||
|
|
||||||
### Generate Invoice (Monthly)
|
### Generate Invoice (Monthly)
|
||||||
|
|||||||
@@ -1,494 +0,0 @@
|
|||||||
# 📚 INDEX DOKUMENTASI AUDIT - Aplikasi Billing B481
|
|
||||||
|
|
||||||
**Tanggal Audit:** Juni 2026
|
|
||||||
**Total Dokumentasi:** 6 file (91 KB)
|
|
||||||
**Status:** ✅ LENGKAP & SIAP PAKAI
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📖 Panduan Membaca Dokumentasi
|
|
||||||
|
|
||||||
### Untuk Pengguna Pertama (5-15 menit)
|
|
||||||
```
|
|
||||||
START HERE:
|
|
||||||
1. AUDIT_SUMMARY.md ← Ringkasan eksekutif (5 min)
|
|
||||||
2. QUICK_REFERENCE.md ← Perintah dasar (10 min)
|
|
||||||
3. README.md ← (Original README aplikasi)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Untuk Admin/Operator (30-60 menit)
|
|
||||||
```
|
|
||||||
RECOMMENDED PATH:
|
|
||||||
1. QUICK_REFERENCE.md ← Common tasks & troubleshooting
|
|
||||||
2. AUDIT_LENGKAP.md ← Section 4 (Endpoints & Routes)
|
|
||||||
3. BACKUP_CHECKLIST.md ← Backup & recovery procedures
|
|
||||||
4. DATABASE_SCHEMA.md ← Untuk query tertentu
|
|
||||||
```
|
|
||||||
|
|
||||||
### Untuk Developer/Maintenance (1-2 jam)
|
|
||||||
```
|
|
||||||
COMPREHENSIVE PATH:
|
|
||||||
1. AUDIT_LENGKAP.md ← Full architecture & features
|
|
||||||
2. DATABASE_SCHEMA.md ← Complete schema with examples
|
|
||||||
3. QUICK_REFERENCE.md ← Common tasks reference
|
|
||||||
4. BACKUP_CHECKLIST.md ← Operational procedures
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 File Documentation Details
|
|
||||||
|
|
||||||
### 1. AUDIT_LENGKAP.md (45.57 KB)
|
|
||||||
**Purpose:** Comprehensive audit report dengan semua detail
|
|
||||||
|
|
||||||
**Sections:**
|
|
||||||
- 📍 Section 1: Informasi Umum Aplikasi
|
|
||||||
- 📍 Section 2: Arsitektur Sistem (directory structure, flow)
|
|
||||||
- 📍 Section 3: Database & Schema (overview)
|
|
||||||
- 📍 Section 4: Endpoints & Routes (SEMUA API endpoints)
|
|
||||||
- 4.1: Portal Pelanggan (/customer)
|
|
||||||
- 4.2: Portal Admin (/admin)
|
|
||||||
- 4.3: Portal Teknisi (/tech)
|
|
||||||
- 4.4: Portal Agen (/agent)
|
|
||||||
- 4.5: Portal Kolektor (/collector)
|
|
||||||
- 4.6: Portal Finance (/finance)
|
|
||||||
- 4.7: Public Routes
|
|
||||||
- 4.8: Settings & Configuration API
|
|
||||||
- 4.9: Voucher Payment API
|
|
||||||
- 📍 Section 5: Fitur-Fitur Utama (15+ modul)
|
|
||||||
- 5.1 Billing & Invoicing
|
|
||||||
- 5.2 MikroTik Integration
|
|
||||||
- 5.3 GenieACS & TR-069
|
|
||||||
- 5.4 OLT & ONU Management
|
|
||||||
- 5.5 GIS Mapping
|
|
||||||
- 5.6 WhatsApp Automation
|
|
||||||
- 5.7 Payment Gateways
|
|
||||||
- 5.8 Agen & Sales
|
|
||||||
- 5.9-5.15 Additional features
|
|
||||||
- 📍 Section 6: Keamanan & Otentikasi
|
|
||||||
- 📍 Section 7: Frontend & UI
|
|
||||||
- 📍 Section 8: Internationalization (i18n)
|
|
||||||
- 📍 Section 9: Configuration & Settings
|
|
||||||
- 📍 Section 10: Dependencies & Libraries
|
|
||||||
- 📍 Section 11: Operational Requirements
|
|
||||||
- 📍 Section 12: Monitoring & Troubleshooting
|
|
||||||
- 📍 Section 13: Production Checklist
|
|
||||||
- 📍 Section 14: Performance Optimization
|
|
||||||
- 📍 Section 15: File Size & Statistics
|
|
||||||
- 📍 Section 16: Documentation & Resources
|
|
||||||
- 📍 Section 17: Advanced Features & Integration
|
|
||||||
- 📍 Section 18: Conclusion & Recommendations
|
|
||||||
|
|
||||||
**When to use:** Referensi lengkap untuk semua aspek aplikasi
|
|
||||||
|
|
||||||
**Example queries:**
|
|
||||||
- "Apa saja endpoint admin?" → See section 4.2
|
|
||||||
- "Bagaimana cara kerja billing?" → See section 5.1
|
|
||||||
- "Apa saja database tables?" → See section 3.2
|
|
||||||
- "Bagaimana setup MikroTik?" → See section 5.2
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. QUICK_REFERENCE.md (6.67 KB)
|
|
||||||
**Purpose:** Quick lookup guide untuk task-task umum
|
|
||||||
|
|
||||||
**Sections:**
|
|
||||||
- Quick Start (npm commands)
|
|
||||||
- Portal access URLs
|
|
||||||
- Key Files location
|
|
||||||
- Common Tasks (5 tasks)
|
|
||||||
- Change Port
|
|
||||||
- Change Company Name
|
|
||||||
- Enable WhatsApp
|
|
||||||
- Setup MikroTik
|
|
||||||
- Setup GenieACS
|
|
||||||
- Security Settings (MUST DO)
|
|
||||||
- Important Endpoints
|
|
||||||
- Database Commands
|
|
||||||
- Cron Jobs Schedule
|
|
||||||
- Troubleshooting (7 common issues)
|
|
||||||
- Multi-Language
|
|
||||||
- Useful Scripts
|
|
||||||
- Pre-Launch Checklist
|
|
||||||
|
|
||||||
**When to use:** Sehari-hari untuk quick answers
|
|
||||||
|
|
||||||
**Example queries:**
|
|
||||||
- "Port berapa aplikasi?" → See "Quick Start"
|
|
||||||
- "MikroTik tidak connect, apa solusinya?" → See "Troubleshooting"
|
|
||||||
- "Bagaimana aktifkan WhatsApp?" → See "Common Tasks"
|
|
||||||
- "Kapan billing dijalankan?" → See "Cron Jobs"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. DATABASE_SCHEMA.md (15.75 KB)
|
|
||||||
**Purpose:** Complete database schema documentation
|
|
||||||
|
|
||||||
**Sections:**
|
|
||||||
- Schema Overview
|
|
||||||
- Detailed Table Structures (20+ tables)
|
|
||||||
- Table relationships & foreign keys
|
|
||||||
- Query Examples (4 examples)
|
|
||||||
- Optimization Tips
|
|
||||||
- Backup & Recovery procedures
|
|
||||||
- Index recommendations
|
|
||||||
|
|
||||||
**Tables Documented:**
|
|
||||||
- settings, packages, customers, routers, olts, odps
|
|
||||||
- invoices, payments, payment_approvals
|
|
||||||
- pppoe_users, pppoe_profiles, pppoe_traffic_samples, pppoe_sessions
|
|
||||||
- hotspot_users, hotspot_profiles, hotspot_sessions
|
|
||||||
- voucher_batches, vouchers
|
|
||||||
- acsdevices, acs_sessions, acs_device_params
|
|
||||||
- onu_mapping, onu_provision, olu_ports
|
|
||||||
- audit_trail, expenses, cash_in
|
|
||||||
- technicians, cashiers, agents, collectors
|
|
||||||
- inventory_items, attendance, tickets
|
|
||||||
- digiflazz_transactions, vomit_log
|
|
||||||
|
|
||||||
**When to use:** Ketika perlu memahami struktur data atau membuat query
|
|
||||||
|
|
||||||
**Example queries:**
|
|
||||||
- "Bagaimana struktur tabel customers?" → See "customers" section
|
|
||||||
- "Query apa untuk cek overdue invoice?" → See "Query Examples"
|
|
||||||
- "Tabel apa untuk tracking PPPoE usage?" → See "pppoe_traffic_samples"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. BACKUP_CHECKLIST.md (10.58 KB)
|
|
||||||
**Purpose:** Comprehensive backup & disaster recovery guide
|
|
||||||
|
|
||||||
**Sections:**
|
|
||||||
- Pre-Audit Backup (CRITICAL)
|
|
||||||
- Step 1-5: Database, Settings, Aplikasi, Uploads
|
|
||||||
- Backup Checklist (verification checklist)
|
|
||||||
- Restore Procedures (5 scenarios)
|
|
||||||
- Restore Database Only
|
|
||||||
- Restore Settings
|
|
||||||
- Full Application Restore
|
|
||||||
- Restore Upload Files
|
|
||||||
- Backup Inventory (tracking template)
|
|
||||||
- Secure Backup Storage (4 options)
|
|
||||||
- Local Storage (USB)
|
|
||||||
- Network Drive (NAS/SMB)
|
|
||||||
- Cloud Storage (rclone, S3)
|
|
||||||
- Encrypted Backup (GPG)
|
|
||||||
- Automated Backup (Cron Job setup)
|
|
||||||
- Disaster Recovery Plan (5 scenarios)
|
|
||||||
- Database Corrupted
|
|
||||||
- Aplikasi Crashed
|
|
||||||
- Akses Denied
|
|
||||||
- HDD Full
|
|
||||||
- Settings Misconfiguration
|
|
||||||
- Best Practices (DO & DON'T)
|
|
||||||
- Recovery Time Objective (RTO/RPO)
|
|
||||||
- Backup Verification Checklist
|
|
||||||
|
|
||||||
**When to use:** Sebelum & sesudah deployment, disaster recovery
|
|
||||||
|
|
||||||
**Example queries:**
|
|
||||||
- "Gimana backup database?" → See "Backup Procedures" Step 1
|
|
||||||
- "Gimana restore jika database corrupt?" → See "Disaster Recovery" Scenario 1
|
|
||||||
- "Setup automated backup gimana?" → See "Automated Backup (Cron Job)"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. AUDIT_SUMMARY.md (12.31 KB)
|
|
||||||
**Purpose:** Executive summary & quick links
|
|
||||||
|
|
||||||
**Sections:**
|
|
||||||
- Executive Summary
|
|
||||||
- Dokumentasi yang Dibuat (daftar lengkap)
|
|
||||||
- Key Findings (Strengths & Areas for Improvement)
|
|
||||||
- Application Statistics
|
|
||||||
- Quick Start (4 steps)
|
|
||||||
- Security Checklist (CRITICAL)
|
|
||||||
- Operational Runbook
|
|
||||||
- Daily, Weekly, Monthly, Ad-Hoc tasks
|
|
||||||
- Maintenance Procedures (5 procedures)
|
|
||||||
- Training Resources
|
|
||||||
- Support & Escalation (3 levels)
|
|
||||||
- Document File Locations
|
|
||||||
- Next Steps (Immediate, Short-term, Long-term)
|
|
||||||
- Audit Completion Status
|
|
||||||
- Document Versions
|
|
||||||
- Audit Methodology
|
|
||||||
- Final Checklist
|
|
||||||
- Success Criteria
|
|
||||||
|
|
||||||
**When to use:** Overview & planning
|
|
||||||
|
|
||||||
**Example queries:**
|
|
||||||
- "Aplikasi ready untuk production?" → See "Audit Completion Status"
|
|
||||||
- "Apa saja dokumentasi yang dibuat?" → See "Dokumentasi yang Telah Dibuat"
|
|
||||||
- "Langkah pertama gimana?" → See "Quick Start"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. DOKUMENTASI_INDEX.md (This file)
|
|
||||||
**Purpose:** Navigation guide untuk semua dokumentasi
|
|
||||||
|
|
||||||
**Sections:**
|
|
||||||
- Panduan Membaca Dokumentasi (paths untuk berbagai user)
|
|
||||||
- File Documentation Details (overview setiap file)
|
|
||||||
- Quick Links & Search Tips
|
|
||||||
- Related Documentation
|
|
||||||
- How to Update Documentation
|
|
||||||
- Troubleshooting Documentation Issues
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Quick Links
|
|
||||||
|
|
||||||
### By Topic
|
|
||||||
|
|
||||||
#### Billing & Invoicing
|
|
||||||
- AUDIT_LENGKAP.md → Section 5.1
|
|
||||||
- DATABASE_SCHEMA.md → invoices, payments tables
|
|
||||||
|
|
||||||
#### MikroTik Management
|
|
||||||
- QUICK_REFERENCE.md → "Setup MikroTik"
|
|
||||||
- AUDIT_LENGKAP.md → Section 5.2
|
|
||||||
- DATABASE_SCHEMA.md → routers, pppoe_users tables
|
|
||||||
|
|
||||||
#### Payment Gateways
|
|
||||||
- AUDIT_LENGKAP.md → Section 5.7
|
|
||||||
- QUICK_REFERENCE.md → "Setup Payment Gateway"
|
|
||||||
|
|
||||||
#### Database
|
|
||||||
- DATABASE_SCHEMA.md → All sections
|
|
||||||
- BACKUP_CHECKLIST.md → Backup procedures
|
|
||||||
|
|
||||||
#### Security
|
|
||||||
- QUICK_REFERENCE.md → "Security Settings"
|
|
||||||
- AUDIT_SUMMARY.md → "Security Checklist"
|
|
||||||
- AUDIT_LENGKAP.md → Section 6
|
|
||||||
|
|
||||||
#### Troubleshooting
|
|
||||||
- QUICK_REFERENCE.md → "Troubleshooting"
|
|
||||||
- AUDIT_LENGKAP.md → Section 12
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 How to Search Documentation
|
|
||||||
|
|
||||||
### Using Text Editor Find (Ctrl+F)
|
|
||||||
|
|
||||||
**Search by feature:**
|
|
||||||
```
|
|
||||||
"billing" → Find billing-related content
|
|
||||||
"whatsapp" → Find WhatsApp integration
|
|
||||||
"endpoint" → Find API endpoints
|
|
||||||
"table" → Find database tables
|
|
||||||
"cron" → Find scheduled tasks
|
|
||||||
"error" → Find error handling
|
|
||||||
```
|
|
||||||
|
|
||||||
**Search by section:**
|
|
||||||
```
|
|
||||||
"## " (two hashes) → Find main sections
|
|
||||||
"### " (three hashes) → Find subsections
|
|
||||||
"- [ ]" → Find checklist items
|
|
||||||
"| " → Find tables
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using grep (Linux/Mac)
|
|
||||||
```bash
|
|
||||||
# Find all mentions of "WhatsApp"
|
|
||||||
grep -n "whatsapp" *.md
|
|
||||||
|
|
||||||
# Find all table structures
|
|
||||||
grep -n "CREATE TABLE" DATABASE_SCHEMA.md
|
|
||||||
|
|
||||||
# Find all endpoints
|
|
||||||
grep -n "GET\|POST\|PUT\|DELETE" AUDIT_LENGKAP.md
|
|
||||||
|
|
||||||
# Find all endpoints for /admin
|
|
||||||
grep -n "/admin" AUDIT_LENGKAP.md
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Documentation Cross-References
|
|
||||||
|
|
||||||
### Related Files to Read Together
|
|
||||||
|
|
||||||
**For Admin Portal:**
|
|
||||||
- AUDIT_LENGKAP.md section 4.2
|
|
||||||
- QUICK_REFERENCE.md "Common Tasks"
|
|
||||||
- AUDIT_SUMMARY.md "Operational Runbook"
|
|
||||||
|
|
||||||
**For Customer Portal:**
|
|
||||||
- AUDIT_LENGKAP.md section 4.1
|
|
||||||
- AUDIT_LENGKAP.md section 5.1 (Billing)
|
|
||||||
|
|
||||||
**For Database Operations:**
|
|
||||||
- DATABASE_SCHEMA.md (all sections)
|
|
||||||
- BACKUP_CHECKLIST.md (all procedures)
|
|
||||||
- QUICK_REFERENCE.md "Database Commands"
|
|
||||||
|
|
||||||
**For Security:**
|
|
||||||
- QUICK_REFERENCE.md "Security Settings"
|
|
||||||
- AUDIT_SUMMARY.md "Security Checklist"
|
|
||||||
- AUDIT_LENGKAP.md section 6
|
|
||||||
|
|
||||||
**For Troubleshooting:**
|
|
||||||
- QUICK_REFERENCE.md "Troubleshooting"
|
|
||||||
- AUDIT_LENGKAP.md section 12
|
|
||||||
- AUDIT_LENGKAP.md section 13
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 How to Update Documentation
|
|
||||||
|
|
||||||
When application changes, update relevant docs:
|
|
||||||
|
|
||||||
1. **New Feature Added:**
|
|
||||||
- Update AUDIT_LENGKAP.md section 5 (Fitur-Fitur)
|
|
||||||
- Update QUICK_REFERENCE.md if it's a common task
|
|
||||||
|
|
||||||
2. **New Endpoint Added:**
|
|
||||||
- Update AUDIT_LENGKAP.md section 4 (Routes)
|
|
||||||
- Add to QUICK_REFERENCE.md "Important Endpoints"
|
|
||||||
|
|
||||||
3. **Database Schema Changed:**
|
|
||||||
- Update DATABASE_SCHEMA.md with new tables
|
|
||||||
- Add query examples if applicable
|
|
||||||
|
|
||||||
4. **Backup Procedure Changed:**
|
|
||||||
- Update BACKUP_CHECKLIST.md
|
|
||||||
- Update QUICK_REFERENCE.md "Database Commands"
|
|
||||||
|
|
||||||
5. **Security Changes:**
|
|
||||||
- Update AUDIT_SUMMARY.md "Security Checklist"
|
|
||||||
- Update AUDIT_LENGKAP.md section 6
|
|
||||||
|
|
||||||
**Version Control:**
|
|
||||||
```markdown
|
|
||||||
Example header update:
|
|
||||||
|
|
||||||
### 6. AUDIT_SUMMARY.md (12.31 KB) → Last updated: 2026-06-20
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ❓ FAQ - Documentation
|
|
||||||
|
|
||||||
**Q: File mana yang harus dibaca duluan?**
|
|
||||||
A: Mulai dengan AUDIT_SUMMARY.md (5 min), lalu QUICK_REFERENCE.md (10 min)
|
|
||||||
|
|
||||||
**Q: Gimana cari informasi tentang endpoint tertentu?**
|
|
||||||
A: Cari di AUDIT_LENGKAP.md section 4, atau grep dengan `/endpoint-name`
|
|
||||||
|
|
||||||
**Q: Database tables untuk X ada di file mana?**
|
|
||||||
A: DATABASE_SCHEMA.md, section "Detailed Table Structures"
|
|
||||||
|
|
||||||
**Q: Backup gimana?**
|
|
||||||
A: Baca BACKUP_CHECKLIST.md dari atas sampai bawah
|
|
||||||
|
|
||||||
**Q: Troubleshooting error X gimana?**
|
|
||||||
A: Lihat QUICK_REFERENCE.md "Troubleshooting", atau AUDIT_LENGKAP.md section 12
|
|
||||||
|
|
||||||
**Q: Dokumentasi sudah lengkap?**
|
|
||||||
A: Ya, 6 file (91 KB) sudah cover semua aspek aplikasi
|
|
||||||
|
|
||||||
**Q: Dokumentasi perlu diupdate?**
|
|
||||||
A: Ya, ikuti "How to Update Documentation" section di atas
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Documentation Statistics
|
|
||||||
|
|
||||||
| File | Size | Sections | Tables | Sections Covered |
|
|
||||||
|------|------|----------|--------|------------------|
|
|
||||||
| AUDIT_LENGKAP.md | 45 KB | 18 | 5+ | Features, API, Security |
|
|
||||||
| QUICK_REFERENCE.md | 7 KB | 10 | 3+ | Common tasks, Quick links |
|
|
||||||
| DATABASE_SCHEMA.md | 16 KB | 6 | 20+ | Database, Tables, Queries |
|
|
||||||
| BACKUP_CHECKLIST.md | 11 KB | 8 | 2+ | Backup, Recovery, RTO/RPO |
|
|
||||||
| AUDIT_SUMMARY.md | 12 KB | 15 | 2+ | Executive, Roadmap |
|
|
||||||
| DOKUMENTASI_INDEX.md | 9 KB | 6 | - | Navigation, Index |
|
|
||||||
|
|
||||||
**Total:** 6 files, ~100 KB, 18+ sections, 30+ tables documented
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Verification Checklist
|
|
||||||
|
|
||||||
Pastikan dokumentasi lengkap:
|
|
||||||
|
|
||||||
- [x] AUDIT_LENGKAP.md → 18 sections ✓
|
|
||||||
- [x] QUICK_REFERENCE.md → 10 sections ✓
|
|
||||||
- [x] DATABASE_SCHEMA.md → 20+ tables ✓
|
|
||||||
- [x] BACKUP_CHECKLIST.md → 8 sections ✓
|
|
||||||
- [x] AUDIT_SUMMARY.md → 15 sections ✓
|
|
||||||
- [x] DOKUMENTASI_INDEX.md → Navigation ✓
|
|
||||||
|
|
||||||
**All files created:** ✅ COMPLETE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Documentation Roadmap
|
|
||||||
|
|
||||||
### Current Status (2026-06-19)
|
|
||||||
- ✅ 6 documentation files created
|
|
||||||
- ✅ ~100 KB comprehensive documentation
|
|
||||||
- ✅ All major features documented
|
|
||||||
- ✅ Backup procedures documented
|
|
||||||
- ✅ Troubleshooting guide created
|
|
||||||
|
|
||||||
### Future Enhancements
|
|
||||||
- ⏳ API Swagger/OpenAPI documentation
|
|
||||||
- ⏳ Video tutorials for common tasks
|
|
||||||
- ⏳ Deployment guide (Docker, K8s)
|
|
||||||
- ⏳ Performance tuning guide
|
|
||||||
- ⏳ Custom integration examples
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Documentation Backup
|
|
||||||
|
|
||||||
**Backup documentation juga!**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create backup
|
|
||||||
tar -czf audit-docs-backup-$(date +%Y-%m-%d).tar.gz \
|
|
||||||
AUDIT_LENGKAP.md \
|
|
||||||
QUICK_REFERENCE.md \
|
|
||||||
DATABASE_SCHEMA.md \
|
|
||||||
BACKUP_CHECKLIST.md \
|
|
||||||
AUDIT_SUMMARY.md \
|
|
||||||
DOKUMENTASI_INDEX.md
|
|
||||||
|
|
||||||
# Store in safe location
|
|
||||||
cp audit-docs-backup-*.tar.gz /mnt/backup/
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Documentation Support
|
|
||||||
|
|
||||||
**Questions about documentation?**
|
|
||||||
|
|
||||||
1. First, try searching the relevant file
|
|
||||||
2. Check cross-references at the end of each section
|
|
||||||
3. Review FAQ section (above)
|
|
||||||
4. Contact: Ali Jaya Net (081947215703)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏁 Next Steps
|
|
||||||
|
|
||||||
1. **READ:** Mulai dengan AUDIT_SUMMARY.md
|
|
||||||
2. **UNDERSTAND:** Baca AUDIT_LENGKAP.md sections yang relevan
|
|
||||||
3. **REFERENCE:** Simpan QUICK_REFERENCE.md untuk daily use
|
|
||||||
4. **BACKUP:** Ikuti BACKUP_CHECKLIST.md procedures
|
|
||||||
5. **MAINTAIN:** Update docs saat ada changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Documentation Version:** 1.0 - Complete
|
|
||||||
**Last Updated:** June 19, 2026
|
|
||||||
**Status:** ✅ READY FOR PRODUCTION
|
|
||||||
|
|
||||||
**Happy Reading! 📖**
|
|
||||||
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,19 +0,0 @@
|
|||||||
# [DESIGN SYSTEM MEMORY: Halogen Kit]
|
|
||||||
|
|
||||||
Terapkan aturan UI/UX dan gaya visual berikut untuk setiap permintaan pembuatan komponen UI, halaman web, atau antarmuka aplikasi:
|
|
||||||
|
|
||||||
## 1. Palette Warna (HEX)
|
|
||||||
- **Background Utama**: `#161A21` (Dark Charcoal / Hampir Hitam)
|
|
||||||
- **Surface / Container 1**: `#1F2420` (Dark Olive Gray)
|
|
||||||
- **Surface / Container 2**: `#2A313C` (Slate Dark Gray)
|
|
||||||
- **Primary Accent**: `#ADFF2F` (High-voltage Lime Green / Neon Accent)
|
|
||||||
- **Primary Text & Light Accent**: `#F2F5F1` (Off-White / High Contrast Light)
|
|
||||||
|
|
||||||
## 2. Tipografi & Teks
|
|
||||||
- Modern, clean sans-serif untuk judul/heading (misal: *Inter*, *Plus Jakarta Sans*, atau *SF Pro Display*).
|
|
||||||
- Teks aksen/sub-detail menggunakan gaya Moleskine/Monospace (*JetBrains Mono*) atau all-caps kecil berjarak (*wide letter-spacing*).
|
|
||||||
|
|
||||||
## 3. Visual & Komponen
|
|
||||||
- **Dark theme dengan kontras tinggi**.
|
|
||||||
- Menggunakan efek **"ringed depth"**, morphing border-radius (sudut membulat halus `rounded-xl` / `rounded-2xl`), dan **glow lembut** (subtle drop-shadow / box-shadow `rgba(173, 255, 47, 0.25 - 0.4)`) pada elemen aksen neon (`#ADFF2F`).
|
|
||||||
- **Garis pembatas (borders)** sangat tipis dan subtle (`1px solid #2A313C`) menggunakan warna permukaan yang sedikit lebih terang dari latar.
|
|
||||||
@@ -1,757 +0,0 @@
|
|||||||
# IMPLEMENTATION GUIDE - SECURITY FIXES
|
|
||||||
**Status:** READY TO IMPLEMENT
|
|
||||||
**Tanggal:** 14 Agustus 2026
|
|
||||||
**Target Completion:** 2-3 minggu
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 QUICK START - CRITICAL FIXES
|
|
||||||
|
|
||||||
### STEP 1: Install Required Packages
|
|
||||||
```bash
|
|
||||||
npm install bcrypt dotenv helmet express-rate-limit
|
|
||||||
npm install --save-dev @types/bcrypt
|
|
||||||
|
|
||||||
# Optional but recommended:
|
|
||||||
npm install express-validator joi
|
|
||||||
npm install better-sqlite3-session-store
|
|
||||||
```
|
|
||||||
|
|
||||||
### STEP 2: Create .env File
|
|
||||||
```bash
|
|
||||||
# Copy template
|
|
||||||
cp .env.example .env
|
|
||||||
|
|
||||||
# Edit .env dengan credentials actual
|
|
||||||
# Edit dengan text editor
|
|
||||||
nano .env
|
|
||||||
```
|
|
||||||
|
|
||||||
File `.env` template:
|
|
||||||
```env
|
|
||||||
# ======== SECURITY ========
|
|
||||||
NODE_ENV=development
|
|
||||||
SESSION_SECRET=GenerateRandomStringHere_$(openssl rand -hex 32)
|
|
||||||
ADMIN_PASSWORD_HASH=Will_Be_Set_By_Setup_Script
|
|
||||||
|
|
||||||
# ======== GENIEACS ========
|
|
||||||
GENIEACS_URL=http://192.168.8.189:7557
|
|
||||||
GENIEACS_USERNAME=admin
|
|
||||||
GENIEACS_PASSWORD=ChangeThisPassword
|
|
||||||
|
|
||||||
# ======== DATABASE ========
|
|
||||||
DATABASE_PATH=./database/billing.db
|
|
||||||
|
|
||||||
# ======== PAYMENT GATEWAYS ========
|
|
||||||
MIDTRANS_SERVER_KEY=Mid_server_xxxxx
|
|
||||||
MIDTRANS_MODE=sandbox
|
|
||||||
|
|
||||||
XENDIT_API_KEY=xnd_development_xxxxx
|
|
||||||
|
|
||||||
TRIPAY_API_KEY=xxxxx
|
|
||||||
TRIPAY_MERCHANT_CODE=xxxxx
|
|
||||||
TRIPAY_MODE=sandbox
|
|
||||||
|
|
||||||
DUITKU_API_KEY=xxxxx
|
|
||||||
DUITKU_MERCHANT_CODE=xxxxx
|
|
||||||
|
|
||||||
# ======== THIRD PARTY ========
|
|
||||||
DIGIFLAZZ_USERNAME=xxxxx
|
|
||||||
DIGIFLAZZ_API_KEY=xxxxx
|
|
||||||
DIGIFLAZZ_WEBHOOK_SECRET=xxxxx
|
|
||||||
|
|
||||||
# ======== WHATSAPP ========
|
|
||||||
WHATSAPP_ENABLED=true
|
|
||||||
|
|
||||||
# ======== TELEGRAM ========
|
|
||||||
TELEGRAM_ENABLED=false
|
|
||||||
TELEGRAM_BOT_TOKEN=xxxxx
|
|
||||||
|
|
||||||
# ======== RADIUS ========
|
|
||||||
RADIUS_SECRET=secret123
|
|
||||||
RADIUS_AUTH_PORT=1812
|
|
||||||
RADIUS_ACCT_PORT=1813
|
|
||||||
|
|
||||||
# ======== LOGGING ========
|
|
||||||
LOG_LEVEL=info
|
|
||||||
```
|
|
||||||
|
|
||||||
### STEP 3: Update .gitignore
|
|
||||||
```bash
|
|
||||||
# Add these lines to .gitignore
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
settings.json # Optional: jika ada credentials di sini
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 FIX #1: Password Hashing with Bcrypt
|
|
||||||
|
|
||||||
### Part A: Create Password Helper Module
|
|
||||||
|
|
||||||
**File:** `utils/passwordHelper.js`
|
|
||||||
```javascript
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
const SALT_ROUNDS = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash password dengan bcrypt
|
|
||||||
* @param {string} plainPassword - Plain text password
|
|
||||||
* @returns {Promise<string>} Hashed password
|
|
||||||
*/
|
|
||||||
async function hashPassword(plainPassword) {
|
|
||||||
if (!plainPassword) throw new Error('Password cannot be empty');
|
|
||||||
return await bcrypt.hash(plainPassword, SALT_ROUNDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify password
|
|
||||||
* @param {string} plainPassword - Plain text password to verify
|
|
||||||
* @param {string} hashedPassword - Hashed password from database
|
|
||||||
* @returns {Promise<boolean>} True if password matches
|
|
||||||
*/
|
|
||||||
async function verifyPassword(plainPassword, hashedPassword) {
|
|
||||||
if (!plainPassword || !hashedPassword) return false;
|
|
||||||
return await bcrypt.compare(plainPassword, hashedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
hashPassword,
|
|
||||||
verifyPassword
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part B: Create Admin Setup Script
|
|
||||||
|
|
||||||
**File:** `scripts/setup-admin-password.js`
|
|
||||||
```javascript
|
|
||||||
#!/usr/bin/env node
|
|
||||||
const readline = require('readline');
|
|
||||||
const db = require('../config/database');
|
|
||||||
const { hashPassword } = require('../utils/passwordHelper');
|
|
||||||
const { logger } = require('../config/logger');
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
});
|
|
||||||
|
|
||||||
const question = (prompt) => new Promise((resolve) => {
|
|
||||||
rl.question(prompt, resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function setupAdmin() {
|
|
||||||
console.log('\n🔐 SETUP ADMIN PASSWORD\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if admin exists
|
|
||||||
const admin = db.prepare('SELECT id FROM administrators LIMIT 1').get();
|
|
||||||
|
|
||||||
if (!admin) {
|
|
||||||
console.log('⚠️ No administrator found in database.');
|
|
||||||
console.log('Please run database migration first.\n');
|
|
||||||
rl.close();
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const username = await question('Admin Username [admin]: ');
|
|
||||||
const password = await question('Admin Password (min 8 chars): ');
|
|
||||||
const passwordConfirm = await question('Confirm Password: ');
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
if (!username.trim() || !password) {
|
|
||||||
console.log('❌ Username and password cannot be empty');
|
|
||||||
rl.close();
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password !== passwordConfirm) {
|
|
||||||
console.log('❌ Passwords do not match');
|
|
||||||
rl.close();
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password.length < 8) {
|
|
||||||
console.log('❌ Password must be at least 8 characters');
|
|
||||||
rl.close();
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash password
|
|
||||||
console.log('\n🔐 Hashing password...');
|
|
||||||
const hashedPassword = await hashPassword(password);
|
|
||||||
|
|
||||||
// Update database
|
|
||||||
const stmt = db.prepare('UPDATE administrators SET username = ?, password_hash = ? WHERE id = ?');
|
|
||||||
stmt.run(username.trim(), hashedPassword, admin.id);
|
|
||||||
|
|
||||||
console.log('✅ Admin password updated successfully!');
|
|
||||||
console.log(` Username: ${username.trim()}`);
|
|
||||||
console.log('\nℹ️ Please keep your password secure and do NOT share with anyone.\n');
|
|
||||||
|
|
||||||
rl.close();
|
|
||||||
process.exit(0);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Setup failed:', err);
|
|
||||||
console.error('❌ Error:', err.message);
|
|
||||||
rl.close();
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setupAdmin();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part C: Update Technician Service
|
|
||||||
|
|
||||||
**File:** `services/techService.js`
|
|
||||||
```javascript
|
|
||||||
const db = require('../config/database');
|
|
||||||
const { hashPassword, verifyPassword } = require('../utils/passwordHelper');
|
|
||||||
|
|
||||||
async function authenticate(username, password) {
|
|
||||||
const tech = db.prepare('SELECT * FROM technicians WHERE username = ? AND is_active = 1').get(username);
|
|
||||||
|
|
||||||
if (!tech) return null;
|
|
||||||
|
|
||||||
// Verify password using bcrypt
|
|
||||||
const isValid = await verifyPassword(password, tech.password_hash);
|
|
||||||
|
|
||||||
if (!isValid) return null;
|
|
||||||
|
|
||||||
// Return user data tanpa password
|
|
||||||
const { password_hash, ...techData } = tech;
|
|
||||||
return techData;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createTechnician(data) {
|
|
||||||
const hashedPassword = await hashPassword(data.password);
|
|
||||||
const stmt = db.prepare(
|
|
||||||
'INSERT INTO technicians (username, password_hash, name, phone, area) VALUES (?, ?, ?, ?, ?)'
|
|
||||||
);
|
|
||||||
return stmt.run(data.username, hashedPassword, data.name, data.phone || '', data.area || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updatePassword(techId, newPassword) {
|
|
||||||
const hashedPassword = await hashPassword(newPassword);
|
|
||||||
return db.prepare('UPDATE technicians SET password_hash = ? WHERE id = ?').run(hashedPassword, techId);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
authenticate,
|
|
||||||
createTechnician,
|
|
||||||
updatePassword,
|
|
||||||
// ... export fungsi lain yang ada
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part D: Update Customer Portal Login
|
|
||||||
|
|
||||||
**File:** `routes/customerPortal.js` (di bagian POST /login)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Find in the file and replace the login logic:
|
|
||||||
|
|
||||||
// ❌ OLD CODE
|
|
||||||
const cust = db.prepare('SELECT * FROM customers WHERE ...')
|
|
||||||
.get(loginId, password);
|
|
||||||
|
|
||||||
// ✅ NEW CODE
|
|
||||||
const cust = db.prepare('SELECT * FROM customers WHERE login_id = ?')
|
|
||||||
.get(loginId);
|
|
||||||
|
|
||||||
if (cust) {
|
|
||||||
const { verifyPassword } = require('../utils/passwordHelper');
|
|
||||||
const isValid = await verifyPassword(password, cust.password_hash);
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
return res.status(401).json({ error: 'Invalid credentials' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regenerate session ID untuk security
|
|
||||||
req.session.regenerate((err) => {
|
|
||||||
if (err) return res.status(500).json({ error: 'Session error' });
|
|
||||||
req.session.customerId = cust.id;
|
|
||||||
res.json({ success: true });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part E: Database Migration
|
|
||||||
|
|
||||||
**File:** `scripts/migrate-password-hashing.js`
|
|
||||||
```javascript
|
|
||||||
#!/usr/bin/env node
|
|
||||||
const db = require('../config/database');
|
|
||||||
const { hashPassword } = require('../utils/passwordHelper');
|
|
||||||
const { logger } = require('../config/logger');
|
|
||||||
|
|
||||||
async function migrate() {
|
|
||||||
console.log('🔄 Starting password hashing migration...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Migrate technicians
|
|
||||||
console.log('📋 Migrating technicians...');
|
|
||||||
const techs = db.prepare('SELECT id, password FROM technicians WHERE password_hash IS NULL').all();
|
|
||||||
|
|
||||||
for (const tech of techs) {
|
|
||||||
if (tech.password) {
|
|
||||||
const hashedPassword = await hashPassword(tech.password);
|
|
||||||
db.prepare('UPDATE technicians SET password_hash = ? WHERE id = ?').run(hashedPassword, tech.id);
|
|
||||||
console.log(` ✅ Tech ${tech.id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate customers (if applicable)
|
|
||||||
console.log('\n📋 Migrating customers...');
|
|
||||||
const custs = db.prepare('SELECT id, password FROM customers WHERE password_hash IS NULL AND password IS NOT NULL').all();
|
|
||||||
|
|
||||||
for (const cust of custs) {
|
|
||||||
if (cust.password) {
|
|
||||||
const hashedPassword = await hashPassword(cust.password);
|
|
||||||
db.prepare('UPDATE customers SET password_hash = ? WHERE id = ?').run(hashedPassword, cust.id);
|
|
||||||
console.log(` ✅ Customer ${cust.id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate administrators
|
|
||||||
console.log('\n📋 Migrating administrators...');
|
|
||||||
const admins = db.prepare('SELECT id, password FROM administrators WHERE password_hash IS NULL').all();
|
|
||||||
|
|
||||||
for (const admin of admins) {
|
|
||||||
if (admin.password) {
|
|
||||||
const hashedPassword = await hashPassword(admin.password);
|
|
||||||
db.prepare('UPDATE administrators SET password_hash = ? WHERE id = ?').run(hashedPassword, admin.id);
|
|
||||||
console.log(` ✅ Admin ${admin.id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n✅ Migration completed successfully!');
|
|
||||||
console.log('\n⚠️ IMPORTANT:');
|
|
||||||
console.log(' 1. Delete plain password columns: ALTER TABLE technicians DROP COLUMN password;');
|
|
||||||
console.log(' 2. Ensure all code uses password_hash column');
|
|
||||||
console.log(' 3. Update settings.json to not store admin password\n');
|
|
||||||
|
|
||||||
logger.info('Password hashing migration completed');
|
|
||||||
process.exit(0);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Migration failed:', err);
|
|
||||||
console.error('❌ Error:', err.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
migrate();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part F: Update Database Schema
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Add password_hash column to tables (if not exists)
|
|
||||||
ALTER TABLE technicians ADD COLUMN password_hash TEXT;
|
|
||||||
ALTER TABLE administrators ADD COLUMN password_hash TEXT;
|
|
||||||
ALTER TABLE customers ADD COLUMN password_hash TEXT;
|
|
||||||
|
|
||||||
-- After migration, drop old password columns:
|
|
||||||
-- ALTER TABLE technicians DROP COLUMN password;
|
|
||||||
-- ALTER TABLE administrators DROP COLUMN password;
|
|
||||||
-- ALTER TABLE customers DROP COLUMN password;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗝️ FIX #2: Environment Variables & Config Management
|
|
||||||
|
|
||||||
### Part A: Create .env.example
|
|
||||||
|
|
||||||
**File:** `.env.example`
|
|
||||||
```env
|
|
||||||
# Copy this file to .env and fill in actual values
|
|
||||||
# DO NOT commit .env to git!
|
|
||||||
|
|
||||||
# ======== APPLICATION ========
|
|
||||||
NODE_ENV=development
|
|
||||||
SERVER_PORT=3001
|
|
||||||
SERVER_HOST=localhost
|
|
||||||
|
|
||||||
# ======== SECURITY ========
|
|
||||||
SESSION_SECRET=change-me-to-random-string-minimum-32-chars
|
|
||||||
COOKIE_SECURE=false # Set to true in production with HTTPS
|
|
||||||
|
|
||||||
# ======== ADMIN SETUP ========
|
|
||||||
# Set these during first-time setup, will be migrated to database
|
|
||||||
ADMIN_USERNAME=admin
|
|
||||||
ADMIN_PASSWORD=change-me-to-strong-password-min-8-chars
|
|
||||||
|
|
||||||
# ======== GENIEACS ========
|
|
||||||
GENIEACS_URL=http://192.168.8.189:7557
|
|
||||||
GENIEACS_USERNAME=admin
|
|
||||||
GENIEACS_PASSWORD=change-me
|
|
||||||
|
|
||||||
# ======== PAYMENT GATEWAYS ========
|
|
||||||
MIDTRANS_ENABLED=true
|
|
||||||
MIDTRANS_SERVER_KEY=Mid_server_xxxxx
|
|
||||||
MIDTRANS_MODE=sandbox
|
|
||||||
|
|
||||||
XENDIT_ENABLED=true
|
|
||||||
XENDIT_API_KEY=xnd_development_xxxxx
|
|
||||||
|
|
||||||
TRIPAY_ENABLED=false
|
|
||||||
TRIPAY_API_KEY=xxxxx
|
|
||||||
TRIPAY_PRIVATE_KEY=xxxxx
|
|
||||||
TRIPAY_MERCHANT_CODE=xxxxx
|
|
||||||
TRIPAY_MODE=sandbox
|
|
||||||
|
|
||||||
DUITKU_ENABLED=true
|
|
||||||
DUITKU_API_KEY=xxxxx
|
|
||||||
DUITKU_MERCHANT_CODE=xxxxx
|
|
||||||
DUITKU_MODE=sandbox
|
|
||||||
|
|
||||||
# ======== DIGIFLAZZ ========
|
|
||||||
DIGIFLAZZ_USERNAME=
|
|
||||||
DIGIFLAZZ_API_KEY=
|
|
||||||
DIGIFLAZZ_WEBHOOK_SECRET=
|
|
||||||
|
|
||||||
# ======== WHATSAPP ========
|
|
||||||
WHATSAPP_ENABLED=true
|
|
||||||
WHATSAPP_AUTH_FOLDER=auth_info_baileys
|
|
||||||
|
|
||||||
# ======== TELEGRAM ========
|
|
||||||
TELEGRAM_ENABLED=false
|
|
||||||
TELEGRAM_BOT_TOKEN=
|
|
||||||
|
|
||||||
# ======== RADIUS ========
|
|
||||||
RADIUS_ENABLED=0
|
|
||||||
RADIUS_SECRET=secret123
|
|
||||||
|
|
||||||
# ======== LOGGING ========
|
|
||||||
LOG_LEVEL=info
|
|
||||||
|
|
||||||
# ======== DATABASE ========
|
|
||||||
DATABASE_PATH=./database/billing.db
|
|
||||||
|
|
||||||
# ======== TIMEZONE ========
|
|
||||||
TIMEZONE=Asia/Jakarta
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part B: Update config/settingsManager.js
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Add at the top of settingsManager.js:
|
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
// Modify getSetting function to check .env first:
|
|
||||||
function getSetting(key, defaultValue = null) {
|
|
||||||
// 1. Check environment variable first (highest priority)
|
|
||||||
const envKey = key.toUpperCase().replace(/([A-Z])/g, '_$1');
|
|
||||||
if (process.env[envKey] !== undefined) {
|
|
||||||
return process.env[envKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check settings.json
|
|
||||||
const settings = getSettings();
|
|
||||||
if (settings[key] !== undefined) {
|
|
||||||
return settings[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Return default value
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part C: Update .gitignore
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Add to .gitignore:
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
.env.development.local
|
|
||||||
.env.staging.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
# Optional: exclude settings.json if it contains credentials
|
|
||||||
# settings.json
|
|
||||||
# Instead: settings.json.example (commit this template)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛡️ FIX #3: Enhanced CSRF Protection
|
|
||||||
|
|
||||||
### Part A: Install CSURF
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install csurf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part B: Add CSRF to app-customer.js
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const csrf = require('csurf');
|
|
||||||
|
|
||||||
// Create CSRF protection middleware
|
|
||||||
const csrfProtection = csrf({
|
|
||||||
cookie: false // Use session instead of cookie
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply to POST/PUT/DELETE routes
|
|
||||||
app.post('/admin/:path*', csrfProtection, adminRoutes);
|
|
||||||
app.post('/customer/:path*', csrfProtection, customerRoutes);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part C: Add CSRF Token to Forms
|
|
||||||
|
|
||||||
```ejs
|
|
||||||
<!-- In EJS templates, add hidden CSRF token field: -->
|
|
||||||
<form method="POST" action="/admin/billing/generate">
|
|
||||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
|
||||||
<!-- Other form fields -->
|
|
||||||
<button type="submit">Generate Invoice</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- For AJAX requests, include in headers: -->
|
|
||||||
<script>
|
|
||||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
|
|
||||||
|
|
||||||
fetch('/api/action', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRF-Token': csrfToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚨 FIX #4: Rate Limiting
|
|
||||||
|
|
||||||
### Part A: Install & Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install express-rate-limit
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part B: Create Rate Limiter Middleware
|
|
||||||
|
|
||||||
**File:** `middleware/rateLimiter.js`
|
|
||||||
```javascript
|
|
||||||
const rateLimit = require('express-rate-limit');
|
|
||||||
|
|
||||||
const loginRateLimiter = rateLimit({
|
|
||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
||||||
max: 5, // Limit each IP to 5 requests per windowMs
|
|
||||||
message: 'Terlalu banyak percobaan login, coba lagi dalam beberapa menit',
|
|
||||||
standardHeaders: true,
|
|
||||||
legacyHeaders: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const apiRateLimiter = rateLimit({
|
|
||||||
windowMs: 1 * 60 * 1000, // 1 minute
|
|
||||||
max: 100, // 100 requests per minute
|
|
||||||
message: 'Terlalu banyak request ke API, coba lagi nanti'
|
|
||||||
});
|
|
||||||
|
|
||||||
const webhookRateLimiter = rateLimit({
|
|
||||||
windowMs: 1 * 60 * 1000,
|
|
||||||
max: 1000, // Higher limit for webhooks
|
|
||||||
skip: (req) => {
|
|
||||||
// Skip rate limiting if webhook signature is valid
|
|
||||||
return req.session?.webhookVerified;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
loginRateLimiter,
|
|
||||||
apiRateLimiter,
|
|
||||||
webhookRateLimiter
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part C: Apply Rate Limiters
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In routes
|
|
||||||
const { loginRateLimiter, apiRateLimiter } = require('../middleware/rateLimiter');
|
|
||||||
|
|
||||||
// Apply to login endpoints
|
|
||||||
router.post('/login', loginRateLimiter, loginController);
|
|
||||||
router.post('/customer/login', loginRateLimiter, customerLoginController);
|
|
||||||
|
|
||||||
// Apply to API endpoints
|
|
||||||
router.get('/api/data', apiRateLimiter, apiController);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 FIX #5: Session Management Improvements
|
|
||||||
|
|
||||||
### Part A: Setup Session Store
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install better-sqlite3-session-store
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part B: Update app-customer.js
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const SessionStore = require('better-sqlite3-session-store')(session);
|
|
||||||
|
|
||||||
const store = new SessionStore({
|
|
||||||
client: db,
|
|
||||||
expired: {
|
|
||||||
clear: true,
|
|
||||||
intervalMs: 15 * 60 * 1000 // Clear expired sessions every 15 minutes
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(session({
|
|
||||||
store: store, // Use database store instead of memory
|
|
||||||
secret: process.env.SESSION_SECRET || getSetting('session_secret'),
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: false,
|
|
||||||
cookie: {
|
|
||||||
secure: process.env.COOKIE_SECURE === 'true',
|
|
||||||
httpOnly: true,
|
|
||||||
sameSite: 'strict', // More restrictive
|
|
||||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
|
||||||
},
|
|
||||||
name: 'customer.sid'
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
### Part C: Session Regeneration on Login
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// After successful login:
|
|
||||||
req.session.regenerate((err) => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: 'Session error' });
|
|
||||||
}
|
|
||||||
req.session.userId = user.id;
|
|
||||||
req.session.userRole = user.role;
|
|
||||||
req.session.loginTime = Date.now();
|
|
||||||
|
|
||||||
// Set inactivity timeout
|
|
||||||
req.session.inactivityTimeout = 30 * 60 * 1000; // 30 minutes
|
|
||||||
|
|
||||||
res.json({ success: true, redirect: '/dashboard' });
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ VERIFICATION CHECKLIST
|
|
||||||
|
|
||||||
After implementing all fixes, verify:
|
|
||||||
|
|
||||||
- [ ] bcrypt password hashing implemented
|
|
||||||
- [ ] Old plain passwords migrated to hashed
|
|
||||||
- [ ] .env file created and populated
|
|
||||||
- [ ] .env added to .gitignore
|
|
||||||
- [ ] Settings.json reviewed for sensitive data
|
|
||||||
- [ ] CSRF protection implemented
|
|
||||||
- [ ] Rate limiting configured
|
|
||||||
- [ ] Session store using database
|
|
||||||
- [ ] Session regeneration on login working
|
|
||||||
- [ ] All tests passing
|
|
||||||
- [ ] Application running without errors
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 TESTING FIXES
|
|
||||||
|
|
||||||
### Test Password Hashing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node -e "
|
|
||||||
const { hashPassword, verifyPassword } = require('./utils/passwordHelper');
|
|
||||||
(async () => {
|
|
||||||
const hash = await hashPassword('test123456');
|
|
||||||
const valid = await verifyPassword('test123456', hash);
|
|
||||||
console.log('Hash:', hash);
|
|
||||||
console.log('Valid:', valid);
|
|
||||||
})();
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Environment Variables
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node -e "
|
|
||||||
require('dotenv').config();
|
|
||||||
console.log('NODE_ENV:', process.env.NODE_ENV);
|
|
||||||
console.log('SESSION_SECRET:', process.env.SESSION_SECRET ? '✅ Set' : '❌ Not set');
|
|
||||||
console.log('GENIEACS_PASSWORD:', process.env.GENIEACS_PASSWORD ? '✅ Set' : '❌ Not set');
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Login Flow
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start application
|
|
||||||
npm start
|
|
||||||
|
|
||||||
# Open browser and test login with new credentials
|
|
||||||
# Check console logs for any errors
|
|
||||||
# Verify session is created in database
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 TROUBLESHOOTING
|
|
||||||
|
|
||||||
### Issue: "bcrypt is not defined"
|
|
||||||
```bash
|
|
||||||
Solution: npm install bcrypt
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: ".env file not loading"
|
|
||||||
```bash
|
|
||||||
Solution: Ensure dotenv is imported at the top of app-customer.js:
|
|
||||||
require('dotenv').config();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: "CSRF token mismatch"
|
|
||||||
```bash
|
|
||||||
Solution: Ensure csrfToken is included in all forms and AJAX requests
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: "Session not persisting"
|
|
||||||
```bash
|
|
||||||
Solution: Verify database schema has session table:
|
|
||||||
sqlite3 database/billing.db ".schema sessions"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 NEXT STEPS
|
|
||||||
|
|
||||||
1. **Immediate (Today):**
|
|
||||||
- Review this guide with team
|
|
||||||
- Create .env file
|
|
||||||
- Test bcrypt locally
|
|
||||||
|
|
||||||
2. **This Week:**
|
|
||||||
- Implement bcrypt in all auth modules
|
|
||||||
- Run password migration script
|
|
||||||
- Deploy to staging for testing
|
|
||||||
|
|
||||||
3. **Next Week:**
|
|
||||||
- Implement CSRF protection
|
|
||||||
- Add rate limiting
|
|
||||||
- Full security testing
|
|
||||||
|
|
||||||
4. **Following Week:**
|
|
||||||
- Monitor production (if live)
|
|
||||||
- Collect logs and monitoring data
|
|
||||||
- Review and optimize
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Questions?** Review AUDIT_SECURITY_2026.md for detailed security analysis.
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
# Multi-Router Implementation - Verification Status
|
|
||||||
|
|
||||||
**Last Updated**: 2026-06-19
|
|
||||||
**Status**: ✅ COMPLETE - Ready for production testing
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Summary
|
|
||||||
|
|
||||||
### 1. Core Features Implemented
|
|
||||||
|
|
||||||
#### Smart Router Detection (`getEffectiveRouterId()`)
|
|
||||||
- **Location**: `services/customerService.js`
|
|
||||||
- **Mode: Aktif (Active Multi-Router)**
|
|
||||||
- Returns customer's explicit `router_id` (can be NULL)
|
|
||||||
- Customers MUST explicitly choose router during setup
|
|
||||||
- Best for environments with multiple independent routers
|
|
||||||
|
|
||||||
- **Mode: Nonaktif (Single Router Fallback)**
|
|
||||||
- Returns customer's `router_id` if set
|
|
||||||
- Falls back to `default_router_id` setting if customer has NULL
|
|
||||||
- Auto-detects first available router if no default set
|
|
||||||
- Best for migration from single-router to multi-router
|
|
||||||
- Allows old customers (NULL router_id) to work transparently
|
|
||||||
|
|
||||||
#### Router Connection Fallback (`getConnection()`)
|
|
||||||
- **Location**: `services/mikrotikService.js`
|
|
||||||
- Priority 1: Use specified `routerId` from database
|
|
||||||
- Priority 2: Auto-detect first active router from database
|
|
||||||
- Priority 3: Fallback to `settings.json` (backward compatibility)
|
|
||||||
- Includes TCP connectivity probing + cache (5s) for performance
|
|
||||||
|
|
||||||
#### Configuration
|
|
||||||
- **Location**: `settings.json`
|
|
||||||
- `multi_router_mode`: "active" | "disabled" (default: "disabled")
|
|
||||||
- `default_router_id`: Optional ID for fallback router
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integration Points - All Verified
|
|
||||||
|
|
||||||
### ✅ Customer Suspension (Isolir)
|
|
||||||
**Admin Portal Route**: `POST /customers/:id/isolate`
|
|
||||||
- Calls `customerSvc.suspendCustomer(id)`
|
|
||||||
- Uses `getEffectiveRouterId()` internally
|
|
||||||
- Updates MikroTik profile → updates database status
|
|
||||||
- Sends WhatsApp notification to customer phone
|
|
||||||
|
|
||||||
**WhatsApp Bot Command**: `!isolir <customer_id>`
|
|
||||||
- Calls `customerSvc.suspendCustomer(id)`
|
|
||||||
- Same multi-router support as admin route
|
|
||||||
|
|
||||||
**Code Flow**:
|
|
||||||
```
|
|
||||||
Admin/Bot Request
|
|
||||||
→ suspendCustomer(id)
|
|
||||||
→ getCustomerById(id)
|
|
||||||
→ getEffectiveRouterId(customer.router_id)
|
|
||||||
→ mikrotikSvc.setPppoeProfile() [respects effectiveRouterId]
|
|
||||||
→ updateCustomer() [database update]
|
|
||||||
→ sendWA() [notification]
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Customer Activation
|
|
||||||
**Admin Portal Route**: `POST /customers/:id/unisolate`
|
|
||||||
- Calls `customerSvc.activateCustomer(id)`
|
|
||||||
- Uses `getEffectiveRouterId()` internally
|
|
||||||
- Updates MikroTik profile → updates database status
|
|
||||||
|
|
||||||
**Code Flow**:
|
|
||||||
```
|
|
||||||
Admin Request
|
|
||||||
→ activateCustomer(id)
|
|
||||||
→ getCustomerById(id)
|
|
||||||
→ getEffectiveRouterId(customer.router_id)
|
|
||||||
→ mikrotikSvc.setPppoeProfile() [respects effectiveRouterId]
|
|
||||||
→ updateCustomer() [database update]
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Payment Processing Integration
|
|
||||||
**Payment Service**: Routes at `/voucher-payment`, payment callbacks
|
|
||||||
- Customer payment → `activateCustomer()` → multi-router support
|
|
||||||
- Files: `services/paymentService.js`, `routes/voucherPaymentAPI.js`
|
|
||||||
|
|
||||||
### ✅ Export/Import Functionality
|
|
||||||
**Admin Portal Routes**: `GET /customers/export`, `POST /customers/import`
|
|
||||||
- Export includes `Router` column (maps to `router_name`)
|
|
||||||
- Import supports both old files (NULL router) and new (router specified)
|
|
||||||
- Backward compatible with existing export files
|
|
||||||
- File: `routes/adminPortal.js` (lines 1900-2000)
|
|
||||||
|
|
||||||
### ✅ Portal Login Support
|
|
||||||
|
|
||||||
**Customer Portal** (`routes/customerPortal.js`)
|
|
||||||
- PPPoE profile loading: Supports multi-router ✅
|
|
||||||
- Queries include `r.name as router_name` join
|
|
||||||
- Router filter applied by customer's effective router
|
|
||||||
|
|
||||||
**Tech Portal** (`routes/techPortal.js`)
|
|
||||||
- PPPoE profiles with router query param support ✅
|
|
||||||
- Route: `/tech/pppoe-profiles?routerId=1`
|
|
||||||
- Validates customer matches requested router
|
|
||||||
|
|
||||||
**ACS Portal** (`routes/acsPortal.js`)
|
|
||||||
- PPPoE profile loading for ONU management ✅
|
|
||||||
- Respects customer's effective router
|
|
||||||
|
|
||||||
### ✅ Connection Types - All Supported
|
|
||||||
|
|
||||||
| Connection Type | Suspend Implementation | Activate Implementation | Router Validation |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **PPPoE** | `setPppoeProfile(username, 'isolir', routerId)` | `setPppoeProfile(username, profileName, routerId)` | Required for isolir ✅ |
|
|
||||||
| **Static IP** | `manageStaticIp({...isolate: true}, routerId)` | `manageStaticIp({...isolate: false}, routerId)` | Required ✅ |
|
|
||||||
| **Hotspot** | `setHotspotUserDisabled(user, true, routerId)` | `upsertHotspotUser({...disabled: false}, routerId)` | Required ✅ |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current Configuration
|
|
||||||
|
|
||||||
### Database Routers
|
|
||||||
```
|
|
||||||
ID | Name | is_active
|
|
||||||
----|----------------|----------
|
|
||||||
1 | Main Router | 1
|
|
||||||
2 | Main Router | 1
|
|
||||||
3 | Main Router | 1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Application Settings
|
|
||||||
```
|
|
||||||
multi_router_mode: "disabled" (default) [from settings.json]
|
|
||||||
default_router_id: "" (empty/unset) [from settings.json]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### Phase 1: Basic Multi-Router Operations
|
|
||||||
- [ ] **Test 1.1**: Suspend customer with explicit router_id (Router 1)
|
|
||||||
- Expected: Isolir works, customer can see they're suspended
|
|
||||||
- Check: Database status = "suspended", WhatsApp notification received
|
|
||||||
|
|
||||||
- [ ] **Test 1.2**: Suspend customer with NULL router_id in "disabled" mode
|
|
||||||
- Expected: Falls back to first router in database
|
|
||||||
- Check: Customer isolir works even though router_id = NULL
|
|
||||||
|
|
||||||
- [ ] **Test 1.3**: Activate customer from suspended state
|
|
||||||
- Expected: Customer can reconnect, service restored
|
|
||||||
- Check: Database status = "active", PPPoE profile changed back
|
|
||||||
|
|
||||||
### Phase 2: Different Connection Types
|
|
||||||
- [ ] **Test 2.1**: Suspend customer with Static IP connection
|
|
||||||
- Expected: IP rate-limited to isolir profile
|
|
||||||
- Check: MikroTik queue rules applied
|
|
||||||
|
|
||||||
- [ ] **Test 2.2**: Suspend customer with Hotspot connection
|
|
||||||
- Expected: Hotspot user disabled = true
|
|
||||||
- Check: Customer cannot login to hotspot
|
|
||||||
|
|
||||||
### Phase 3: WhatsApp Bot Integration
|
|
||||||
- [ ] **Test 3.1**: Run `!isolir <customer_id>` command via WhatsApp
|
|
||||||
- Expected: Customer isolir works, admin gets response
|
|
||||||
- Check: Admin receives ✅ confirmation in WhatsApp
|
|
||||||
|
|
||||||
- [ ] **Test 3.2**: Run on customer with NULL router_id
|
|
||||||
- Expected: Uses fallback router, works correctly
|
|
||||||
- Check: Customer gets isolir'd despite NULL router
|
|
||||||
|
|
||||||
### Phase 4: Payment & Auto-Isolir
|
|
||||||
- [ ] **Test 4.1**: Customer makes payment after isolir
|
|
||||||
- Expected: Auto-activates via payment webhook
|
|
||||||
- Check: Customer service restored immediately
|
|
||||||
|
|
||||||
- [ ] **Test 4.2**: Auto-isolir on missed payment (cron job)
|
|
||||||
- Expected: Customers automatically suspended on isolir_day
|
|
||||||
- Check: Log shows "Auto-isolir" entries
|
|
||||||
|
|
||||||
### Phase 5: Export/Import Cycle
|
|
||||||
- [ ] **Test 5.1**: Export customers, verify "Router" column present
|
|
||||||
- Expected: CSV has "Router" column with router names
|
|
||||||
- Check: Column exists in exported file
|
|
||||||
|
|
||||||
- [ ] **Test 5.2**: Modify export, re-import with different router
|
|
||||||
- Expected: Customer router_id updated in database
|
|
||||||
- Check: New router_id reflected in customer edit view
|
|
||||||
|
|
||||||
### Phase 6: Mode Switching (Aktif ↔ Nonaktif)
|
|
||||||
- [ ] **Test 6.1**: Switch from "Nonaktif" → "Aktif"
|
|
||||||
- Expected: Mode changes, no auto-assignments (existing customers keep current router)
|
|
||||||
- Check: Settings updated, app doesn't crash
|
|
||||||
|
|
||||||
- [ ] **Test 6.2**: Switch from "Aktif" → "Nonaktif"
|
|
||||||
- Expected: Mode changes, customers without router_id get assigned default
|
|
||||||
- Check: Auto-assignment completed, NULL router_id → router_id populated
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting Guide
|
|
||||||
|
|
||||||
### Symptom: Customer suspendsion not working
|
|
||||||
**Check 1**: Verify customer has router_id set
|
|
||||||
```bash
|
|
||||||
SELECT id, name, router_id, pppoe_username FROM customers WHERE id = ?
|
|
||||||
```
|
|
||||||
- If NULL: Check multi_router_mode setting - must be "disabled" for fallback to work
|
|
||||||
- If not NULL: Verify router exists and is active
|
|
||||||
|
|
||||||
**Check 2**: Verify MikroTik connectivity
|
|
||||||
```bash
|
|
||||||
node -e "const m = require('./services/mikrotikService'); m.checkConnection(1).then(ok => console.log('Router 1:', ok))"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Check 3**: Check application logs
|
|
||||||
```bash
|
|
||||||
tail -f logs/combined.log | grep -i "isolir\|suspend"
|
|
||||||
tail -f logs/error.log
|
|
||||||
```
|
|
||||||
|
|
||||||
### Symptom: WhatsApp isolir command fails
|
|
||||||
**Check**: Verify WhatsApp is enabled and logged in
|
|
||||||
- Check `settings.json`: `whatsapp_enabled: true`
|
|
||||||
- Check logs for "whatsapp" entries
|
|
||||||
- Verify phone number is in `whatsapp_admin_numbers`
|
|
||||||
|
|
||||||
### Symptom: Mode switching doesn't auto-assign routers
|
|
||||||
**Check**: Review logs during mode switch
|
|
||||||
```bash
|
|
||||||
grep -i "auto" logs/combined.log
|
|
||||||
```
|
|
||||||
- Should see messages about finding default router
|
|
||||||
- Verify at least one router has `is_active = 1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Notes
|
|
||||||
|
|
||||||
### MikroTik Connection Caching
|
|
||||||
- Connection probe cache: 5 seconds
|
|
||||||
- PPPoE profiles cache: 15 seconds
|
|
||||||
- PPPoE secrets cache: 5 seconds
|
|
||||||
- Active sessions cache: 2 seconds (real-time)
|
|
||||||
|
|
||||||
### Optimization Implemented
|
|
||||||
- SOAP fault reduction: ~40% fewer unnecessary queries
|
|
||||||
- Removed invalid WiFi refresh attempts on CIOT ONUs
|
|
||||||
- WANConnectionDevice loop reduced from 5 to 3
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. **Run Phase 1-3 Tests** on staging environment with multiple routers
|
|
||||||
2. **Verify WhatsApp bot** works with multi-router customers
|
|
||||||
3. **Test payment** auto-activation with mixed router customers
|
|
||||||
4. **Monitor logs** for any connection errors during testing
|
|
||||||
5. **Deploy** to production after all tests pass
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Code References
|
|
||||||
|
|
||||||
- Smart router logic: `services/customerService.js` lines 12-38
|
|
||||||
- Suspend implementation: `services/customerService.js` lines 360-415
|
|
||||||
- Activate implementation: `services/customerService.js` lines 417-468
|
|
||||||
- MikroTik fallback: `services/mikrotikService.js` lines 169-240
|
|
||||||
- Admin routes: `routes/adminPortal.js` lines 2024-2032, 2034-2042
|
|
||||||
- WhatsApp bot: `services/whatsappBot.mjs` line 1187
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Rollback Plan
|
|
||||||
|
|
||||||
If issues occur in production:
|
|
||||||
1. Set `multi_router_mode: "disabled"` in `settings.json`
|
|
||||||
2. Set `default_router_id` to the ID of your primary router
|
|
||||||
3. Restart application with `pm2 restart all`
|
|
||||||
4. All NULL router_id customers will automatically use the default router
|
|
||||||
5. Existing operations continue working (backward compatible)
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
# 📱 Panduan Lengkap & Dokumen Listing Google Play Store
|
|
||||||
**Aplikasi:** Alijaya Customer & Billing Management System
|
|
||||||
**Package Name:** `com.alijaya.customer`
|
|
||||||
**File Bundle:** `AlijayaCustomer.aab` (5.56 MB)
|
|
||||||
**Keystore:** `alijaya-release.jks`
|
|
||||||
|
|
||||||
Dokumen ini berisi seluruh teks deskripsi, kebijakan privasi, keamanan data (data safety), kredensial akun pengujian untuk tim Google, dan panduan langkah demi langkah yang siap di-*copy-paste* langsung ke **Google Play Console**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 📋 Data Utama Listing Toko (Store Listing)
|
|
||||||
|
|
||||||
### A. Judul Aplikasi (App Title)
|
|
||||||
> **Batas:** Maksimal 30 Karakter
|
|
||||||
```text
|
|
||||||
Alijaya Digital
|
|
||||||
```
|
|
||||||
*(Alternatif jika ingin nama lain: `Alijaya Billing`, `Alijaya Net`, atau `Alijaya Customer`)*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### B. Deskripsi Singkat (Short Description)
|
|
||||||
> **Batas:** Maksimal 80 Karakter
|
|
||||||
```text
|
|
||||||
Aplikasi layanan internet pelanggan, tagihan, beli pulsa, dan absensi petugas.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### C. Deskripsi Lengkap (Full Description)
|
|
||||||
> **Batas:** Maksimal 4.000 Karakter
|
|
||||||
```text
|
|
||||||
Alijaya Digital adalah aplikasi resmi layanan internet dan manajemen billing terpadu untuk pelanggan, agen, teknisi, kolektor, dan kasir jaringan Alijaya Digital Network.
|
|
||||||
|
|
||||||
Aplikasi ini memberikan kemudahan akses informasi layanan internet, pembayaran tagihan, pembelian paket data/pulsa, hingga operasional lapangan secara cepat, transparan, dan terpercaya.
|
|
||||||
|
|
||||||
Fitur Unggulan untuk Pelanggan:
|
|
||||||
• Cek Tagihan & Status Langganan: Informasi tagihan bulanan real-time, jatuh tempo, dan rincian pemakaian paket internet.
|
|
||||||
• Pembayaran Otomatis & QRIS: Pembayaran tagihan mudah dan instan menggunakan QRIS dinamis serta berbagai metode pembayaran digital lainnya.
|
|
||||||
• Dompet & Top-Up Saldo: Isi saldo dompet akun untuk kemudahan transaksi berkala.
|
|
||||||
• Pembelian Pulsa & PPOB: Pembelian pulsa seluler, paket internet operator, dan token listrik langsung dari aplikasi.
|
|
||||||
• Cek Kecepatan & Tiket Gangguan: Uji kecepatan koneksi internet dan ajukan tiket keluhan gangguan langsung ke tim teknis dengan respon cepat.
|
|
||||||
• Manajemen Router Wi-Fi: Pantau dan atur jaringan Wi-Fi rumah secara praktis.
|
|
||||||
|
|
||||||
Fitur Khusus Petugas & Mitra (Teknisi, Kolektor, Kasir, & Agen):
|
|
||||||
• Absensi Kerja Berbasis Geo-Tagging: Sistem presensi kehadiran harian dengan validasi GPS akurat dan foto selfie ber-watermark otomatis (lokasi, tanggal, dan waktu).
|
|
||||||
• Manajemen Tugas SPK Lapangan: Penanganan tiket gangguan pelanggan dan instalasi pasang baru secara terorganisir.
|
|
||||||
• Manajemen Tagihan & Rute Kolektor: Navigasi rute penagihan berbasis peta Google Maps dan pencetakan struk pembayaran thermal via Bluetooth.
|
|
||||||
• Loket Kasir & Mitra Agen: Pembelian voucher hotspot dan transaksi PPOB secara aman.
|
|
||||||
|
|
||||||
Unduh aplikasi Alijaya Digital sekarang dan nikmati kemudahan mengelola seluruh kebutuhan layanan internet Anda dalam satu genggaman!
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 🏷️ Kategori & Kontak Pengembang (Store Settings)
|
|
||||||
|
|
||||||
- **Jenis Aplikasi:** Aplikasi (Application)
|
|
||||||
- **Kategori:** Alat (Tools) atau Produktivitas (Productivity) / Bisnis (Business)
|
|
||||||
- **Email Dukungan (Support Email):** `alijayanet@gmail.com` *(atau email resmi Anda)*
|
|
||||||
- **Nomor Telepon Dukungan:** `+6287820851413`
|
|
||||||
- **Situs Web (Website):** `http://alijaya.net` *(atau domain server Anda)*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 🛡️ Kebijakan Privasi (Privacy Policy)
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Google Play mewajibkan URL Kebijakan Privasi aktif karena aplikasi menggunakan izin Kamera dan Lokasi. Anda dapat menaruh URL berikut:
|
|
||||||
> `http://<domain-server-anda>/privacy-policy` atau gunakan draf teks resmi di bawah ini:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# KEBIJAKAN PRIVASI (PRIVACY POLICY) - ALIJAYA DIGITAL
|
|
||||||
|
|
||||||
Terakhir diperbarui: 30 Agustus 2026
|
|
||||||
|
|
||||||
Alijaya Digital Network ("kami") berkomitmen untuk melindungi dan menghormati privasi pengguna aplikasi mobile Alijaya Digital ("Aplikasi"). Kebijakan Privasi ini menjelaskan bagaimana kami mengumpulkan, menggunakan, dan melindungi data pribadi Anda.
|
|
||||||
|
|
||||||
1. INFORMASI YANG KAMI KUMPULKAN
|
|
||||||
Kami hanya mengumpulkan informasi yang relevan dan dibutuhkan untuk menjalankan fitur aplikasi:
|
|
||||||
a. Informasi Akun: Nama, nomor telepon/WhatsApp, alamat rumah/pemasangan internet, ID pelanggan, dan username PPPoE/Hotspot.
|
|
||||||
b. Informasi Lokasi (GPS): Data koordinat lokasi (Latitude & Longitude) digunakan saat:
|
|
||||||
- Pelanggan mengajukan instalasi pasang baru atau pemetaan lokasi ODP/tiang terdekat.
|
|
||||||
- Petugas (Teknisi, Kolektor, Kasir) melakukan absensi kehadiran kerja berbasis validasi radius kantor (Geofencing).
|
|
||||||
- Petugas kolektor membuka rute penagihan pelanggan melalui Google Maps.
|
|
||||||
c. Kamera & Foto: Izin kamera digunakan untuk:
|
|
||||||
- Pengambilan foto selfie wajah petugas saat absensi kehadiran dengan watermark Geo-Tagging posisi.
|
|
||||||
- Pengunggahan foto bukti kendala teknis saat pelaporan tiket gangguan.
|
|
||||||
- Pemindaian QR Code (Barcode voucher / QRIS).
|
|
||||||
d. Akses Bluetooth: Digunakan oleh petugas kolektor dan kasir untuk menghubungkan printer thermal Bluetooth dalam pencetakan struk pembayaran.
|
|
||||||
|
|
||||||
2. PENGGUNAAN INFORMASI
|
|
||||||
Informasi yang dikumpulkan digunakan semata-mata untuk:
|
|
||||||
- Memverifikasi identitas pengguna dan mengelola akun langganan internet.
|
|
||||||
- Memproses pembayaran tagihan dan riwayat transaksi.
|
|
||||||
- Memvalidasi presensi kerja dan koordinat lokasi tugas lapangan karyawan resmi.
|
|
||||||
- Menghubungkan komunikasi layanan pelanggan melalui WhatsApp.
|
|
||||||
|
|
||||||
3. KEAMANAN DATA
|
|
||||||
Kami menerapkan standar keamanan teknis dan enkripsi data (HTTPS/SSL) dalam transmisi data antara aplikasi dan server. Kami tidak pernah menjual, menyewakan, atau membagikan data pribadi Anda kepada pihak ketiga mana pun untuk kepentingan pemasaran.
|
|
||||||
|
|
||||||
4. HAK PENGGUNA & PENGHAPUSAN DATA
|
|
||||||
Pengguna memiliki hak untuk meminta rincian data yang tersimpan atau meminta penghapusan akun beserta data terkait dengan menghubungi kami melalui email dukungan: alijayanet@gmail.com atau WhatsApp resmi customer support kami.
|
|
||||||
|
|
||||||
5. HUBUNGI KAMI
|
|
||||||
Jika Anda memiliki pertanyaan seputar Kebijakan Privasi ini, silakan hubungi:
|
|
||||||
Alijaya Digital Network
|
|
||||||
Email: alijayanet@gmail.com
|
|
||||||
Telepon/WA: +62 878-2085-1413
|
|
||||||
Alamat: Indramayu, Jawa Barat, Indonesia.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 🔒 Deklarasi Keamanan Data (Data Safety Questionnaire)
|
|
||||||
|
|
||||||
Saat mengisi menu **Keamanan Data (Data Safety)** di Google Play Console, pilih jawaban berikut:
|
|
||||||
|
|
||||||
| Pertanyaan Google Play | Jawaban yang Dipilih |
|
|
||||||
| :--- | :--- |
|
|
||||||
| **Apakah aplikasi mengumpulkan data pengguna?** | **Ya (Yes)** |
|
|
||||||
| **Apakah semua data dienkripsi saat transit?** | **Ya (Yes, HTTPS/TLS)** |
|
|
||||||
| **Apakah ada cara bagi pengguna untuk meminta penghapusan data?** | **Ya (Yes)** |
|
|
||||||
|
|
||||||
### Rincian Kategori Data yang Dikumpulkan:
|
|
||||||
1. **Lokasi (Location)**:
|
|
||||||
- *Lokasi Akurat (Precise Location)* & *Lokasi Perkiraan (Approximate Location)*.
|
|
||||||
- **Tujuan:** Fungsionalitas aplikasi (App functionality) & Pengelolaan akun (Account management) untuk absensi karyawan dan rute pelanggan.
|
|
||||||
- **Apakah dibagikan ke pihak ketiga?** Tidak (No).
|
|
||||||
2. **Foto dan Video (Photos and Videos)**:
|
|
||||||
- *Foto (Photos)*.
|
|
||||||
- **Tujuan:** Fungsionalitas aplikasi (Absensi selfie karyawan dan lampiran tiket komplain).
|
|
||||||
- **Apakah dibagikan ke pihak ketiga?** Tidak (No).
|
|
||||||
3. **Info Pribadi (Personal Info)**:
|
|
||||||
- *Nama (Name)*, *Nomor Telepon (Phone number)*, *Alamat (Address)*.
|
|
||||||
- **Tujuan:** Fungsionalitas aplikasi & Komunikasi pelanggan.
|
|
||||||
- **Apakah dibagikan ke pihak ketiga?** Tidak (No).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 🎯 Target Audiens & Rating Konten
|
|
||||||
|
|
||||||
1. **Target Usia (Target Age):**
|
|
||||||
- Pilih: **18 tahun ke atas (18+)** *(karena mencakup pembayaran tagihan langganan internet rumah)*.
|
|
||||||
2. **Aplikasi Berita (News App):** Pilih **Tidak (No)**.
|
|
||||||
3. **Iklan (Ads):** Pilih **Tidak, aplikasi saya tidak mengandung iklan (No, my app does not contain ads)**.
|
|
||||||
4. **Pelacak Kontak COVID-19:** Pilih **Tidak (No)**.
|
|
||||||
5. **Aplikasi Keuangan:** Pilih **Tidak (Bukan institusi perbankan/pinjol)**, hanya sistem pembayaran tagihan ISP sendiri.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 👤 Akun Akses Pengujian untuk Reviewer Google (App Access)
|
|
||||||
|
|
||||||
Google mewajibkan penyediaan akun demo agar penguji dari pihak Google dapat masuk dan menguji fitur di dalam aplikasi tanpa terhalang login:
|
|
||||||
|
|
||||||
- **Opsi yang Dipilih:** *Sebagian atau semua fungsi dibatasi (All or some functionality is restricted)*
|
|
||||||
- **Nama Kredensial:** `Akun Penguji Google Play`
|
|
||||||
- **Username / ID Pelanggan:** `demo` *(atau buat akun khusus pengujian di admin)*
|
|
||||||
- **Password:** `demo123`
|
|
||||||
- **Instruksi Pengujian:**
|
|
||||||
```text
|
|
||||||
Aplikasi ini menggunakan koneksi ke server Alijaya Billing System.
|
|
||||||
Gunakan username: demo dan password: demo123 untuk menguji portal pelanggan.
|
|
||||||
Koneksi internet aktif diperlukan.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 🔑 Kredensial Keystore & Spesifikasi Teknis
|
|
||||||
|
|
||||||
Simpan data teknis ini di tempat yang aman:
|
|
||||||
|
|
||||||
| Parameter | Nilai |
|
|
||||||
| :--- | :--- |
|
|
||||||
| **Package Name** | `com.alijaya.customer` |
|
|
||||||
| **Target SDK** | `Android 15 (SDK 35)` |
|
|
||||||
| **Min SDK** | `Android 7.0 (SDK 24)` |
|
|
||||||
| **Version Code** | `2` |
|
|
||||||
| **Version Name** | `1.2.0` |
|
|
||||||
| **File Keystore** | `alijaya-release.jks` |
|
|
||||||
| **Keystore Alias** | `alijaya` |
|
|
||||||
| **Keystore Password** | `AlijayaRelease2026!` |
|
|
||||||
| **Key Password** | `AlijayaRelease2026!` |
|
|
||||||
| **Validity** | 10.000 Hari (~27 Tahun) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 🎨 Kebutuhan Aset Grafis (Graphic Assets)
|
|
||||||
|
|
||||||
Sebelum menekan tombol rilis, siapkan file gambar berikut:
|
|
||||||
|
|
||||||
1. **Ikon Aplikasi (App Icon):**
|
|
||||||
- Ukuran: `512 x 512 piksel`
|
|
||||||
- Format: PNG 32-bit dengan transparansi
|
|
||||||
- Ukuran file maksimal: 1 MB
|
|
||||||
2. **Grafis Fitur (Feature Graphic):**
|
|
||||||
- Ukuran: `1024 x 500 piksel`
|
|
||||||
- Format: JPEG atau PNG 24-bit (tanpa transparansi)
|
|
||||||
- Ukuran file maksimal: 15 MB
|
|
||||||
3. **Tangkapan Layar Ponsel (Phone Screenshots):**
|
|
||||||
- Minimal 2 screenshot, maksimal 8 screenshot.
|
|
||||||
- Rasio 16:9 atau 9:16 (contoh resolusi ideal: `1080 x 1920 piksel` atau `1080 x 2400 piksel`).
|
|
||||||
- Disarankan mengambil screenshot dari:
|
|
||||||
1. Layar Beranda Pelanggan (Cek Tagihan & Beli Pulsa)
|
|
||||||
2. Layar Pembayaran QRIS / Invoice
|
|
||||||
3. Layar Speedtest & Tiket Gangguan
|
|
||||||
4. Layar Dashboard Tugas Teknisi Lapangan
|
|
||||||
5. Layar Absensi Selfie dengan Watermark Geo-Tagging
|
|
||||||
@@ -1,346 +0,0 @@
|
|||||||
================================================================================
|
|
||||||
AUDIT LENGKAP APLIKASI BILLING B481
|
|
||||||
STATUS: ✅ SELESAI
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
TANGGAL AUDIT: Juni 2026
|
|
||||||
APLIKASI STATUS: 🟢 PRODUCTION READY
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
DOKUMENTASI YANG TELAH DIBUAT (6 FILE - 104 KB TOTAL)
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
1. AUDIT_LENGKAP.md (45.57 KB)
|
|
||||||
Audit komprehensif dengan 18 section mencakup:
|
|
||||||
- Informasi umum aplikasi
|
|
||||||
- Arsitektur sistem lengkap
|
|
||||||
- Database schema overview
|
|
||||||
- Semua endpoints & routes (100+ API)
|
|
||||||
- 15+ fitur utama yang didokumentasikan
|
|
||||||
- Keamanan & otentikasi
|
|
||||||
- Production checklist
|
|
||||||
|
|
||||||
📖 BACA INI UNTUK: Referensi lengkap semua aspek aplikasi
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
2. QUICK_REFERENCE.md (6.67 KB)
|
|
||||||
Panduan cepat untuk task-task umum:
|
|
||||||
- Quick start commands
|
|
||||||
- Common configuration tasks
|
|
||||||
- Security settings (MUST DO)
|
|
||||||
- Important endpoints
|
|
||||||
- Database commands
|
|
||||||
- Troubleshooting (7 issues)
|
|
||||||
- Pre-launch checklist
|
|
||||||
|
|
||||||
📖 BACA INI UNTUK: Daily operations & quick lookups
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
3. DATABASE_SCHEMA.md (15.75 KB)
|
|
||||||
Dokumentasi lengkap database:
|
|
||||||
- Schema overview
|
|
||||||
- 30+ tabel dengan struktur lengkap
|
|
||||||
- Foreign key relationships
|
|
||||||
- Query examples
|
|
||||||
- Optimization tips
|
|
||||||
- Backup & recovery procedures
|
|
||||||
|
|
||||||
📖 BACA INI UNTUK: Database operations & queries
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
4. BACKUP_CHECKLIST.md (10.58 KB)
|
|
||||||
Prosedur backup & disaster recovery:
|
|
||||||
- Pre-audit backup procedures
|
|
||||||
- Step-by-step backup guide
|
|
||||||
- Restore procedures (5 scenarios)
|
|
||||||
- Automated backup setup
|
|
||||||
- Disaster recovery plan
|
|
||||||
- Best practices
|
|
||||||
|
|
||||||
📖 BACA INI UNTUK: Backup planning & restoration
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
5. AUDIT_SUMMARY.md (12.31 KB)
|
|
||||||
Executive summary & roadmap:
|
|
||||||
- Key findings & recommendations
|
|
||||||
- Security checklist (CRITICAL)
|
|
||||||
- Operational runbook
|
|
||||||
- Maintenance procedures
|
|
||||||
- Success criteria
|
|
||||||
- Next steps
|
|
||||||
|
|
||||||
📖 BACA INI UNTUK: Overview & planning
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
6. DOKUMENTASI_INDEX.md (13.69 KB)
|
|
||||||
Navigation guide untuk semua dokumentasi:
|
|
||||||
- Quick links by topic
|
|
||||||
- How to search documentation
|
|
||||||
- Cross-references
|
|
||||||
- FAQ section
|
|
||||||
- Documentation roadmap
|
|
||||||
|
|
||||||
📖 BACA INI UNTUK: Mencari informasi spesifik
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
MULAI DARI MANA?
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
UNTUK PEMULA (5-15 MENIT):
|
|
||||||
1. Baca file ini (README_AUDIT.txt)
|
|
||||||
2. Baca AUDIT_SUMMARY.md (5 menit) - ringkasan lengkap
|
|
||||||
3. Baca QUICK_REFERENCE.md (10 menit) - perintah dasar
|
|
||||||
|
|
||||||
UNTUK ADMIN/OPERATOR (30-60 MENIT):
|
|
||||||
1. QUICK_REFERENCE.md - common tasks
|
|
||||||
2. AUDIT_LENGKAP.md section 4 - endpoints & routes
|
|
||||||
3. BACKUP_CHECKLIST.md - backup procedures
|
|
||||||
4. QUICK_REFERENCE.md troubleshooting - tips
|
|
||||||
|
|
||||||
UNTUK DEVELOPER/MAINTENANCE (1-2 JAM):
|
|
||||||
1. AUDIT_LENGKAP.md - full architecture
|
|
||||||
2. DATABASE_SCHEMA.md - database structure
|
|
||||||
3. BACKUP_CHECKLIST.md - operational procedures
|
|
||||||
4. DOKUMENTASI_INDEX.md - untuk searching
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
FITUR UTAMA YANG DIDOKUMENTASIKAN
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
✅ Portal Pelanggan (Self-service)
|
|
||||||
✅ Portal Admin (Manajemen lengkap)
|
|
||||||
✅ Portal Teknisi (Field support)
|
|
||||||
✅ Portal Agen (Sales & pembayaran)
|
|
||||||
✅ Portal Kolektor (Approval pembayaran)
|
|
||||||
✅ Portal Finance (Laporan keuangan)
|
|
||||||
|
|
||||||
✅ Billing & Invoicing (Tagihan otomatis)
|
|
||||||
✅ MikroTik Integration (PPPoE, Hotspot, Voucher)
|
|
||||||
✅ GenieACS & TR-069 (CPE/ONU management)
|
|
||||||
✅ OLT PON Management (SNMP-based)
|
|
||||||
✅ GIS Mapping (Peta pelanggan & ODP)
|
|
||||||
✅ WhatsApp Automation (Baileys)
|
|
||||||
|
|
||||||
✅ Payment Gateway (Midtrans, Tripay, Xendit, Duitku)
|
|
||||||
✅ Inventory Management (Gudang)
|
|
||||||
✅ Ticketing System (Support)
|
|
||||||
✅ Attendance Geofencing (Kehadiran GPS)
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
STATISTIK DOKUMENTASI
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Total files: 6
|
|
||||||
Total size: 104.56 KB
|
|
||||||
Sections documented: 18+
|
|
||||||
Database tables: 30+
|
|
||||||
API endpoints: 100+
|
|
||||||
Features covered: 15+
|
|
||||||
Backup procedures: 5 scenarios
|
|
||||||
Troubleshooting issues: 15+
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
CHECKLIST - APA YANG HARUS DILAKUKAN SEGERA
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
CRITICAL (BEFORE PRODUCTION):
|
|
||||||
□ Baca AUDIT_SUMMARY.md security checklist
|
|
||||||
□ Change session_secret di settings.json
|
|
||||||
□ Change admin password
|
|
||||||
□ Ganti admin_api_key
|
|
||||||
□ Enable HTTPS (via reverse proxy)
|
|
||||||
□ Test backup & restore procedure
|
|
||||||
|
|
||||||
IMPORTANT (THIS WEEK):
|
|
||||||
□ Baca semua dokumentasi yang relevan
|
|
||||||
□ Create full backup (database + files + code)
|
|
||||||
□ Store backup di lokasi aman
|
|
||||||
□ Setup automated daily backup
|
|
||||||
□ Test restore backup
|
|
||||||
|
|
||||||
MEDIUM (THIS MONTH):
|
|
||||||
□ Setup monitoring & alerting
|
|
||||||
□ Setup log rotation
|
|
||||||
□ Document customizations
|
|
||||||
□ Create operational runbook
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
FILE LOCATIONS
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Semua file dokumentasi tersimpan di root aplikasi:
|
|
||||||
|
|
||||||
d:\billing-B481\
|
|
||||||
├── AUDIT_LENGKAP.md (45.57 KB) ← Main reference
|
|
||||||
├── QUICK_REFERENCE.md (6.67 KB) ← Daily use
|
|
||||||
├── DATABASE_SCHEMA.md (15.75 KB) ← DB operations
|
|
||||||
├── BACKUP_CHECKLIST.md (10.58 KB) ← Backup procedures
|
|
||||||
├── AUDIT_SUMMARY.md (12.31 KB) ← Overview & planning
|
|
||||||
├── DOKUMENTASI_INDEX.md (13.69 KB) ← Navigation
|
|
||||||
├── README_AUDIT.txt (This file)
|
|
||||||
└── [original files...]
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
PENTING: BACKUP DOKUMENTASI INI JUGA!
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Jangan lupa backup semua file dokumentasi ke lokasi aman:
|
|
||||||
|
|
||||||
OPTION 1 - Manual Backup:
|
|
||||||
cp AUDIT_*.md QUICK_REFERENCE.md DATABASE_SCHEMA.md BACKUP_CHECKLIST.md /backup/
|
|
||||||
|
|
||||||
OPTION 2 - Tar Backup:
|
|
||||||
tar -czf audit-docs-backup-$(date +%Y-%m-%d).tar.gz \
|
|
||||||
AUDIT_*.md QUICK_REFERENCE.md DATABASE_SCHEMA.md BACKUP_CHECKLIST.md
|
|
||||||
|
|
||||||
OPTION 3 - Via Script:
|
|
||||||
Add to your backup script & cron job
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
STRUKTUR DOKUMENTASI
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
┌─ QUICK START
|
|
||||||
│ ├─ AUDIT_SUMMARY.md (5 min overview)
|
|
||||||
│ └─ QUICK_REFERENCE.md (10 min guide)
|
|
||||||
│
|
|
||||||
├─ DETAILED REFERENCE
|
|
||||||
│ ├─ AUDIT_LENGKAP.md (Complete audit - 18 sections)
|
|
||||||
│ ├─ DATABASE_SCHEMA.md (Database reference - 30+ tables)
|
|
||||||
│ └─ DOKUMENTASI_INDEX.md (Navigation guide)
|
|
||||||
│
|
|
||||||
├─ OPERATIONAL
|
|
||||||
│ └─ BACKUP_CHECKLIST.md (Backup & recovery)
|
|
||||||
│
|
|
||||||
└─ NAVIGATION
|
|
||||||
└─ README_AUDIT.txt (This guide)
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
QUICK LINKS - CARI JAWABAN CEPAT
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Q: Apa saja portal aplikasi?
|
|
||||||
A: AUDIT_LENGKAP.md section 1.3
|
|
||||||
|
|
||||||
Q: Bagaimana setup MikroTik?
|
|
||||||
A: QUICK_REFERENCE.md atau AUDIT_LENGKAP.md section 5.2
|
|
||||||
|
|
||||||
Q: Bagaimana backup database?
|
|
||||||
A: BACKUP_CHECKLIST.md section "Backup Database"
|
|
||||||
|
|
||||||
Q: Apa saja table database?
|
|
||||||
A: DATABASE_SCHEMA.md
|
|
||||||
|
|
||||||
Q: Bagaimana troubleshooting error X?
|
|
||||||
A: QUICK_REFERENCE.md "Troubleshooting" section
|
|
||||||
|
|
||||||
Q: Aplikasi ready untuk production?
|
|
||||||
A: AUDIT_SUMMARY.md "Audit Completion Status"
|
|
||||||
|
|
||||||
Q: Gimana setup automated backup?
|
|
||||||
A: BACKUP_CHECKLIST.md "Automated Backup (Cron Job)"
|
|
||||||
|
|
||||||
Q: Apa saja security settings yang harus diubah?
|
|
||||||
A: QUICK_REFERENCE.md "Security Settings (MUST DO)"
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
VERIFIKASI SEMUA FILE SUDAH ADA
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
✅ AUDIT_LENGKAP.md (45.57 KB)
|
|
||||||
✅ QUICK_REFERENCE.md (6.67 KB)
|
|
||||||
✅ DATABASE_SCHEMA.md (15.75 KB)
|
|
||||||
✅ BACKUP_CHECKLIST.md (10.58 KB)
|
|
||||||
✅ AUDIT_SUMMARY.md (12.31 KB)
|
|
||||||
✅ DOKUMENTASI_INDEX.md (13.69 KB)
|
|
||||||
✅ README_AUDIT.txt (This file)
|
|
||||||
|
|
||||||
TOTAL: 7 files, 104.56 KB dokumentasi lengkap
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
NEXT STEPS (URUTAN PENTING)
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
1. READ (This week)
|
|
||||||
□ AUDIT_SUMMARY.md (5 min) - Wajib dibaca semua orang!
|
|
||||||
□ QUICK_REFERENCE.md (10 min) - Reference untuk daily use
|
|
||||||
□ AUDIT_LENGKAP.md (30 min) - Jika butuh detail
|
|
||||||
□ DATABASE_SCHEMA.md (20 min) - Jika ada query khusus
|
|
||||||
□ BACKUP_CHECKLIST.md (10 min) - Sebelum backup
|
|
||||||
|
|
||||||
2. BACKUP (Immediately)
|
|
||||||
□ Create full backup (database + files + code)
|
|
||||||
□ Store backup in safe location (USB/NAS/Cloud)
|
|
||||||
□ Test restore procedure
|
|
||||||
□ Document backup location
|
|
||||||
|
|
||||||
3. SECURE (This week)
|
|
||||||
□ Follow security checklist di AUDIT_SUMMARY.md
|
|
||||||
□ Change passwords & secrets
|
|
||||||
□ Setup HTTPS
|
|
||||||
□ Setup firewall rules
|
|
||||||
|
|
||||||
4. MAINTAIN (Ongoing)
|
|
||||||
□ Setup automated daily backup
|
|
||||||
□ Setup monitoring & alerting
|
|
||||||
□ Review logs regularly
|
|
||||||
□ Test restore monthly
|
|
||||||
□ Update documentation when changes occur
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
SUPPORT & RESOURCES
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Original README:
|
|
||||||
README.md (Original aplikasi documentation)
|
|
||||||
|
|
||||||
GitHub:
|
|
||||||
https://github.com/alijayanet/billing-rtrw
|
|
||||||
|
|
||||||
Contact:
|
|
||||||
Ali Jaya Net
|
|
||||||
Phone: 081947215703
|
|
||||||
Email: alijayanet@gmail.com
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
DOKUMEN STATUS
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Audit Date: Juni 2026
|
|
||||||
Audit Version: 1.0 (Final)
|
|
||||||
Status: ✅ COMPLETE & APPROVED FOR PRODUCTION
|
|
||||||
Documentation: 6 files, 104 KB total
|
|
||||||
Last Updated: 2026-06-19
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
CATATAN PENTING
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
1. Dokumentasi ini adalah hasil audit teliti & lengkap
|
|
||||||
2. Semua file tersimpan di root aplikasi billing-B481
|
|
||||||
3. Documentasi sudah production-ready & siap digunakan
|
|
||||||
4. Update dokumentasi jika ada perubahan pada aplikasi
|
|
||||||
5. Backup dokumentasi ini juga (jangan lupa!)
|
|
||||||
6. Share dokumentasi dengan tim operasional
|
|
||||||
7. Review dokumentasi secara berkala
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
SELAMAT MENGGUNAKAN! 🎉
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Jika ada pertanyaan atau butuh informasi lebih lanjut:
|
|
||||||
1. Cek dokumentasi terlebih dahulu (gunakan DOKUMENTASI_INDEX.md untuk search)
|
|
||||||
2. Baca FAQ section di file-file yang relevan
|
|
||||||
3. Hubungi tim technical support
|
|
||||||
|
|
||||||
Terima kasih telah menggunakan dokumentasi audit lengkap ini!
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
Generated: 2026-06-19
|
|
||||||
Status: ✅ AUDIT COMPLETE & DOCUMENTED
|
|
||||||
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
# Rencana Desain & Modernisasi UI/UX — Billing RT/RW Net
|
|
||||||
*(Mengadopsi Referensi uiverse.io, ColorlibHQ/gentelella, dan Google Fonts)*
|
|
||||||
|
|
||||||
> **Status Server Biznet**: ✅ **Berhasil Dimatikan & Dinonaktifkan** (`systemctl stop/disable`, `pm2 stop/delete`). Tidak ada lagi tabrakan bot Telegram ataupun service jaringan.
|
|
||||||
> **Server Produksi Aktif**: VPS `deneva` (`116.212.74.115` — port `3001`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Konsep Desain & Pendekatan Dua Arah (Dual-Persona)
|
|
||||||
|
|
||||||
Aplikasi ini melayani dua profil pengguna yang berbeda secara mendasar:
|
|
||||||
1. **Pelanggan (Customer Portal & Landing/Login)**: Menggunakan smartphone, membutuhkan antarmuka yang modern, bersih, intuitif, cepat, dan kaya animasi mikro.
|
|
||||||
* **Referensi Utama**: **`uiverse.io`** (Glassmorphism, animasi tombol neon/glow, switch toggle interaktif, floating dock, card 3D halus).
|
|
||||||
2. **Admin & Teknisi (Admin & NOC Portal)**: Membutuhkan visualisasi data densitas tinggi (*high-density information*), pemantauan lalu lintas real-time, status ONU dalam jumlah ratusan, serta tabel data yang efisien.
|
|
||||||
* **Referensi Utama**: **`ColorlibHQ/gentelella`** (Gentelella Admin — Tile KPI counters, multi-tab `x_panel` containers, collapsible sidebar, traffic charts, dense status badges).
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
subgraph UI_Reference_System["Sistem Referensi Desain"]
|
|
||||||
UIVERSE["uiverse.io (Modern CSS Components)"]
|
|
||||||
GENTELELLA["ColorlibHQ/gentelella (Admin Dashboard)"]
|
|
||||||
GFONTS["Google Fonts (Universal Typography)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Customer_Side["Portal Pelanggan (PWA & Login)"]
|
|
||||||
CustLogin["views/login.ejs (Hero & Glass Card)"]
|
|
||||||
CustDash["views/dashboard.ejs (5-Tab App Shell)"]
|
|
||||||
CustIsolated["views/isolated.ejs (Walled Garden)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Admin_Side["Portal Admin, NOC & Teknisi"]
|
|
||||||
AdminDash["views/admin/dashboard.ejs (Tile Stats & Charts)"]
|
|
||||||
AdminTables["views/admin/* (Customers, OLT, Billing)"]
|
|
||||||
AdminSidebar["views/admin/partials/sidebar.ejs"]
|
|
||||||
AdminMap["views/admin/map.ejs (Network Topology)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
UIVERSE -->|Glass Cards, Neon Toggles, Pill Buttons| Customer_Side
|
|
||||||
GENTELELLA -->|Tile Counters, X-Panels, Dense Tables| Admin_Side
|
|
||||||
GFONTS -->|Plus Jakarta Sans, Inter, JetBrains Mono| Customer_Side
|
|
||||||
GFONTS --> Admin_Side
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Tipografi Universal (Google Fonts)
|
|
||||||
|
|
||||||
Untuk memastikan kompatibilitas rendering 100% di semua browser (Chrome, Safari iOS, Firefox, Edge, Android WebView) tanpa ketergantungan pada font sistem lokal:
|
|
||||||
|
|
||||||
| Peruntukan | Font Family | Bobot (Weights) | Karakteristik & Kegunaan |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **Customer Portal & Landing** | **Plus Jakarta Sans** | `300, 400, 500, 600, 700, 800` | Modern geometric sans, sangat nyaman dibaca di layar AMOLED HP, angka tagihan tegas dan modern. |
|
|
||||||
| **Admin & Operasional** | **Inter** | `300, 400, 500, 600, 700, 800` | Standar industri dashboard, metrik angka rapat (*tabular numbers*), hierarki teks rapi. |
|
|
||||||
| **Data Teknis & Jaringan** | **JetBrains Mono** | `400, 500, 700` | Alamat IP, MAC Address, OID SNMP, Serial Number ONU, nilai redaman optik (dBm). |
|
|
||||||
|
|
||||||
### Standar Preconnect & Loading:
|
|
||||||
```html
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Peta Komponen & Transformasi Desain
|
|
||||||
|
|
||||||
### 3.1 Portal Admin (Adopsi `ColorlibHQ/gentelella`)
|
|
||||||
|
|
||||||
#### A. Gentelella Top Metric Stats ("Tile Count")
|
|
||||||
Pada dashboard admin (`views/admin/dashboard.ejs`), susunan metrik diganti dengan pola **Gentelella Tile Count**:
|
|
||||||
* **Format**: Baris kartu metrik ringkas horizontal dengan ikon latar samar, angka besar tebal, label deskriptif, dan indikator persentase tren (hijau naik / merah turun).
|
|
||||||
* **Komponen yang Diterapkan**:
|
|
||||||
1. `Total Pelanggan`: Angka aktif vs total, delta pertumbuhan bulanan.
|
|
||||||
2. `PPPoE Online`: Real-time active sessions dengan status dot hijau berkedip.
|
|
||||||
3. `Total ONU & Signal`: Jumlah online/offline + ONU dengan redaman kritis (< -27 dBm).
|
|
||||||
4. `Tagihan & Kas Masuk`: Pendapatan bulan berjalan vs tagihan tertunda (*unpaid*).
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
|
|
||||||
│ TOTAL PELANGGAN │ PPPoE ONLINE │ STATUS FTTH │ PENDAPATAN BULAN │
|
|
||||||
│ 1,248 │ 1,180 │ 102 / 105 │ Rp 42.500.000 │
|
|
||||||
│ ▲ +12 bln ini │ ● 94.5% online │ ⚠️ 3 Redaman Low│ ▲ 88% Tertagih │
|
|
||||||
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
#### B. Kontainer Widget Bergaya `x_panel`
|
|
||||||
* Seluruh card data admin menggunakan struktur `x_panel`:
|
|
||||||
* `x_title`: Judul section dengan border bawah tipis, sub-teks muted, dan *toolbox* kanan (tombol refresh data via AJAX, filter tanggal, expand/collapse).
|
|
||||||
* `x_content`: Area konten responsif dengan padding lega, kompatibel dengan mode gelap dan terang.
|
|
||||||
|
|
||||||
#### C. Tabel Data Responsif (*Dense Table*)
|
|
||||||
* Border halus (`border-slate-800`), font `Inter` 13px, baris zebra-stripe halus saat hover.
|
|
||||||
* Badge status terstandarisasi:
|
|
||||||
* Online: Badge hijau lembut (`bg-emerald-500/10 text-emerald-400 border border-emerald-500/20`).
|
|
||||||
* Offline / Suspend: Badge merah lembut (`bg-rose-500/10 text-rose-400 border border-rose-500/20`).
|
|
||||||
* Isolir: Badge amber (`bg-amber-500/10 text-amber-400 border border-amber-500/20`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.2 Portal Pelanggan & Login (Adopsi `uiverse.io`)
|
|
||||||
|
|
||||||
#### A. Card Glassmorphism dengan Border Glow
|
|
||||||
* Card profil router, tagihan, dan paket internet menggunakan teknik glassmorphism tingkat tinggi dari `uiverse.io`:
|
|
||||||
* Background: `rgba(30, 41, 59, 0.75)` dengan `backdrop-filter: blur(16px)`.
|
|
||||||
* Border: Gradasi tipis `linear-gradient(135deg, rgba(45, 212, 191, 0.3), rgba(99, 102, 241, 0.1), transparent)`.
|
|
||||||
* Shadow: `0 10px 30px -10px rgba(0, 0, 0, 0.5)`.
|
|
||||||
|
|
||||||
#### B. Switch Toggle & Input Kustom (Form Ganti WiFi & Settings)
|
|
||||||
* Mengganti checkbox standar dengan **animated toggle switch** gaya `uiverse.io`:
|
|
||||||
* Toggle 2.4 GHz vs 5 GHz dengan slider pill bercahaya.
|
|
||||||
* Input password dengan animated floating border dan icon eye reveal responsif.
|
|
||||||
* Tombol submit: Efek hover *lift-up* (`translateY(-2px)`) dengan box-shadow glow warna Teal `#14b8a6`.
|
|
||||||
|
|
||||||
#### C. Floating Action Dock (PWA Mobile Navigation)
|
|
||||||
* Penyempurnaan 5-tab dock navigasi bawah:
|
|
||||||
* Efek frosted glass yang lebih pekat (`backdrop-blur-xl`).
|
|
||||||
* Active pill bergradasi halus dengan transisi kurva cubic-bezier (`cubic-bezier(0.34, 1.56, 0.64, 1)`).
|
|
||||||
* Touch feedback haptic visual saat tab ditekan di layar HP.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Design Tokens & Palette Warna
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Brand Core (Skyfiber Identity) */
|
|
||||||
--brand-teal-50: #f0fdfa;
|
|
||||||
--brand-teal-400: #2dd4bf;
|
|
||||||
--brand-teal-500: #14b8a6;
|
|
||||||
--brand-teal-600: #0d9488;
|
|
||||||
--brand-indigo: #6366f1;
|
|
||||||
--brand-cyan: #06b6d4;
|
|
||||||
|
|
||||||
/* Dark Theme Palette (Default) */
|
|
||||||
--bg-dark-base: #0b0f19;
|
|
||||||
--bg-dark-card: #1e293b;
|
|
||||||
--bg-dark-panel: #161b26;
|
|
||||||
--border-dark: rgba(148, 163, 184, 0.15);
|
|
||||||
--text-dark-high: #f8fafc;
|
|
||||||
--text-dark-med: #94a3b8;
|
|
||||||
--text-dark-low: #64748b;
|
|
||||||
|
|
||||||
/* Light Theme Palette */
|
|
||||||
--bg-light-base: #f8fafc;
|
|
||||||
--bg-light-card: #ffffff;
|
|
||||||
--bg-light-panel: #f1f5f9;
|
|
||||||
--border-light: rgba(15, 23, 42, 0.08);
|
|
||||||
--text-light-high:#0f172a;
|
|
||||||
--text-light-med: #475569;
|
|
||||||
--text-light-low: #94a3b8;
|
|
||||||
|
|
||||||
/* Status Colors */
|
|
||||||
--status-online: #10b981;
|
|
||||||
--status-offline: #f43f5e;
|
|
||||||
--status-warning: #f59e0b;
|
|
||||||
--status-info: #38bdf8;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Rencana Tahapan Eksekusi (Implementation Milestones)
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
gantt
|
|
||||||
title Roadmap Eksekusi Desain & Modernisasi
|
|
||||||
dateFormat X
|
|
||||||
axisFormat Fase %s
|
|
||||||
|
|
||||||
section Persiapan
|
|
||||||
Matikan Service Biznet & Verifikasi Port :done, 0, 1
|
|
||||||
Integrasi Google Fonts Universal :done, 1, 2
|
|
||||||
|
|
||||||
section Portal Pelanggan (uiverse.io)
|
|
||||||
Modernisasi Komponen WiFi & Card Glass :active, 2, 4
|
|
||||||
Input Micro-Animations & Button Glow : 4, 5
|
|
||||||
Verifikasi PWA Mobile & Responsive Check : 5, 6
|
|
||||||
|
|
||||||
section Portal Admin (Gentelella)
|
|
||||||
Implementasi Top Tile Stats Count : 6, 8
|
|
||||||
Standardisasi X-Panel & Action Toolbox : 8, 9
|
|
||||||
Penyelarasan Tabel Data & Badges : 9, 10
|
|
||||||
|
|
||||||
section Validasi & Deploy
|
|
||||||
Pengujian Lintas Browser & Dark/Light Mode : 10, 11
|
|
||||||
Restart Service & Verifikasi Uptime : 11, 12
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tahap 1: Penguatan Komponen Customer Portal (Uiverse Style)
|
|
||||||
1. Terapkan card glassmorphism dengan glow gradient pada form ganti SSID & Password di `views/dashboard.ejs`.
|
|
||||||
2. Sempurnakan animated button untuk aksi "Simpan Wi-Fi", "Beli Voucher", dan "Cek Tagihan".
|
|
||||||
3. Pastikan visual state mode terang (*light mode*) memiliki kontras tajam pada teks muted dan input field.
|
|
||||||
|
|
||||||
### Tahap 2: Modernisasi Admin Dashboard (Gentelella Style)
|
|
||||||
1. Perbarui bagian atas `views/admin/dashboard.ejs` dengan modul **Gentelella Tile Stats**:
|
|
||||||
* Total Pelanggan, Status Sesi MikroTik Aktif, Status ONU (Online/Offline/Warning Redaman), dan Tagihan Lunas/Tertunda.
|
|
||||||
2. Tata ulang card monitoring ke dalam format `x_panel` yang memiliki toolbar aksi (Refresh AJAX, filter status, expand).
|
|
||||||
|
|
||||||
### Tahap 3: Uji Coba Lintas Perangkat & Validasi Akhir
|
|
||||||
1. Uji di mobile browser (Android Chrome & iOS Safari).
|
|
||||||
2. Verifikasi seluruh endpoint AJAX, ganti SSID modem, dan refresh ONU tetap berfungsi tanpa gangguan.
|
|
||||||
3. Pastikan log journalctl pada `billing-rtrw.service` di `deneva` tetap bersih dan stabil.
|
|
||||||
@@ -1,729 +0,0 @@
|
|||||||
# RINGKASAN APLIKASI - RTRWNET BILLING & MANAGEMENT SYSTEM
|
|
||||||
**Status:** ✅ FULLY EXPLORED & DOCUMENTED
|
|
||||||
**Tanggal:** 14 Agustus 2026
|
|
||||||
**Format:** Quick Reference untuk understanding aplikasi
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 APLIKASI OVERVIEW
|
|
||||||
|
|
||||||
### What is RTRWNET?
|
|
||||||
**RTRWNET** adalah sistem manajemen terintegrasi untuk **ISP/RTRW** yang mengelola:
|
|
||||||
- ✅ Billing & invoicing otomatis
|
|
||||||
- ✅ Manajemen pelanggan & paket
|
|
||||||
- ✅ Jaringan fisik (MikroTik, OLT, ODP, cable routing)
|
|
||||||
- ✅ Device management (CPE/ONU via TR-069/GenieACS)
|
|
||||||
- ✅ Payment gateway integration (Midtrans, Tripay, Xendit, Duitku)
|
|
||||||
- ✅ Agent/reseller management
|
|
||||||
- ✅ WhatsApp automation
|
|
||||||
- ✅ Support ticketing
|
|
||||||
- ✅ Peta jaringan & GIS
|
|
||||||
- ✅ Monitoring & health check
|
|
||||||
|
|
||||||
**Single Platform:** Satu dashboard untuk admin, teknisi, agen, dan pelanggan.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 PORTAL UTAMA
|
|
||||||
|
|
||||||
| Portal | URL | User | Fungsi Utama |
|
|
||||||
|--------|-----|------|--------------|
|
|
||||||
| **Customer** | `/customer` | Pelanggan | Cek tagihan, bayar, ubah Wi-Fi, lihat status |
|
|
||||||
| **Admin** | `/admin` | Admin | Dashboard, billing, customer, MikroTik, settings |
|
|
||||||
| **Teknisi** | `/tech` | Teknisi | Tiket, peta, device monitoring, support |
|
|
||||||
| **Agen** | `/agent` | Reseller/Agent | Bayar tagihan pelanggan, jual voucher, jual pulsa |
|
|
||||||
| **Kolektor** | `/collector` | Collector | Cek & ajukan pembayaran (pending approval) |
|
|
||||||
| **Finance** | `/finance` | Admin/Finance | Laporan keuangan & pengeluaran |
|
|
||||||
| **Public** | `/` | Publik | Cek tagihan tanpa login, halaman info |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ ARSITEKTUR TEKNIS
|
|
||||||
|
|
||||||
### Backend Services (34 services)
|
|
||||||
```
|
|
||||||
1. billingService.js → Invoice generation, promo, prorata
|
|
||||||
2. customerService.js → CRUD customer, paket, isolir
|
|
||||||
3. mikrotikService.js → PPPoE, hotspot, firewall, monitoring
|
|
||||||
4. oltService.js → OLT management via SNMP
|
|
||||||
5. whatsappService.js → WhatsApp gateway (Baileys)
|
|
||||||
6. paymentService.js → Payment gateway integration
|
|
||||||
7. voucherPaymentService.js → E-voucher & payment methods
|
|
||||||
8. agentService.js → Agent & Digiflazz integration
|
|
||||||
9. cronService.js → Scheduled tasks automation
|
|
||||||
10. monitoringService.js → System health & metrics
|
|
||||||
11. ticketService.js → Support ticketing
|
|
||||||
12. inventoryService.js → Stock & asset management
|
|
||||||
13. auditTrailService.js → Action logging
|
|
||||||
14. backupService.js → Database backup
|
|
||||||
15. onuProvisionService.js → ONU TR-069 provisioning
|
|
||||||
... + 19 services lainnya (acsServerService, radiusServerService, dll)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database (SQLite)
|
|
||||||
```
|
|
||||||
Total: 50+ tables
|
|
||||||
|
|
||||||
Kategori:
|
|
||||||
- Master: packages, customers, routers, olts, odps
|
|
||||||
- Billing: invoices, payments, payment_approvals
|
|
||||||
- Network: pppoe_users, hotspot_users, vouchers
|
|
||||||
- Device: acs_devices, onu_mapping
|
|
||||||
- Internal: technicians, agents, collectors, audit_trail
|
|
||||||
- Other: expenses, cash_in, tickets, inventory_items
|
|
||||||
```
|
|
||||||
|
|
||||||
### Routes/Endpoints (250+ endpoints)
|
|
||||||
```
|
|
||||||
/customer/* → Customer portal (login, billing, device)
|
|
||||||
/admin/* → Admin panel (all management)
|
|
||||||
/tech/* → Technician portal (tickets, map, devices)
|
|
||||||
/agent/* → Agent portal (payment, voucher, pulsa)
|
|
||||||
/collector/* → Collector portal (approval workflow)
|
|
||||||
/finance/* → Finance reporting
|
|
||||||
/acs/* → Built-in TR-069 ACS server
|
|
||||||
/api/webhook/* → Payment webhooks
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend (EJS Templates)
|
|
||||||
```
|
|
||||||
18 main views + partials:
|
|
||||||
- customer/ → 6 views (billing, device, voucher, etc)
|
|
||||||
- admin/ → Subdirectory dengan main routes
|
|
||||||
- tech/ → 3 views (dashboard, tickets, monitoring)
|
|
||||||
- agent/ → 3 views
|
|
||||||
- collector/ → 2 views
|
|
||||||
- Public pages → login, register, check_billing, tos, privacy
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💰 FITUR BILLING
|
|
||||||
|
|
||||||
### Invoice Cycle
|
|
||||||
```
|
|
||||||
1. Otomatis: Cron @ 1 Jan setiap bulan jam 00:01
|
|
||||||
- Generate invoice untuk semua customer
|
|
||||||
- Kalkulasi promo price (jika applicable)
|
|
||||||
- Kalkulasi prorata (bulan pertama)
|
|
||||||
- Kalkulasi tax (PPN 11%)
|
|
||||||
- Status: 'unpaid'
|
|
||||||
|
|
||||||
2. Notifikasi WhatsApp otomatis ke customer
|
|
||||||
|
|
||||||
3. Customer bayar via:
|
|
||||||
- Payment gateway (Midtrans, Tripay, Xendit, Duitku)
|
|
||||||
- QRIS static (QR fixed dengan nominal unik)
|
|
||||||
- Manual via admin/agen
|
|
||||||
- Kolektor (pending approval)
|
|
||||||
|
|
||||||
4. Status berubah: 'unpaid' → 'paid'
|
|
||||||
|
|
||||||
5. Notifikasi paid + auto-unisolir jika dalam suspend
|
|
||||||
|
|
||||||
6. Late payment → Auto-isolir @ hari yang ditentukan
|
|
||||||
```
|
|
||||||
|
|
||||||
### Promo & Prorata
|
|
||||||
```
|
|
||||||
Promo:
|
|
||||||
- Paket A: Rp 350rb/bulan, promo Rp 250rb (3 siklus)
|
|
||||||
- Pelanggan baru: dapat promo 3 bulan pertama
|
|
||||||
- Counter ditrack di customer.promo_cycles_used
|
|
||||||
- Reset otomatis saat ganti paket
|
|
||||||
|
|
||||||
Prorata:
|
|
||||||
- Tanggal pasang: 15 Agustus
|
|
||||||
- Invoice pertama: Rp 350rb * (16 hari / 31 hari) = Rp 180rb
|
|
||||||
- Bulan berikutnya: Rp 350rb penuh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Payment Gateway
|
|
||||||
```
|
|
||||||
Aktif (bisa kombinasi):
|
|
||||||
- Midtrans → CC, Transfer, E-wallet
|
|
||||||
- Tripay → Bank Transfer, QRIS, E-wallet
|
|
||||||
- Xendit → Virtual Account, QR
|
|
||||||
- Duitku → Aggregator
|
|
||||||
- QRIS Static → Fixed QR code
|
|
||||||
|
|
||||||
Webhook Generic:
|
|
||||||
- Endpoint: POST /api/webhook/v1/payment-notif
|
|
||||||
- Accept dari bank/e-wallet apapun
|
|
||||||
- Auto-match nominal → mark paid
|
|
||||||
- Log semua untuk audit
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 👥 CUSTOMER MANAGEMENT
|
|
||||||
|
|
||||||
### Data per Customer
|
|
||||||
```
|
|
||||||
Identitas:
|
|
||||||
- Nama, HP, Email, NIK, Alamat
|
|
||||||
- GPS Latitude/Longitude
|
|
||||||
- Area/Kabupaten
|
|
||||||
|
|
||||||
Jaringan:
|
|
||||||
- Paket (Starter/Pro/Premium)
|
|
||||||
- MikroTik Router assign
|
|
||||||
- PPPoE username
|
|
||||||
- Hotspot username (optional)
|
|
||||||
- Static IP (optional)
|
|
||||||
- OLT & ONU Port
|
|
||||||
- GenieACS Tag
|
|
||||||
|
|
||||||
Status & Billing:
|
|
||||||
- Status: active/suspended/inactive
|
|
||||||
- Isolir day (tanggal isolir otomatis)
|
|
||||||
- Auto-isolir enable/disable
|
|
||||||
- Promo cycles used
|
|
||||||
```
|
|
||||||
|
|
||||||
### Isolir Workflow
|
|
||||||
```
|
|
||||||
Manual Isolir:
|
|
||||||
1. Admin klik "Isolir" di customer detail
|
|
||||||
2. Sistem hapus/disable PPPoE user di MikroTik
|
|
||||||
3. Status berubah: active → suspended
|
|
||||||
4. Redirect ke page /isolated (jika online)
|
|
||||||
|
|
||||||
Auto-Isolir:
|
|
||||||
1. Cron job setiap hari jam 02:00
|
|
||||||
2. Cek customer: status=active + unpaid invoices + today >= isolate_day
|
|
||||||
3. Suspend PPPoE ke MikroTik
|
|
||||||
4. Notifikasi WhatsApp
|
|
||||||
|
|
||||||
Unisolir:
|
|
||||||
1. Pelanggan bayar
|
|
||||||
2. Admin klik "Buka Isolir"
|
|
||||||
3. Restore PPPoE user
|
|
||||||
4. Status: suspended → active
|
|
||||||
5. Customer online lagi
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🌐 MIKROTIK INTEGRATION
|
|
||||||
|
|
||||||
### PPPoE Workflow
|
|
||||||
```
|
|
||||||
1. Create customer dengan PPPoE username
|
|
||||||
2. Sistem generate PPPoE secret (password)
|
|
||||||
3. Connect ke MikroTik Router API
|
|
||||||
4. Add user ke PPPoE service
|
|
||||||
5. Customer bisa login dengan username/password
|
|
||||||
|
|
||||||
Live Monitoring:
|
|
||||||
- View active sessions
|
|
||||||
- Bytes in/out per session
|
|
||||||
- Session duration
|
|
||||||
- IP address
|
|
||||||
|
|
||||||
FUP (Fair Usage Policy):
|
|
||||||
- Paket: limit 100GB/bulan
|
|
||||||
- Cron setiap jam cek usage
|
|
||||||
- Jika > 100GB: ganti profile ke 1Mbps
|
|
||||||
- Reset 1 Jan bulan berikutnya
|
|
||||||
|
|
||||||
Night Mode (Jam Kalong):
|
|
||||||
- 00:00 - 06:00: Speed 20Mbps
|
|
||||||
- 06:00 - 00:00: Speed 10Mbps
|
|
||||||
- Cron @ 00:00 & 06:00 switch profile
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hotspot
|
|
||||||
```
|
|
||||||
Similar ke PPPoE:
|
|
||||||
- Create user dengan limit (uptime/data)
|
|
||||||
- Monitor active sessions
|
|
||||||
- Bandwidth limiting
|
|
||||||
```
|
|
||||||
|
|
||||||
### Voucher Hotspot
|
|
||||||
```
|
|
||||||
1. Admin create batch (qty 100, validity 30 hari)
|
|
||||||
2. Sistem generate 100 kode voucher unik
|
|
||||||
3. Sync ke MikroTik hotspot
|
|
||||||
4. Customer pakai untuk login hotspot portal
|
|
||||||
5. Agent bisa jual voucher ke pelanggan
|
|
||||||
6. Track status: created/active/used/expired
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎛️ GENIEACS & TR-069
|
|
||||||
|
|
||||||
### Built-in ACS
|
|
||||||
```
|
|
||||||
Aplikasi punya ACS internal (lightweight):
|
|
||||||
- Tidak perlu GenieACS eksternal
|
|
||||||
- Support ZTE, Huawei, FiberHome, dll
|
|
||||||
- Auto-detect device properties
|
|
||||||
- Port: 7547 (configurable)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Device Management
|
|
||||||
```
|
|
||||||
Features:
|
|
||||||
- List semua ONU/CPE
|
|
||||||
- View device status (online/offline)
|
|
||||||
- Change SSID / Wi-Fi password
|
|
||||||
- Bulk SSID change
|
|
||||||
- Reboot device
|
|
||||||
- Provision WAN (VLAN, bridge, PPPoE)
|
|
||||||
- View device logs
|
|
||||||
- Parameter configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
### Workflow
|
|
||||||
```
|
|
||||||
1. ONU/CPE boot → connect ke ACS server
|
|
||||||
2. Sistem auto-detect device properties
|
|
||||||
3. Admin bisa configure via panel
|
|
||||||
4. Change SSID → send ke device via TR-069
|
|
||||||
5. Device update config otomatis
|
|
||||||
6. Customer notif via WhatsApp
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔌 OLT & ONU MANAGEMENT
|
|
||||||
|
|
||||||
### OLT Setup
|
|
||||||
```
|
|
||||||
Konfigurasi:
|
|
||||||
- IP address OLT
|
|
||||||
- SNMP community string
|
|
||||||
- Telnet credentials (untuk config WAN)
|
|
||||||
- Web interface credentials
|
|
||||||
|
|
||||||
Multi-OLT:
|
|
||||||
- Bisa manage banyak OLT sekaligus
|
|
||||||
- Per pelanggan assign ke OLT mana
|
|
||||||
```
|
|
||||||
|
|
||||||
### ONU Monitoring
|
|
||||||
```
|
|
||||||
Via SNMP query:
|
|
||||||
- Status (online/offline)
|
|
||||||
- Serial number
|
|
||||||
- Optical signal (dBm)
|
|
||||||
- Distance ke OLT
|
|
||||||
- RX/TX power
|
|
||||||
|
|
||||||
Operations:
|
|
||||||
- Reboot ONU
|
|
||||||
- Rename (friendly name)
|
|
||||||
- Authorize ONU (EPON)
|
|
||||||
- Configure WAN (via Telnet/TR-069/REST API)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💳 PAYMENT FLOW
|
|
||||||
|
|
||||||
### Customer Bayar Invoice
|
|
||||||
```
|
|
||||||
1. Customer login portal
|
|
||||||
2. Lihat tagihan belum bayar
|
|
||||||
3. Click "Bayar"
|
|
||||||
4. Pilih metode pembayaran (dari gateway aktif)
|
|
||||||
5. Redirect ke payment gateway
|
|
||||||
6. Customer input data (CC/transfer/etc)
|
|
||||||
7. Gateway proses
|
|
||||||
8. Success → Webhook ke aplikasi
|
|
||||||
9. Aplikasi verify amount & signature
|
|
||||||
10. Mark invoice PAID
|
|
||||||
11. Send WhatsApp confirmation
|
|
||||||
12. Auto-unisolir jika customer dalam suspend
|
|
||||||
13. Dashboard terupdate
|
|
||||||
```
|
|
||||||
|
|
||||||
### Webhook Payment
|
|
||||||
```
|
|
||||||
Generic endpoint untuk bank/e-wallet:
|
|
||||||
POST /api/webhook/v1/payment-notif
|
|
||||||
|
|
||||||
Bisa terima dari:
|
|
||||||
- Bank (SMS gateway → webhook)
|
|
||||||
- E-wallet (API callback)
|
|
||||||
- QRIS (bank QRIS provider)
|
|
||||||
|
|
||||||
Sistem:
|
|
||||||
- Parse amount dari notifikasi
|
|
||||||
- Find invoice / voucher dengan nominal cocok
|
|
||||||
- Verify jika nominal sudah digunakan (duplikasi)
|
|
||||||
- Mark as PAID
|
|
||||||
- Log untuk audit
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗺️ GIS MAPPING
|
|
||||||
|
|
||||||
### Peta Admin
|
|
||||||
```
|
|
||||||
Features:
|
|
||||||
- Leaflet map (OpenStreetMap + Satellite)
|
|
||||||
- Marker customer (warna: hijau/merah/biru)
|
|
||||||
- Marker ODP (orange)
|
|
||||||
- Draw cable path (customer → ODP)
|
|
||||||
- Popup detail (klik marker)
|
|
||||||
- Zoom/pan/locate
|
|
||||||
|
|
||||||
Interaksi:
|
|
||||||
- Click customer → detail + isolir button
|
|
||||||
- Click ODP → info + available ports
|
|
||||||
- Draw polyline → cable path (simpan)
|
|
||||||
- Real-time PPPoE traffic graph
|
|
||||||
- Google Maps route (untuk tech)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cable Path
|
|
||||||
```
|
|
||||||
Simpan rute dari customer ke ODP:
|
|
||||||
- Array of [lat, lng] coordinates
|
|
||||||
- Calculate distance (KM)
|
|
||||||
- Description (aerial/underground/etc)
|
|
||||||
- Edit/delete existing path
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tech GPS (Optional)
|
|
||||||
```
|
|
||||||
- Tech login portal dengan mobile
|
|
||||||
- Sistem track GPS lokasi tech
|
|
||||||
- Geofence alert (keluar/masuk area kantor)
|
|
||||||
- Nearby customers di radius tertentu
|
|
||||||
- Chat WhatsApp dari peta
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💬 WHATSAPP AUTOMATION
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
```
|
|
||||||
1. Admin buka /admin/whatsapp
|
|
||||||
2. Scan QR code dengan WhatsApp app
|
|
||||||
3. Verify & approve
|
|
||||||
4. Bot siap mengirim pesan
|
|
||||||
|
|
||||||
Gateway:
|
|
||||||
- Baileys (Unofficial WebSocket)
|
|
||||||
- Meta Cloud API (Official, jika setup)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Automated Messages
|
|
||||||
```
|
|
||||||
Invoice Created:
|
|
||||||
"Tagihan Anda bulan ini siap!
|
|
||||||
Nominal: Rp 350.000
|
|
||||||
Bayar di: [link]
|
|
||||||
Due: 10 Agustus"
|
|
||||||
|
|
||||||
Invoice Paid:
|
|
||||||
"✅ Pembayaran diterima!
|
|
||||||
Tagihan Rp 350.000 LUNAS
|
|
||||||
Terima kasih!"
|
|
||||||
|
|
||||||
Isolir Reminder (H-1):
|
|
||||||
"⚠️ Reminder: Jatuh tempo BESOK
|
|
||||||
Tagihan Rp 350.000
|
|
||||||
Bayar untuk hindari isolir"
|
|
||||||
|
|
||||||
Isolir Notice:
|
|
||||||
"⚠️ Layanan Anda DIPUTUS
|
|
||||||
Penyebab: Tagihan belum dibayar
|
|
||||||
Hubungi [support]"
|
|
||||||
|
|
||||||
Unisolir:
|
|
||||||
"✅ Layanan Anda kembali AKTIF
|
|
||||||
Terima kasih pembayarannya!"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Admin Commands (via WhatsApp)
|
|
||||||
```
|
|
||||||
"BILLING" → Send laporan billing hari ini
|
|
||||||
"PPPOE" → List active PPPoE sessions
|
|
||||||
"BALANCE" → Cek saldo Digiflazz
|
|
||||||
"TOPUP 1000000" → Top-up agent
|
|
||||||
"INVOICE" → List unpaid invoices
|
|
||||||
```
|
|
||||||
|
|
||||||
### Broadcast
|
|
||||||
```
|
|
||||||
Select customers → Create message → Schedule → Send
|
|
||||||
- Set delay antar pesan (3-5 detik)
|
|
||||||
- Track delivery status
|
|
||||||
- Pause/resume/stop anytime
|
|
||||||
- Retry failed messages
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 👔 AGENT MANAGEMENT
|
|
||||||
|
|
||||||
### Agent Fitur
|
|
||||||
```
|
|
||||||
Dashboard:
|
|
||||||
- Total saldo (Rp)
|
|
||||||
- Today's transaction
|
|
||||||
- Commission earned
|
|
||||||
- Top customers
|
|
||||||
|
|
||||||
Pembayaran:
|
|
||||||
- Cari pelanggan
|
|
||||||
- Lihat unpaid invoices
|
|
||||||
- Bayar dari saldo agent
|
|
||||||
- Print struk
|
|
||||||
|
|
||||||
Voucher:
|
|
||||||
- Lihat inventory
|
|
||||||
- Jual ke customer
|
|
||||||
- Print code
|
|
||||||
- Track sale
|
|
||||||
|
|
||||||
Digiflazz (Pulsa/Data):
|
|
||||||
- Browse produk (Telkomsel, Indosat, dll)
|
|
||||||
- Category → Brand → Nominal
|
|
||||||
- Buy untuk customer
|
|
||||||
- Webhook status update
|
|
||||||
```
|
|
||||||
|
|
||||||
### Admin Agent Control
|
|
||||||
```
|
|
||||||
- Create/edit agent
|
|
||||||
- Top-up balance
|
|
||||||
- Set commission %
|
|
||||||
- View transaction history
|
|
||||||
- Special pricing per agent
|
|
||||||
- Performance report
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎫 TICKETING SYSTEM
|
|
||||||
|
|
||||||
### Ticket Status Flow
|
|
||||||
```
|
|
||||||
open → in_progress → resolved → closed
|
|
||||||
|
|
||||||
Open:
|
|
||||||
- Customer create di portal
|
|
||||||
- Auto-assign ke admin/tech
|
|
||||||
|
|
||||||
In Progress:
|
|
||||||
- Tech ambil ticket
|
|
||||||
- Add notes/photos
|
|
||||||
- Update status
|
|
||||||
|
|
||||||
Resolved:
|
|
||||||
- Tech mark selesai
|
|
||||||
- Notif ke customer
|
|
||||||
|
|
||||||
Closed:
|
|
||||||
- Customer confirm selesai
|
|
||||||
- Optional: give rating (1-5)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Admin Features
|
|
||||||
```
|
|
||||||
- Dashboard tickets (count per status)
|
|
||||||
- Assign ke technician
|
|
||||||
- Reassign jika perlu
|
|
||||||
- View ticket history
|
|
||||||
- SLA tracking
|
|
||||||
- Performance report
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tech Features
|
|
||||||
```
|
|
||||||
- Pool tickets (assigned to me)
|
|
||||||
- View detail
|
|
||||||
- Take ticket
|
|
||||||
- Upload photos (before/after)
|
|
||||||
- Add resolution notes
|
|
||||||
- Mark resolved
|
|
||||||
```
|
|
||||||
|
|
||||||
### Customer Features
|
|
||||||
```
|
|
||||||
- Create ticket
|
|
||||||
- View my tickets
|
|
||||||
- See status update
|
|
||||||
- Add comment/photo
|
|
||||||
- Rate satisfaction
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ AUTOMATION (CRON JOBS)
|
|
||||||
|
|
||||||
| Waktu | Tugas | Detail |
|
|
||||||
|-------|-------|--------|
|
|
||||||
| 1 Jan @ 00:01 | Generate invoices | Monthly billing untuk semua customer |
|
|
||||||
| Setiap hari @ 02:00 | Auto-isolir | Suspend customer overdue |
|
|
||||||
| Setiap hari @ 09:00 | Reminder tagihan | WhatsApp reminder H-1 isolir |
|
|
||||||
| 00:00 & 06:00 | Night mode | Ganti profile PPPoE malam/siang |
|
|
||||||
| Setiap jam | FUP check | Check usage vs limit, ganti profile |
|
|
||||||
| Setiap 10 menit | Usage sync | Sinkron PPPoE traffic dari MikroTik |
|
|
||||||
| Setiap hari @ 02:00 | Database backup | Auto-backup dengan retention |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 DATABASE OVERVIEW
|
|
||||||
|
|
||||||
### Key Tables
|
|
||||||
```
|
|
||||||
customers
|
|
||||||
├── Identitas (name, phone, email, nik)
|
|
||||||
├── Jaringan (pppoe_username, hotspot_username, static_ip)
|
|
||||||
├── Device (olt_id, onu_port, genieacs_tag)
|
|
||||||
├── Billing (package_id, isolate_day, auto_isolate)
|
|
||||||
└── Status (status, created_at, updated_at)
|
|
||||||
|
|
||||||
invoices
|
|
||||||
├── Amount (amount, status, qris_amount_unique)
|
|
||||||
├── Period (period_month, period_year)
|
|
||||||
├── Payment (paid_at, payment_method)
|
|
||||||
└── Notes (promo/prorata info)
|
|
||||||
|
|
||||||
payments
|
|
||||||
├── Transaction data (gateway_id, signature, status)
|
|
||||||
├── Customer info
|
|
||||||
└── Invoice link
|
|
||||||
|
|
||||||
pppoe_users
|
|
||||||
├── Credentials (username, secret)
|
|
||||||
├── Profile & limits
|
|
||||||
└── Session tracking
|
|
||||||
|
|
||||||
... + 40+ tables lainnya
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 SUMMARY - FITUR UTAMA YANG BERJALAN
|
|
||||||
|
|
||||||
✅ **Billing & Invoicing**
|
|
||||||
- Auto generate bulanan
|
|
||||||
- Promo cycles tracking
|
|
||||||
- Prorata bulan pertama
|
|
||||||
- Multi-gateway payment
|
|
||||||
|
|
||||||
✅ **Customer Lifecycle**
|
|
||||||
- CRUD pelanggan
|
|
||||||
- Paket management
|
|
||||||
- Auto-isolir/unisolir
|
|
||||||
- Status tracking
|
|
||||||
|
|
||||||
✅ **Network Management**
|
|
||||||
- MikroTik PPPoE/Hotspot
|
|
||||||
- Voucher hotspot
|
|
||||||
- Firewall & rate limiting
|
|
||||||
- Night mode automation
|
|
||||||
|
|
||||||
✅ **Device Management**
|
|
||||||
- Built-in TR-069 ACS
|
|
||||||
- GenieACS integration
|
|
||||||
- SSID/password change
|
|
||||||
- Device provisioning
|
|
||||||
|
|
||||||
✅ **OLT/ONU Management**
|
|
||||||
- SNMP monitoring
|
|
||||||
- Reboot/rename/authorize
|
|
||||||
- WAN configuration
|
|
||||||
- Multi-brand support
|
|
||||||
|
|
||||||
✅ **Payment Processing**
|
|
||||||
- 4 payment gateways
|
|
||||||
- QRIS static
|
|
||||||
- Generic webhook
|
|
||||||
- Payment verification
|
|
||||||
|
|
||||||
✅ **GIS Mapping**
|
|
||||||
- Leaflet maps
|
|
||||||
- Cable path tracking
|
|
||||||
- GPS tech tracking
|
|
||||||
- Real-time graphs
|
|
||||||
|
|
||||||
✅ **WhatsApp Automation**
|
|
||||||
- Invoice notification
|
|
||||||
- Payment reminder
|
|
||||||
- Isolir notice
|
|
||||||
- Broadcast messaging
|
|
||||||
|
|
||||||
✅ **Agent Management**
|
|
||||||
- Pembayaran dari saldo
|
|
||||||
- Voucher sales
|
|
||||||
- Pulsa/data (Digiflazz)
|
|
||||||
- Commission tracking
|
|
||||||
|
|
||||||
✅ **Support Ticketing**
|
|
||||||
- Create/assign/resolve
|
|
||||||
- Photo upload
|
|
||||||
- Rating system
|
|
||||||
- History tracking
|
|
||||||
|
|
||||||
✅ **Monitoring**
|
|
||||||
- System health metrics
|
|
||||||
- Service status
|
|
||||||
- Alert system
|
|
||||||
- Performance tracking
|
|
||||||
|
|
||||||
✅ **Automation**
|
|
||||||
- 7 scheduled jobs
|
|
||||||
- Configurable timing
|
|
||||||
- Error handling
|
|
||||||
- Logging
|
|
||||||
|
|
||||||
✅ **Multi-Portal**
|
|
||||||
- Customer self-service
|
|
||||||
- Admin full control
|
|
||||||
- Technician field support
|
|
||||||
- Agent sales
|
|
||||||
- Collector approval
|
|
||||||
|
|
||||||
✅ **Compliance**
|
|
||||||
- Audit trail logging
|
|
||||||
- Role-based access
|
|
||||||
- Session management
|
|
||||||
- Data validation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 PRODUCTION STATUS
|
|
||||||
|
|
||||||
**Aplikasi ini SUDAH BERJALAN** dan mencakup:
|
|
||||||
- ✅ Complete billing system
|
|
||||||
- ✅ Full customer management
|
|
||||||
- ✅ Network device integration
|
|
||||||
- ✅ Payment processing
|
|
||||||
- ✅ Multi-user portals
|
|
||||||
- ✅ Automation & scheduling
|
|
||||||
- ✅ Monitoring & health check
|
|
||||||
|
|
||||||
**Siap untuk:**
|
|
||||||
- ✅ Production deployment
|
|
||||||
- ✅ Scaling operasi ISP
|
|
||||||
- ✅ Integration ke sistem existing
|
|
||||||
- ✅ Customization per requirements
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📖 DOKUMENTASI TERSEDIA
|
|
||||||
|
|
||||||
1. **FITUR_APLIKASI_LENGKAP.md** ← Dokumentasi ini (16 fitur utama + detail)
|
|
||||||
2. **AUDIT_LENGKAP.md** (Existing) ← Technical deep dive
|
|
||||||
3. **DATABASE_SCHEMA.md** (Existing) ← Struktur data lengkap
|
|
||||||
4. **QUICK_REFERENCE.md** (Existing) ← Common commands
|
|
||||||
5. **AUDIT_SECURITY_2026.md** (Baru) ← Security audit findings
|
|
||||||
6. **IMPLEMENTATION_GUIDE_SECURITY.md** (Baru) ← Fix guide
|
|
||||||
7. **README.md** (Existing) ← Instalasi & setup
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ✅ Aplikasi komprehensif, well-documented, siap production
|
|
||||||
**Next Step:** Implementasi security fixes (bcrypt, .env, CSRF) sebelum production
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# RUJUKAN ARSITEKTUR & FITUR SISTEM BILLING RTRW-NET
|
|
||||||
|
|
||||||
Dokumen ini disimpan sebagai **rujukan utama** arsitektur, fitur backend, frontend, dan skema database sebelum dilakukan penambahan fitur RADIUS Server.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. DOKUMENTASI MODUL & KOMPONEN EKSISTING
|
|
||||||
|
|
||||||
### A. Core Engine & Entry Point
|
|
||||||
- **`app-customer.js`**: Main entry point Express.js v4.
|
|
||||||
- Memuat middleware security: CORS, Referer/Origin CSRF Protection, Express Session, Body Parser limit 1MB.
|
|
||||||
- Pengatur i18n multi-bahasa (`locales/`).
|
|
||||||
- Menjalankan background service: Auto-backup (`scheduleAutoBackup`), Cron Billing (`cronService.js`), WhatsApp Bot, Telegram Bot.
|
|
||||||
- Mengunci DNS ke IPv4 (`dns.setDefaultResultOrder('ipv4first')`) untuk mencegah timeout IPv6 di server produksi.
|
|
||||||
|
|
||||||
### B. Database & Stored Layer (`config/database.js`)
|
|
||||||
- **Engine**: SQLite 3 menggunakan driver `better-sqlite3` (Native C++ extension, high performance).
|
|
||||||
- **Mode**: `WAL` (Write-Ahead Logging) dengan `foreign_keys = ON`.
|
|
||||||
- **Fungsi Kustom SQLite**: `NOW_LOCAL()` (memformat tanggal-waktu berdasarkan timezone lokal `Asia/Jakarta`).
|
|
||||||
- **Tabel Utama**:
|
|
||||||
1. `settings` (Dynamic Key-Value Store)
|
|
||||||
2. `packages` (Paket Internet: harga, speed up/down, promo, jam kalong, FUP)
|
|
||||||
3. `customers` (Data Pelanggan: no_pelanggan, nama, phone, pppoe_user, pppoe_secret, status, isolir_date, router_id, odp_id, genieacs_tag)
|
|
||||||
4. `invoices` & `payments` (Tagihan & Pembayaran)
|
|
||||||
5. `routers` (Daftar Router MikroTik: host, port 8728, username, password, status)
|
|
||||||
6. `pppoe_users` & `pppoe_traffic_samples` & `pppoe_sessions`
|
|
||||||
7. `hotspot_users` & `hotspot_profiles` & `hotspot_sessions`
|
|
||||||
8. `voucher_batches` & `vouchers` & `public_voucher_orders`
|
|
||||||
9. `olts`, `odps`, `onu_mapping`, `acsdevices` (Modul FTTH & TR-069 ACS)
|
|
||||||
10. `technicians`, `cashiers`, `collectors`, `agents` (Akses multi-role)
|
|
||||||
11. `audit_trail` (Log aktivitas pengguna)
|
|
||||||
|
|
||||||
### C. Services Layer (`services/`)
|
|
||||||
1. **`mikrotikService.js`**:
|
|
||||||
- Menghubungkan aplikasi ke RouterOS via API port 8728/8729 menggunakan `routeros-client`.
|
|
||||||
- Mengelola PPPoE Secret (Add, Update, Delete, Toggle Enable/Disable).
|
|
||||||
- Mengelola Hotspot User & Active Sessions.
|
|
||||||
- Mengambil trafik interface & active connections secara realtime.
|
|
||||||
- Mengatur isolir pelanggan via MikroTik API (mengubah profile / IP pool).
|
|
||||||
2. **`customerService.js`**:
|
|
||||||
- CRUD Pelanggan, komparasi data lokal vs router.
|
|
||||||
- Penanganan siklus isolir & pemulihan status otomatis.
|
|
||||||
3. **`billingService.js`**:
|
|
||||||
- Penjadwalan & pembuatan invoice bulanan.
|
|
||||||
- Pembayaran invoice (Cash, Transfer, QRIS, Gateway Midtrans/Tripay/Xendit).
|
|
||||||
- Eksekusi auto-isolir saat jatuh tempo.
|
|
||||||
4. **`cronService.js`**:
|
|
||||||
- Menjalankan penagihan otomatis jam 00:05.
|
|
||||||
- Cek auto-isolir harian jam 01:00.
|
|
||||||
- Sampling statistik trafik jam 10 menit sekali.
|
|
||||||
- Notifikasi reminder tagihan via WA / Telegram.
|
|
||||||
5. **`oltService.js` & `onuProvisionService.js` & `acsServerService.js`**:
|
|
||||||
- Integrasi OLT ZTE/Huawei/FiberHome via SNMP & Telnet.
|
|
||||||
- TR-069 Auto-Provisioning CPE via GenieACS.
|
|
||||||
|
|
||||||
### D. Route & Portal Layer (`routes/`)
|
|
||||||
1. **`/admin` (`adminPortal.js`)**: Portal Utama Admin (Dashboard, Pelanggan, Paket, Router, Transaksi, Laporan, OLT/ACS, Settings).
|
|
||||||
2. **`/customer` (`customerPortal.js`)**: Portal Pelanggan Self-Service (Cek Tagihan, Bayar QRIS, Tiket Komplain).
|
|
||||||
3. **`/agent` (`agentPortal.js`)**: Portal Agen (Jual Voucher, Topup Pulsa Digiflazz).
|
|
||||||
4. **`/collector` (`collectorPortal.js`)**: Portal Kolektor Penagih Lapangan.
|
|
||||||
5. **`/tech` (`techPortal.js`)**: Portal Teknisi (Tiket Gangguan, Pasang Baru, Mapping ODP).
|
|
||||||
6. **`/finance` (`financePortal.js`)**: Portal Keuangan (Pemasukan & Pengeluaran Kas).
|
|
||||||
|
|
||||||
### E. Frontend & Views (`views/`)
|
|
||||||
- Menggunakan **EJS (Embedded JavaScript)** dengan template responsive.
|
|
||||||
- Menggunakan CSS Vanilla & Custom Theme (Dark/Light Mode), Bootstrap Icons / FontAwesome.
|
|
||||||
- AJAX Fetch API untuk interaksi data tanpa reload halaman.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. ATURAN INTEGRASI FITUR RADIUS (NON-BREAKING GUARANTEE)
|
|
||||||
|
|
||||||
1. **Prinsip Utama**: Seluruh modul MikroTik API eksisting **TIDAK BOLEH DIUBAH ATAU DIHAPUS**.
|
|
||||||
2. **Fleksibilitas**: Sistem akan mendukung 2 mode otentikasi router:
|
|
||||||
- **Mode 1 (Default Eksisting)**: MikroTik API (`routeros-client`).
|
|
||||||
- **Mode 2 (Fitur Baru)**: RADIUS Server (UDP Port 1812/1813).
|
|
||||||
- **Mode 3 (Hybrid)**: MikroTik API + RADIUS Server berjalan bersamaan.
|
|
||||||
3. **Database Integration**: RADIUS Server akan langsung membaca & menulis ke `database/billing.db` eksisting:
|
|
||||||
- Mengotentikasi kredensial pengguna dari tabel `customers` / `pppoe_users` / `hotspot_users`.
|
|
||||||
- Mengirimkan atribut RADIUS standar (`Framed-IP-Address`, `Mikrotik-Rate-Limit`, `Session-Timeout`).
|
|
||||||
- Menerima paket RADIUS Accounting (Start/Interim/Stop) dan mencatat penggunaan kuota/bandwidth ke tabel `pppoe_traffic_samples` & `radius_accounting_logs`.
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
# UI/UX Rules — Billing RTRW (SKYFIBER)
|
|
||||||
|
|
||||||
> Dokumen acuan wajib untuk **semua** pekerjaan UI/UX di project ini.
|
|
||||||
> Setiap kali membuat / mengubah tampilan, ikuti aturan di bawah.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Peran
|
|
||||||
|
|
||||||
**Act as an Expert Senior Frontend Developer.**
|
|
||||||
|
|
||||||
Your task is to generate a highly responsive, modern, and fluid web layout for
|
|
||||||
**[Sebutkan apa yang ingin dibuat, misal: a Dashboard / Landing Page / Navigation Bar]**
|
|
||||||
using **[Sebutkan teknologi, misal: HTML & Tailwind CSS / HTML & Vanilla CSS / EJS]**.
|
|
||||||
|
|
||||||
> Di project ini umumnya: **EJS + Vanilla CSS** (`public/css/admin.css`, `landing.css`,
|
|
||||||
> `auth.css`, `admin-theme.css`) dan **Bootstrap Icons** (bukan emoji di UI).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Strict Layout & Responsiveness Requirements
|
|
||||||
|
|
||||||
### 1. Fluid Typography & Spacing
|
|
||||||
- Gunakan fungsi CSS **`clamp()`** untuk ukuran font, padding, dan margin agar skala
|
|
||||||
mulus di semua ukuran layar (mobile → desktop 4K) **tanpa** hanya mengandalkan
|
|
||||||
media query yang patah-patah.
|
|
||||||
- Contoh: `font-size: clamp(0.95rem, 0.9rem + 0.5vw, 1.25rem);`
|
|
||||||
`padding: clamp(12px, 2vw, 28px);`
|
|
||||||
|
|
||||||
### 2. Zero Overlap Guarantee
|
|
||||||
Pastikan teks, kartu, tombol, dan ikon **tidak pernah** tumpang tindih atau keluar
|
|
||||||
dari kontainernya:
|
|
||||||
- `overflow-wrap: break-word;` atau `word-break: break-word;` untuk teks panjang
|
|
||||||
(mis. link, nama panjang, kode).
|
|
||||||
- `min-width: 0;` pada **flex/grid children** agar tidak meluber keluar parent.
|
|
||||||
- Gunakan **`flex-wrap: wrap` + CSS `gap`** (bukan margin tetap) untuk jarak antar item.
|
|
||||||
- Gambar & ikon harus responsif: `max-width: 100%; height: auto;` atau
|
|
||||||
`aspect-ratio` + `object-fit: contain|cover`.
|
|
||||||
|
|
||||||
### 3. Flexible & Mappable Structure (CSS Grid / Flexbox)
|
|
||||||
- Bangun layout yang **self-adapting**.
|
|
||||||
- **CSS Grid** dengan `repeat(auto-fit, minmax(..., 1fr))` untuk grid kartu agar
|
|
||||||
otomatis memetakan ke ruang layar yang tersedia.
|
|
||||||
Contoh: `grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));`
|
|
||||||
- **Flexbox** untuk perataan (alignment) & distribusi.
|
|
||||||
- **Hindari tinggi tetap** (`height: 100px`); gunakan **`min-height`** agar konten
|
|
||||||
bisa memanjang dengan aman.
|
|
||||||
|
|
||||||
### 4. Mobile-First Approach
|
|
||||||
- Mulai dari layout **vertikal** yang solid untuk mobile.
|
|
||||||
- Lalu naikkan dengan **media query** (`@media (min-width: ...)`) atau breakpoint
|
|
||||||
Tailwind (`md:`, `lg:`) untuk memetakan ke **grid/flex horizontal** di layar besar.
|
|
||||||
|
|
||||||
### 5. Debug-Friendly & Minimal Bugs
|
|
||||||
- Tulis HTML yang **bersih, semantik, dan dangkal** (hindari *div soup*).
|
|
||||||
- Gunakan **CSS Custom Properties (Variabel) terpusat** untuk warna, spacing, dan
|
|
||||||
font agar perubahan cepat & mudah.
|
|
||||||
Contoh: `--primary: #ADFF2F; --bg: #0A0D14; --radius: 14px;`
|
|
||||||
- Beri **komentar jelas** di kode yang menjelaskan logika layout.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
Output **tepat kode yang diperlukan**, pastikan:
|
|
||||||
- **siap dijalankan** (ready to run),
|
|
||||||
- **bebas bug** (bug-free),
|
|
||||||
- **mudah di-debug**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Catatan khusus project ini
|
|
||||||
|
|
||||||
- **Tema**: dark (default) + lime `#ADFF2F` di atas near-black `#0A0D14`; harus ada
|
|
||||||
toggle dark/light di setiap halaman.
|
|
||||||
- **Ikon**: **Bootstrap Icons** saja untuk UI (emoji hanya untuk teks WhatsApp).
|
|
||||||
- **Brand**: nama & logo diambil dari settings (`brandName` / `brandLogo`), jangan hardcode.
|
|
||||||
- **Bilingual (ID/EN)** via `t('key', 'fallback')` — identifier kode tetap Inggris.
|
|
||||||
- **Admin**: markup sidebar & `admin.css`/`dashboard.ejs` diperlakukan "frozen";
|
|
||||||
penyesuaian via overlay `admin-theme.css`.
|
|
||||||
- **Scroll**: area konten admin = `.mw` (scroll container). Jangan bikin konten
|
|
||||||
terpotong; `.page` dibiarkan tumbuh (`flex: 0 0 auto`) agar `.mw` bisa scroll.
|
|
||||||
- **Modal**: satu scroll saja (`.mb` yang scroll, `.mbody` `overflow: visible`) —
|
|
||||||
hindari *nested scroll* yang mengunci.
|
|
||||||
- **Responsif**: gunakan `100dvh` (bukan `100vh`) untuk area penuh-layar di mobile.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Checklist cepat sebelum selesai
|
|
||||||
|
|
||||||
- [ ] Ukuran font/spacing pakai `clamp()` di elemen utama.
|
|
||||||
- [ ] Tidak ada teks/ikon yang meluber/overlap (cek `min-width:0`, `overflow-wrap`, `flex-wrap`).
|
|
||||||
- [ ] Grid kartu pakai `repeat(auto-fit, minmax(..., 1fr))`.
|
|
||||||
- [ ] Tidak ada `height` tetap pada blok konten (pakai `min-height`).
|
|
||||||
- [ ] Mobile-first + breakpoint untuk layar besar.
|
|
||||||
- [ ] HTML semantik & dangkal; warna/spacing via CSS variables.
|
|
||||||
- [ ] Dark/light berfungsi; ikon Bootstrap Icons; bilingual ID/EN.
|
|
||||||
- [ ] Scroll & modal normal di mobile **dan** desktop.
|
|
||||||
@@ -1,567 +0,0 @@
|
|||||||
# Changelog Perubahan Kode — Billing RTRW
|
|
||||||
|
|
||||||
Project : `/opt/billing-rtrw`
|
|
||||||
Host : `deneva`
|
|
||||||
Service : `billing-rtrw` (systemd, port 3001)
|
|
||||||
Owner : `billing:billing`
|
|
||||||
|
|
||||||
> Project ini **belum memakai Git**, jadi file ini adalah satu-satunya catatan
|
|
||||||
> perubahan. Setiap perubahan kode selalu disertai backup file asli (lihat
|
|
||||||
> bagian *Rollback* pada tiap entri).
|
|
||||||
|
|
||||||
Format: `## [versi] — tanggal` (terbaru di atas).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.7] — 2026-09-28 16:55 — Full UI/UX Modernization & Database Optimization (Gentelella Tiles, uiverse.io Effects & Google Fonts)
|
|
||||||
|
|
||||||
### 1. Modernisasi Lengkap Seluruh Halaman Utama Admin (Gentelella Admin Style)
|
|
||||||
- **views/admin/dashboard.ejs**: Implementasi Tile KPI Stat horizontal (Total ONU, Online, Offline, Warning Redaman) dan Tile Keuangan (Pendapatan, Pelanggan Aktif, Tagihan Belum Bayar, Total Piutang) dengan indikator tren visual.
|
|
||||||
- **views/admin/customers.ejs**: Mengubah stat-grid filter pelanggan menjadi Gentelella Tile Cards interaktif (Total, Aktif, Disuspend, Nonaktif/Baru) dengan highlight filter aktif dan angka monospaced.
|
|
||||||
- **views/admin/billing.ejs**: Mengubah summary tagihan menjadi Gentelella Tile Cards (Total Tagihan, Sudah Bayar, Belum Bayar, Rasio Lunas).
|
|
||||||
- **views/admin/monitoring.ejs & views/admin/olts.ejs**: Menambahkan Google Fonts universal (Inter + JetBrains Mono).
|
|
||||||
|
|
||||||
### 2. Harmonisasi Desain Portal Pelanggan & Login (uiverse.io Style)
|
|
||||||
- **views/login.ejs**: Menyelaraskan seluruh palette warna dari hardcoded Indigo ke tema resmi Skyfiber (Teal #0d9488 & Cyan #06b6d4), menambahkan Google Fonts Plus Jakarta Sans, dan uiverse elevated glass card.
|
|
||||||
- **views/dashboard.ejs**: Menambahkan Google Fonts JetBrains Mono untuk pembacaan parameter teknis (IP, MAC, redaman dBm) dan efek micro-animation tombol .btn-uiverse-glow.
|
|
||||||
|
|
||||||
### 3. Optimasi Database SQLite
|
|
||||||
- Eksekusi PRAGMA integrity_check (hasil: ok).
|
|
||||||
- Eksekusi PRAGMA optimize untuk query planner performa tinggi.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- public/css/admin.css
|
|
||||||
- views/admin/dashboard.ejs
|
|
||||||
- views/admin/customers.ejs
|
|
||||||
- views/admin/billing.ejs
|
|
||||||
- views/admin/monitoring.ejs
|
|
||||||
- views/admin/olts.ejs
|
|
||||||
- views/login.ejs
|
|
||||||
- views/dashboard.ejs
|
|
||||||
- views/isolated.ejs
|
|
||||||
- update-code.md
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cp /opt/billing-rtrw/views/admin/customers.ejs.bak-gentelella /opt/billing-rtrw/views/admin/customers.ejs
|
|
||||||
cp /opt/billing-rtrw/views/admin/billing.ejs.bak-gentelella /opt/billing-rtrw/views/admin/billing.ejs
|
|
||||||
cp /opt/billing-rtrw/views/admin/dashboard.ejs.bak-gentelella /opt/billing-rtrw/views/admin/dashboard.ejs
|
|
||||||
cp /opt/billing-rtrw/views/login.ejs.bak-plusjakarta /opt/billing-rtrw/views/login.ejs
|
|
||||||
cp /opt/billing-rtrw/views/dashboard.ejs.bak-uiverse /opt/billing-rtrw/views/dashboard.ejs
|
|
||||||
sudo systemctl restart billing-rtrw.service
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.6] — 2026-09-28 16:35 — Modernisasi UI/UX (Gentelella Admin Tiles, uiverse.io Effects & Google Fonts) + Matikan Service Biznet
|
|
||||||
|
|
||||||
### 1. Pematian Layanan di Server Biznet (Pencegahan Tabrakan Service)
|
|
||||||
- Service `billing-rtrw.service` di host `biznet` di-stop dan di-disable (`sudo systemctl stop & disable`).
|
|
||||||
- Process PM2 di `biznet` di-stop dan di-delete.
|
|
||||||
- Hasil: Menghilangkan konflik 409 pada Telegram Bot dan mencegah duplikasi trigger cron isolir/tagihan antar-server.
|
|
||||||
|
|
||||||
### 2. Adopsi Desain Gentelella Admin (views/admin/dashboard.ejs & public/css/admin.css)
|
|
||||||
- Mengganti grid metrik lama dengan modul **Gentelella Tile Stats (`.tile_count` & `.tile_stats_count`)**:
|
|
||||||
- Border samping berwarna aksen (`tile-primary`, `tile-success`, `tile-danger`, `tile-warning`).
|
|
||||||
- Label `count_top` uppercase dengan ikon jelas.
|
|
||||||
- Nilai metrik `count` berbobot 800 dengan font monospaced tabular.
|
|
||||||
- Sub-label `count_bottom` dengan indikator tren (`trend-up`, `trend-down`, `trend-warn`).
|
|
||||||
- Menambahkan class kontainer `.x_panel` & `.x_title` dan soft badges (`.badge-soft-success`, `.badge-soft-danger`, dll).
|
|
||||||
|
|
||||||
### 3. Adopsi Desain uiverse.io (views/dashboard.ejs)
|
|
||||||
- Menambahkan efek glassmorphism tingkat tinggi dengan border glow gradient dan shadow elevasi.
|
|
||||||
- Menambahkan animasi tombol mikro-interaktif (`.btn-uiverse-glow`) dengan hover lift-up (`translateY(-2px)`) dan klik aktif responsif.
|
|
||||||
|
|
||||||
### 4. Standarisasi Google Fonts Universal
|
|
||||||
- Mengintegrasikan **Inter** (antarmuka admin & tabel data), **Plus Jakarta Sans** (portal pelanggan & public landing), dan **JetBrains Mono** (data teknis IP, MAC, serial, dBm) melalui Google Fonts dengan koneksi teroptimasi (`preconnect` dan `display=swap`).
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- public/css/admin.css
|
|
||||||
- views/admin/dashboard.ejs
|
|
||||||
- views/dashboard.ejs
|
|
||||||
- update-code.md
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cp /opt/billing-rtrw/public/css/admin.css.bak-gentelella /opt/billing-rtrw/public/css/admin.css
|
|
||||||
cp /opt/billing-rtrw/views/admin/dashboard.ejs.bak-gentelella /opt/billing-rtrw/views/admin/dashboard.ejs
|
|
||||||
cp /opt/billing-rtrw/views/dashboard.ejs.bak-uiverse /opt/billing-rtrw/views/dashboard.ejs
|
|
||||||
sudo systemctl restart billing-rtrw.service
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.5] — 2026-09-28 16:20 — Fix Telegram Polling Flood, Unifikasi Tipografi (Plus Jakarta Sans) & Harmonize Login UI
|
|
||||||
|
|
||||||
### 1. Perbaikan Telegram Bot Polling Error Flood (services/telegramBot.js)
|
|
||||||
- Log server sebelumnya dibanjiri pesan error setiap 8 detik: error: Telegram Polling Error:.
|
|
||||||
- Penyebab: Terdeteksi error code 409 Conflict dari API Telegram karena bot token yang sama masih aktif dijalankan di server lama (biznet/migrasi).
|
|
||||||
- Solusi:
|
|
||||||
1. Menambahkan penanganan error detail (error?.response?.body?.description || error.message) agar terbaca jelas di logger.
|
|
||||||
2. Implementasi throttling logger (maksimal 1 log per 2 menit untuk error berulang yang sama).
|
|
||||||
3. Memisahkan level warning khusus bila terjadi 409 Conflict, sehingga journalctl bersih tanpa spam error.
|
|
||||||
|
|
||||||
### 2. Unifikasi Tipografi & Harmonisasi Desain (views/login.ejs & views/isolated.ejs)
|
|
||||||
- Menyelaraskan seluruh font pada portal publik & login pelanggan ke **Plus Jakarta Sans** (Google Fonts).
|
|
||||||
- Menyesuaikan palette warna primer & hover pada halaman login (views/login.ejs) menggunakan tema Teal/Cyan (#0d9488, #14b8a6, #2dd4bf) agar selaras dengan dashboard pelanggan.
|
|
||||||
- Menambahkan **Plus Jakarta Sans** pada halaman terisolir (views/isolated.ejs).
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- services/telegramBot.js
|
|
||||||
- views/login.ejs
|
|
||||||
- views/isolated.ejs
|
|
||||||
- update-code.md
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cp /opt/billing-rtrw/services/telegramBot.js.bak-pollfix /opt/billing-rtrw/services/telegramBot.js
|
|
||||||
cp /opt/billing-rtrw/views/login.ejs.bak-plusjakarta /opt/billing-rtrw/views/login.ejs
|
|
||||||
cp /opt/billing-rtrw/views/isolated.ejs.bak-plusjakarta /opt/billing-rtrw/views/isolated.ejs
|
|
||||||
sudo systemctl restart billing-rtrw.service
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.4] — 2026-09-27 18:35 — Fix Theme Toggle (Dark/Light Mode), FOUC Prevention & Light Theme Styling
|
|
||||||
|
|
||||||
### Masalah
|
|
||||||
- Tombol toggle tema (#theme-icon di header dashboard pelanggan) tidak berfungsi sama sekali saat diklik.
|
|
||||||
- Penyebab:
|
|
||||||
1. Logika Negasi Terbalik di toggleTheme(): Saat mode awal gelap, contains('light') bernilai false sehingga !false adalah true. Kode terus-menerus menyimpan dan menerapkan mode 'dark', tidak pernah beralih ke 'light'.
|
|
||||||
2. Class Collision pada <html>: <html class='dark'> memiliki class 'dark' permanen. applyTheme lama hanya menambahkan class 'light' tanpa menghapus class 'dark'. Tailwind CSS (darkMode: 'class') tetap mengaktifkan semua style dark:... selama class 'dark' ada di <html>.
|
|
||||||
3. FOUC saat reload: Tidak ada script sinkronisasi langsung di <head>, sehingga halaman sempat berkedip gelap sebelum DOMContentLoaded berjalan.
|
|
||||||
4. Missing data-theme: Tidak mengeset atribut data-theme='light'/'dark' yang diperlukan oleh theme.css.
|
|
||||||
|
|
||||||
### Solusi & Perbaikan
|
|
||||||
1. Perbaikan toggleTheme() & applyTheme(mode):
|
|
||||||
- Menghapus class 'dark' dan menambahkan 'light' saat beralih ke mode terang, dan sebaliknya saat mode gelap.
|
|
||||||
- Menetapkan atribut data-theme pada <html> secara konsisten.
|
|
||||||
- Sinkronisasi kedua key penyimpanan (skyfiber-customer-theme & app-theme).
|
|
||||||
- Mengubah ikon toggle secara dinamis: fa-sun text-amber-500 pada mode terang, fa-moon text-amber-400 pada mode gelap.
|
|
||||||
2. Inline Script di <head>:
|
|
||||||
- Menambahkan skrip sinkronisasi tema instan di dalam <head> sebelum browser merender body untuk mencegah kedip (FOUC).
|
|
||||||
3. Penyempurnaan Styling Mode Terang (Light Mode):
|
|
||||||
- Floating glass dock disesuaikan dengan latar frosted transparan putih dan sliding pill bergradasi lembut.
|
|
||||||
- Penyesuaian kontras input form, teks judul, card modal, dan gradien banner (tagihan lunas, tagihan aktif, info router).
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- views/dashboard.ejs
|
|
||||||
- update-code.md
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cp /opt/billing-rtrw/views/dashboard.ejs.bak-themefix-20260927183449 /opt/billing-rtrw/views/dashboard.ejs
|
|
||||||
systemctl restart billing-rtrw.service
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.3] — 2026-09-27 18:30 — Fix PWA Auto-Login, Persistent Remember Token & Session Lifetime
|
|
||||||
|
|
||||||
### Masalah
|
|
||||||
- Ketika PWA di-install di HP dan user login, saat aplikasi PWA di-kill atau ditutup, user terlempar ke halaman login lagi.
|
|
||||||
- Penyebab utama:
|
|
||||||
1. `start_url` di `manifest.webmanifest` diset ke `/customer/login?source=pwa`.
|
|
||||||
2. Rute `/customer/login` dan `/` memeriksa `req.session.customer`, sementara rute login hanya menyimpan `req.session.phone` dan `req.session.pppoe_username` (`customer` selalu undefined), sehingga selalu merender form login ulang.
|
|
||||||
3. Masa berlaku cookie sesi bawaan hanya 24 jam dan disimpan di RAM (MemoryStore) tanpa token persistensi native.
|
|
||||||
4. Service worker (`public/sw.js`) melakukan fallback navigasi ke `/customer/login` saat jaringan lambat/offline.
|
|
||||||
|
|
||||||
### Solusi & Perbaikan
|
|
||||||
1. **PWA Start URL:** Mengubah `start_url` di `/manifest.webmanifest` menjadi `/customer/dashboard`.
|
|
||||||
2. **Persistent Auto-Login Token (90 Hari):**
|
|
||||||
- Layanan baru `services/customerAuthTokenService.js` dengan tabel SQLite `customer_auth_tokens`.
|
|
||||||
- Mengeluarkan cookie `customer_remember` (90 hari, httpOnly, secure, lax) saat login langsung maupun verifikasi OTP.
|
|
||||||
- Middleware auto-login di `routes/customerPortal.js` yang secara otomatis memulihkan sesi pelanggan ketika aplikasi dibuka kembali.
|
|
||||||
3. **Penyelarasan Guard Sesi:**
|
|
||||||
- Rute `/customer/` dan `/customer/login` kini memeriksa `req.session.phone || req.session.customer` sehingga pengguna yang sudah login langsung dialihkan ke `/customer/dashboard`.
|
|
||||||
- Objek `req.session.customer` kini diisi lengkap pada direct login dan OTP.
|
|
||||||
4. **Masa Berlaku Cookie Sesi:** Ditingkatkan dari 24 jam menjadi 30 hari.
|
|
||||||
5. **Logout Bersih:** Rute `/customer/logout` (mendukung POST dan GET) mencabut token dari database dan menghapus cookie `customer_remember` serta `customer.sid`.
|
|
||||||
6. **Fallback Offline SW v6:** Navigasi offline dialihkan ke `/offline.html` dengan tombol "Coba Lagi", bukan ke halaman login.
|
|
||||||
7. **Penyimpanan Lokal Form:** Menyimpan ID pelanggan terakhir di `localStorage` pada `views/login.ejs`.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- `services/customerAuthTokenService.js` (file baru)
|
|
||||||
- `app-customer.js`
|
|
||||||
- `routes/customerPortal.js`
|
|
||||||
- `public/sw.js`
|
|
||||||
- `views/login.ejs`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.2] — 2026-09-27 18:15 — Multi-Vendor WiFi SSID & Password Changer (Huawei / Nokia / FiberHome / ZTE)
|
|
||||||
|
|
||||||
### Perbaikan Ganti SSID & Password WiFi Multi-Vendor
|
|
||||||
- Mengatasi kegagalan ganti password TR-069 akibat penumpukan multiple parameter paths dalam satu payload SOAP atomik.
|
|
||||||
- Menghindari pengiriman `PreSharedKey.1.PreSharedKey` (format hex 64 digit) bersamaan dengan `KeyPassphrase`, yang sebelumnya memicu SOAP fault 9007 (Invalid Parameter Value) di modem.
|
|
||||||
- **Deteksi Vendor Otomatis (`HWTC`, `FiberHome`, `Nokia`, `ZTE`, `ZICG`, `CIOT`, `CDTC`):**
|
|
||||||
- Huawei / FiberHome / Nokia: memprioritaskan `PreSharedKey.1.KeyPassphrase` (menghindari fault 9007 karena penolakan `KeyPassphrase` top-level).
|
|
||||||
- ZTE / ZICG / CIOT: memprioritaskan `KeyPassphrase`.
|
|
||||||
- **SSID 2.4G & 5G:** Mengirim path tunggal yang tepat untuk 2.4 GHz (`WLANConfiguration.1.SSID`) dan 5 GHz (`WLANConfiguration.5.SSID`) jika perangkat dual-band, tanpa menimpa interface guest/sekunder.
|
|
||||||
- Fallback cerdas berbasis standar TR-098 per vendor.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- `services/customerDeviceService.js`
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cp /opt/billing-rtrw/services/customerDeviceService.js.bak-wififix /opt/billing-rtrw/services/customerDeviceService.js
|
|
||||||
sudo systemctl restart billing-rtrw
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.1] — 2026-09-27 16:30 — 5-Tab Floating Dock, PPOB Soon, Profil Tiket & WhatsApp Anti-Ban
|
|
||||||
|
|
||||||
### 1. Floating Dock 5-Tab (Dashboard Pelanggan)
|
|
||||||
- Menghilangkan tombol tengah (+) Aksi Cepat dan popup modal sheet.
|
|
||||||
- Menjadikan navbar 5 tombol langsung: Beranda, Router, PPOB (badge gradient "Soon"), Tagihan (ping unread), dan Profil.
|
|
||||||
- Pusat Bantuan & Pembuatan Tiket Kendala diintegrasikan ke dalam Tab Profil (`tab-profile`) dengan tombol cepat ke form pelaporan dan WhatsApp CS.
|
|
||||||
- Ditambahkan tombol "Kembali ke Profil" di halaman tiket bantuan (`tab-bantuan`).
|
|
||||||
- Banner "Layanan PPOB Segera Hadir" pada tab PPOB (`tab-ppob`).
|
|
||||||
|
|
||||||
### 2. Sinkronisasi Upstream & Fix Anti-Ban WhatsApp
|
|
||||||
- Verifikasi repository upstream `alijayanet/billing-rtrw`: base commit `ffd3c62` sudah mencakup seluruh perbaikan OLT Hioso (gap-fill DDM, Buffer UTF-8 decoding, slow SNMP session) dan timezone midnight hourCycle h23.
|
|
||||||
- Menambahkan proteksi anti-ban WhatsApp pada alert monitoring (`services/whatsappBot.mjs`):
|
|
||||||
- Mematuhi toggle `genieacs_monitoring_enabled` (tidak kirim alert jika toggle OFF).
|
|
||||||
- Jeda acak (8-20 detik) antar penerima saat kirim broadcast alert ke admin/teknisi.
|
|
||||||
- Menjadwalkan pemeriksaan monitoring harian pada jam 07:00 pagi (`config/genieacs.js`) lewat `node-cron` untuk mencegah spam alert berulang.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- `views/dashboard.ejs`
|
|
||||||
- `services/whatsappBot.mjs`
|
|
||||||
- `config/genieacs.js`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.0] — 2026-09-26 22:55 — Floating Dock Navbar + CDN Pin + SW v5
|
|
||||||
|
|
||||||
Perubahan UI besar (navbar) sekaligus perbaikan regresi PWA/offline.
|
|
||||||
|
|
||||||
### Navbar: floating glass dock
|
|
||||||
- Bottom nav lama (waterdrop, 6 tombol horizontal) diganti **floating glass
|
|
||||||
dock** sesuai referensi `adaptive_glassmorphism_floating_dock.html`.
|
|
||||||
- Struktur: 4 tombol langsung (**Beranda, Router, Tagihan, Profil**) + 1 tombol
|
|
||||||
tengah `+` yang membuka **quick-action sheet** berisi
|
|
||||||
**PPOB, Bantuan, Reconnect, Poll ONU**.
|
|
||||||
PPOB & Bantuan sengaja dipindah ke sheet agar dock tidak terlalu padat.
|
|
||||||
- ** keenam tab tetap bisa diakses**, `id="tab-*"` tidak berubah:
|
|
||||||
`tab-beranda`, `tab-router`, `tab-ppob`, `tab-tagihan`, `tab-bantuan`,
|
|
||||||
`tab-profile`.
|
|
||||||
- CSS baru: `.dock-glass`, `.dock-pill` (sliding pill), `.dock-item`,
|
|
||||||
`.dock-icon`, quick-sheet overlay. `safe-area-inset-bottom` untuk iPhone.
|
|
||||||
- JS baru: `updatePillPosition()`, `toggleQuickSheet()`, `openQuickSheet()`,
|
|
||||||
`closeQuickSheet()`, state `currentTab`, `isSheetOpen`.
|
|
||||||
`switchTab()` sekarang menandai `.dock-item` aktif dan memindahkan pill.
|
|
||||||
- Support keyboard: angka `1`–`6` pindah tab, `Escape` menutup sheet.
|
|
||||||
- Ikon memakai **Lucide** (`data-lucide`) + `lucide.createIcons()`.
|
|
||||||
- CSS `.waterdrop-active` yang tidak terpakai lagi dibuang.
|
|
||||||
|
|
||||||
### Perbaikan regresi offline/PWA
|
|
||||||
- **Semua CDN dashboard kini di-pin ke versi tetap** (sebelumnya `@latest` /
|
|
||||||
tanpa versi → bisa berubah atau break tanpa notice):
|
|
||||||
|
|
||||||
||Aset|URL|
|
|
||||||
|---|---|
|
|
||||||
|Lucide|`https://unpkg.com/lucide@1.48.0/dist/umd/lucide.min.js`|
|
|
||||||
|Chart.js|`https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js`|
|
|
||||||
|Tailwind|`https://cdn.tailwindcss.com/3.4.17`|
|
|
||||||
|
|
||||||
`https://unpkg.com/lucide@latest` dan `https://cdn.tailwindcss.com`
|
|
||||||
(tanpa versi) sama-sama **redirect 302** tiap load → di-pin ke URL final.
|
|
||||||
- **Bug:** service worker hanya cache-first `cdn.jsdelivr.net`, sehingga
|
|
||||||
Tailwind, FontAwesome, Google Fonts, dan Lucide **tidak tersedia saat
|
|
||||||
offline** — layout PWA hancur. Sekarang `sw.js` punya array `CDN_HOSTS`:
|
|
||||||
`cdn.jsdelivr.net`, `unpkg.com`, `cdnjs.cloudflare.com`,
|
|
||||||
`cdn.tailwindcss.com`, `fonts.googleapis.com`, `fonts.gstatic.com`.
|
|
||||||
- Cache bust: `CACHE_NAME` / `CDN_CACHE` `customer-pwa-v4` → **`-v5`**, plus
|
|
||||||
5 URL baru masuk `PRECACHE_URLS`.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- `views/dashboard.ejs` (1481 baris)
|
|
||||||
- `public/sw.js` (173 baris)
|
|
||||||
|
|
||||||
### Verifikasi
|
|
||||||
- Render EJS OK, `div` seimbang `160/160`.
|
|
||||||
- Live `/customer/dashboard` HTTP 200, `div` seimbang `184/184`.
|
|
||||||
- Semua CDN membalas 200 tanpa redirect.
|
|
||||||
- `systemctl is-active billing-rtrw` = `active`, journal tanpa error.
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cd /opt/billing-rtrw
|
|
||||||
cp views/dashboard.ejs.bak-twpin-225510 views/dashboard.ejs
|
|
||||||
cp public/sw.js.bak-twpin-225510 public/sw.js
|
|
||||||
sudo systemctl restart billing-rtrw
|
|
||||||
```
|
|
||||||
|
|
||||||
### Catatan
|
|
||||||
- Tampilan visual (sliding pill, animasi sheet, posisi icon) **belum dicek di
|
|
||||||
browser sungguhan** — verifikasi di atas semuanya server-side.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.8.2] — 2026-09-26 22:30 — Fix flash message toast
|
|
||||||
|
|
||||||
- `notif` dari server sebelumnya tidak tampil. Kini di-render sebagai variabel
|
|
||||||
`FLASH` (`{ text, type }`) lalu dimunculkan lewat `#toast-container`.
|
|
||||||
- Pemetaan tipe: `danger → error`, `warning → info`, selain itu `info`.
|
|
||||||
- Render diproteksi dari injeksi `</script>` via `.replace(/</g, '\u003c')`.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- `views/dashboard.ejs`
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cp views/dashboard.ejs.bak-flashfix-20260926221014 views/dashboard.ejs
|
|
||||||
sudo systemctl restart billing-rtrw
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.8.1] — 2026-09-26 22:10 — Fix route `/customer/reboot` + ACS Connection Request 401
|
|
||||||
|
|
||||||
### 1. Route reboot
|
|
||||||
- `POST /customer/reboot` gagal auth karena hanya memakai `session.phone`
|
|
||||||
sebagai token device.
|
|
||||||
- Kini memakai `buildCustomerDeviceTokens()` sehingga mencoba seluruh kandidat
|
|
||||||
token (tag, username, serial, MAC, IP).
|
|
||||||
|
|
||||||
### 2. Connection Request 401 (fix kritis)
|
|
||||||
- ACS membalas **HTTP 401** saat perangkat menjalankan Connection Request karena
|
|
||||||
`ConnectionRequestUsername` / `ConnectionRequestPassword` belum diset.
|
|
||||||
- Diperbaiki dengan `provisionConnectionRequestAuth(deviceId)` di
|
|
||||||
`services/acsServerService.js`, dipanggil dari `handleInform()`.
|
|
||||||
- Alur: Basic challenge → 401 → **Digest auth → HTTP 200**.
|
|
||||||
- Default username `skyfiber`, password = `crypto.randomBytes(12)` hex per
|
|
||||||
perangkat, disimpan di database.
|
|
||||||
- Berlaku hanya untuk perangkat yang sedang online; 3 perangkat offline akan
|
|
||||||
ter-provisioning otomatis saat melakukan Inform.
|
|
||||||
|
|
||||||
### Hasil
|
|
||||||
- 102/105 perangkat sukses di-provisioning, 0 provisioning task gagal.
|
|
||||||
- Perubahan SSID/reboot kini berlaku ~10 detik (sebelumnya gagal).
|
|
||||||
- 3 perangkat offline akan ter-provisioning otomatis saat Inform berikutnya.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- `routes/customerPortal.js` (3967 baris)
|
|
||||||
- `services/acsServerService.js` (1711 baris)
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cd /opt/billing-rtrw
|
|
||||||
cp routes/customerPortal.js.bak-rebootfix-20260926221014 routes/customerPortal.js
|
|
||||||
cp services/acsServerService.js.bak-crfix-20260926221014 services/acsServerService.js
|
|
||||||
sudo systemctl restart billing-rtrw
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.8.0] — 2026-09-26 18:04–21:07 — Redesign dashboard (APPS-style)
|
|
||||||
|
|
||||||
Redesain tampilan dashboard pelanggan dengan gaya APPS.
|
|
||||||
|
|
||||||
### Yang ditambahkan
|
|
||||||
- **Banner promo** dari database (`GET /customer/api/promo-slides`).
|
|
||||||
- **Chart pemakaian isolate-day** (maks. 3 bulan riwayat), menggantikan
|
|
||||||
polling traffic realtime yang sebelumnya membebani server.
|
|
||||||
Helper: `buildUsageSummary(profile, activePackage)`.
|
|
||||||
- **LibreSpeed** speedtest inline (sub-router `section-speedtest`).
|
|
||||||
- **Quick-action Reconnect** (`POST /customer/customer/reconnect`) dan
|
|
||||||
**Poll ONU** (`POST /customer/api/device/refresh`).
|
|
||||||
- Sub-tab router: **WiFi**, **Speedtest**, **Pemakaian**.
|
|
||||||
- Tampilan AXS/PPOB (`/customer/ppob`, `/customer/topup`).
|
|
||||||
- Link WhatsApp admin otomatis ke nomor pertama di `settings.whatsapp_admin_numbers`.
|
|
||||||
- Tema persisten via `localStorage`.
|
|
||||||
- Tombol toggle lihat IP (`isIpVisible`).
|
|
||||||
|
|
||||||
### Perbaikan
|
|
||||||
- Semua endpoint palsu `/api/customer/*` diarahkan ke route `/customer/*` asli.
|
|
||||||
- Badge **GPON**: merah `Bad GPON` bila ONU tidak terdeteksi **atau** PPPoE offline.
|
|
||||||
- Form WiFi/ONT otomatis disabled bila ONU absent, dengan pesan penjelasan.
|
|
||||||
- Variabel yang dihitung defensif di ketiga cabang render:
|
|
||||||
`hasUnpaid`, `latestUnpaid`, `latestInvoice`, `pppoeIP`, `pppoeUptime`,
|
|
||||||
`stMaxMbps`.
|
|
||||||
- Penghapusan modal Bootstrap yang rusak.
|
|
||||||
- Layout mobile dirapikan.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- `views/dashboard.ejs`
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cp views/dashboard.ejs.bak-prefn-20260926210734 views/dashboard.ejs
|
|
||||||
# atau ke markup/endpoint stabil terakhir:
|
|
||||||
cp views/dashboard.ejs.bak-pre-newdesign-20260926181219 views/dashboard.ejs
|
|
||||||
sudo systemctl restart billing-rtrw
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.7.0] — 2026-09-26 12:55–14:24 — Self-registration, tabular layout, PWA
|
|
||||||
|
|
||||||
- **Self-registration** (`/customer/register`) + pairing tag perangkat
|
|
||||||
(`POST /customer/change-tag`).
|
|
||||||
- Tata letak tabel Payments/Fitur dikembalikan ke mode tabular
|
|
||||||
(`.bak-tabrapikan`, `.bak-tabcss`).
|
|
||||||
- Toggle lihat/sembunyikan password WiFi.
|
|
||||||
- Tampilan jatuh tempo (due) tagihan.
|
|
||||||
- Struktur `div` diperbaiki (tag tidak seimbang) — `.bak-divfix`.
|
|
||||||
- Navbar sticky di mobile — `.bak-sticky`.
|
|
||||||
- **Service worker** diperbaiki & di-cache ulang — `.bak-swfix`, `sw3`.
|
|
||||||
- **`rxPower` ACS** ditangani di `services/acsServerService.js`.
|
|
||||||
|
|
||||||
### File disentuh
|
|
||||||
- `views/dashboard.ejs`, `routes/customerPortal.js`, `public/sw.js`,
|
|
||||||
`services/acsServerService.js`
|
|
||||||
|
|
||||||
### Rollback
|
|
||||||
```bash
|
|
||||||
cd /opt/billing-rtrw
|
|
||||||
cp views/dashboard.ejs.bak-divfix-20260926142250 views/dashboard.ejs
|
|
||||||
cp public/sw.js.bak-swfix-20260926142327 public/sw.js
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Daftar endpoint yang dipakai dashboard
|
|
||||||
|
|
||||||
Semua Calls dari `views/dashboard.ejs` (sudah diverifikasi terhadap router):
|
|
||||||
|
|
||||||
| Aksi | Method | Endpoint |
|
|
||||||
|---|---|---|
|
|
||||||
| Promo slides | GET | `/customer/api/promo-slides` |
|
|
||||||
| Poll ONU / refresh | POST | `/customer/api/device/refresh` |
|
|
||||||
| Reconnect | POST | `/customer/customer/reconnect` |
|
|
||||||
| Ganti SSID | POST | `/customer/change-ssid` |
|
|
||||||
| Ganti password | POST | `/customer/change-password` |
|
|
||||||
| Reboot | POST | `/customer/reboot` |
|
|
||||||
| Buat tiket (+foto) | POST | `/customer/tickets/create` (multipart) |
|
|
||||||
| Bayar invoice | GET | `/customer/payment/create/:id` |
|
|
||||||
| PPOB | GET | `/customer/ppob` |
|
|
||||||
| Top up | GET | `/customer/topup` |
|
|
||||||
| Logout | GET | `/customer/logout` |
|
|
||||||
|
|
||||||
> Catatan: **tidak ada** route `/customer/poll-onu`. Tombol "Poll ONU"
|
|
||||||
> memanggil `POST /customer/api/device/refresh`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## TODO / tindak lanjut
|
|
||||||
|
|
||||||
Detail lengkap ada di `/home/oxpoe/Documents/CATATAN-OPS-SKYFIBER.txt`
|
|
||||||
section 11 (mode `600`, jangan dibagikan).
|
|
||||||
|
|
||||||
### Keamanan — perlu tindakan
|
|
||||||
- [ ] Rotasi `telegram_bot_token` (via `@BotFather` → `/revoke`; token **baru**
|
|
||||||
jangan dikirim ke chat mana pun).
|
|
||||||
- [ ] Rotasi `admin_password`.
|
|
||||||
- [ ] Rotasi `admin_api_key`.
|
|
||||||
- [ ] Amankan backup `backups/settings_20260926_211113.json` (masih memuat
|
|
||||||
token lama) atau hapus setelah rotasi.
|
|
||||||
- [ ] Inisialisasi Git (`git init` + commit awal) supaya changelog berikutnya
|
|
||||||
punya riwayat & diff.
|
|
||||||
|
|
||||||
### Fungsional
|
|
||||||
- [ ] **Password WiFi pelanggan uji masih nilai sementara** dan harus
|
|
||||||
dikembalikan ke nilai asli (nilai uji tercatat di notes section 11).
|
|
||||||
- [ ] 3 perangkat belum punya CR credentials, akan provisioning otomatis saat
|
|
||||||
online:
|
|
||||||
- `001141-CL601-ZTEGCBBFB774`
|
|
||||||
- `B4E46B-SIGMA Z99LX XPON-ZICG27766207`
|
|
||||||
- `B4E46B-SIGMA Z99LX XPON-ZICG27766F6A`
|
|
||||||
- [ ] Alur pembayaran belum diuji end-to-end (butuh invoice uji).
|
|
||||||
- [ ] Cek visual floating dock di browser/HP sungguhan.
|
|
||||||
- [ ] Test PWA mode offline (SW v5) — belum pernah diuji.
|
|
||||||
- [ ] Keputusan: apakah FUP enforcement mengikuti isolate-day.
|
|
||||||
- [ ] Parity peta admin desktop vs mobile.
|
|
||||||
- [ ] Pemetaan kecepatan WiFi 2.4GHz vs 5GHz.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cara rollback umum
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /opt/billing-rtrw
|
|
||||||
sudo systemctl stop billing-rtrw
|
|
||||||
# salin backup yang dikehendali ke file aslinya
|
|
||||||
sudo systemctl start billing-rtrw
|
|
||||||
sudo journalctl -u billing-rtrw -n 50 --no-pager
|
|
||||||
```
|
|
||||||
|
|
||||||
Backup tersedia (format `.bak-<label>-<timestamp>`):
|
|
||||||
|
|
||||||
| File | Backup |
|
|
||||||
|---|---|
|
|
||||||
| `views/dashboard.ejs` | `.bak-rxpower`, `.bak-selfapp-*`, `.bak-akun-*`, `.bak-due-*`, `.bak-tabcss-*`, `.bak-usage-ui-*`, `.bak-togglepass-*`, `.bak-tabrapikan-*`, `.bak-mobile-*`, `.bak-usageview-*`, `.bak-usagehelper-*`, `.bak-tailadmin-*`, `.bak-divfix-*`, `.bak-sticky-*`, `.bak-prefn-*`, `.bak-pre-newdesign-*`, `.bak-flashfix-*`, `.bak-dock-*`, `.bak-cdnpin-*`, `.bak-twpin-*` |
|
|
||||||
| `routes/customerPortal.js` | `.bak-selfapp-*`, `.bak-usageview-*`, `.bak-usagehelper-*`, `.bak-akun-*`, `.bak-rebootfix-*` |
|
|
||||||
| `services/acsServerService.js` | `.bak-rxpower-*`, `.bak-crfix-*` |
|
|
||||||
| `public/sw.js` | `.bak-selfapp-*`, `.bak-sw3-*`, `.bak-swfix-*`, `.bak-v4-*`, `.bak-twpin-*` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.8] - 2026-09-28: Rombak Total UI/UX Multi-Portal (5-Color Modern Design System)
|
|
||||||
|
|
||||||
### Ringkasan Rilis
|
|
||||||
Rombak visual dan interaksi total pada seluruh portal aplikasi (Landing Page/Pelanggan, Customer PWA Dashboard, Admin Portal, Teknisi, Kolektor, dan Agen) menggunakan palette 5-warna konsisten, tipografi Google Fonts universal, elemen cyber-glassmorphism terinspirasi dari uiverse.io, dan panel KPI dense terinspirasi dari Gentelella.
|
|
||||||
|
|
||||||
### Design System & Palette
|
|
||||||
- **Dark 1 (Latar Utama)**: `#161A21`
|
|
||||||
- **Dark 2 (Gelap Sedang / Surfaces & Cards)**: `#1F2420`
|
|
||||||
- **Dark 3 (Abu-abu Gelap / Aksesori & Borders)**: `#2A313C`
|
|
||||||
- **Accent Lime (Aksen Hijau Neon)**: `#ADFF2F`
|
|
||||||
- **Light (Putih Terang / Teks)**: `#F2F5F1`
|
|
||||||
- **Typography**:
|
|
||||||
- `Plus Jakarta Sans`: Landing, PWA Pelanggan, Heading & Kartu
|
|
||||||
- `Inter`: Admin tabel, form controls, dense grid
|
|
||||||
- `JetBrains Mono`: Angka teknis (IP PPPoE, MAC, Serial, RX/TX Optical Power, Speed, Bandwidth)
|
|
||||||
|
|
||||||
### Modul yang Dirombak
|
|
||||||
1. **Landing Page & Customer Login (`views/login.ejs`)**:
|
|
||||||
- Cyber-glassmorphism card hero, paket internet (`#packages`), fitur (`#features`), layanan (`#services`), contact.
|
|
||||||
- PWA Install Modal dengan status online/offline otomatis.
|
|
||||||
2. **Halaman Isolir / Walled Garden (`views/isolated.ejs`)**:
|
|
||||||
- Palette `#161A21`, `#1F2420`, `#2A313C`, dan aksen neon `#ADFF2F`.
|
|
||||||
3. **Customer PWA Dashboard (`views/dashboard.ejs` & `views/partials/customer_bottom_nav.ejs`)**:
|
|
||||||
- Floating Glass Dock 5-Tab (`#1F2420` glass) dengan sliding active pill neon lime (`#ADFF2F`).
|
|
||||||
- Kartu status PPPoE/ONU dengan indikator neon pulse, telemetry optik, ganti SSID/password, speedtest gauge, dan Chart.js riwayat pemakaian.
|
|
||||||
4. **Admin Portal (`public/css/admin.css` & `views/admin/*`)**:
|
|
||||||
- Update CSS variables: `--bg: #161A21`, `--bg2: #1F2420`, `--bg3: #2A313C`, `--primary: #ADFF2F`, `--text: #F2F5F1`.
|
|
||||||
- Sidebar Gentelella modern dengan indikator aktif neon lime.
|
|
||||||
- KPI Tile Stats (`.tile_stats_count`) pada Dashboard, Customers, dan Billing.
|
|
||||||
- Perbaikan dark theme pada `views/admin/olts.ejs`, `monitoring.ejs`, `settings.ejs`, dan `login.ejs`.
|
|
||||||
5. **Technician Portal (`views/tech/*`)**:
|
|
||||||
- `views/tech/partials/header.ejs`, `views/tech/partials/bottom_nav.ejs`, `views/tech/dashboard.ejs`, `monitoring.ejs`, `attendance.ejs`, `map.ejs`, dan `login.ejs`.
|
|
||||||
6. **Collector & Agent Portals (`views/collector/*`, `views/agent/*`, `public/css/theme.css`)**:
|
|
||||||
- `public/css/theme.css` sebagai global theme provider untuk tema gelap 5-warna.
|
|
||||||
- Modernisasi login dan dashboard collector & agent.
|
|
||||||
|
|
||||||
### Verifikasi & Integritas
|
|
||||||
- 88 dari 88 template EJS aktif berhasil dikompilasi dengan `ejs.compile` (0 error).
|
|
||||||
- Seluruh endpoint HTTP menghasilkan kode `200 OK` / valid redirect.
|
|
||||||
- Database SQLite integrity check: `ok`.
|
|
||||||
- Service `billing-rtrw.service` berjalan stabil di Systemd VPS Deneva.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.9] - 2026-09-28: Penghapusan Total Komponen Light Mode (Konsistensi Eksklusif Halogen Kit)
|
|
||||||
|
|
||||||
### Ringkasan Perubahan
|
|
||||||
Menghapus seluruh komponen *light mode*, tombol pengalih tema (*theme toggle button / FAB*), script peralihan mode terang, dan aturan CSS override di semua portal aplikasi. Seluruh aplikasi kini konsisten 100% berjalan dalam **Mode Gelap Halogen Kit**:
|
|
||||||
1. **`public/js/theme.js`**: Menghapus fungsi injeksi tombol floating tema (`injectThemeFab`), fungsi `toggleAppTheme`, dan memaksa `data-theme=dark` di localStorage secara permanen.
|
|
||||||
2. **`public/css/theme.css`**: Menghapus seluruh blok aturan `html[data-theme=light]` dan styling tombol mengambang `.theme-fab`.
|
|
||||||
3. **Admin Sidebar (`views/admin/partials/sidebar.ejs`)`**: Menghapus tombol toggle tema pada bagian brand/header sidebar dan menyederhanakan inisialisasi dark mode.
|
|
||||||
4. **Customer PWA Dashboard (`views/dashboard.ejs`)`**: Menghapus tombol pengalih tema (`#theme-toggle-btn`) pada header topbar, menghapus fungsi `toggleTheme()` / `applyTheme()`, dan membersihkan seluruh override class CSS `html.light`.
|
|
||||||
5. **Verifikasi**: Seluruh 88 template EJS tervalidasi 0 error dan service berjalan stabil.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [v1.9.9] - 2026-09-28: Penghapusan Total Komponen Light Mode (Konsistensi Eksklusif Halogen Kit)
|
|
||||||
|
|
||||||
### Ringkasan Perubahan
|
|
||||||
Menghapus seluruh komponen *light mode*, tombol pengalih tema (*theme toggle button / FAB*), script peralihan mode terang, dan aturan CSS override `html.light` di semua portal aplikasi. Seluruh aplikasi kini konsisten 100% berjalan dalam **Mode Gelap Halogen Kit**:
|
|
||||||
1. **`public/js/theme.js`**: Menghapus fungsi injeksi tombol floating tema (`injectThemeFab`), fungsi `toggleAppTheme`, dan memaksa `data-theme="dark"` di localStorage secara permanen.
|
|
||||||
2. **`public/css/theme.css`**: Menghapus seluruh blok aturan `html[data-theme="light"]` dan styling tombol mengambang `.theme-fab`.
|
|
||||||
3. **Admin Sidebar (`views/admin/partials/sidebar.ejs`)**: Menghapus tombol toggle tema pada bagian brand/header sidebar dan menyederhanakan inisialisasi dark mode.
|
|
||||||
4. **Customer PWA Dashboard (`views/dashboard.ejs`)**: Menghapus tombol pengalih tema (`#theme-toggle-btn`) pada header topbar, menghapus fungsi `toggleTheme()` / `applyTheme()`, dan membersihkan seluruh override class CSS `html.light`.
|
|
||||||
5. **Verifikasi**: Seluruh 88 template EJS tervalidasi 0 error dan service berjalan stabil.
|
|
||||||
@@ -14,7 +14,7 @@ let TARGET = process.env.ISOLIR_REDIRECT_URL || '';
|
|||||||
|
|
||||||
// Coba ambil target dari settings.json bila tersedia
|
// Coba ambil target dari settings.json bila tersedia
|
||||||
try {
|
try {
|
||||||
const { getSetting } = require('/opt/billing-rtrw/config/settingsManager');
|
const { getSetting } = require('./config/settingsManager');
|
||||||
const s = getSetting('isolir_portal_url', '') || '';
|
const s = getSetting('isolir_portal_url', '') || '';
|
||||||
if (s) TARGET = s;
|
if (s) TARGET = s;
|
||||||
} catch (_) { /* abaikan */ }
|
} catch (_) { /* abaikan */ }
|
||||||
@@ -37,6 +37,8 @@ const server = http.createServer((req, res) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.on('error', (e) => console.error('[isolir-redirect] listen error:', e.message));
|
||||||
|
|
||||||
server.listen(PORT, '0.0.0.0', () => {
|
server.listen(PORT, '0.0.0.0', () => {
|
||||||
console.log(`[isolir-redirect] listening on 0.0.0.0:${PORT} -> ${TARGET}`);
|
console.log(`[isolir-redirect] listening on 0.0.0.0:${PORT} -> ${TARGET}`);
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-1
@@ -1052,6 +1052,7 @@ router.get('/', async (req, res) => {
|
|||||||
model: d._deviceId?._ProductClass || '-',
|
model: d._deviceId?._ProductClass || '-',
|
||||||
rx_power: rxPower,
|
rx_power: rxPower,
|
||||||
pppoe_ip: ip,
|
pppoe_ip: ip,
|
||||||
|
ip: ip,
|
||||||
pppoe_user: pppoeUser,
|
pppoe_user: pppoeUser,
|
||||||
customer_name: customerName,
|
customer_name: customerName,
|
||||||
acs_server_id: server.id,
|
acs_server_id: server.id,
|
||||||
@@ -1317,7 +1318,7 @@ router.put('/api/servers/legacy', requireAdmin, express.json(), async (req, res)
|
|||||||
if (clearPassword) currentSettings.genieacs_password = '';
|
if (clearPassword) currentSettings.genieacs_password = '';
|
||||||
else if (String(passwordInput || '').trim()) currentSettings.genieacs_password = String(passwordInput || '');
|
else if (String(passwordInput || '').trim()) currentSettings.genieacs_password = String(passwordInput || '');
|
||||||
|
|
||||||
const settingsPath = path.join(__dirname, '../settings.json');
|
const settingsPath = require('../config/paths').settingsPath;
|
||||||
fs.writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2), 'utf8');
|
fs.writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2), 'utf8');
|
||||||
|
|
||||||
res.json({ success: true, message: 'Default ACS berhasil diperbarui.' });
|
res.json({ success: true, message: 'Default ACS berhasil diperbarui.' });
|
||||||
|
|||||||
+11
-5
@@ -384,7 +384,9 @@ function getUpdateInfo(repoRoot) {
|
|||||||
|
|
||||||
const inside = runCmd('git', ['rev-parse', '--is-inside-work-tree'], repoRoot);
|
const inside = runCmd('git', ['rev-parse', '--is-inside-work-tree'], repoRoot);
|
||||||
if (!inside.ok) {
|
if (!inside.ok) {
|
||||||
info.error = 'Folder ini belum menjadi git repository.';
|
// Tanpa .git (Docker/container): update dilakukan via rebuild image (bukan git)
|
||||||
|
info.container = true;
|
||||||
|
info.containerMessage = 'Instalasi ini berjalan sebagai kontainer/gambar (tanpa .git). Pembaruan dilakukan dengan membangun ulang image dari repo terbaru.';
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4554,6 +4556,10 @@ router.get('/update', requireAdminSession, requireSidebarMenuAccess('update'), r
|
|||||||
|
|
||||||
router.post('/update/run', requireAdminSession, restrictToAdmin, (req, res) => {
|
router.post('/update/run', requireAdminSession, restrictToAdmin, (req, res) => {
|
||||||
const repoRoot = path.resolve(__dirname, '..');
|
const repoRoot = path.resolve(__dirname, '..');
|
||||||
|
if (!runCmd('git', ['rev-parse', '--is-inside-work-tree'], repoRoot).ok) {
|
||||||
|
req.session._msg = { type: 'error', text: 'Instalasi kontainer (tanpa .git): pembaruan dilakukan dengan rebuild image, bukan lewat git.' };
|
||||||
|
return res.redirect('/admin/update');
|
||||||
|
}
|
||||||
const log = [];
|
const log = [];
|
||||||
const pushCmd = (label, r) => {
|
const pushCmd = (label, r) => {
|
||||||
log.push(`$ ${label}`.trim());
|
log.push(`$ ${label}`.trim());
|
||||||
@@ -5234,7 +5240,7 @@ router.post('/api/genieacs/settings', requireAdmin, async (req, res) => {
|
|||||||
// Save to file
|
// Save to file
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const settingsPath = path.join(__dirname, '../settings.json');
|
const settingsPath = require('../config/paths').settingsPath;
|
||||||
fs.writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2), 'utf8');
|
fs.writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2), 'utf8');
|
||||||
|
|
||||||
// Log audit
|
// Log audit
|
||||||
@@ -6496,7 +6502,7 @@ router.get('/whatsapp/gowa/qr.png', requireAdminSession, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/whatsapp', requireAdminSession, requireSidebarMenuAccess('whatsapp'), async (req, res) => {
|
router.get('/whatsapp', requireAdminSession, requireSidebarMenuAccess('whatsapp'), async (req, res) => {
|
||||||
const waGatewayType = getSetting('wa_gateway_type', 'baileys');
|
const waGatewayType = getSetting('wa_gateway_type', 'gowa');
|
||||||
const metaSettings = {
|
const metaSettings = {
|
||||||
meta_phone_number_id: getSetting('meta_phone_number_id', ''),
|
meta_phone_number_id: getSetting('meta_phone_number_id', ''),
|
||||||
meta_waba_id: getSetting('meta_waba_id', ''),
|
meta_waba_id: getSetting('meta_waba_id', ''),
|
||||||
@@ -6611,7 +6617,7 @@ router.post('/whatsapp/test-fonnte', requireAdminSession, express.json(), async
|
|||||||
|
|
||||||
// LIVE CHAT ROUTES
|
// LIVE CHAT ROUTES
|
||||||
router.get('/whatsapp/live-chat', requireAdminSession, requireSidebarMenuAccess('whatsapp'), async (req, res) => {
|
router.get('/whatsapp/live-chat', requireAdminSession, requireSidebarMenuAccess('whatsapp'), async (req, res) => {
|
||||||
const waGatewayType = getSetting('wa_gateway_type', 'baileys');
|
const waGatewayType = getSetting('wa_gateway_type', 'gowa');
|
||||||
const customers = customerSvc.getAllCustomers();
|
const customers = customerSvc.getAllCustomers();
|
||||||
res.render('admin/whatsapp_live_chat', {
|
res.render('admin/whatsapp_live_chat', {
|
||||||
title: 'Live Chat WhatsApp',
|
title: 'Live Chat WhatsApp',
|
||||||
@@ -7075,7 +7081,7 @@ router.post('/whatsapp/run-reminder-now', requireAdminSession, async (req, res)
|
|||||||
|
|
||||||
router.get('/api/whatsapp/status', requireAdmin, async (req, res) => {
|
router.get('/api/whatsapp/status', requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const gatewayType = getSetting('wa_gateway_type', 'baileys');
|
const gatewayType = getSetting('wa_gateway_type', 'gowa');
|
||||||
if (gatewayType === 'fonnte') {
|
if (gatewayType === 'fonnte') {
|
||||||
const token = getSetting('fonnte_token', '');
|
const token = getSetting('fonnte_token', '');
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -2088,7 +2088,7 @@ router.get('/app/admin/olts', requireAdminApiAuth, (req, res) => {
|
|||||||
// ─── ADMIN NATIVE: WHATSAPP STATUS ───────────────────────────────────────────
|
// ─── ADMIN NATIVE: WHATSAPP STATUS ───────────────────────────────────────────
|
||||||
router.get('/app/admin/whatsapp/status', requireAdminApiAuth, async (req, res) => {
|
router.get('/app/admin/whatsapp/status', requireAdminApiAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const gwType = String(require('../config/settingsManager').getSetting('wa_gateway_type', 'baileys') || 'baileys').toLowerCase();
|
const gwType = String(require('../config/settingsManager').getSetting('wa_gateway_type', 'gowa') || 'baileys').toLowerCase();
|
||||||
let isConnected = false;
|
let isConnected = false;
|
||||||
let waNumber = '-';
|
let waNumber = '-';
|
||||||
let waName = gwType.toUpperCase();
|
let waName = gwType.toUpperCase();
|
||||||
@@ -2190,7 +2190,7 @@ router.post('/app/admin/settings/update', requireAdminApiAuth, (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const allowed = ['company_header', 'company_subheader', 'company_phone', 'company_address', 'timezone', 'qris_enabled', 'auto_isolate_enabled', 'webhook_secret'];
|
const allowed = ['company_header', 'company_subheader', 'company_phone', 'company_address', 'timezone', 'qris_enabled', 'auto_isolate_enabled', 'webhook_secret'];
|
||||||
const updates = req.body || {};
|
const updates = req.body || {};
|
||||||
const settingsPath = require('path').join(__dirname, '../settings.json');
|
const settingsPath = require('../config/paths').settingsPath;
|
||||||
const current = JSON.parse(require('fs').readFileSync(settingsPath, 'utf8'));
|
const current = JSON.parse(require('fs').readFileSync(settingsPath, 'utf8'));
|
||||||
let changed = 0;
|
let changed = 0;
|
||||||
for (const key of allowed) {
|
for (const key of allowed) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const { getSetting, getCurrentDateInTimezone, getNowLocalISO } = require('../con
|
|||||||
const projectRoot = path.join(__dirname, '..');
|
const projectRoot = path.join(__dirname, '..');
|
||||||
const backupDir = path.join(projectRoot, 'backups');
|
const backupDir = path.join(projectRoot, 'backups');
|
||||||
const dbPath = path.join(projectRoot, 'database', 'billing.db');
|
const dbPath = path.join(projectRoot, 'database', 'billing.db');
|
||||||
const settingsPath = path.join(projectRoot, 'settings.json');
|
const { settingsPath } = require('../config/paths');
|
||||||
|
|
||||||
// Pastikan direktori backup ada
|
// Pastikan direktori backup ada
|
||||||
if (!fs.existsSync(backupDir)) {
|
if (!fs.existsSync(backupDir)) {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ async function runAutoBillingReminder() {
|
|||||||
const billingEnabled = getSetting('whatsapp_billing_to_customer_enabled', true);
|
const billingEnabled = getSetting('whatsapp_billing_to_customer_enabled', true);
|
||||||
if (!enabled || !waEnabled || !billingEnabled) return { target: 0, sent: 0, failed: 0, skipped: 'disabled' };
|
if (!enabled || !waEnabled || !billingEnabled) return { target: 0, sent: 0, failed: 0, skipped: 'disabled' };
|
||||||
|
|
||||||
const gatewayType = getSetting('wa_gateway_type', 'baileys');
|
const gatewayType = getSetting('wa_gateway_type', 'gowa');
|
||||||
const waSvc = require('./whatsappService');
|
const waSvc = require('./whatsappService');
|
||||||
|
|
||||||
if (gatewayType === 'meta') {
|
if (gatewayType === 'meta') {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ async function checkDependencies() {
|
|||||||
mikrotik: [],
|
mikrotik: [],
|
||||||
genieacs: { status: 'unknown', message: '' },
|
genieacs: { status: 'unknown', message: '' },
|
||||||
whatsapp: { status: 'unknown', message: '' },
|
whatsapp: { status: 'unknown', message: '' },
|
||||||
|
telegram: { status: 'unknown', message: '' },
|
||||||
paymentGateways: [],
|
paymentGateways: [],
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
};
|
};
|
||||||
@@ -104,39 +105,54 @@ async function checkDependencies() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check WhatsApp Gateway (using whatsappStatus from whatsappBot)
|
// 3. Check WhatsApp Gateway (GoWA / Baileys sesuai setting wa_gateway_type)
|
||||||
try {
|
try {
|
||||||
// Import whatsappStatus dynamically to get real-time status
|
const { getSetting } = require('../config/settingsManager');
|
||||||
const whatsappBotModule = await import('./whatsappBot.mjs');
|
const gw = String(getSetting('wa_gateway_type', 'gowa') || 'baileys').toLowerCase();
|
||||||
const waStatus = whatsappBotModule.whatsappStatus;
|
if (gw === 'gowa') {
|
||||||
|
const base = String(getSetting('gowa_base_url', 'http://127.0.0.1:3000') || '').replace(/\/+$/, '');
|
||||||
// Check connection status
|
const u = getSetting('gowa_username', '');
|
||||||
// Possible values: 'connecting', 'qr', 'open', 'loggedOut', 'close'
|
const p = getSetting('gowa_password', '');
|
||||||
const isOnline = waStatus.connection === 'open';
|
const cfg = { timeout: 6000, validateStatus: (s) => s < 500 };
|
||||||
const isQR = waStatus.connection === 'qr';
|
if (u && p) cfg.auth = { username: u, password: p };
|
||||||
const isLoggedOut = waStatus.connection === 'loggedOut';
|
const resp = await axios.get(`${base}/app/status`, cfg);
|
||||||
|
const res = (resp.data && resp.data.results) || {};
|
||||||
let message = 'WhatsApp is disconnected';
|
const isOnline = !!res.is_connected;
|
||||||
if (isOnline) {
|
results.whatsapp = {
|
||||||
const phone = waStatus.user?.id ? String(waStatus.user.id).split(':')[0] : 'Unknown';
|
status: isOnline ? 'online' : 'offline',
|
||||||
message = `WhatsApp connected (${phone})`;
|
message: isOnline ? `GoWA connected (${res.jid || res.device_id || '-'})` : 'GoWA tidak terhubung'
|
||||||
} else if (isQR) {
|
};
|
||||||
message = 'WhatsApp waiting for QR scan';
|
|
||||||
} else if (isLoggedOut) {
|
|
||||||
message = 'WhatsApp logged out - scan QR again';
|
|
||||||
} else {
|
} else {
|
||||||
message = `WhatsApp ${waStatus.connection || 'disconnected'}`;
|
const whatsappBotModule = await import('./whatsappBot.mjs');
|
||||||
|
const waStatus = whatsappBotModule.whatsappStatus;
|
||||||
|
const isOnline = waStatus && waStatus.connection === 'open';
|
||||||
|
const isQR = waStatus && waStatus.connection === 'qr';
|
||||||
|
const isLoggedOut = waStatus && waStatus.connection === 'loggedOut';
|
||||||
|
let message = 'WhatsApp is disconnected';
|
||||||
|
if (isOnline) { const phone = waStatus.user && waStatus.user.id ? String(waStatus.user.id).split(':')[0] : 'Unknown'; message = `WhatsApp connected (${phone})`; }
|
||||||
|
else if (isQR) message = 'WhatsApp waiting for QR scan';
|
||||||
|
else if (isLoggedOut) message = 'WhatsApp logged out - scan QR again';
|
||||||
|
else message = `WhatsApp ${(waStatus && waStatus.connection) || 'disconnected'}`;
|
||||||
|
results.whatsapp = { status: isOnline ? 'online' : 'offline', message };
|
||||||
}
|
}
|
||||||
|
|
||||||
results.whatsapp = {
|
|
||||||
status: isOnline ? 'online' : 'offline',
|
|
||||||
message: message
|
|
||||||
};
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
results.whatsapp = {
|
results.whatsapp = { status: 'offline', message: `WhatsApp check error: ${err.message}` };
|
||||||
status: 'offline',
|
}
|
||||||
message: `WhatsApp check error: ${err.message}`
|
|
||||||
};
|
// 4. Check Telegram Bot
|
||||||
|
try {
|
||||||
|
const { getSetting } = require('../config/settingsManager');
|
||||||
|
const enabled = !!getSetting('telegram_enabled', false);
|
||||||
|
const token = String(getSetting('telegram_bot_token', '') || '').trim();
|
||||||
|
if (!enabled || !token) {
|
||||||
|
results.telegram = { status: 'offline', message: !enabled ? 'Telegram dinonaktifkan' : 'Token belum diisi' };
|
||||||
|
} else {
|
||||||
|
const resp = await axios.get(`https://api.telegram.org/bot${token}/getMe`, { timeout: 6000, validateStatus: (s) => s < 500 });
|
||||||
|
if (resp.data && resp.data.ok) results.telegram = { status: 'online', message: 'Terhubung sebagai @' + ((resp.data.result && resp.data.result.username) || '-') };
|
||||||
|
else results.telegram = { status: 'offline', message: (resp.data && resp.data.description) || 'getMe gagal' };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
results.telegram = { status: 'offline', message: `Telegram check error: ${err.message}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ async function sendFonnteMessage(toPhone, messageText, options = {}) {
|
|||||||
* Test Koneksi / Kirim Pesan Uji Coba Fonnte
|
* Test Koneksi / Kirim Pesan Uji Coba Fonnte
|
||||||
*/
|
*/
|
||||||
async function testFonnteConnection(targetPhone, customMessage) {
|
async function testFonnteConnection(targetPhone, customMessage) {
|
||||||
const msg = customMessage || '🔔 *Tes Koneksi Fonnte WhatsApp Gateway Berhasil!*\n\nSistem Billing RTRW Net siap mengirimkan notifikasi otomatis melalui gateway Fonnte.';
|
const msg = customMessage || '🔔 *Tes Koneksi Fonnte WhatsApp Gateway Berhasil!*\n\nOPEN-ISP siap mengirimkan notifikasi otomatis melalui gateway Fonnte.';
|
||||||
return await sendFonnteMessage(targetPhone, msg);
|
return await sendFonnteMessage(targetPhone, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1555,13 +1555,13 @@ async function generateIsolirPortalScript() {
|
|||||||
let port = 443;
|
let port = 443;
|
||||||
let isHttps = true;
|
let isHttps = true;
|
||||||
try {
|
try {
|
||||||
const u = new URL(normalized || 'http://127.0.0.1:4555');
|
const u = new URL(normalized || 'http://127.0.0.1:3001');
|
||||||
hostname = u.hostname;
|
hostname = u.hostname;
|
||||||
port = u.port ? parseInt(u.port, 10) : (u.protocol === 'https:' ? 443 : 80);
|
port = u.port ? parseInt(u.port, 10) : (u.protocol === 'https:' ? 443 : 80);
|
||||||
isHttps = u.protocol === 'https:';
|
isHttps = u.protocol === 'https:';
|
||||||
} catch {
|
} catch {
|
||||||
hostname = 'GANTI-host-portal-billing';
|
hostname = 'GANTI-host-portal-billing';
|
||||||
port = 4555;
|
port = 3001;
|
||||||
isHttps = false;
|
isHttps = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1724,7 +1724,7 @@ async function setupIsolirFirewall(routerId = null) {
|
|||||||
try {
|
try {
|
||||||
const raw = String(settings.app_url || '').trim();
|
const raw = String(settings.app_url || '').trim();
|
||||||
const normalized = raw && /^https?:\/\//i.test(raw) ? raw : (raw ? `https://${raw}` : '');
|
const normalized = raw && /^https?:\/\//i.test(raw) ? raw : (raw ? `https://${raw}` : '');
|
||||||
const u = new URL(normalized || 'http://127.0.0.1:4555');
|
const u = new URL(normalized || 'http://127.0.0.1:3001');
|
||||||
const isHttps = u.protocol === 'https:';
|
const isHttps = u.protocol === 'https:';
|
||||||
const port = u.port ? parseInt(u.port, 10) : (isHttps ? 443 : 80);
|
const port = u.port ? parseInt(u.port, 10) : (isHttps ? 443 : 80);
|
||||||
httpNatPort = isHttps ? 80 : port;
|
httpNatPort = isHttps ? 80 : port;
|
||||||
@@ -2042,7 +2042,7 @@ async function getBackup(routerId = null) {
|
|||||||
rscLines.push(`# Router : ${routerName} (${routerHost})`);
|
rscLines.push(`# Router : ${routerName} (${routerHost})`);
|
||||||
rscLines.push(`# Tanggal : ${nowStr}`);
|
rscLines.push(`# Tanggal : ${nowStr}`);
|
||||||
rscLines.push(`# Format : RouterOS Script (.rsc) ROS7 Ready`);
|
rscLines.push(`# Format : RouterOS Script (.rsc) ROS7 Ready`);
|
||||||
rscLines.push(`# Generator: Billing RTRW System`);
|
rscLines.push(`# Generator: OPEN-ISP Billing System`);
|
||||||
rscLines.push(`# ===================================================\n`);
|
rscLines.push(`# ===================================================\n`);
|
||||||
|
|
||||||
// 1. IP Pools
|
// 1. IP Pools
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const path = require('path');
|
|||||||
const { getSetting } = require('../config/settingsManager');
|
const { getSetting } = require('../config/settingsManager');
|
||||||
|
|
||||||
function gateway() {
|
function gateway() {
|
||||||
return String(getSetting('wa_gateway_type', 'baileys') || 'baileys').toLowerCase();
|
return String(getSetting('wa_gateway_type', 'gowa') || 'baileys').toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function spx(t) {
|
function spx(t) {
|
||||||
|
|||||||
@@ -933,7 +933,7 @@ export const whatsappStatus = {
|
|||||||
_connection: 'connecting',
|
_connection: 'connecting',
|
||||||
get connection() {
|
get connection() {
|
||||||
try {
|
try {
|
||||||
const gw = getSetting('wa_gateway_type', 'baileys');
|
const gw = getSetting('wa_gateway_type', 'gowa');
|
||||||
if (gw === 'fonnte') {
|
if (gw === 'fonnte') {
|
||||||
const token = getSetting('fonnte_token', '');
|
const token = getSetting('fonnte_token', '');
|
||||||
return token ? 'open' : 'close';
|
return token ? 'open' : 'close';
|
||||||
@@ -954,7 +954,7 @@ export const whatsappStatus = {
|
|||||||
_user: null,
|
_user: null,
|
||||||
get user() {
|
get user() {
|
||||||
try {
|
try {
|
||||||
const gw = getSetting('wa_gateway_type', 'baileys');
|
const gw = getSetting('wa_gateway_type', 'gowa');
|
||||||
if (gw === 'fonnte') {
|
if (gw === 'fonnte') {
|
||||||
return { id: 'fonnte_gateway', name: 'Fonnte Gateway' };
|
return { id: 'fonnte_gateway', name: 'Fonnte Gateway' };
|
||||||
}
|
}
|
||||||
@@ -973,7 +973,7 @@ export const whatsappStatus = {
|
|||||||
_phoneNumber: null,
|
_phoneNumber: null,
|
||||||
get phoneNumber() {
|
get phoneNumber() {
|
||||||
try {
|
try {
|
||||||
const gw = getSetting('wa_gateway_type', 'baileys');
|
const gw = getSetting('wa_gateway_type', 'gowa');
|
||||||
if (gw === 'fonnte') return 'Fonnte Gateway';
|
if (gw === 'fonnte') return 'Fonnte Gateway';
|
||||||
if (gw === 'meta') return getSetting('meta_phone_number_id', 'Meta API');
|
if (gw === 'meta') return getSetting('meta_phone_number_id', 'Meta API');
|
||||||
if (gw === 'gowa') return getSetting('gowa_device_id', '') || 'GOWA Gateway';
|
if (gw === 'gowa') return getSetting('gowa_device_id', '') || 'GOWA Gateway';
|
||||||
@@ -986,7 +986,7 @@ export const whatsappStatus = {
|
|||||||
_pushName: null,
|
_pushName: null,
|
||||||
get pushName() {
|
get pushName() {
|
||||||
try {
|
try {
|
||||||
const gw = getSetting('wa_gateway_type', 'baileys');
|
const gw = getSetting('wa_gateway_type', 'gowa');
|
||||||
if (gw === 'fonnte') return 'Fonnte Gateway';
|
if (gw === 'fonnte') return 'Fonnte Gateway';
|
||||||
if (gw === 'meta') return 'Meta Cloud API';
|
if (gw === 'meta') return 'Meta Cloud API';
|
||||||
if (gw === 'gowa') return 'GOWA Gateway';
|
if (gw === 'gowa') return 'GOWA Gateway';
|
||||||
@@ -1032,7 +1032,7 @@ function loadWhatsappAdminSendList() {
|
|||||||
* @param {string} priority - Priority level: 'high', 'medium', 'low'
|
* @param {string} priority - Priority level: 'high', 'medium', 'low'
|
||||||
*/
|
*/
|
||||||
export async function sendMonitoringAlert(message, priority = 'medium') {
|
export async function sendMonitoringAlert(message, priority = 'medium') {
|
||||||
const gatewayType = getSetting('wa_gateway_type', 'baileys');
|
const gatewayType = getSetting('wa_gateway_type', 'gowa');
|
||||||
if (gatewayType === 'baileys' && (!currentSock || whatsappStatus._connection !== 'open')) {
|
if (gatewayType === 'baileys' && (!currentSock || whatsappStatus._connection !== 'open')) {
|
||||||
logger.warn('[WhatsApp] Bot belum siap (koneksi belum terbuka), tidak dapat mengirim alert monitoring');
|
logger.warn('[WhatsApp] Bot belum siap (koneksi belum terbuka), tidak dapat mengirim alert monitoring');
|
||||||
return { success: false, message: 'Bot belum siap' };
|
return { success: false, message: 'Bot belum siap' };
|
||||||
@@ -1139,7 +1139,7 @@ export async function simulateHumanTyping(sock, jid, text = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function sendWA(to, text, options = {}) {
|
export async function sendWA(to, text, options = {}) {
|
||||||
const gatewayType = getSetting('wa_gateway_type', 'baileys');
|
const gatewayType = getSetting('wa_gateway_type', 'gowa');
|
||||||
|
|
||||||
let finalText = text;
|
let finalText = text;
|
||||||
if (options.spintax !== false && typeof text === 'string' && text.includes('{') && text.includes('}')) {
|
if (options.spintax !== false && typeof text === 'string' && text.includes('{') && text.includes('}')) {
|
||||||
@@ -1219,7 +1219,7 @@ export async function sendWA(to, text, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function sendWAImage(to, imageBuffer, caption = '', options = {}) {
|
export async function sendWAImage(to, imageBuffer, caption = '', options = {}) {
|
||||||
const gatewayType = getSetting('wa_gateway_type', 'baileys');
|
const gatewayType = getSetting('wa_gateway_type', 'gowa');
|
||||||
|
|
||||||
let finalCaption = caption;
|
let finalCaption = caption;
|
||||||
if (options.spintax !== false && typeof caption === 'string' && caption.includes('{') && caption.includes('}')) {
|
if (options.spintax !== false && typeof caption === 'string' && caption.includes('{') && caption.includes('}')) {
|
||||||
@@ -1286,7 +1286,7 @@ export async function sendWAImage(to, imageBuffer, caption = '', options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function sendWADocument(to, documentBuffer, filename = 'Invoice.pdf', caption = '', mimetype = 'application/pdf', options = {}) {
|
export async function sendWADocument(to, documentBuffer, filename = 'Invoice.pdf', caption = '', mimetype = 'application/pdf', options = {}) {
|
||||||
const gatewayType = getSetting('wa_gateway_type', 'baileys');
|
const gatewayType = getSetting('wa_gateway_type', 'gowa');
|
||||||
|
|
||||||
let finalCaption = caption;
|
let finalCaption = caption;
|
||||||
if (options.spintax !== false && typeof caption === 'string' && caption.includes('{') && caption.includes('}')) {
|
if (options.spintax !== false && typeof caption === 'string' && caption.includes('{') && caption.includes('}')) {
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ function formatUnpaidInvoicesSummary(unpaidInvoices) {
|
|||||||
* @param {object} options Opsi tambahan: { templateName, parameters, langCode, url, filename }
|
* @param {object} options Opsi tambahan: { templateName, parameters, langCode, url, filename }
|
||||||
*/
|
*/
|
||||||
async function sendWhatsAppMessage(toPhone, messageText, options = {}) {
|
async function sendWhatsAppMessage(toPhone, messageText, options = {}) {
|
||||||
const gatewayType = getSetting('wa_gateway_type', 'baileys'); // 'baileys', 'fonnte', or 'meta'
|
const gatewayType = getSetting('wa_gateway_type', 'gowa'); // 'baileys', 'fonnte', or 'meta'
|
||||||
|
|
||||||
if (gatewayType === 'fonnte') {
|
if (gatewayType === 'fonnte') {
|
||||||
// Mode FONNTE API GATEWAY (Cloud / Self-Hosted)
|
// Mode FONNTE API GATEWAY (Cloud / Self-Hosted)
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
B 4.8.1-R18
|
0.5.1
|
||||||
+1
-1
@@ -174,7 +174,7 @@
|
|||||||
<span class="text-muted" style="font-size:12px;">-</span>
|
<span class="text-muted" style="font-size:12px;">-</span>
|
||||||
<% } %>
|
<% } %>
|
||||||
</td>
|
</td>
|
||||||
<td><code><%= d.ip %></code></td>
|
<td><code><%= d.ip || d.pppoe_ip || '-' %></code></td>
|
||||||
<td><%= d.rx_power %> dBm</td>
|
<td><%= d.rx_power %> dBm</td>
|
||||||
<td><%= d.pppoe_user %></td>
|
<td><%= d.pppoe_user %></td>
|
||||||
<td><span class="acs-server-tag"><%= d.acs_server_name %></span></td>
|
<td><span class="acs-server-tag"><%= d.acs_server_name %></span></td>
|
||||||
|
|||||||
@@ -247,7 +247,7 @@
|
|||||||
<td class="text-muted"><%= i+1 %></td>
|
<td class="text-muted"><%= i+1 %></td>
|
||||||
<td class="fw6">
|
<td class="fw6">
|
||||||
<a href="javascript:void(0)" onclick="viewCustDetail(<%= i %>)" style="color:var(--primary);text-decoration:none;font-weight:700;display:inline-flex;align-items:center;gap:5px;cursor:pointer;" title="<%= t('admin.cust.tt_detail','Klik untuk lihat detail pelanggan & MikroTik') %>">
|
<a href="javascript:void(0)" onclick="viewCustDetail(<%= i %>)" style="color:var(--primary);text-decoration:none;font-weight:700;display:inline-flex;align-items:center;gap:5px;cursor:pointer;" title="<%= t('admin.cust.tt_detail','Klik untuk lihat detail pelanggan & MikroTik') %>">
|
||||||
<%= c.name %>
|
<%= c.name %><% var _miss=[]; if(!c.phone)_miss.push('HP'); if(!c.lat||!c.lng)_miss.push('koordinat'); if(!c.genieacs_tag)_miss.push('ACS'); %><% if(_miss.length===0){ %><i class="bi bi-check-circle-fill" style="font-size:12px;color:#22c55e;vertical-align:middle;" title="<%= t('admin.customers.data_ok','Data lengkap') %>"></i><% } else { %><i class="bi bi-x-circle-fill" style="font-size:12px;color:#ef4444;vertical-align:middle;" title="<%= t('admin.customers.data_incomplete','Belum lengkap') %>: <%- _miss.join(', ') %>"></i><% } %>
|
||||||
<i class="bi bi-info-circle-fill" style="font-size:12px;opacity:0.75;"></i>
|
<i class="bi bi-info-circle-fill" style="font-size:12px;opacity:0.75;"></i>
|
||||||
</a>
|
</a>
|
||||||
<% if (c.customer_code) { %><div style="font-size:11px;font-weight:700;color:var(--muted);margin-top:2px;display:flex;align-items:center;gap:4px;" title="ID Pelanggan"><i class="bi bi-person-badge"></i> <%= c.customer_code %></div><% } %>
|
<% if (c.customer_code) { %><div style="font-size:11px;font-weight:700;color:var(--muted);margin-top:2px;display:flex;align-items:center;gap:4px;" title="ID Pelanggan"><i class="bi bi-person-badge"></i> <%= c.customer_code %></div><% } %>
|
||||||
|
|||||||
@@ -568,13 +568,21 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>WhatsApp Bot</td>
|
<td>WhatsApp Bot<div class="sc-label" style="font-size:11px"><%= dependencies.whatsapp.message %></div></td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span class="badge <%= dependencies.whatsapp.status === 'online' ? 'bs' : 'bd' %>">
|
<span class="badge <%= dependencies.whatsapp.status === 'online' ? 'bs' : 'bd' %>">
|
||||||
<%= dependencies.whatsapp.status.toUpperCase() %>
|
<%= dependencies.whatsapp.status.toUpperCase() %>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Telegram Bot<div class="sc-label" style="font-size:11px"><%= dependencies.telegram ? dependencies.telegram.message : '' %></div></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span class="badge <%= (dependencies.telegram && dependencies.telegram.status === 'online') ? 'bs' : 'bd' %>">
|
||||||
|
<%= dependencies.telegram ? dependencies.telegram.status.toUpperCase() : 'UNKNOWN' %>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="sc-label mt16 mb8">Router MikroTik</p>
|
<p class="sc-label mt16 mb8">Router MikroTik</p>
|
||||||
|
|||||||
@@ -415,8 +415,8 @@
|
|||||||
/radius incoming set accept=yes port=3799
|
/radius incoming set accept=yes port=3799
|
||||||
|
|
||||||
# 3. Tambahkan Server RADIUS Billing ke MikroTik
|
# 3. Tambahkan Server RADIUS Billing ke MikroTik
|
||||||
/radius remove [find comment="RTRW-Billing-RADIUS"]
|
/radius remove [find comment="OPEN-ISP-Billing-RADIUS"]
|
||||||
/radius add service=ppp address=<%= typeof serverIp !== 'undefined' ? serverIp : '192.168.8.88' %> secret="<%= radiusStatus.secret %>" authentication-port=<%= radiusStatus.authPort %> accounting-port=<%= radiusStatus.acctPort %> timeout=3000ms comment="RTRW-Billing-RADIUS"
|
/radius add service=ppp address=<%= typeof serverIp !== 'undefined' ? serverIp : '192.168.8.88' %> secret="<%= radiusStatus.secret %>" authentication-port=<%= radiusStatus.authPort %> accounting-port=<%= radiusStatus.acctPort %> timeout=3000ms comment="OPEN-ISP-Billing-RADIUS"
|
||||||
|
|
||||||
# 4. Tambahkan IP Address Gateway Subnet Isolir RADIUS
|
# 4. Tambahkan IP Address Gateway Subnet Isolir RADIUS
|
||||||
/ip address remove [find comment="Gateway-Isolir-RADIUS"]
|
/ip address remove [find comment="Gateway-Isolir-RADIUS"]
|
||||||
|
|||||||
@@ -29,6 +29,17 @@
|
|||||||
<% if (info && info.error) { %>
|
<% if (info && info.error) { %>
|
||||||
<div class="alert alert-d"><i class="bi bi-exclamation-circle"></i> <%= info.error %></div>
|
<div class="alert alert-d"><i class="bi bi-exclamation-circle"></i> <%= info.error %></div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% if (info && info.container) { %>
|
||||||
|
<div class="alert" style="background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:10px;padding:12px 14px;font-size:13px;line-height:1.55">
|
||||||
|
<b><i class="bi bi-box-seam"></i> <%= t('admin.update.container_title', 'Instalasi Container (tanpa .git)') %></b><br>
|
||||||
|
<%= t('admin.update.container_msg', 'Pembaruan TIDAK lewat git. Bangun ulang image dari repo terbaru lalu ganti container:') %>
|
||||||
|
<div style="margin-top:8px;font-family:monospace;font-size:12px;background:rgba(0,0,0,.25);padding:8px 10px;border-radius:8px">
|
||||||
|
docker compose build && docker compose up -d<br>
|
||||||
|
# atau: build image baru, lalu ganti tag/container
|
||||||
|
</div>
|
||||||
|
<%= t('admin.update.container_safe', 'Data (settings.json, database, uploads) tetap aman bila disimpan di volume/mount.') %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
<div class="form-row" style="align-items:flex-start">
|
<div class="form-row" style="align-items:flex-start">
|
||||||
<div class="fg">
|
<div class="fg">
|
||||||
<label class="fl"><%= t('admin.update.server_version', 'Versi Server (version.txt)') %></label>
|
<label class="fl"><%= t('admin.update.server_version', 'Versi Server (version.txt)') %></label>
|
||||||
@@ -51,6 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% if (!(info && info.container)) { %>
|
||||||
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:12px">
|
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:12px">
|
||||||
<form method="GET" action="/admin/update">
|
<form method="GET" action="/admin/update">
|
||||||
<button type="submit" class="btn btn-g"><i class="bi bi-arrow-repeat"></i> <%= t('admin.update.check', 'Cek Versi') %></button>
|
<button type="submit" class="btn btn-g"><i class="bi bi-arrow-repeat"></i> <%= t('admin.update.check', 'Cek Versi') %></button>
|
||||||
@@ -63,6 +75,7 @@
|
|||||||
<div style="margin-top:12px;color:var(--muted);font-size:12px;line-height:1.5">
|
<div style="margin-top:12px;color:var(--muted);font-size:12px;line-height:1.5">
|
||||||
<%= t('admin.update.keep_files', 'File yang tidak ditimpa saat update: settings.json dan folder database. Setelah update selesai, restart aplikasi agar versi baru aktif.') %>
|
<%= t('admin.update.keep_files', 'File yang tidak ditimpa saat update: settings.json dan folder database. Setelah update selesai, restart aplikasi agar versi baru aktif.') %>
|
||||||
</div>
|
</div>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,9 @@
|
|||||||
<a href="/admin/whatsapp/live-chat" class="wa-tab <%= waTab==='livechat' ? 'active' : '' %>"><i class="bi bi-chat-dots-fill"></i> <%= t('admin.wa.tab_livechat','Live Chat') %></a>
|
<a href="/admin/whatsapp/live-chat" class="wa-tab <%= waTab==='livechat' ? 'active' : '' %>"><i class="bi bi-chat-dots-fill"></i> <%= t('admin.wa.tab_livechat','Live Chat') %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PENGATURAN HYBRID GATEWAY (BAILEYS VS FONNTE VS META API) -->
|
<!-- PENGATURAN GATEWAY (GOWA / FONNTE / META API) -->
|
||||||
<div class="card mb16" style="padding: 20px;">
|
<div class="card mb16" style="padding: 20px;">
|
||||||
<h6 style="margin-bottom: 15px; font-weight: 700; color: var(--primary);"><i class="bi bi-sliders"></i> Mode WhatsApp Gateway (Baileys / Fonnte / GOWA / Meta API)</h6>
|
<h6 style="margin-bottom: 15px; font-weight: 700; color: var(--primary);"><i class="bi bi-sliders"></i> Mode WhatsApp Gateway (GoWA / Fonnte / Meta API)</h6>
|
||||||
|
|
||||||
<form method="POST" action="/admin/whatsapp/gateway-settings">
|
<form method="POST" action="/admin/whatsapp/gateway-settings">
|
||||||
<div class="form-row mb16">
|
<div class="form-row mb16">
|
||||||
@@ -334,14 +334,6 @@ async function sendTestGowa() {
|
|||||||
<h2 id="status-text"><%= t('admin.whatsapp.checking', 'Mengecek status...') %></h2>
|
<h2 id="status-text"><%= t('admin.whatsapp.checking', 'Mengecek status...') %></h2>
|
||||||
<p id="status-desc" class="text-muted"><%= t('admin.whatsapp.please_wait', 'Harap tunggu sebentar.') %></p>
|
<p id="status-desc" class="text-muted"><%= t('admin.whatsapp.please_wait', 'Harap tunggu sebentar.') %></p>
|
||||||
|
|
||||||
<% if (waGatewayType === 'baileys') { %>
|
|
||||||
<div id="qr-section" style="display:none">
|
|
||||||
<p class="mb-3"><%= t('admin.whatsapp.qr_instruction', 'Silakan pindai QR Code ini dengan aplikasi WhatsApp Anda:') %></p>
|
|
||||||
<div id="qr-container"></div>
|
|
||||||
<p class="mt-3 text-sm text-muted"><%= t('admin.whatsapp.qr_auto_refresh', 'QR Code akan diperbarui secara otomatis.') %></p>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<div id="info-section" style="display:none" class="wa-info">
|
<div id="info-section" style="display:none" class="wa-info">
|
||||||
<div class="alert alert-s">
|
<div class="alert alert-s">
|
||||||
<i class="bi bi-check-circle-fill"></i> <%= t('admin.whatsapp.connected_title', 'WhatsApp Terhubung') %>
|
<i class="bi bi-check-circle-fill"></i> <%= t('admin.whatsapp.connected_title', 'WhatsApp Terhubung') %>
|
||||||
@@ -355,14 +347,6 @@ async function sendTestGowa() {
|
|||||||
<form method="POST" action="/admin/whatsapp/test-notification" onsubmit="return confirm('<%= t('admin.whatsapp.confirm_test', 'Kirim test notifikasi WhatsApp ke admin sekarang?') %>');">
|
<form method="POST" action="/admin/whatsapp/test-notification" onsubmit="return confirm('<%= t('admin.whatsapp.confirm_test', 'Kirim test notifikasi WhatsApp ke admin sekarang?') %>');">
|
||||||
<button class="btn btn-p"><i class="bi bi-send-check"></i> <%= t('admin.whatsapp.test_notification', 'Kirim Test Notif') %></button>
|
<button class="btn btn-p"><i class="bi bi-send-check"></i> <%= t('admin.whatsapp.test_notification', 'Kirim Test Notif') %></button>
|
||||||
</form>
|
</form>
|
||||||
<% if (waGatewayType === 'baileys') { %>
|
|
||||||
<form method="POST" action="/admin/whatsapp/repair-keys" onsubmit="return confirm('Bersihkan cache pre-keys & sesi usang untuk mengatasi masalah pesan pending / Bad-MAC tanpa perlu scan QR ulang?');">
|
|
||||||
<button class="btn btn-g" style="border-color: #3b82f6; color: #2563eb;"><i class="bi bi-wrench-adjustable"></i> Perbaiki Sesi (Anti Waiting)</button>
|
|
||||||
</form>
|
|
||||||
<form method="POST" action="/admin/whatsapp/reset" onsubmit="return confirm('<%= t('admin.whatsapp.confirm_reset', 'Apakah Anda yakin ingin menghapus sesi WhatsApp? Anda harus scan QR ulang setelah ini.') %>')">
|
|
||||||
<button class="btn btn-d"><i class="bi bi-trash"></i> <%= t('admin.whatsapp.reset_session', 'Hapus Sesi / Reset') %></button>
|
|
||||||
</form>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user