import { Nupst } from '../nupst.js'; import { logger } from '../logger.js'; import * as helpers from '../helpers/index.js'; import { type IGroupConfig } from '../daemon.js'; /** * Class for handling group-related CLI commands * Provides interface for managing UPS groups */ export class GroupHandler { private readonly nupst: Nupst; /** * Create a new Group handler * @param nupst Reference to the main Nupst instance */ constructor(nupst: Nupst) { this.nupst = nupst; } /** * List all UPS groups */ public async list(): Promise { try { // Try to load configuration try { await this.nupst.getDaemon().loadConfig(); } catch (error) { const errorBoxWidth = 45; logger.logBoxTitle('Configuration Error', errorBoxWidth); logger.logBoxLine('No configuration found.'); logger.logBoxLine("Please run 'nupst setup' first to create a configuration."); logger.logBoxEnd(); return; } // Get current configuration const config = this.nupst.getDaemon().getConfig(); // Check if multi-UPS config if (!config.groups || !Array.isArray(config.groups)) { // Legacy or missing groups configuration const boxWidth = 45; logger.logBoxTitle('UPS Groups', boxWidth); logger.logBoxLine('No groups configured.'); logger.logBoxLine('Use "nupst group add" to add a UPS group.'); logger.logBoxEnd(); return; } // Display group list const boxWidth = 60; logger.logBoxTitle('UPS Groups', boxWidth); if (config.groups.length === 0) { logger.logBoxLine('No UPS groups configured.'); logger.logBoxLine('Use "nupst group add" to add a UPS group.'); } else { logger.logBoxLine(`Found ${config.groups.length} group(s)`); logger.logBoxLine(''); logger.logBoxLine('ID | Name | Mode | UPS Devices'); logger.logBoxLine('-----------+----------------------+--------------+----------------'); for (const group of config.groups) { const id = group.id.padEnd(10, ' ').substring(0, 10); const name = (group.name || '').padEnd(20, ' ').substring(0, 20); const mode = (group.mode || 'unknown').padEnd(12, ' ').substring(0, 12); // Count UPS devices in this group const upsInGroup = config.upsDevices.filter(ups => ups.groups.includes(group.id)); const upsCount = upsInGroup.length; const upsNames = upsInGroup.map(ups => ups.name).join(', '); logger.logBoxLine(`${id} | ${name} | ${mode} | ${upsCount > 0 ? upsNames : 'None'}`); } } logger.logBoxEnd(); } catch (error) { logger.error(`Failed to list UPS groups: ${error.message}`); } } /** * Add a new UPS group */ public async add(): Promise { try { // Import readline module for user input const readline = await import('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); // Helper function to prompt for input const prompt = (question: string): Promise => { return new Promise((resolve) => { rl.question(question, (answer: string) => { resolve(answer); }); }); }; try { // Try to load configuration try { await this.nupst.getDaemon().loadConfig(); } catch (error) { logger.error('No configuration found. Please run "nupst setup" first to create a configuration.'); return; } // Get current configuration const config = this.nupst.getDaemon().getConfig(); // Initialize groups array if not exists if (!config.groups) { config.groups = []; } // Check if upsDevices is initialized if (!config.upsDevices) { config.upsDevices = []; } logger.log('\nNUPST Add Group'); logger.log('==============\n'); logger.log('This will guide you through creating a new UPS group.\n'); // Generate a new unique group ID const groupId = helpers.shortId(); // Get group name const name = await prompt('Group Name: '); // Get group mode const modeInput = await prompt('Group Mode (redundant/nonRedundant) [redundant]: '); const mode = modeInput.toLowerCase() === 'nonredundant' ? 'nonRedundant' : 'redundant'; // Get optional description const description = await prompt('Group Description (optional): '); // Create the new group const newGroup: IGroupConfig = { id: groupId, name: name || `Group-${groupId}`, mode, description: description || undefined }; // Add the group to the configuration config.groups.push(newGroup); // Save the configuration await this.nupst.getDaemon().saveConfig(config); // Display summary const boxWidth = 45; logger.logBoxTitle('Group Created', boxWidth); logger.logBoxLine(`ID: ${newGroup.id}`); logger.logBoxLine(`Name: ${newGroup.name}`); logger.logBoxLine(`Mode: ${newGroup.mode}`); if (newGroup.description) { logger.logBoxLine(`Description: ${newGroup.description}`); } logger.logBoxEnd(); // Check if there are UPS devices to assign to this group if (config.upsDevices.length > 0) { const assignUps = await prompt('Would you like to assign UPS devices to this group now? (y/N): '); if (assignUps.toLowerCase() === 'y') { await this.assignUpsToGroup(newGroup.id, config, prompt); // Save again after assigning UPS devices await this.nupst.getDaemon().saveConfig(config); } } // Check if service is running and restart it if needed this.nupst.getUpsHandler().restartServiceIfRunning(); logger.log('\nGroup setup complete!'); } finally { rl.close(); } } catch (error) { logger.error(`Add group error: ${error.message}`); } } /** * Edit an existing UPS group * @param groupId ID of the group to edit */ public async edit(groupId: string): Promise { try { // Import readline module for user input const readline = await import('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); // Helper function to prompt for input const prompt = (question: string): Promise => { return new Promise((resolve) => { rl.question(question, (answer: string) => { resolve(answer); }); }); }; try { // Try to load configuration try { await this.nupst.getDaemon().loadConfig(); } catch (error) { logger.error('No configuration found. Please run "nupst setup" first to create a configuration.'); return; } // Get current configuration const config = this.nupst.getDaemon().getConfig(); // Check if groups are initialized if (!config.groups || !Array.isArray(config.groups)) { logger.error('No groups configured. Please run "nupst group add" first to create a group.'); return; } // Find the group to edit const groupIndex = config.groups.findIndex(group => group.id === groupId); if (groupIndex === -1) { logger.error(`Group with ID "${groupId}" not found.`); return; } const group = config.groups[groupIndex]; logger.log(`\nNUPST Edit Group: ${group.name} (${group.id})`); logger.log('==============================================\n'); // Edit group name const newName = await prompt(`Group Name [${group.name}]: `); if (newName.trim()) { group.name = newName; } // Edit group mode const currentMode = group.mode || 'redundant'; const modeInput = await prompt(`Group Mode (redundant/nonRedundant) [${currentMode}]: `); if (modeInput.trim()) { group.mode = modeInput.toLowerCase() === 'nonredundant' ? 'nonRedundant' : 'redundant'; } // Edit description const currentDesc = group.description || ''; const newDesc = await prompt(`Group Description [${currentDesc}]: `); if (newDesc.trim() || newDesc === '') { group.description = newDesc.trim() || undefined; } // Update the group in the configuration config.groups[groupIndex] = group; // Save the configuration await this.nupst.getDaemon().saveConfig(config); // Display summary const boxWidth = 45; logger.logBoxTitle('Group Updated', boxWidth); logger.logBoxLine(`ID: ${group.id}`); logger.logBoxLine(`Name: ${group.name}`); logger.logBoxLine(`Mode: ${group.mode}`); if (group.description) { logger.logBoxLine(`Description: ${group.description}`); } logger.logBoxEnd(); // Edit UPS assignments if requested const editAssignments = await prompt('Would you like to edit UPS assignments for this group? (y/N): '); if (editAssignments.toLowerCase() === 'y') { await this.assignUpsToGroup(group.id, config, prompt); // Save again after editing assignments await this.nupst.getDaemon().saveConfig(config); } // Check if service is running and restart it if needed this.nupst.getUpsHandler().restartServiceIfRunning(); logger.log('\nGroup edit complete!'); } finally { rl.close(); } } catch (error) { logger.error(`Edit group error: ${error.message}`); } } /** * Delete an existing UPS group * @param groupId ID of the group to delete */ public async delete(groupId: string): Promise { try { // Try to load configuration try { await this.nupst.getDaemon().loadConfig(); } catch (error) { logger.error('No configuration found. Please run "nupst setup" first to create a configuration.'); return; } // Get current configuration const config = this.nupst.getDaemon().getConfig(); // Check if groups are initialized if (!config.groups || !Array.isArray(config.groups)) { logger.error('No groups configured.'); return; } // Find the group to delete const groupIndex = config.groups.findIndex(group => group.id === groupId); if (groupIndex === -1) { logger.error(`Group with ID "${groupId}" not found.`); return; } const groupToDelete = config.groups[groupIndex]; // Get confirmation before deleting const readline = await import('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const confirm = await new Promise(resolve => { rl.question(`Are you sure you want to delete group "${groupToDelete.name}" (${groupId})? [y/N]: `, answer => { resolve(answer.toLowerCase()); }); }); rl.close(); if (confirm !== 'y' && confirm !== 'yes') { logger.log('Deletion cancelled.'); return; } // Remove this group from all UPS device group assignments if (config.upsDevices && Array.isArray(config.upsDevices)) { for (const ups of config.upsDevices) { const groupIndex = ups.groups.indexOf(groupId); if (groupIndex !== -1) { ups.groups.splice(groupIndex, 1); } } } // Remove the group from the array config.groups.splice(groupIndex, 1); // Save the configuration await this.nupst.getDaemon().saveConfig(config); logger.log(`Group "${groupToDelete.name}" (${groupId}) has been deleted.`); // Check if service is running and restart it if needed this.nupst.getUpsHandler().restartServiceIfRunning(); } catch (error) { logger.error(`Failed to delete group: ${error.message}`); } } /** * Assign UPS devices to groups * @param ups UPS configuration to update * @param groups Available groups * @param prompt Function to prompt for user input */ public async assignUpsToGroups( ups: any, groups: any[], prompt: (question: string) => Promise ): Promise { // Initialize groups array if it doesn't exist if (!ups.groups) { ups.groups = []; } // Show current group assignments logger.log('\nCurrent Group Assignments:'); if (ups.groups && ups.groups.length > 0) { for (const groupId of ups.groups) { const group = groups.find(g => g.id === groupId); if (group) { logger.log(`- ${group.name} (${group.id})`); } else { logger.log(`- Unknown group (${groupId})`); } } } else { logger.log('- None'); } // Show available groups logger.log('\nAvailable Groups:'); if (groups.length === 0) { logger.log('- No groups available. Use "nupst group add" to create groups.'); return; } for (let i = 0; i < groups.length; i++) { const group = groups[i]; const assigned = ups.groups && ups.groups.includes(group.id); logger.log(`${i + 1}) ${group.name} (${group.id}) [${assigned ? 'Assigned' : 'Not Assigned'}]`); } // Prompt for group selection const selection = await prompt('\nSelect groups to assign/unassign (comma-separated numbers, or "clear" to remove all): '); if (selection.toLowerCase() === 'clear') { // Clear all group assignments ups.groups = []; logger.log('All group assignments cleared.'); return; } if (!selection.trim()) { // No change if empty input return; } // Process selections const selections = selection.split(',').map(s => s.trim()); for (const sel of selections) { const index = parseInt(sel, 10) - 1; if (isNaN(index) || index < 0 || index >= groups.length) { logger.error(`Invalid selection: ${sel}`); continue; } const group = groups[index]; // Initialize groups array if needed (should already be done above) if (!ups.groups) { ups.groups = []; } // Toggle assignment const groupIndex = ups.groups.indexOf(group.id); if (groupIndex === -1) { // Add to group ups.groups.push(group.id); logger.log(`Added to group: ${group.name} (${group.id})`); } else { // Remove from group ups.groups.splice(groupIndex, 1); logger.log(`Removed from group: ${group.name} (${group.id})`); } } } /** * Assign UPS devices to a specific group * @param groupId Group ID to assign UPS devices to * @param config Full configuration * @param prompt Function to prompt for user input */ public async assignUpsToGroup( groupId: string, config: any, prompt: (question: string) => Promise ): Promise { if (!config.upsDevices || config.upsDevices.length === 0) { logger.log('No UPS devices available. Use "nupst add" to add UPS devices.'); return; } const group = config.groups.find(g => g.id === groupId); if (!group) { logger.error(`Group with ID "${groupId}" not found.`); return; } // Show current assignments logger.log(`\nUPS devices in group "${group.name}" (${group.id}):`); const upsInGroup = config.upsDevices.filter(ups => ups.groups && ups.groups.includes(groupId)); if (upsInGroup.length === 0) { logger.log('- None'); } else { for (const ups of upsInGroup) { logger.log(`- ${ups.name} (${ups.id})`); } } // Show all UPS devices logger.log('\nAvailable UPS devices:'); for (let i = 0; i < config.upsDevices.length; i++) { const ups = config.upsDevices[i]; const assigned = ups.groups && ups.groups.includes(groupId); logger.log(`${i + 1}) ${ups.name} (${ups.id}) [${assigned ? 'Assigned' : 'Not Assigned'}]`); } // Prompt for UPS selection const selection = await prompt('\nSelect UPS devices to assign/unassign (comma-separated numbers, or "clear" to remove all): '); if (selection.toLowerCase() === 'clear') { // Clear all UPS from this group for (const ups of config.upsDevices) { if (ups.groups) { const groupIndex = ups.groups.indexOf(groupId); if (groupIndex !== -1) { ups.groups.splice(groupIndex, 1); } } } logger.log(`All UPS devices removed from group "${group.name}".`); return; } if (!selection.trim()) { // No change if empty input return; } // Process selections const selections = selection.split(',').map(s => s.trim()); for (const sel of selections) { const index = parseInt(sel, 10) - 1; if (isNaN(index) || index < 0 || index >= config.upsDevices.length) { logger.error(`Invalid selection: ${sel}`); continue; } const ups = config.upsDevices[index]; // Initialize groups array if needed if (!ups.groups) { ups.groups = []; } // Toggle assignment const groupIndex = ups.groups.indexOf(groupId); if (groupIndex === -1) { // Add to group ups.groups.push(groupId); logger.log(`Added "${ups.name}" to group "${group.name}"`); } else { // Remove from group ups.groups.splice(groupIndex, 1); logger.log(`Removed "${ups.name}" from group "${group.name}"`); } } } }