feat: add comprehensive configuration options to setup page

- Add advanced settings section with all .env.example options
- Include metric/discovery intervals configuration
- Add alert system configuration (enabled/thresholds)
- Add PBS node name field (required for non-Sys.Audit tokens)
- Update config API to handle all settings
- Group settings logically in generated .env file
- Load and save all configuration options properly

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
courtmanr@gmail.com
2025-05-30 10:53:51 +01:00
parent a8896ac3c0
commit 97ae118b2c
2 changed files with 233 additions and 4 deletions
+72 -3
View File
@@ -26,12 +26,34 @@ class ConfigApi {
host: config.PBS_HOST,
port: config.PBS_PORT || '8007',
tokenId: config.PBS_TOKEN_ID,
nodeName: config.PBS_NODE_NAME,
// Don't send the secret
} : null
} : null,
advanced: {
metricInterval: config.PULSE_METRIC_INTERVAL_MS,
discoveryInterval: config.PULSE_DISCOVERY_INTERVAL_MS,
alerts: {
cpu: {
enabled: config.ALERT_CPU_ENABLED !== 'false',
threshold: config.ALERT_CPU_THRESHOLD
},
memory: {
enabled: config.ALERT_MEMORY_ENABLED !== 'false',
threshold: config.ALERT_MEMORY_THRESHOLD
},
disk: {
enabled: config.ALERT_DISK_ENABLED !== 'false',
threshold: config.ALERT_DISK_THRESHOLD
},
down: {
enabled: config.ALERT_DOWN_ENABLED !== 'false'
}
}
}
};
} catch (error) {
console.error('Error reading configuration:', error);
return { proxmox: null, pbs: null };
return { proxmox: null, pbs: null, advanced: {} };
}
}
@@ -58,10 +80,50 @@ class ConfigApi {
existingConfig.PBS_PORT = config.pbs.port || '8007';
existingConfig.PBS_TOKEN_ID = config.pbs.tokenId;
existingConfig.PBS_TOKEN_SECRET = config.pbs.tokenSecret;
if (config.pbs.nodeName) {
existingConfig.PBS_NODE_NAME = config.pbs.nodeName;
}
// Always allow self-signed certificates by default for PBS
existingConfig.PBS_ALLOW_SELF_SIGNED_CERT = 'true';
}
// Add advanced settings
if (config.advanced) {
// Service intervals
if (config.advanced.metricInterval) {
existingConfig.PULSE_METRIC_INTERVAL_MS = config.advanced.metricInterval;
}
if (config.advanced.discoveryInterval) {
existingConfig.PULSE_DISCOVERY_INTERVAL_MS = config.advanced.discoveryInterval;
}
// Alert settings
if (config.advanced.alerts) {
const alerts = config.advanced.alerts;
if (alerts.cpu) {
existingConfig.ALERT_CPU_ENABLED = alerts.cpu.enabled ? 'true' : 'false';
if (alerts.cpu.threshold) {
existingConfig.ALERT_CPU_THRESHOLD = alerts.cpu.threshold;
}
}
if (alerts.memory) {
existingConfig.ALERT_MEMORY_ENABLED = alerts.memory.enabled ? 'true' : 'false';
if (alerts.memory.threshold) {
existingConfig.ALERT_MEMORY_THRESHOLD = alerts.memory.threshold;
}
}
if (alerts.disk) {
existingConfig.ALERT_DISK_ENABLED = alerts.disk.enabled ? 'true' : 'false';
if (alerts.disk.threshold) {
existingConfig.ALERT_DISK_THRESHOLD = alerts.disk.threshold;
}
}
if (alerts.down) {
existingConfig.ALERT_DOWN_ENABLED = alerts.down.enabled ? 'true' : 'false';
}
}
}
// Write back to .env file
await this.writeEnvFile(existingConfig);
@@ -170,7 +232,14 @@ class ConfigApi {
// Group related settings
const groups = {
'Proxmox VE Settings': ['PROXMOX_HOST', 'PROXMOX_PORT', 'PROXMOX_TOKEN_ID', 'PROXMOX_TOKEN_SECRET', 'PROXMOX_ALLOW_SELF_SIGNED_CERT'],
'Proxmox Backup Server Settings': ['PBS_HOST', 'PBS_PORT', 'PBS_TOKEN_ID', 'PBS_TOKEN_SECRET', 'PBS_ALLOW_SELF_SIGNED_CERT'],
'Proxmox Backup Server Settings': ['PBS_HOST', 'PBS_PORT', 'PBS_TOKEN_ID', 'PBS_TOKEN_SECRET', 'PBS_NODE_NAME', 'PBS_ALLOW_SELF_SIGNED_CERT'],
'Pulse Service Settings': ['PULSE_METRIC_INTERVAL_MS', 'PULSE_DISCOVERY_INTERVAL_MS'],
'Alert System Configuration': [
'ALERT_CPU_ENABLED', 'ALERT_CPU_THRESHOLD', 'ALERT_CPU_DURATION',
'ALERT_MEMORY_ENABLED', 'ALERT_MEMORY_THRESHOLD', 'ALERT_MEMORY_DURATION',
'ALERT_DISK_ENABLED', 'ALERT_DISK_THRESHOLD', 'ALERT_DISK_DURATION',
'ALERT_DOWN_ENABLED', 'ALERT_DOWN_DURATION'
],
'Other Settings': [] // Will contain all other keys
};
+161 -1
View File
@@ -99,6 +99,96 @@
</div>
</div>
<!-- Advanced Settings (Optional) -->
<details class="border border-gray-200 dark:border-gray-700 rounded-lg">
<summary class="px-6 py-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
<span class="text-lg font-medium text-gray-800 dark:text-gray-200">Advanced Settings (Optional)</span>
</summary>
<div class="p-6 pt-0 space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="metric-interval" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Metric Update Interval (ms)
</label>
<input type="number" id="metric-interval" name="metric-interval"
placeholder="2000 (default)"
min="1000" max="60000"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">How often to fetch VM/Container metrics</p>
</div>
<div>
<label for="discovery-interval" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Discovery Interval (ms)
</label>
<input type="number" id="discovery-interval" name="discovery-interval"
placeholder="30000 (default)"
min="5000" max="300000"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">How often to discover nodes and VMs</p>
</div>
</div>
<!-- Alert Settings -->
<div class="border-t border-gray-200 dark:border-gray-700 pt-4">
<h3 class="text-md font-medium text-gray-800 dark:text-gray-200 mb-3">Alert Settings</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<label class="flex items-center">
<input type="checkbox" id="alert-cpu-enabled" name="alert-cpu-enabled" checked
class="mr-2 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
<span class="text-sm text-gray-700 dark:text-gray-300">CPU Alerts</span>
</label>
<label class="flex items-center">
<input type="checkbox" id="alert-memory-enabled" name="alert-memory-enabled" checked
class="mr-2 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
<span class="text-sm text-gray-700 dark:text-gray-300">Memory Alerts</span>
</label>
<label class="flex items-center">
<input type="checkbox" id="alert-disk-enabled" name="alert-disk-enabled" checked
class="mr-2 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
<span class="text-sm text-gray-700 dark:text-gray-300">Disk Alerts</span>
</label>
<label class="flex items-center">
<input type="checkbox" id="alert-down-enabled" name="alert-down-enabled" checked
class="mr-2 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
<span class="text-sm text-gray-700 dark:text-gray-300">Down Alerts</span>
</label>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label for="alert-cpu-threshold" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
CPU Alert Threshold (%)
</label>
<input type="number" id="alert-cpu-threshold" name="alert-cpu-threshold"
placeholder="85 (default)"
min="50" max="100"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="alert-memory-threshold" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Memory Alert Threshold (%)
</label>
<input type="number" id="alert-memory-threshold" name="alert-memory-threshold"
placeholder="90 (default)"
min="50" max="100"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="alert-disk-threshold" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Disk Alert Threshold (%)
</label>
<input type="number" id="alert-disk-threshold" name="alert-disk-threshold"
placeholder="95 (default)"
min="50" max="100"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
</div>
</div>
</details>
<!-- PBS Configuration (Optional) -->
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-6">
<h2 class="text-xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Proxmox Backup Server (Optional)</h2>
@@ -122,6 +212,16 @@
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="pbs-node-name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
PBS Node Name
</label>
<input type="text" id="pbs-node-name" name="pbs-node-name"
placeholder="Internal hostname (run 'hostname' on PBS)"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Required unless token has Sys.Audit permission</p>
</div>
<div>
<label for="pbs-token-id" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
API Token ID
@@ -301,9 +401,33 @@
host: formData.get('pbs-host'),
port: formData.get('pbs-port') || '8007',
tokenId: formData.get('pbs-token-id'),
tokenSecret: formData.get('pbs-token-secret')
tokenSecret: formData.get('pbs-token-secret'),
nodeName: formData.get('pbs-node-name')
};
}
// Add advanced settings
config.advanced = {
metricInterval: formData.get('metric-interval'),
discoveryInterval: formData.get('discovery-interval'),
alerts: {
cpu: {
enabled: formData.get('alert-cpu-enabled') === 'on',
threshold: formData.get('alert-cpu-threshold')
},
memory: {
enabled: formData.get('alert-memory-enabled') === 'on',
threshold: formData.get('alert-memory-threshold')
},
disk: {
enabled: formData.get('alert-disk-enabled') === 'on',
threshold: formData.get('alert-disk-threshold')
},
down: {
enabled: formData.get('alert-down-enabled') === 'on'
}
}
};
hideMessages();
const button = document.getElementById('save-button');
@@ -353,6 +477,42 @@
document.getElementById('pbs-host').value = config.pbs.host || '';
document.getElementById('pbs-port').value = config.pbs.port || '';
document.getElementById('pbs-token-id').value = config.pbs.tokenId || '';
document.getElementById('pbs-node-name').value = config.pbs.nodeName || '';
}
if (config.advanced) {
if (config.advanced.metricInterval) {
document.getElementById('metric-interval').value = config.advanced.metricInterval;
}
if (config.advanced.discoveryInterval) {
document.getElementById('discovery-interval').value = config.advanced.discoveryInterval;
}
// Load alert settings
if (config.advanced.alerts) {
const alerts = config.advanced.alerts;
if (alerts.cpu !== undefined) {
document.getElementById('alert-cpu-enabled').checked = alerts.cpu.enabled !== false;
if (alerts.cpu.threshold) {
document.getElementById('alert-cpu-threshold').value = alerts.cpu.threshold;
}
}
if (alerts.memory !== undefined) {
document.getElementById('alert-memory-enabled').checked = alerts.memory.enabled !== false;
if (alerts.memory.threshold) {
document.getElementById('alert-memory-threshold').value = alerts.memory.threshold;
}
}
if (alerts.disk !== undefined) {
document.getElementById('alert-disk-enabled').checked = alerts.disk.enabled !== false;
if (alerts.disk.threshold) {
document.getElementById('alert-disk-threshold').value = alerts.disk.threshold;
}
}
if (alerts.down !== undefined) {
document.getElementById('alert-down-enabled').checked = alerts.down.enabled !== false;
}
}
}
}
} catch (error) {