208 lines
8.0 KiB
JavaScript
208 lines
8.0 KiB
JavaScript
const axios = require('axios');
|
|
const https = require('https');
|
|
|
|
class MikrotikService {
|
|
constructor() {
|
|
this.client = null;
|
|
}
|
|
|
|
getConfiguredRouterIds() {
|
|
const ids = [1]; // Default router always exists (or assumed ID 1)
|
|
// Scan env for MIKROTIK_HOST_X
|
|
for (const key in process.env) {
|
|
if (key.startsWith('MIKROTIK_HOST_')) {
|
|
const id = parseInt(key.replace('MIKROTIK_HOST_', ''));
|
|
if (!isNaN(id) && !ids.includes(id)) {
|
|
ids.push(id);
|
|
}
|
|
}
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
getConfig(routerId = 1) {
|
|
|
|
// Default to Router 1 if not specified or if routerId is 1.
|
|
// If routerId is provided (e.g. 3), look for MIKROTIK_HOST_3
|
|
let host;
|
|
|
|
if (routerId && routerId != 1) {
|
|
// STRICT MODE: Secondary routers MUST have their own config.
|
|
// Do NOT fall back to main router, or we'll connect to the wrong device.
|
|
host = process.env[`MIKROTIK_HOST_${routerId}`];
|
|
if (!host) {
|
|
throw new Error(`Configuration for Router ${routerId} (MIKROTIK_HOST_${routerId}) not found in .env`);
|
|
}
|
|
} else {
|
|
// Default Router 1
|
|
host = process.env.MIKROTIK_HOST;
|
|
if (!host) throw new Error("Mikrotik Host (Router 1) not configured in .env");
|
|
}
|
|
|
|
const suffix = (routerId && routerId != 1) ? `_${routerId}` : '';
|
|
|
|
// Only fallback to default credentials if specifically Router 1.
|
|
// For Router 3+, we expect specific creds OR we can allow fallback for creds but NOT host.
|
|
// Actually, safer to rely on suffix or default global ONLY if logic permits.
|
|
// Let's allow fallback for User/Pass/Port (common to reuse creds) but Host was the critical one.
|
|
const user = process.env[`MIKROTIK_USER${suffix}`] || process.env.MIKROTIK_USER || 'admin';
|
|
const password = process.env[`MIKROTIK_PASSWORD${suffix}`] || process.env.MIKROTIK_PASSWORD || '';
|
|
let port = parseInt(process.env[`MIKROTIK_PORT${suffix}`] || process.env.MIKROTIK_PORT || 80);
|
|
if (port === 8728) port = 80;
|
|
|
|
return {
|
|
host,
|
|
user,
|
|
password,
|
|
port,
|
|
protocol: (port === 443) ? 'https' : 'http'
|
|
};
|
|
}
|
|
|
|
getClient(routerId = 1) {
|
|
const config = this.getConfig(routerId);
|
|
const baseURL = `${config.protocol}://${config.host}:${config.port}/rest`;
|
|
|
|
return axios.create({
|
|
baseURL,
|
|
auth: {
|
|
username: config.user,
|
|
password: config.password
|
|
},
|
|
httpsAgent: new https.Agent({
|
|
rejectUnauthorized: false
|
|
}),
|
|
timeout: 10000
|
|
});
|
|
}
|
|
|
|
async execute(method, url, data = null, routerId = 1) {
|
|
try {
|
|
const client = this.getClient(routerId);
|
|
const response = await client.request({
|
|
method,
|
|
url,
|
|
data
|
|
});
|
|
return response.data;
|
|
} catch (error) {
|
|
if (error.response) {
|
|
// throw new Error(`Mikrotik API Error: ${error.response.status} ${JSON.stringify(error.response.data)}`);
|
|
// Return empty for 404 on list calls?
|
|
throw new Error(`Mikrotik Error (Router ${routerId}): ${error.response.status} - ${JSON.stringify(error.response.data)}`);
|
|
} else if (error.request) {
|
|
console.error(`[Mikrotik Router ${routerId} No Response]`, error.message);
|
|
throw new Error(`Mikrotik Router ${routerId} Unreachable`);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
async getSystemResource(routerId = 1) {
|
|
const data = await this.execute('GET', '/system/resource', null, routerId);
|
|
return Array.isArray(data) ? data[0] : data;
|
|
}
|
|
|
|
// --- HOTSPOT ---
|
|
async getHotspotUsers(routerId = 1) {
|
|
return this.execute('GET', '/ip/hotspot/user', null, routerId);
|
|
}
|
|
|
|
async getActiveHotspot(routerId = 1) {
|
|
return this.execute('GET', '/ip/hotspot/active', null, routerId);
|
|
}
|
|
|
|
async getActiveUsers(routerId = 1) { // Alias for consistency
|
|
return this.getActiveHotspot(routerId);
|
|
}
|
|
|
|
async enableUser(name, routerId = 1) {
|
|
const users = await this.execute('GET', `/ip/hotspot/user?name=${encodeURIComponent(name)}`, null, routerId);
|
|
if (!users || users.length === 0) throw new Error(`User ${name} not found on Router ${routerId}`);
|
|
const id = users[0]['.id'];
|
|
await this.execute('PATCH', `/ip/hotspot/user/${id}`, { disabled: "false" }, routerId);
|
|
}
|
|
|
|
async disableUser(name, routerId = 1) {
|
|
const users = await this.execute('GET', `/ip/hotspot/user?name=${encodeURIComponent(name)}`, null, routerId);
|
|
if (!users || users.length === 0) throw new Error(`User ${name} not found on Router ${routerId}`);
|
|
const id = users[0]['.id'];
|
|
await this.execute('PATCH', `/ip/hotspot/user/${id}`, { disabled: "true" }, routerId);
|
|
// Kick
|
|
try {
|
|
const active = await this.execute('GET', `/ip/hotspot/active?user=${encodeURIComponent(name)}`, null, routerId);
|
|
if (active && active.length > 0) {
|
|
await this.execute('DELETE', `/ip/hotspot/active/${active[0]['.id']}`, null, routerId);
|
|
}
|
|
} catch (e) { }
|
|
}
|
|
|
|
|
|
// --- PPPoE ---
|
|
async getPppoeSecrets(routerId = 1) {
|
|
return this.execute('GET', '/ppp/secret', null, routerId);
|
|
}
|
|
|
|
async getActivePppoe(routerId = 1) {
|
|
return this.execute('GET', '/ppp/active', null, routerId);
|
|
}
|
|
|
|
async disablePppoeUser(name, routerId = 1) {
|
|
const users = await this.execute('GET', `/ppp/secret?name=${encodeURIComponent(name)}`, null, routerId);
|
|
if (!users || users.length === 0) throw new Error(`User ${name} not found on Router ${routerId}`);
|
|
const id = users[0]['.id'];
|
|
await this.execute('PATCH', `/ppp/secret/${id}`, { disabled: "true" }, routerId);
|
|
|
|
try {
|
|
const active = await this.execute('GET', `/ppp/active?name=${encodeURIComponent(name)}`, null, routerId);
|
|
if (active && active.length > 0) {
|
|
await this.execute('DELETE', `/ppp/active/${active[0]['.id']}`, null, routerId);
|
|
}
|
|
} catch (e) { }
|
|
return true;
|
|
}
|
|
|
|
async enablePppoeUser(name, routerId = 1) {
|
|
const users = await this.execute('GET', `/ppp/secret?name=${encodeURIComponent(name)}`, null, routerId);
|
|
if (!users || users.length === 0) throw new Error(`User ${name} not found on Router ${routerId}`);
|
|
const id = users[0]['.id'];
|
|
await this.execute('PATCH', `/ppp/secret/${id}`, { disabled: "false" }, routerId);
|
|
return true;
|
|
}
|
|
|
|
async getPppoeSecret(name, routerId = 1) {
|
|
const users = await this.execute('GET', `/ppp/secret?name=${encodeURIComponent(name)}`, null, routerId);
|
|
return (users && users.length > 0) ? users[0] : null;
|
|
}
|
|
|
|
|
|
async getInterfaces(routerId = 1) {
|
|
return this.execute('GET', '/interface', null, routerId);
|
|
}
|
|
|
|
async monitorInterfaceTraffic(interfaceName, routerId = 1) {
|
|
const result = await this.execute('POST', '/interface/monitor-traffic', {
|
|
interface: interfaceName,
|
|
once: 'true'
|
|
}, routerId);
|
|
return Array.isArray(result) ? result[0] : result;
|
|
}
|
|
|
|
async findInterface(partialName, routerId = 1) {
|
|
const interfaces = await this.getInterfaces(routerId);
|
|
const exact = interfaces.find(i => i.name === partialName);
|
|
if (exact) return exact;
|
|
|
|
const cleanInput = partialName.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
const fuzzy = interfaces.find(i => {
|
|
const cleanName = i.name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
return cleanName.includes(cleanInput);
|
|
});
|
|
|
|
return fuzzy || null;
|
|
}
|
|
}
|
|
|
|
module.exports = new MikrotikService();
|