pbx-admin
Version:
CLI tool to manage PBX administrators on multiple Vodia PBX servers
400 lines (360 loc) • 15.4 kB
JavaScript
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const jmespath = require('jmespath');
// Configuration file path
const CONFIG_FILE = path.join(require('os').homedir(), '.pbx-admin.conf');
// Load configuration
function loadConfig() {
if (!fs.existsSync(CONFIG_FILE)) {
console.error(`Error: Configuration file '${CONFIG_FILE}' not found.`);
process.exit(1);
}
const config = {
username: null,
password: null,
server: null
};
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
content.split('\n').forEach(line => {
if (!line.trim()) return;
const match = line.match(/^([^:]+):\s*(.+)$/) || line.match(/^([^:]+):(.+)$/);
if (match) {
const key = match[1].trim();
const value = match[2].trim();
if (key === 'username') config.username = value;
if (key === 'password') config.password = value;
if (key === 'server') config.server = value.split(' ').filter(Boolean);
}
});
if (!config.username || !config.password || !config.server || config.server.length === 0) {
console.error('Error: Missing required variables in the configuration file. Ensure "username", "password", and "server" are set.');
process.exit(1);
}
return config;
}
// Make API requests to all servers
async function makeRequests(hostnames, options) {
const results = [];
for (const hostname of hostnames) {
try {
const url = `${hostname}${options.path}`;
const response = await axios({
method: options.method || 'GET',
url,
auth: {
username: options.auth.username,
password: options.auth.password
},
data: options.data,
httpsAgent: new (require('https').Agent)({
rejectUnauthorized: false
})
});
results.push({
hostname,
success: true,
data: response.data
});
} catch (error) {
results.push({
hostname,
success: false,
error: error.response ? error.response.data : error.message
});
}
}
return results;
}
// Main function
async function main() {
const config = loadConfig();
const argv = yargs(hideBin(process.argv))
.command('add', 'Add a new PBX administrator', (yargs) => {
return yargs
.option('username', {
describe: 'Admin username',
type: 'string',
required: true
})
.option('password', {
describe: 'Admin password',
type: 'string',
required: true
})
.option('display', {
describe: 'Admin display name',
type: 'string',
default: null
})
.option('email', {
describe: 'Admin email',
type: 'string',
default: null
})
.option('ip', {
describe: 'IP restrictions',
type: 'string',
default: null
})
.option('api', {
describe: 'API access flag',
type: 'boolean',
default: false
})
.option('phone', {
describe: 'Admin phone number',
type: 'string',
default: null
});
})
.command('delete <username>', 'Delete the specified PBX administrator', (yargs) => {
return yargs
.positional('username', {
describe: 'Admin username to delete',
type: 'string'
});
})
.command('modify', 'Modify an existing PBX administrator', (yargs) => {
return yargs
.option('username', {
describe: 'Admin username',
type: 'string',
required: true
})
.option('password', {
describe: 'New admin password',
type: 'string'
})
.option('display', {
describe: 'New admin display name',
type: 'string'
})
.option('email', {
describe: 'New admin email',
type: 'string'
})
.option('ip', {
describe: 'New IP restrictions',
type: 'string'
})
.option('api', {
describe: 'New API access flag',
type: 'boolean'
})
.option('phone', {
describe: 'New admin phone number',
type: 'string'
})
.check(argv => {
if (!argv.password && !argv.display && !argv.email && !argv.ip &&
argv.api === undefined && !argv.phone) {
throw new Error('At least one modification option must be provided');
}
return true;
});
})
.command('list_versions', 'List version, build date and uptime of all servers')
.command('system_usage', 'Get system usage statistics for all PBXs', () => {})
.command('config_global_setting', 'Set global setting for the PBXs', (yargs) => {
return yargs
.option('config_variable', {
describe: 'Configuration variable name',
type: 'string',
required: true
})
.option('config_value', {
describe: 'Configuration value',
type: 'string',
required: true
});
})
.command('blocked_ips', 'List all blocked IP addresses with duration in hours', () => {})
.demandCommand(1, 'You need to specify a command')
.help()
.argv;
const command = argv._[0];
try {
switch (command) {
case 'add':
const adminData = {
name: argv.username,
display: argv.display || argv.username,
password: argv.password,
adr: argv.ip || undefined,
email: argv.email || undefined,
api: argv.api,
phone: argv.phone || undefined
};
const results = await makeRequests(config.server, {
path: '/rest/system/administrators',
method: 'POST',
auth: {
username: config.username,
password: config.password
},
data: adminData
});
results.forEach(result => {
if (result.success) {
console.log(`Admin ${argv.username} added to ${result.hostname}`);
} else {
console.error(`Failed to add admin to ${result.hostname}:`, result.error);
}
});
break;
case 'delete':
const deleteResults = await makeRequests(config.server, {
path: `/rest/system/administrators?name=${argv.username}`,
method: 'DELETE',
auth: {
username: config.username,
password: config.password
}
});
deleteResults.forEach(result => {
if (result.success) {
console.log(`Admin ${argv.username} deleted from ${result.hostname}`);
} else {
console.error(`Failed to delete admin from ${result.hostname}:`, result.error);
}
});
break;
case 'modify':
const modifyData = {
name: argv.username
};
if (argv.password) modifyData.password = argv.password;
if (argv.display) modifyData.display = argv.display;
if (argv.email) modifyData.email = argv.email;
if (argv.ip) modifyData.adr = argv.ip;
if (argv.api !== undefined) modifyData.api = argv.api;
if (argv.phone) modifyData.phone = argv.phone;
const modifyResults = await makeRequests(config.server, {
path: '/rest/system/administrators',
method: 'POST',
auth: {
username: config.username,
password: config.password
},
data: modifyData
});
modifyResults.forEach(result => {
if (result.success) {
console.log(`Admin ${argv.username} modified on ${result.hostname}`);
} else {
console.error(`Failed to modify admin on ${result.hostname}:`, result.error);
}
});
break;
case 'list_versions':
const versionResults = await makeRequests(config.server, {
path: '/rest/system/status',
auth: {
username: config.username,
password: config.password
}
});
versionResults.forEach(result => {
if (result.success) {
const version = result.data.version;
const buildDate = result.data.build_date;
const uptimeDays = (result.data.uptime / 86400).toFixed(1);
console.log(`PBX ${result.hostname}: Version ${version}, Build ${buildDate}, Uptime ${uptimeDays} days`);
} else {
console.error(`Failed to get version from ${result.hostname}:`, result.error);
}
});
break;
case 'system_usage':
const usageResults = await makeRequests(config.server, {
path: '/rest/system/usage',
auth: {
username: config.username,
password: config.password
}
});
console.log(`PBX,Tenant,Extensions,Call Queues,Conferences`);
usageResults.forEach(result => {
if (result.success) {
result.data.forEach(pbx => {
const name = pbx.name; // Keep full domain name
const extensions = pbx.extensions || 0;
const acd = pbx.acd || 0;
const conferences = pbx.conf || 0;
console.log(`"${result.hostname}","${name}",${extensions},${acd},${conferences}`);
});
} else {
console.error(`Failed to get usage from ${result.hostname}:`, result.error);
}
});
break;
case 'config_global_setting':
const configData = {
[argv.config_variable]: argv.config_value
};
const configResults = await makeRequests(config.server, {
path: '/rest/system/config',
method: 'POST',
auth: {
username: config.username,
password: config.password
},
data: configData
});
configResults.forEach(result => {
if (result.success) {
console.log(`Configuration ${argv.config_variable} set to ${argv.config_value} on ${result.hostname}`);
} else {
console.error(`Failed to set configuration on ${result.hostname}:`, result.error);
}
});
break;
case 'blocked_ips':
const blockedResults = await makeRequests(config.server, {
path: '/rest/system/access',
auth: {
username: config.username,
password: config.password
}
});
blockedResults.forEach(result => {
if (result.success) {
const blockedIps = result.data.filter(item => item.enabled === false);
if (blockedIps.length === 0) {
console.log(`No blocked IPs found on ${result.hostname}`);
return;
}
console.log(`\nBlocked IPs on ${result.hostname}:`);
console.log('='.repeat(80));
console.log('IP Address'.padEnd(20) + 'Type'.padEnd(10) + 'Port'.padEnd(10) +
'Duration (hrs)'.padEnd(15) + 'Comment');
console.log('-'.repeat(80));
blockedIps.forEach(ip => {
const durationHours = ip.duration ? (ip.duration / 3600).toFixed(2) : 'N/A';
console.log(
(ip.adr || '').padEnd(20) +
(ip.type || '').padEnd(10) +
(ip.port || '0').toString().padEnd(10) +
durationHours.padEnd(15) +
(ip.comment || '')
);
});
} else {
console.error(`Failed to get blocked IPs from ${result.hostname}:`, result.error);
}
});
break;
default:
console.error('Error: Invalid command');
process.exit(1);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
main();