300 lines
11 KiB
JavaScript
300 lines
11 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Direct connection test to Omada Controller
|
||
|
|
* Uses Node.js https module directly for better SSL control
|
||
|
|
*/
|
||
|
|
|
||
|
|
import https from 'https';
|
||
|
|
import { readFileSync } from 'fs';
|
||
|
|
import { join } from 'path';
|
||
|
|
import { homedir } from 'os';
|
||
|
|
|
||
|
|
// Load environment variables
|
||
|
|
const envPath = join(homedir(), '.env');
|
||
|
|
let envVars = {};
|
||
|
|
|
||
|
|
try {
|
||
|
|
const envFile = readFileSync(envPath, 'utf8');
|
||
|
|
envFile.split('\n').forEach(line => {
|
||
|
|
if (line.includes('=') && !line.trim().startsWith('#')) {
|
||
|
|
const [key, ...values] = line.split('=');
|
||
|
|
if (key && /^[A-Z_][A-Z0-9_]*$/.test(key.trim())) {
|
||
|
|
let value = values.join('=').trim();
|
||
|
|
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||
|
|
(value.startsWith("'") && value.endsWith("'"))) {
|
||
|
|
value = value.slice(1, -1);
|
||
|
|
}
|
||
|
|
envVars[key.trim()] = value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error loading .env file:', error.message);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
const baseUrl = envVars.OMADA_CONTROLLER_URL || 'https://192.168.11.8:8043';
|
||
|
|
const username = envVars.OMADA_ADMIN_USERNAME || envVars.OMADA_API_KEY;
|
||
|
|
const password = envVars.OMADA_ADMIN_PASSWORD || envVars.OMADA_API_SECRET;
|
||
|
|
const siteId = envVars.OMADA_SITE_ID;
|
||
|
|
const verifySSL = envVars.OMADA_VERIFY_SSL !== 'false';
|
||
|
|
|
||
|
|
if (!username || !password) {
|
||
|
|
console.error('Error: Missing credentials');
|
||
|
|
console.error('Required: OMADA_ADMIN_USERNAME/OMADA_API_KEY and OMADA_ADMIN_PASSWORD/OMADA_API_SECRET');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse base URL
|
||
|
|
const urlObj = new URL(baseUrl);
|
||
|
|
const hostname = urlObj.hostname;
|
||
|
|
const port = urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80);
|
||
|
|
|
||
|
|
console.log('=== Omada Controller Direct Connection Test ===\n');
|
||
|
|
console.log(`Controller URL: ${baseUrl}`);
|
||
|
|
console.log(`Hostname: ${hostname}`);
|
||
|
|
console.log(`Port: ${port}`);
|
||
|
|
console.log(`Site ID: ${siteId || 'auto-detect'}`);
|
||
|
|
console.log(`SSL Verification: ${verifySSL}\n`);
|
||
|
|
|
||
|
|
// Create HTTPS agent
|
||
|
|
const agent = new https.Agent({
|
||
|
|
rejectUnauthorized: verifySSL,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Function to make API request
|
||
|
|
function apiRequest(method, path, data = null, token = null) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const options = {
|
||
|
|
hostname,
|
||
|
|
port,
|
||
|
|
path,
|
||
|
|
method,
|
||
|
|
agent,
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
if (token) {
|
||
|
|
options.headers['Authorization'] = `Bearer ${token}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
const req = https.request(options, (res) => {
|
||
|
|
let body = '';
|
||
|
|
res.on('data', (chunk) => {
|
||
|
|
body += chunk;
|
||
|
|
});
|
||
|
|
res.on('end', () => {
|
||
|
|
try {
|
||
|
|
const json = JSON.parse(body);
|
||
|
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||
|
|
resolve(json);
|
||
|
|
} else {
|
||
|
|
reject(new Error(`HTTP ${res.statusCode}: ${body}`));
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
resolve({ raw: body, statusCode: res.statusCode });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
req.on('error', (error) => {
|
||
|
|
reject(error);
|
||
|
|
});
|
||
|
|
|
||
|
|
if (data) {
|
||
|
|
req.write(JSON.stringify(data));
|
||
|
|
}
|
||
|
|
req.end();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async function test() {
|
||
|
|
try {
|
||
|
|
console.log('1. Testing login...');
|
||
|
|
const loginResponse = await apiRequest('POST', '/api/v2/login', {
|
||
|
|
username,
|
||
|
|
password,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (loginResponse.errorCode !== 0) {
|
||
|
|
console.error(` ✗ Login failed: ${loginResponse.msg || 'Unknown error'}`);
|
||
|
|
console.error(` Error Code: ${loginResponse.errorCode}`);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
const token = loginResponse.result?.token || loginResponse.token;
|
||
|
|
if (!token) {
|
||
|
|
console.error(' ✗ No token received');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(' ✓ Login successful');
|
||
|
|
console.log(` Token: ${token.substring(0, 20)}...\n`);
|
||
|
|
|
||
|
|
console.log('2. Getting site information...');
|
||
|
|
// Try different endpoint formats
|
||
|
|
let sitesResponse;
|
||
|
|
try {
|
||
|
|
sitesResponse = await apiRequest('GET', '/api/v2/sites', null, token);
|
||
|
|
} catch (e) {
|
||
|
|
// If redirect, try with omadacId parameter
|
||
|
|
try {
|
||
|
|
sitesResponse = await apiRequest('GET', `/api/v2/sites?omadacId=${siteId}`, null, token);
|
||
|
|
} catch (e2) {
|
||
|
|
console.log(' Note: Sites endpoint may require different format');
|
||
|
|
sitesResponse = { errorCode: -1, msg: 'Could not query sites endpoint' };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sitesResponse.errorCode === 0 && sitesResponse.result) {
|
||
|
|
const sites = Array.isArray(sitesResponse.result) ? sitesResponse.result : [];
|
||
|
|
console.log(` ✓ Found ${sites.length} site(s)`);
|
||
|
|
sites.forEach((site, index) => {
|
||
|
|
console.log(` Site ${index + 1}: ${site.name} (${site.id})`);
|
||
|
|
if (site.id === siteId) {
|
||
|
|
console.log(` ^ Using this site`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} else if (sitesResponse.statusCode === 302 || sitesResponse.raw === '') {
|
||
|
|
console.log(' Note: Sites endpoint returned redirect (may require web interface)');
|
||
|
|
console.log(` Using provided site ID: ${siteId}`);
|
||
|
|
} else {
|
||
|
|
console.log(' Response:', JSON.stringify(sitesResponse, null, 2));
|
||
|
|
}
|
||
|
|
console.log('');
|
||
|
|
|
||
|
|
const effectiveSiteId = siteId || (sitesResponse.result?.[0]?.id);
|
||
|
|
if (effectiveSiteId) {
|
||
|
|
console.log(`3. Using site ID: ${effectiveSiteId}\n`);
|
||
|
|
|
||
|
|
console.log('4. Listing devices...');
|
||
|
|
let devicesResponse;
|
||
|
|
try {
|
||
|
|
devicesResponse = await apiRequest('GET', `/api/v2/sites/${effectiveSiteId}/devices`, null, token);
|
||
|
|
} catch (e) {
|
||
|
|
try {
|
||
|
|
devicesResponse = await apiRequest('GET', `/api/v2/sites/${effectiveSiteId}/devices?omadacId=${effectiveSiteId}`, null, token);
|
||
|
|
} catch (e2) {
|
||
|
|
devicesResponse = { errorCode: -1, msg: 'Could not query devices endpoint' };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (devicesResponse.errorCode === 0 && devicesResponse.result) {
|
||
|
|
const devices = Array.isArray(devicesResponse.result) ? devicesResponse.result : [];
|
||
|
|
console.log(` ✓ Found ${devices.length} device(s)`);
|
||
|
|
|
||
|
|
if (devices.length > 0) {
|
||
|
|
console.log('\n Devices:');
|
||
|
|
|
||
|
|
const routers = devices.filter(d => d.type === 'router' || d.type === 'Gateway');
|
||
|
|
const switches = devices.filter(d => d.type === 'switch' || d.type === 'Switch');
|
||
|
|
const aps = devices.filter(d => d.type === 'ap' || d.type === 'AccessPoint');
|
||
|
|
|
||
|
|
if (routers.length > 0) {
|
||
|
|
console.log('\n ROUTERS:');
|
||
|
|
routers.forEach((device, index) => {
|
||
|
|
const status = device.status === 1 ? '🟢 Online' : device.status === 0 ? '🔴 Offline' : '🟡 Unknown';
|
||
|
|
console.log(` ${index + 1}. ${device.name || device.mac || 'Unknown'}`);
|
||
|
|
console.log(` Model: ${device.model || 'N/A'}`);
|
||
|
|
console.log(` Status: ${status}`);
|
||
|
|
console.log(` IP: ${device.ip || 'N/A'}`);
|
||
|
|
console.log(` MAC: ${device.mac || 'N/A'}`);
|
||
|
|
console.log(` Device ID: ${device.id || 'N/A'}`);
|
||
|
|
console.log('');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (switches.length > 0) {
|
||
|
|
console.log('\n SWITCHES:');
|
||
|
|
switches.forEach((device, index) => {
|
||
|
|
const status = device.status === 1 ? '🟢 Online' : device.status === 0 ? '🔴 Offline' : '🟡 Unknown';
|
||
|
|
console.log(` ${index + 1}. ${device.name || device.mac || 'Unknown'}`);
|
||
|
|
console.log(` Model: ${device.model || 'N/A'}`);
|
||
|
|
console.log(` Status: ${status}`);
|
||
|
|
console.log(` IP: ${device.ip || 'N/A'}`);
|
||
|
|
console.log(` MAC: ${device.mac || 'N/A'}`);
|
||
|
|
console.log(` Device ID: ${device.id || 'N/A'}`);
|
||
|
|
console.log('');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (aps.length > 0) {
|
||
|
|
console.log('\n ACCESS POINTS:');
|
||
|
|
aps.forEach((device, index) => {
|
||
|
|
const status = device.status === 1 ? '🟢 Online' : device.status === 0 ? '🔴 Offline' : '🟡 Unknown';
|
||
|
|
console.log(` ${index + 1}. ${device.name || device.mac || 'Unknown'}`);
|
||
|
|
console.log(` Model: ${device.model || 'N/A'}`);
|
||
|
|
console.log(` Status: ${status}`);
|
||
|
|
console.log(` IP: ${device.ip || 'N/A'}`);
|
||
|
|
console.log('');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const others = devices.filter(d =>
|
||
|
|
d.type !== 'router' && d.type !== 'Gateway' &&
|
||
|
|
d.type !== 'switch' && d.type !== 'Switch' &&
|
||
|
|
d.type !== 'ap' && d.type !== 'AccessPoint'
|
||
|
|
);
|
||
|
|
|
||
|
|
if (others.length > 0) {
|
||
|
|
console.log('\n OTHER DEVICES:');
|
||
|
|
others.forEach((device, index) => {
|
||
|
|
const status = device.status === 1 ? '🟢 Online' : device.status === 0 ? '🔴 Offline' : '🟡 Unknown';
|
||
|
|
console.log(` ${index + 1}. ${device.name || device.mac || 'Unknown'} (${device.type || 'Unknown'})`);
|
||
|
|
console.log(` Status: ${status}`);
|
||
|
|
console.log(` IP: ${device.ip || 'N/A'}`);
|
||
|
|
console.log('');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.log(' No devices found');
|
||
|
|
}
|
||
|
|
} else if (devicesResponse.statusCode === 302 || devicesResponse.raw === '') {
|
||
|
|
console.log(' Note: Devices endpoint returned redirect (API may require different format)');
|
||
|
|
console.log(' Try accessing web interface at https://192.168.11.8:8043 to view devices');
|
||
|
|
} else {
|
||
|
|
console.log(' Response:', JSON.stringify(devicesResponse, null, 2));
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('5. Listing VLANs...');
|
||
|
|
const vlansResponse = await apiRequest('GET', `/api/v2/sites/${effectiveSiteId}/vlans`, null, token);
|
||
|
|
|
||
|
|
if (vlansResponse.errorCode === 0 && vlansResponse.result) {
|
||
|
|
const vlans = Array.isArray(vlansResponse.result) ? vlansResponse.result : [];
|
||
|
|
console.log(` ✓ Found ${vlans.length} VLAN(s)`);
|
||
|
|
|
||
|
|
if (vlans.length > 0) {
|
||
|
|
console.log('\n VLANs:');
|
||
|
|
vlans.forEach((vlan, index) => {
|
||
|
|
console.log(` ${index + 1}. VLAN ${vlan.vlanId || 'N/A'}: ${vlan.name || 'Unnamed'}`);
|
||
|
|
if (vlan.subnet) console.log(` Subnet: ${vlan.subnet}`);
|
||
|
|
if (vlan.gateway) console.log(` Gateway: ${vlan.gateway}`);
|
||
|
|
console.log('');
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
console.log(' No VLANs configured');
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.log(' Response:', JSON.stringify(vlansResponse, null, 2));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('\n=== Connection test completed successfully! ===');
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.error('\n=== Test failed ===');
|
||
|
|
console.error('Error:', error.message);
|
||
|
|
if (error.stack) {
|
||
|
|
console.error('\nStack trace:');
|
||
|
|
console.error(error.stack);
|
||
|
|
}
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
test();
|
||
|
|
|