Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
95fa4f8b0b | |||
c2f2f1e2ee | |||
936f86c346 | |||
7ff1a7da36 | |||
a87710144c | |||
23fd5cc5cd |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/nupst",
|
"name": "@serve.zone/nupst",
|
||||||
"version": "4.0.4",
|
"version": "4.0.9",
|
||||||
"exports": "./mod.ts",
|
"exports": "./mod.ts",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run --allow-all mod.ts",
|
"dev": "deno run --allow-all mod.ts",
|
||||||
|
92
install.sh
92
install.sh
@@ -10,15 +10,7 @@
|
|||||||
# With version specification:
|
# With version specification:
|
||||||
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v4.0.0
|
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v4.0.0
|
||||||
#
|
#
|
||||||
# Non-interactive mode (auto-confirm):
|
|
||||||
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y
|
|
||||||
#
|
|
||||||
# Downloaded script:
|
|
||||||
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh -o nupst-install.sh
|
|
||||||
# sudo bash nupst-install.sh
|
|
||||||
#
|
|
||||||
# Options:
|
# Options:
|
||||||
# -y, --yes Automatically answer yes to all prompts
|
|
||||||
# -h, --help Show this help message
|
# -h, --help Show this help message
|
||||||
# --version VERSION Install specific version (e.g., v4.0.0)
|
# --version VERSION Install specific version (e.g., v4.0.0)
|
||||||
# --install-dir DIR Installation directory (default: /opt/nupst)
|
# --install-dir DIR Installation directory (default: /opt/nupst)
|
||||||
@@ -26,7 +18,6 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Default values
|
# Default values
|
||||||
AUTO_YES=0
|
|
||||||
SHOW_HELP=0
|
SHOW_HELP=0
|
||||||
SPECIFIED_VERSION=""
|
SPECIFIED_VERSION=""
|
||||||
INSTALL_DIR="/opt/nupst"
|
INSTALL_DIR="/opt/nupst"
|
||||||
@@ -36,10 +27,6 @@ GITEA_REPO="serve.zone/nupst"
|
|||||||
# Parse command line arguments
|
# Parse command line arguments
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
-y|--yes)
|
|
||||||
AUTO_YES=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-h|--help)
|
-h|--help)
|
||||||
SHOW_HELP=1
|
SHOW_HELP=1
|
||||||
shift
|
shift
|
||||||
@@ -67,7 +54,6 @@ if [ $SHOW_HELP -eq 1 ]; then
|
|||||||
echo "Usage: $0 [options]"
|
echo "Usage: $0 [options]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " -y, --yes Automatically answer yes to all prompts"
|
|
||||||
echo " -h, --help Show this help message"
|
echo " -h, --help Show this help message"
|
||||||
echo " --version VERSION Install specific version (e.g., v4.0.0)"
|
echo " --version VERSION Install specific version (e.g., v4.0.0)"
|
||||||
echo " --install-dir DIR Installation directory (default: /opt/nupst)"
|
echo " --install-dir DIR Installation directory (default: /opt/nupst)"
|
||||||
@@ -78,9 +64,6 @@ if [ $SHOW_HELP -eq 1 ]; then
|
|||||||
echo ""
|
echo ""
|
||||||
echo " # Install specific version"
|
echo " # Install specific version"
|
||||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v4.0.0"
|
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v4.0.0"
|
||||||
echo ""
|
|
||||||
echo " # Non-interactive installation"
|
|
||||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y"
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -90,36 +73,6 @@ if [ "$EUID" -ne 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect if script is being piped or run directly
|
|
||||||
INTERACTIVE=1
|
|
||||||
if [ ! -t 0 ] || [ ! -t 1 ]; then
|
|
||||||
# Either stdin or stdout is not a terminal
|
|
||||||
if [ $AUTO_YES -ne 1 ]; then
|
|
||||||
echo "Script detected it's running in a non-interactive environment without -y flag."
|
|
||||||
echo "Attempting to find a controlling terminal for interactive prompts..."
|
|
||||||
# Try to use a controlling terminal for user input
|
|
||||||
if exec < /dev/tty 2>/dev/null && [ -t 0 ]; then
|
|
||||||
INTERACTIVE=1
|
|
||||||
else
|
|
||||||
INTERACTIVE=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $INTERACTIVE -eq 0 ]; then
|
|
||||||
echo "ERROR: No controlling terminal available for interactive prompts."
|
|
||||||
echo ""
|
|
||||||
echo "For interactive installation (RECOMMENDED):"
|
|
||||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh -o nupst-install.sh"
|
|
||||||
echo " sudo bash nupst-install.sh"
|
|
||||||
echo ""
|
|
||||||
echo "For non-interactive installation with auto-confirm:"
|
|
||||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "Interactive terminal found, continuing with prompts..."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Helper function to detect OS and architecture
|
# Helper function to detect OS and architecture
|
||||||
detect_platform() {
|
detect_platform() {
|
||||||
local os=$(uname -s)
|
local os=$(uname -s)
|
||||||
@@ -229,22 +182,6 @@ if [ -d "$INSTALL_DIR" ]; then
|
|||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $AUTO_YES -eq 0 ] && [ $INTERACTIVE -eq 1 ]; then
|
|
||||||
if [ $OLD_NODE_INSTALL -eq 1 ]; then
|
|
||||||
echo "This will replace your Node.js installation with a pre-compiled binary."
|
|
||||||
echo "Your configuration in /etc/nupst/config.json will be preserved."
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
echo "Installation directory already exists: $INSTALL_DIR"
|
|
||||||
echo "Do you want to update/reinstall? (Y/n): "
|
|
||||||
read -r update_confirm
|
|
||||||
|
|
||||||
if [[ "$update_confirm" =~ ^[Nn]$ ]]; then
|
|
||||||
echo "Installation cancelled."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Updating existing installation at $INSTALL_DIR..."
|
echo "Updating existing installation at $INSTALL_DIR..."
|
||||||
|
|
||||||
# Check if service exists (enabled or running) and stop it if active
|
# Check if service exists (enabled or running) and stop it if active
|
||||||
@@ -273,17 +210,6 @@ if [ -d "$INSTALL_DIR" ]; then
|
|||||||
echo "Old installation files removed."
|
echo "Old installation files removed."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [ $AUTO_YES -eq 0 ] && [ $INTERACTIVE -eq 1 ]; then
|
|
||||||
echo "NUPST will be installed to: $INSTALL_DIR"
|
|
||||||
echo "Continue? (Y/n): "
|
|
||||||
read -r install_confirm
|
|
||||||
|
|
||||||
if [[ "$install_confirm" =~ ^[Nn]$ ]]; then
|
|
||||||
echo "Installation cancelled."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Creating installation directory: $INSTALL_DIR"
|
echo "Creating installation directory: $INSTALL_DIR"
|
||||||
mkdir -p "$INSTALL_DIR"
|
mkdir -p "$INSTALL_DIR"
|
||||||
fi
|
fi
|
||||||
@@ -329,22 +255,8 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Create symlink for global access
|
# Create symlink for global access
|
||||||
if [ $AUTO_YES -eq 0 ] && [ $INTERACTIVE -eq 1 ]; then
|
ln -sf "$BINARY_PATH" "$BIN_DIR/nupst"
|
||||||
echo "Create symlink in $BIN_DIR for global access? (Y/n): "
|
echo "Symlink created: $BIN_DIR/nupst -> $BINARY_PATH"
|
||||||
read -r symlink_confirm
|
|
||||||
|
|
||||||
if [[ ! "$symlink_confirm" =~ ^[Nn]$ ]]; then
|
|
||||||
ln -sf "$BINARY_PATH" "$BIN_DIR/nupst"
|
|
||||||
echo "Symlink created: $BIN_DIR/nupst -> $BINARY_PATH"
|
|
||||||
else
|
|
||||||
echo "Symlink creation skipped."
|
|
||||||
echo "To use NUPST, run: $BINARY_PATH"
|
|
||||||
echo "Or manually create symlink: sudo ln -sf $BINARY_PATH $BIN_DIR/nupst"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
ln -sf "$BINARY_PATH" "$BIN_DIR/nupst"
|
|
||||||
echo "Symlink created: $BIN_DIR/nupst -> $BINARY_PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
16
readme.md
16
readme.md
@@ -29,15 +29,8 @@ dependencies.
|
|||||||
The easiest way to install NUPST is using the automated installer:
|
The easiest way to install NUPST is using the automated installer:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Download and run installer (most reliable)
|
# One-line installation
|
||||||
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh -o nupst-install.sh
|
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
||||||
sudo bash nupst-install.sh
|
|
||||||
rm nupst-install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# One-line installation (non-interactive with auto-confirm)
|
|
||||||
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer will:
|
The installer will:
|
||||||
@@ -76,7 +69,6 @@ sudo mv nupst /usr/local/bin/nupst
|
|||||||
The installer script (`install.sh`) supports the following options:
|
The installer script (`install.sh`) supports the following options:
|
||||||
|
|
||||||
```
|
```
|
||||||
-y, --yes Automatically answer yes to all prompts
|
|
||||||
-h, --help Show help message
|
-h, --help Show help message
|
||||||
--version VERSION Install specific version (e.g., --version v4.0.0)
|
--version VERSION Install specific version (e.g., --version v4.0.0)
|
||||||
--install-dir DIR Custom installation directory (default: /opt/nupst)
|
--install-dir DIR Custom installation directory (default: /opt/nupst)
|
||||||
@@ -373,7 +365,7 @@ sudo nupst service disable
|
|||||||
Re-run the installer to update to the latest version:
|
Re-run the installer to update to the latest version:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y
|
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer will:
|
The installer will:
|
||||||
@@ -461,7 +453,7 @@ The installer script automatically handles the entire migration while preserving
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run the installer (handles stop/update/restart automatically)
|
# Run the installer (handles stop/update/restart automatically)
|
||||||
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y
|
curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
||||||
|
|
||||||
# Verify
|
# Verify
|
||||||
nupst service status
|
nupst service status
|
||||||
|
@@ -129,81 +129,57 @@ export class ServiceHandler {
|
|||||||
try {
|
try {
|
||||||
// Check if running as root
|
// Check if running as root
|
||||||
this.checkRootAccess(
|
this.checkRootAccess(
|
||||||
'This command must be run as root to update NUPST and refresh the systemd service.',
|
'This command must be run as root to update NUPST.',
|
||||||
);
|
);
|
||||||
|
|
||||||
const boxWidth = 45;
|
console.log('');
|
||||||
logger.logBoxTitle('NUPST Update Process', boxWidth);
|
logger.info('Checking for updates...');
|
||||||
logger.logBoxLine('Updating NUPST from repository...');
|
|
||||||
|
|
||||||
// Determine the installation directory (assuming it's either /opt/nupst or the current directory)
|
|
||||||
const { existsSync } = await import('fs');
|
|
||||||
let installDir = '/opt/nupst';
|
|
||||||
|
|
||||||
if (!existsSync(installDir)) {
|
|
||||||
// If not installed in /opt/nupst, use the current directory
|
|
||||||
const { dirname } = await import('path');
|
|
||||||
installDir = dirname(dirname(process.argv[1])); // Go up two levels from the executable
|
|
||||||
logger.logBoxLine(`Using local installation directory: ${installDir}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Update the repository
|
// Get current version
|
||||||
logger.logBoxLine('Pulling latest changes from git repository...');
|
const currentVersion = this.nupst.getVersion();
|
||||||
execSync(`cd ${installDir} && git fetch origin && git reset --hard origin/main`, {
|
|
||||||
stdio: 'pipe',
|
// Fetch latest version from Gitea API
|
||||||
|
const apiUrl = 'https://code.foss.global/api/v1/repos/serve.zone/nupst/releases/latest';
|
||||||
|
const response = execSync(`curl -sSL ${apiUrl}`).toString();
|
||||||
|
const release = JSON.parse(response);
|
||||||
|
const latestVersion = release.tag_name; // e.g., "v4.0.7"
|
||||||
|
|
||||||
|
// Normalize versions for comparison (ensure both have "v" prefix)
|
||||||
|
const normalizedCurrent = currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}`;
|
||||||
|
const normalizedLatest = latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}`;
|
||||||
|
|
||||||
|
logger.dim(`Current version: ${normalizedCurrent}`);
|
||||||
|
logger.dim(`Latest version: ${normalizedLatest}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Compare normalized versions
|
||||||
|
if (normalizedCurrent === normalizedLatest) {
|
||||||
|
logger.success('Already up to date!');
|
||||||
|
console.log('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`New version available: ${latestVersion}`);
|
||||||
|
logger.dim('Downloading and installing...');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Download and run the install script
|
||||||
|
// This handles everything: download binary, stop service, replace, restart
|
||||||
|
const installUrl = 'https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh';
|
||||||
|
|
||||||
|
execSync(`curl -sSL ${installUrl} | bash`, {
|
||||||
|
stdio: 'inherit', // Show install script output to user
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Run the install.sh script
|
console.log('');
|
||||||
logger.logBoxLine('Running install.sh to update NUPST...');
|
logger.success(`Updated to ${latestVersion}`);
|
||||||
execSync(`cd ${installDir} && bash ./install.sh`, { stdio: 'pipe' });
|
console.log('');
|
||||||
|
|
||||||
// 3. Run the setup.sh script with force flag to update Node.js and dependencies
|
|
||||||
logger.logBoxLine('Running setup.sh to update Node.js and dependencies...');
|
|
||||||
execSync(`cd ${installDir} && bash ./setup.sh --force`, { stdio: 'pipe' });
|
|
||||||
|
|
||||||
// 4. Refresh the systemd service
|
|
||||||
logger.logBoxLine('Refreshing systemd service...');
|
|
||||||
|
|
||||||
// First check if service exists
|
|
||||||
let serviceExists = false;
|
|
||||||
try {
|
|
||||||
const output = execSync('systemctl list-unit-files | grep nupst.service').toString();
|
|
||||||
serviceExists = output.includes('nupst.service');
|
|
||||||
} catch (error) {
|
|
||||||
// If grep fails (service not found), serviceExists remains false
|
|
||||||
serviceExists = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serviceExists) {
|
|
||||||
// Stop the service if it's running
|
|
||||||
const isRunning =
|
|
||||||
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
|
||||||
if (isRunning) {
|
|
||||||
logger.logBoxLine('Stopping nupst service...');
|
|
||||||
execSync('systemctl stop nupst.service');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reinstall the service
|
|
||||||
logger.logBoxLine('Reinstalling systemd service...');
|
|
||||||
await this.nupst.getSystemd().install();
|
|
||||||
|
|
||||||
// Restart the service if it was running
|
|
||||||
if (isRunning) {
|
|
||||||
logger.logBoxLine('Restarting nupst service...');
|
|
||||||
execSync('systemctl start nupst.service');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.logBoxLine('Systemd service not installed, skipping service refresh.');
|
|
||||||
logger.logBoxLine('Run "nupst enable" to install the service.');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.logBoxLine('Update completed successfully!');
|
|
||||||
logger.logBoxEnd();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.logBoxLine('Error during update process:');
|
console.log('');
|
||||||
logger.logBoxLine(`${error instanceof Error ? error.message : String(error)}`);
|
logger.error('Update failed');
|
||||||
logger.logBoxEnd();
|
logger.dim(`${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
console.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -44,17 +44,30 @@ export class MigrationV3ToV4 extends BaseMigration {
|
|||||||
readonly toVersion = '4.0';
|
readonly toVersion = '4.0';
|
||||||
|
|
||||||
async shouldRun(config: any): Promise<boolean> {
|
async shouldRun(config: any): Promise<boolean> {
|
||||||
// V3 format has upsList instead of upsDevices
|
// V3 format has upsList OR has upsDevices with flat structure (host at top level)
|
||||||
return !!config.upsList && !config.upsDevices;
|
if (config.upsList && !config.upsDevices) {
|
||||||
|
return true; // Classic v3 with upsList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if upsDevices exists but has flat structure (v3 format)
|
||||||
|
if (config.upsDevices && config.upsDevices.length > 0) {
|
||||||
|
const firstDevice = config.upsDevices[0];
|
||||||
|
// V3 has host at top level, v4 has it nested in snmp object
|
||||||
|
return !!firstDevice.host && !firstDevice.snmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async migrate(config: any): Promise<any> {
|
async migrate(config: any): Promise<any> {
|
||||||
logger.info(`${this.getName()}: Migrating v3 config to v4 format...`);
|
logger.info(`${this.getName()}: Migrating v3 config to v4 format...`);
|
||||||
logger.dim(` - Renaming upsList → upsDevices`);
|
|
||||||
logger.dim(` - Restructuring UPS devices (flat → nested snmp config)`);
|
logger.dim(` - Restructuring UPS devices (flat → nested snmp config)`);
|
||||||
|
|
||||||
|
// Get devices from either upsList or upsDevices (for partially migrated configs)
|
||||||
|
const sourceDevices = config.upsList || config.upsDevices;
|
||||||
|
|
||||||
// Transform each UPS device from v3 flat structure to v4 nested structure
|
// Transform each UPS device from v3 flat structure to v4 nested structure
|
||||||
const transformedDevices = config.upsList.map((device: any) => {
|
const transformedDevices = sourceDevices.map((device: any) => {
|
||||||
// Build SNMP config object
|
// Build SNMP config object
|
||||||
const snmpConfig: any = {
|
const snmpConfig: any = {
|
||||||
host: device.host,
|
host: device.host,
|
||||||
|
148
ts/systemd.ts
148
ts/systemd.ts
@@ -3,6 +3,7 @@ import { promises as fs } from 'node:fs';
|
|||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
import { NupstDaemon } from './daemon.ts';
|
import { NupstDaemon } from './daemon.ts';
|
||||||
import { logger } from './logger.ts';
|
import { logger } from './logger.ts';
|
||||||
|
import { theme, symbols, getBatteryColor, getRuntimeColor, formatPowerStatus } from './colors.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for managing systemd service
|
* Class for managing systemd service
|
||||||
@@ -171,18 +172,50 @@ WantedBy=multi-user.target
|
|||||||
private displayServiceStatus(): void {
|
private displayServiceStatus(): void {
|
||||||
try {
|
try {
|
||||||
const serviceStatus = execSync('systemctl status nupst.service').toString();
|
const serviceStatus = execSync('systemctl status nupst.service').toString();
|
||||||
const boxWidth = 45;
|
const lines = serviceStatus.split('\n');
|
||||||
logger.logBoxTitle('Service Status', boxWidth);
|
|
||||||
// Process each line of the status output
|
// Parse key information from systemctl output
|
||||||
serviceStatus.split('\n').forEach((line) => {
|
let isActive = false;
|
||||||
logger.logBoxLine(line);
|
let pid = '';
|
||||||
});
|
let memory = '';
|
||||||
logger.logBoxEnd();
|
let cpu = '';
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.includes('Active:')) {
|
||||||
|
isActive = line.includes('active (running)');
|
||||||
|
} else if (line.includes('Main PID:')) {
|
||||||
|
const match = line.match(/Main PID:\s+(\d+)/);
|
||||||
|
if (match) pid = match[1];
|
||||||
|
} else if (line.includes('Memory:')) {
|
||||||
|
const match = line.match(/Memory:\s+([\d.]+[A-Z])/);
|
||||||
|
if (match) memory = match[1];
|
||||||
|
} else if (line.includes('CPU:')) {
|
||||||
|
const match = line.match(/CPU:\s+([\d.]+(?:ms|s))/);
|
||||||
|
if (match) cpu = match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display beautiful status
|
||||||
|
console.log('');
|
||||||
|
if (isActive) {
|
||||||
|
console.log(`${symbols.running} ${theme.success('Service:')} ${theme.statusActive('active (running)')}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('inactive')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid || memory || cpu) {
|
||||||
|
const details = [];
|
||||||
|
if (pid) details.push(`PID: ${theme.dim(pid)}`);
|
||||||
|
if (memory) details.push(`Memory: ${theme.dim(memory)}`);
|
||||||
|
if (cpu) details.push(`CPU: ${theme.dim(cpu)}`);
|
||||||
|
console.log(` ${details.join(' ')}`);
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const boxWidth = 45;
|
console.log('');
|
||||||
logger.logBoxTitle('Service Status', boxWidth);
|
console.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('not installed')}`);
|
||||||
logger.logBoxLine('Service is not running');
|
console.log('');
|
||||||
logger.logBoxEnd();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +232,7 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
// Check if we have the new multi-UPS config format
|
// Check if we have the new multi-UPS config format
|
||||||
if (config.upsDevices && Array.isArray(config.upsDevices) && config.upsDevices.length > 0) {
|
if (config.upsDevices && Array.isArray(config.upsDevices) && config.upsDevices.length > 0) {
|
||||||
logger.log(`Found ${config.upsDevices.length} UPS device(s) in configuration`);
|
console.log(theme.info(`UPS Devices (${config.upsDevices.length}):`));
|
||||||
|
|
||||||
// Show status for each UPS
|
// Show status for each UPS
|
||||||
for (const ups of config.upsDevices) {
|
for (const ups of config.upsDevices) {
|
||||||
@@ -207,6 +240,7 @@ WantedBy=multi-user.target
|
|||||||
}
|
}
|
||||||
} else if (config.snmp) {
|
} else if (config.snmp) {
|
||||||
// Legacy single UPS configuration
|
// Legacy single UPS configuration
|
||||||
|
console.log(theme.info('UPS Devices (1):'));
|
||||||
const legacyUps = {
|
const legacyUps = {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
name: 'Default UPS',
|
name: 'Default UPS',
|
||||||
@@ -217,15 +251,16 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
await this.displaySingleUpsStatus(legacyUps, snmp);
|
await this.displaySingleUpsStatus(legacyUps, snmp);
|
||||||
} else {
|
} else {
|
||||||
logger.error('No UPS devices found in configuration');
|
console.log('');
|
||||||
|
console.log(`${symbols.warning} ${theme.warning('No UPS devices configured')}`);
|
||||||
|
console.log(` ${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to add a device')}`);
|
||||||
|
console.log('');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const boxWidth = 45;
|
console.log('');
|
||||||
logger.logBoxTitle('UPS Status', boxWidth);
|
console.log(`${symbols.error} ${theme.error('Failed to retrieve UPS status')}`);
|
||||||
logger.logBoxLine(
|
console.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`);
|
||||||
`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`,
|
console.log('');
|
||||||
);
|
|
||||||
logger.logBoxEnd();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,24 +270,6 @@ WantedBy=multi-user.target
|
|||||||
* @param snmp SNMP manager
|
* @param snmp SNMP manager
|
||||||
*/
|
*/
|
||||||
private async displaySingleUpsStatus(ups: any, snmp: any): Promise<void> {
|
private async displaySingleUpsStatus(ups: any, snmp: any): Promise<void> {
|
||||||
const boxWidth = 45;
|
|
||||||
logger.logBoxTitle(`Connecting to UPS: ${ups.name}`, boxWidth);
|
|
||||||
logger.logBoxLine(`ID: ${ups.id}`);
|
|
||||||
logger.logBoxLine(`Host: ${ups.snmp.host}:${ups.snmp.port}`);
|
|
||||||
logger.logBoxLine(`UPS Model: ${ups.snmp.upsModel || 'cyberpower'}`);
|
|
||||||
|
|
||||||
if (ups.groups && ups.groups.length > 0) {
|
|
||||||
// Get group names if available
|
|
||||||
const config = this.daemon.getConfig();
|
|
||||||
const groupNames = ups.groups.map((groupId: string) => {
|
|
||||||
const group = config.groups?.find((g: { id: string }) => g.id === groupId);
|
|
||||||
return group ? group.name : groupId;
|
|
||||||
});
|
|
||||||
logger.logBoxLine(`Groups: ${groupNames.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.logBoxEnd();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a test config with a short timeout
|
// Create a test config with a short timeout
|
||||||
const testConfig = {
|
const testConfig = {
|
||||||
@@ -262,32 +279,43 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
const status = await snmp.getUpsStatus(testConfig);
|
const status = await snmp.getUpsStatus(testConfig);
|
||||||
|
|
||||||
logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth);
|
// Determine status symbol based on power status
|
||||||
logger.logBoxLine(`Power Status: ${status.powerStatus}`);
|
let statusSymbol = symbols.unknown;
|
||||||
logger.logBoxLine(`Battery Capacity: ${status.batteryCapacity}%`);
|
if (status.powerStatus === 'online') {
|
||||||
logger.logBoxLine(`Runtime Remaining: ${status.batteryRuntime} minutes`);
|
statusSymbol = symbols.running;
|
||||||
|
} else if (status.powerStatus === 'onBattery') {
|
||||||
|
statusSymbol = symbols.warning;
|
||||||
|
}
|
||||||
|
|
||||||
// Show threshold status
|
// Display UPS name and power status
|
||||||
logger.logBoxLine('');
|
console.log(` ${statusSymbol} ${theme.highlight(ups.name)} - ${formatPowerStatus(status.powerStatus)}`);
|
||||||
logger.logBoxLine('Thresholds:');
|
|
||||||
logger.logBoxLine(
|
// Display battery with color coding
|
||||||
` Battery: ${status.batteryCapacity}% / ${ups.thresholds.battery}% ${
|
const batteryColor = getBatteryColor(status.batteryCapacity);
|
||||||
status.batteryCapacity < ups.thresholds.battery ? '⚠️' : '✓'
|
const batterySymbol = status.batteryCapacity >= ups.thresholds.battery ? symbols.success : symbols.warning;
|
||||||
}`,
|
console.log(` Battery: ${batteryColor(status.batteryCapacity + '%')} ${batterySymbol} Runtime: ${getRuntimeColor(status.batteryRuntime)(status.batteryRuntime + ' min')}`);
|
||||||
);
|
|
||||||
logger.logBoxLine(
|
// Display host info
|
||||||
` Runtime: ${status.batteryRuntime} min / ${ups.thresholds.runtime} min ${
|
console.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`);
|
||||||
status.batteryRuntime < ups.thresholds.runtime ? '⚠️' : '✓'
|
|
||||||
}`,
|
// Display groups if any
|
||||||
);
|
if (ups.groups && ups.groups.length > 0) {
|
||||||
|
const config = this.daemon.getConfig();
|
||||||
|
const groupNames = ups.groups.map((groupId: string) => {
|
||||||
|
const group = config.groups?.find((g: { id: string }) => g.id === groupId);
|
||||||
|
return group ? group.name : groupId;
|
||||||
|
});
|
||||||
|
console.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
logger.logBoxEnd();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth);
|
// Display error for this UPS
|
||||||
logger.logBoxLine(
|
console.log(` ${symbols.error} ${theme.highlight(ups.name)} - ${theme.error('Connection failed')}`);
|
||||||
`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`,
|
console.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`);
|
||||||
);
|
console.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`);
|
||||||
logger.logBoxEnd();
|
console.log('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user