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:
commit
eaf05f2811
280 files changed
+123975
No files matched your search
@@ -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
|
||||
};
|
||||
Reference in new issue
Block a user