OPEN-ISP v0.5 (pickle-lime) — rilis open-source perdana

Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor,
admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS.

Kredit & lisensi (ISC):
- Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak
  atas base kode dan repo yang bermanfaat.
- Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder.
- Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
This commit is contained in:
oxpoe committed 2026-10-09 05:51:21 +07:00
commit eaf05f2811
280 files changed
+123975

No files matched your search

+211
View File
@@ -0,0 +1,211 @@
/**
* Service: System Diagnostics & Troubleshooting
* Melakukan pengecekan terhadap dependensi eksternal dan diagnosa masalah
*/
const { logger } = require('../config/logger');
const db = require('../config/database');
const mikrotikService = require('./mikrotikService');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
/**
* Perform a full system dependency check
*/
async function checkDependencies() {
const results = {
mikrotik: [],
genieacs: { status: 'unknown', message: '' },
whatsapp: { status: 'unknown', message: '' },
paymentGateways: [],
timestamp: new Date().toISOString()
};
// 1. Check MikroTik Routers
try {
const routers = db.prepare('SELECT * FROM routers').all();
for (const r of routers) {
try {
// Simple connectivity check (get identity or similar)
const isOnline = await mikrotikService.checkConnection(r.id);
results.mikrotik.push({
name: r.name,
host: r.host,
status: isOnline ? 'online' : 'offline',
error: isOnline ? null : 'Connection failed'
});
} catch (err) {
results.mikrotik.push({
name: r.name,
host: r.host,
status: 'offline',
error: err.message
});
}
}
} catch (err) {
logger.error(`[Diagnostics] MikroTik check failed: ${err.message}`);
}
// 2. Check GenieACS
try {
const { getSetting } = require('../config/settingsManager');
const { isBuiltinAcsEnabled } = require('../config/genieacs');
if (isBuiltinAcsEnabled()) {
const count = db.prepare('SELECT COUNT(*) as c FROM acs_devices').get();
results.genieacs = {
status: 'online',
message: `Built-in ACS active (${count.c} devices)`
};
} else {
const acsUrl = getSetting('genieacs_url', 'http://localhost:7557');
const username = getSetting('genieacs_username', '');
const password = getSetting('genieacs_password', '');
// Try to get devices list to verify GenieACS is working
const devicesUrl = `${acsUrl}/devices?limit=1`;
const config = {
timeout: 5000,
validateStatus: (status) => status < 500 // Accept any status < 500
};
if (username && password) {
config.auth = { username, password };
}
const response = await axios.get(devicesUrl, config);
// If we get a response (even 401), GenieACS is online
if (response.status === 200 || response.status === 401) {
results.genieacs = {
status: 'online',
message: response.status === 200 ? 'GenieACS is responding' : 'GenieACS online (auth required)'
};
} else {
results.genieacs = {
status: 'warning',
message: `GenieACS responding with status ${response.status}`
};
}
}
} catch (err) {
// Check if it's a connection error or timeout
if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
results.genieacs = {
status: 'offline',
message: 'GenieACS unreachable (connection refused)'
};
} else {
results.genieacs = {
status: 'offline',
message: `GenieACS error: ${err.message}`
};
}
}
// 3. Check WhatsApp Gateway (using whatsappStatus from whatsappBot)
try {
// Import whatsappStatus dynamically to get real-time status
const whatsappBotModule = await import('./whatsappBot.mjs');
const waStatus = whatsappBotModule.whatsappStatus;
// Check connection status
// Possible values: 'connecting', 'qr', 'open', 'loggedOut', 'close'
const isOnline = waStatus.connection === 'open';
const isQR = waStatus.connection === 'qr';
const isLoggedOut = waStatus.connection === 'loggedOut';
let message = 'WhatsApp is disconnected';
if (isOnline) {
const phone = 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.connection || 'disconnected'}`;
}
results.whatsapp = {
status: isOnline ? 'online' : 'offline',
message: message
};
} catch (err) {
results.whatsapp = {
status: 'offline',
message: `WhatsApp check error: ${err.message}`
};
}
return results;
}
/**
* Get recent errors from log file
*/
function getRecentErrors(limit = 10) {
try {
const logPath = path.join(__dirname, '../logs/error.log');
if (!fs.existsSync(logPath)) return [];
const content = fs.readFileSync(logPath, 'utf8');
const lines = content.split('\n').filter(line => line.trim() !== '');
return lines.slice(-limit).reverse();
} catch (err) {
return [`Error reading log: ${err.message}`];
}
}
/**
* Comprehensive Customer Diagnostics
*/
async function diagnoseCustomer(customerId) {
const customer = db.prepare('SELECT * FROM customers WHERE id = ?').get(customerId);
if (!customer) throw new Error('Customer not found');
const report = {
customer: { name: customer.name, pppoe: customer.pppoe_username },
billing: { status: 'clean', unpaidCount: 0 },
mikrotik: { status: 'unknown', details: null },
genieacs: { status: 'unknown', signal: null },
timestamp: new Date().toISOString()
};
// 1. Billing Check
const unpaid = db.prepare("SELECT COUNT(*) as count FROM invoices WHERE customer_id = ? AND status = 'unpaid'").get(customerId);
report.billing.unpaidCount = unpaid.count;
if (unpaid.count > 0) report.billing.status = 'warning';
// 2. MikroTik Check
if (customer.pppoe_username && customer.router_id) {
try {
const active = await mikrotikService.getPppoeActive(customer.router_id);
const session = active.find(s => s.name === customer.pppoe_username);
if (session) {
report.mikrotik = {
status: 'online',
details: {
uptime: session.uptime,
address: session.address,
caller_id: session['caller-id']
}
};
} else {
report.mikrotik.status = 'offline';
}
} catch (err) {
report.mikrotik.status = 'error';
report.mikrotik.error = err.message;
}
}
return report;
}
module.exports = {
checkDependencies,
getRecentErrors,
diagnoseCustomer
};