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

+350
View File
@@ -0,0 +1,350 @@
/**
* Settings API Routes
* Untuk manage settings lewat API dengan security & validation
*/
const express = require('express');
const router = express.Router();
const { logger } = require('../config/logger');
const { getSetting, getSettings, saveSettings } = require('../config/settingsManager');
const { getMaskedSettings } = require('../config/settingsEncryption');
const { getAllRules } = require('../config/settingsValidator');
const { getChangeHistory, getAuditStats, exportAuditLog } = require('../config/settingsAudit');
const {
requireSuperAdmin,
validateSettingsMiddleware,
maskSensitiveValues,
logSettingsAccess,
rateLimitSettings
} = require('../middleware/settingsMiddleware');
// Apply middleware
router.use(rateLimitSettings);
router.use(maskSensitiveValues);
router.use(logSettingsAccess);
/**
* GET /api/settings
* Get all settings (masked)
*/
router.get('/', requireSuperAdmin, (req, res) => {
try {
const settings = getSettings();
const masked = getMaskedSettings(settings);
return res.json({
success: true,
settings: masked
});
} catch (error) {
logger.error(`[settings-api] Error getting settings: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* GET /api/settings/rules
* Get validation rules untuk setiap field
*/
router.get('/rules', requireSuperAdmin, (req, res) => {
try {
const rules = getAllRules();
return res.json({
success: true,
rules
});
} catch (error) {
logger.error(`[settings-api] Error getting rules: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* GET /api/settings/:field
* Get single setting (masked)
*/
router.get('/:field', requireSuperAdmin, (req, res) => {
try {
const { field } = req.params;
const value = getSetting(field);
if (value === null || value === undefined) {
return res.status(404).json({
error: 'Setting not found'
});
}
const { getMaskedSettings } = require('../config/settingsEncryption');
const masked = getMaskedSettings({ [field]: value });
return res.json({
success: true,
field,
value: masked[field]
});
} catch (error) {
logger.error(`[settings-api] Error getting setting: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* POST /api/settings
* Update settings dengan validation & encryption
*/
router.post('/', requireSuperAdmin, validateSettingsMiddleware, (req, res) => {
try {
const settings = req.validatedSettings;
const actor = req.session?.username || 'unknown';
const metadata = {
ip: req.ip,
userAgent: req.get('user-agent')
};
const result = saveSettings(settings, actor, metadata);
if (!result.success) {
return res.status(400).json({
error: 'Failed to save settings',
errors: result.errors
});
}
return res.json({
success: true,
message: 'Settings saved successfully',
changes: result.changes
});
} catch (error) {
logger.error(`[settings-api] Error saving settings: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* PUT /api/settings/:field
* Update single setting
*/
router.put('/:field', requireSuperAdmin, (req, res) => {
try {
const { field } = req.params;
const { value } = req.body;
if (value === undefined) {
return res.status(400).json({
error: 'Value is required'
});
}
const settings = { [field]: value };
const actor = req.session?.username || 'unknown';
const metadata = {
ip: req.ip,
userAgent: req.get('user-agent')
};
const result = saveSettings(settings, actor, metadata);
if (!result.success) {
return res.status(400).json({
error: 'Failed to save setting',
errors: result.errors
});
}
return res.json({
success: true,
message: `Setting ${field} updated successfully`
});
} catch (error) {
logger.error(`[settings-api] Error updating setting: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* GET /api/settings/audit/history
* Get settings change history
*/
router.get('/audit/history', requireSuperAdmin, (req, res) => {
try {
const limit = Math.min(parseInt(req.query.limit) || 100, 1000);
const history = getChangeHistory(limit);
return res.json({
success: true,
count: history.length,
history
});
} catch (error) {
logger.error(`[settings-api] Error getting history: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* GET /api/settings/audit/stats
* Get audit statistics
*/
router.get('/audit/stats', requireSuperAdmin, (req, res) => {
try {
const stats = getAuditStats();
return res.json({
success: true,
stats
});
} catch (error) {
logger.error(`[settings-api] Error getting stats: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* GET /api/settings/audit/export
* Export audit log
*/
router.get('/audit/export', requireSuperAdmin, (req, res) => {
try {
const format = req.query.format || 'json';
const result = exportAuditLog(format);
if (!result) {
return res.status(500).json({
error: 'Failed to export audit log'
});
}
if (format === 'csv') {
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="settings-audit.csv"');
return res.send(result.data);
}
return res.json({
success: true,
format: 'json',
count: result.count,
data: result.data
});
} catch (error) {
logger.error(`[settings-api] Error exporting audit: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* POST /api/settings/test-connection/:service
* Test connection ke external service
*/
router.post('/test-connection/:service', requireSuperAdmin, async (req, res) => {
try {
const { service } = req.params;
let result = { success: false, message: '' };
switch (service) {
case 'genieacs':
result = await testGenieACSConnection();
break;
case 'mikrotik':
result = await testMikroTikConnection();
break;
default:
return res.status(400).json({
error: 'Unknown service'
});
}
return res.json(result);
} catch (error) {
logger.error(`[settings-api] Error testing connection: ${error.message}`);
return res.status(500).json({
error: 'Internal server error'
});
}
});
/**
* Test GenieACS connection
*/
async function testGenieACSConnection() {
try {
const axios = require('axios');
const url = getSetting('genieacs_url');
const username = getSetting('genieacs_username');
const password = getSetting('genieacs_password');
if (!url || !username || !password) {
return { success: false, message: 'GenieACS credentials not configured' };
}
const response = await axios.get(`${url}/devices`, {
auth: { username, password },
timeout: 5000
});
return { success: true, message: 'GenieACS connection successful' };
} catch (error) {
return { success: false, message: `GenieACS connection failed: ${error.message}` };
}
}
/**
* Test MikroTik connection
*/
async function testMikroTikConnection() {
try {
const { RouterOSClient } = require('routeros-client');
const host = getSetting('mikrotik_host');
const user = getSetting('mikrotik_user');
const password = getSetting('mikrotik_password');
const port = getSetting('mikrotik_port') || 8728;
if (!host || !user || !password) {
return { success: false, message: 'MikroTik credentials not configured' };
}
const useTls = Number(port) === 8729 || getSetting('mikrotik_tls') === true;
const api = new RouterOSClient({
host,
port: Number(port),
user,
password,
tls: useTls ? { rejectUnauthorized: false } : false, // dukung self-signed cert (8729)
timeout: 5000
});
const client = await api.connect();
await client.close();
return { success: true, message: 'MikroTik connection successful' };
} catch (error) {
return { success: false, message: `MikroTik connection failed: ${error.message}` };
}
}
/**
* Test Tripay connection
*/
/**
* Test Midtrans connection
*/
module.exports = router;