diff --git a/src/public/index.html b/src/public/index.html
index 0ec008efb..bccc303cb 100644
--- a/src/public/index.html
+++ b/src/public/index.html
@@ -906,9 +906,6 @@
Alert Configuration
-
- Alert Management
-
System
@@ -972,6 +969,7 @@
+
diff --git a/src/public/js/alertsHandler.js b/src/public/js/alertsHandler.js
index 1ae69e2db..65dad71da 100644
--- a/src/public/js/alertsHandler.js
+++ b/src/public/js/alertsHandler.js
@@ -249,7 +249,11 @@ PulseApp.alerts = (() => {
${SEVERITY_ICONS.info}
-
No active alerts
+ No active alerts
+
+ Manage Alerts
+
`;
return;
@@ -314,6 +318,20 @@ PulseApp.alerts = (() => {
`;
}
+ // Add Manage Alerts button to the bottom
+ content += `
+
+
+
+
+
+
+ Manage Alerts
+
+
+ `;
+
alertDropdown.innerHTML = content;
// Restore scroll position if acknowledged section exists and was expanded
diff --git a/src/public/js/ui/alertManagementModal.js b/src/public/js/ui/alertManagementModal.js
new file mode 100644
index 000000000..b6770a6f8
--- /dev/null
+++ b/src/public/js/ui/alertManagementModal.js
@@ -0,0 +1,1125 @@
+PulseApp.ui = PulseApp.ui || {};
+
+PulseApp.ui.alertManagementModal = (() => {
+ let isInitialized = false;
+ let activeTab = 'alerts';
+ let currentConfig = {};
+ let formDataCache = {};
+
+ function init() {
+ if (isInitialized) return;
+
+ console.log('[AlertManagementModal] Initializing alert management modal...');
+
+ // Create modal HTML
+ createModalHTML();
+
+ // Set up event listeners
+ setupEventListeners();
+
+ isInitialized = true;
+ console.log('[AlertManagementModal] Alert management modal initialized');
+ }
+
+ function createModalHTML() {
+ const existingModal = document.getElementById('alert-management-modal');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ const modalHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Alerts
+
+
+
+
+
+ Alert Rules
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
+ }
+
+ function setupEventListeners() {
+ const modal = document.getElementById('alert-management-modal');
+ const closeButton = document.getElementById('alert-management-modal-close');
+ const cancelButton = document.getElementById('alert-management-cancel-button');
+ const saveButton = document.getElementById('alert-management-save-button');
+
+ if (closeButton) {
+ closeButton.addEventListener('click', closeModal);
+ }
+
+ if (cancelButton) {
+ cancelButton.addEventListener('click', closeModal);
+ }
+
+ if (saveButton) {
+ saveButton.addEventListener('click', saveConfiguration);
+ }
+
+ // Close modal when clicking outside
+ if (modal) {
+ modal.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ closeModal();
+ }
+ });
+ }
+
+ // Handle escape key
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape' && modal && !modal.classList.contains('hidden')) {
+ closeModal();
+ }
+ });
+
+ // Set up tab navigation
+ setupTabNavigation();
+ }
+
+ function setupTabNavigation() {
+ const tabButtons = document.querySelectorAll('.alert-tab');
+
+ tabButtons.forEach(button => {
+ button.addEventListener('click', (e) => {
+ const tabName = e.target.getAttribute('data-tab');
+ switchTab(tabName);
+ });
+ });
+ }
+
+ function switchTab(tabName) {
+ // Preserve current form data before switching tabs
+ preserveCurrentFormData();
+
+ activeTab = tabName;
+
+ // Update tab buttons
+ const tabButtons = document.querySelectorAll('.alert-tab');
+ tabButtons.forEach(button => {
+ const isActive = button.getAttribute('data-tab') === tabName;
+
+ if (isActive) {
+ button.classList.add('active');
+ button.classList.remove('border-transparent', 'text-gray-500', 'dark:text-gray-400');
+ button.classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
+ } else {
+ button.classList.remove('active');
+ button.classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
+ button.classList.add('border-transparent', 'text-gray-500', 'dark:text-gray-400');
+ }
+ });
+
+ // Update content
+ renderTabContent();
+
+ // Restore form data for the new tab
+ setTimeout(() => {
+ restoreFormData(tabName);
+ }, 100);
+ }
+
+ function renderTabContent() {
+ const modalBody = document.getElementById('alert-management-modal-body');
+ if (!modalBody) return;
+
+ switch (activeTab) {
+ case 'alerts':
+ modalBody.innerHTML = renderAlertsTab();
+ initializeAlertsTab();
+ break;
+ case 'alert-rules':
+ modalBody.innerHTML = renderAlertRulesTab();
+ initializeAlertRulesTab();
+ break;
+ case 'notifications':
+ modalBody.innerHTML = renderNotificationsTab();
+ initializeNotificationsTab();
+ break;
+ default:
+ modalBody.innerHTML = 'Unknown tab
';
+ }
+ }
+
+ function renderAlertsTab() {
+ return `
+
+
+
Current Alerts
+
+
+
+
+ Refresh
+
+
+
+
+
+
Loading current alerts...
+
+
+
+
Quick Actions
+
+
+ Acknowledge All
+
+
+ View History
+
+
+
+
+ `;
+ }
+
+ function renderAlertRulesTab() {
+ return `
+
+
+
+
Alert Rules
+
Manage system and custom alert rules
+
+
+
+ All Rules
+ System Alerts
+ Custom Alerts
+ Enabled Only
+ Disabled Only
+
+
+ + Add Custom Alert
+
+
+
+
+
+
+
+
+
+
+
+
System Alerts
+
Built-in
+
+
Core monitoring alerts that cannot be deleted
+
+
+
+
Loading system alerts...
+
+
+
+
+
+
+
+
+
+
+
Custom Alerts
+
User-created
+
+
Custom threshold and rule-based alerts
+
+
+
+
Loading custom alerts...
+
+
+
+ `;
+ }
+
+ function renderNotificationsTab() {
+ const config = currentConfig || {};
+ const smtp = config.advanced?.smtp || {};
+
+ return `
+
+
+
Notification Settings
+
Configure how alerts are delivered beyond the Pulse UI
+
+
+
+
+
Global Defaults
+
Choose where all alerts should be sent by default. Individual rules can override these settings.
+
+
+
+
+
+
+ Email
+
+
+
Email Notifications
+
Send alerts via email
+
+
+
+
+
+
+
+
+ Webhook
+
+
+
Webhook Notifications
+
Send alerts to external services
+
+
+
+
+
+
+
+
+
Email Configuration
+
+
+
+
Email configuration will be loaded...
+
+
+
+
+
+
Webhook Configuration
+
+
+
+
+ Webhook URL
+
+
+
+ Supports Discord, Slack, Microsoft Teams, and custom webhooks
+
+
+
+
+
+ Test Webhook
+
+
+
+
+
+
+
+
Per-Rule Overrides
+
+ Advanced users can customize notification delivery for specific alert rules.
+ Go to the Alert Rules tab and click on individual rules to configure custom delivery settings.
+
+
+ Manage Alert Rules →
+
+
+
+ `;
+ }
+
+ function initializeAlertsTab() {
+ // Set up refresh button
+ const refreshBtn = document.getElementById('refresh-alerts-btn');
+ if (refreshBtn) {
+ refreshBtn.addEventListener('click', loadCurrentAlerts);
+ }
+
+ // Set up action buttons
+ const acknowledgeAllBtn = document.getElementById('acknowledge-all-alerts-btn');
+ if (acknowledgeAllBtn) {
+ acknowledgeAllBtn.addEventListener('click', () => {
+ if (PulseApp.alerts && PulseApp.alerts.markAllAsAcknowledged) {
+ PulseApp.alerts.markAllAsAcknowledged();
+ setTimeout(loadCurrentAlerts, 500); // Refresh after acknowledging
+ }
+ });
+ }
+
+ const viewHistoryBtn = document.getElementById('view-alert-history-btn');
+ if (viewHistoryBtn) {
+ viewHistoryBtn.addEventListener('click', () => {
+ // TODO: Implement alert history view
+ console.log('Alert history view not yet implemented');
+ });
+ }
+
+ // Load current alerts
+ loadCurrentAlerts();
+ }
+
+ function initializeAlertRulesTab() {
+ // Set up filter dropdown
+ const filterSelect = document.getElementById('alert-rules-filter');
+ if (filterSelect) {
+ filterSelect.addEventListener('change', filterAlertRules);
+ }
+
+ // Set up add custom alert button
+ const addCustomBtn = document.getElementById('add-custom-alert-btn');
+ if (addCustomBtn) {
+ addCustomBtn.addEventListener('click', openCustomAlertModal);
+ }
+
+ // Load system and custom alerts
+ loadSystemAlerts();
+ loadCustomAlerts();
+ }
+
+ function initializeNotificationsTab() {
+ // Load email configuration into the email section
+ loadEmailConfiguration();
+
+ // Set up webhook test button
+ const testWebhookBtn = document.getElementById('test-webhook-btn');
+ if (testWebhookBtn) {
+ testWebhookBtn.addEventListener('click', testWebhookConnection);
+ }
+ }
+
+ function filterAlertRules() {
+ const filterValue = document.getElementById('alert-rules-filter')?.value;
+ // TODO: Implement filtering logic
+ console.log('Filtering alert rules by:', filterValue);
+ }
+
+ function loadSystemAlerts() {
+ const systemAlertsContent = document.getElementById('system-alerts-content');
+ if (!systemAlertsContent) return;
+
+ // Create system alert rules (CPU, Memory, Disk, Down)
+ const systemAlerts = [
+ { id: 'cpu', name: 'CPU Usage', description: 'Alert when CPU usage exceeds threshold', enabled: true, threshold: 85 },
+ { id: 'memory', name: 'Memory Usage', description: 'Alert when memory usage exceeds threshold', enabled: true, threshold: 90 },
+ { id: 'disk', name: 'Disk Usage', description: 'Alert when disk usage exceeds threshold', enabled: true, threshold: 95 },
+ { id: 'down', name: 'System Down', description: 'Alert when VM/LXC goes offline', enabled: true, threshold: null }
+ ];
+
+ systemAlertsContent.innerHTML = systemAlerts.map(alert => createSystemAlertCard(alert)).join('');
+ }
+
+ function loadCustomAlerts() {
+ const customAlertsContent = document.getElementById('custom-alerts-content');
+ if (!customAlertsContent) return;
+
+ // Load custom alerts from settings if available
+ if (PulseApp.ui.settings && PulseApp.ui.settings.loadThresholdConfigurations) {
+ // This will populate custom threshold configurations
+ PulseApp.ui.settings.loadThresholdConfigurations();
+ }
+
+ // For now, show placeholder
+ customAlertsContent.innerHTML = `
+
+
+
+
+
No custom alerts configured
+
+ Create your first custom alert
+
+
+ `;
+ }
+
+ function createSystemAlertCard(alert) {
+ const deliveryBadges = `
+
+
+ Pulse
+
+
+ Email
+
+
+ Webhook
+
+
+ `;
+
+ return `
+
+
+
+
${alert.name}
+ ${alert.enabled ?
+ 'Enabled ' :
+ 'Disabled '
+ }
+
+
${alert.description}
+ ${deliveryBadges}
+
+
+ ${alert.threshold ? `
+
${alert.threshold}%
+ ` : ''}
+
+
+
+
+
+
+ `;
+ }
+
+ function loadEmailConfiguration() {
+ const emailConfigSection = document.getElementById('email-config-section');
+ if (!emailConfigSection) return;
+
+ // Use existing email configuration from settings if available
+ if (PulseApp.ui.settings && PulseApp.ui.settings.renderAlertsTab) {
+ const config = currentConfig || {};
+ const fullContent = PulseApp.ui.settings.renderAlertsTab(config.alerts || {}, config);
+
+ // Extract just the email configuration section
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = fullContent;
+ const emailSection = tempDiv.querySelector('.bg-gray-50:nth-child(2)'); // Email section
+
+ if (emailSection) {
+ // Remove the outer container and just show the inner content
+ const emailContent = emailSection.innerHTML;
+ emailConfigSection.innerHTML = emailContent;
+
+ // Initialize email functionality
+ if (PulseApp.ui.settings.setupEmailProviderSelection) {
+ setTimeout(() => PulseApp.ui.settings.setupEmailProviderSelection(), 100);
+ }
+ if (PulseApp.ui.settings.setupEmailTestButton) {
+ setTimeout(() => PulseApp.ui.settings.setupEmailTestButton(), 100);
+ }
+ }
+ }
+ }
+
+ function testWebhookConnection() {
+ const webhookUrl = document.querySelector('input[name="WEBHOOK_URL"]')?.value;
+ if (!webhookUrl) {
+ alert('Please enter a webhook URL first');
+ return;
+ }
+
+ // Use existing webhook test functionality from settings if available
+ if (PulseApp.ui.settings && PulseApp.ui.settings.setupWebhookTestButton) {
+ PulseApp.ui.settings.setupWebhookTestButton();
+ } else {
+ // Fallback test
+ console.log('Testing webhook:', webhookUrl);
+ alert('Webhook test functionality will be implemented');
+ }
+ }
+
+ function openCustomAlertModal(presetThresholds = null) {
+ // Check if settings modal has the threshold functionality we can reuse
+ if (PulseApp.ui.settings && PulseApp.ui.settings.showThresholdModal && !presetThresholds) {
+ // Use the existing threshold modal from settings (only if no presets)
+ PulseApp.ui.settings.showThresholdModal();
+ } else {
+ // Create our own custom alert modal with optional preset values
+ createCustomAlertModal(presetThresholds);
+ }
+ }
+
+ function createCustomAlertModal(presetThresholds = null) {
+ // Remove any existing custom alert modal
+ const existingModal = document.getElementById('custom-alert-modal');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ // Generate alert name and determine best metric from presets
+ let suggestedName = 'Custom Alert';
+ let primaryMetric = 'cpu';
+ let primaryThreshold = 85;
+ let multipleThresholds = false;
+
+ if (presetThresholds && presetThresholds.length > 0) {
+ // Create a descriptive name based on active thresholds
+ const thresholdNames = presetThresholds.map(t => `${t.type.toUpperCase()}: ${t.value}${t.type === 'cpu' || t.type === 'memory' || t.type === 'disk' ? '%' : ''}`);
+ suggestedName = `Alert for ${thresholdNames.join(', ')}`;
+
+ // Use the first threshold as the primary one for the form
+ primaryMetric = presetThresholds[0].type;
+ primaryThreshold = presetThresholds[0].value;
+
+ // Track if we have multiple thresholds
+ multipleThresholds = presetThresholds.length > 1;
+ }
+
+ const modalHTML = `
+
+ `;
+
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
+ setupCustomAlertModalEvents();
+ }
+
+ function setupCustomAlertModalEvents() {
+ const modal = document.getElementById('custom-alert-modal');
+ const closeBtn = document.getElementById('custom-alert-modal-close');
+ const cancelBtn = document.getElementById('custom-alert-cancel-button');
+ const saveBtn = document.getElementById('custom-alert-save-button');
+ const targetTypeSelect = document.querySelector('select[name="targetType"]');
+ const metricSelect = document.querySelector('select[name="metric"]');
+
+ // Close modal events
+ [closeBtn, cancelBtn].forEach(btn => {
+ if (btn) {
+ btn.addEventListener('click', () => {
+ modal.remove();
+ });
+ }
+ });
+
+ // Close on outside click
+ modal.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ modal.remove();
+ }
+ });
+
+ // Handle target type changes
+ if (targetTypeSelect) {
+ targetTypeSelect.addEventListener('change', (e) => {
+ const specificSection = document.getElementById('specific-target-section');
+ if (e.target.value === 'specific' || e.target.value === 'node') {
+ specificSection.classList.remove('hidden');
+ const input = specificSection.querySelector('input');
+ input.required = true;
+ input.placeholder = e.target.value === 'node' ? 'e.g., pve-node-1' : 'e.g., 100 or vm-name';
+ } else {
+ specificSection.classList.add('hidden');
+ specificSection.querySelector('input').required = false;
+ }
+ });
+ }
+
+ // Handle metric changes
+ if (metricSelect) {
+ metricSelect.addEventListener('change', (e) => {
+ const thresholdSection = document.getElementById('threshold-section');
+ const thresholdInput = document.querySelector('input[name="threshold"]');
+ const operatorSelect = document.querySelector('select[name="operator"]');
+ const percentSpan = thresholdSection.querySelector('span');
+
+ if (e.target.value === 'status') {
+ thresholdSection.style.display = 'none';
+ thresholdInput.required = false;
+ } else {
+ thresholdSection.style.display = 'block';
+ thresholdInput.required = true;
+
+ // Update placeholder and limits based on metric
+ switch (e.target.value) {
+ case 'cpu':
+ thresholdInput.placeholder = '85';
+ thresholdInput.max = '100';
+ percentSpan.textContent = '%';
+ break;
+ case 'memory':
+ thresholdInput.placeholder = '90';
+ thresholdInput.max = '100';
+ percentSpan.textContent = '%';
+ break;
+ case 'disk':
+ thresholdInput.placeholder = '95';
+ thresholdInput.max = '100';
+ percentSpan.textContent = '%';
+ break;
+ case 'network':
+ thresholdInput.placeholder = '100';
+ thresholdInput.max = '1000';
+ percentSpan.textContent = 'MB/s';
+ break;
+ }
+ }
+ });
+ }
+
+ // Handle save
+ if (saveBtn) {
+ saveBtn.addEventListener('click', saveCustomAlert);
+ }
+ }
+
+ function saveCustomAlert() {
+ const form = document.getElementById('custom-alert-form');
+ const formData = new FormData(form);
+
+ // Validate form
+ if (!form.checkValidity()) {
+ form.reportValidity();
+ return;
+ }
+
+ // Check if we have multiple thresholds from the dashboard
+ const multipleThresholdsData = formData.get('multipleThresholds');
+ let thresholds = [];
+
+ if (multipleThresholdsData) {
+ // Parse the JSON data containing all the preset thresholds
+ try {
+ thresholds = JSON.parse(multipleThresholdsData.replace(/"/g, '"'));
+ console.log('Using multiple thresholds from dashboard:', thresholds);
+ } catch (error) {
+ console.warn('Failed to parse multiple thresholds data:', error);
+ }
+ }
+
+ // If no multiple thresholds, use the single threshold from the form
+ if (thresholds.length === 0) {
+ const threshold = formData.get('threshold') ? parseFloat(formData.get('threshold')) : null;
+ if (threshold !== null) {
+ thresholds = [{
+ type: formData.get('metric'),
+ operator: formData.get('operator') || '>',
+ value: threshold
+ }];
+ }
+ }
+
+ // Build alert configuration
+ const alertConfig = {
+ name: formData.get('alertName'),
+ targetType: formData.get('targetType'),
+ specificTarget: formData.get('specificTarget'),
+ thresholds: thresholds, // Use array of thresholds instead of single values
+ sendEmail: formData.has('sendEmail'),
+ sendWebhook: formData.has('sendWebhook'),
+ enabled: true,
+ createdAt: Date.now()
+ };
+
+ console.log('Creating custom alert:', alertConfig);
+
+ // TODO: Save to backend
+ // For now, just show success and close modal
+ const thresholdSummary = thresholds.map(t => `${t.type.toUpperCase()}: ${t.value}${['cpu', 'memory', 'disk'].includes(t.type) ? '%' : ''}`).join(', ');
+ alert(`Custom alert created successfully!\n\nAlert: ${alertConfig.name}\nThresholds: ${thresholdSummary}\n\nNote: Backend integration still needs to be implemented.`);
+
+ // Close modal
+ document.getElementById('custom-alert-modal').remove();
+
+ // Refresh the custom alerts list
+ loadCustomAlerts();
+ }
+
+ function loadCurrentAlerts() {
+ const contentDiv = document.getElementById('current-alerts-content');
+ if (!contentDiv) return;
+
+ // Get alerts from the alerts handler
+ if (PulseApp.alerts && PulseApp.alerts.getActiveAlerts) {
+ const activeAlerts = PulseApp.alerts.getActiveAlerts();
+ renderCurrentAlerts(activeAlerts);
+ } else {
+ contentDiv.innerHTML = 'Alert system not initialized
';
+ }
+ }
+
+ function renderCurrentAlerts(alerts) {
+ const contentDiv = document.getElementById('current-alerts-content');
+ if (!contentDiv) return;
+
+ if (!alerts || alerts.length === 0) {
+ contentDiv.innerHTML = `
+
+
+
+
+
No Active Alerts
+
All systems are running normally.
+
+ `;
+ return;
+ }
+
+ const unacknowledgedAlerts = alerts.filter(alert => !alert.acknowledged);
+ const acknowledgedAlerts = alerts.filter(alert => alert.acknowledged);
+
+ let content = '';
+
+ if (unacknowledgedAlerts.length > 0) {
+ content += `
+
+
+ Unacknowledged Alerts (${unacknowledgedAlerts.length})
+
+ ${unacknowledgedAlerts.map(alert => renderAlertCard(alert, false)).join('')}
+
+ `;
+ }
+
+ if (acknowledgedAlerts.length > 0) {
+ content += `
+
+
+ Acknowledged Alerts (${acknowledgedAlerts.length})
+
+ ${acknowledgedAlerts.map(alert => renderAlertCard(alert, true)).join('')}
+
+ `;
+ }
+
+ contentDiv.innerHTML = content;
+ }
+
+ function renderAlertCard(alert, acknowledged = false) {
+ const severityColors = {
+ 'critical': 'border-red-400 bg-red-50 dark:bg-red-900/10',
+ 'warning': 'border-yellow-400 bg-yellow-50 dark:bg-yellow-900/10',
+ 'info': 'border-blue-400 bg-blue-50 dark:bg-blue-900/10'
+ };
+
+ const severity = alert.severity || 'info';
+ const colorClass = severityColors[severity] || severityColors.info;
+
+ const duration = Math.round((Date.now() - alert.triggeredAt) / 1000);
+ const durationStr = duration < 60 ? `${duration}s` :
+ duration < 3600 ? `${Math.round(duration/60)}m` :
+ `${Math.round(duration/3600)}h`;
+
+ const acknowledgedClass = acknowledged ? 'opacity-60' : '';
+
+ let currentValueDisplay = '';
+ if (alert.metric === 'status') {
+ currentValueDisplay = alert.currentValue;
+ } else if (typeof alert.currentValue === 'number') {
+ const isPercentageMetric = ['cpu', 'memory', 'disk'].includes(alert.metric);
+ currentValueDisplay = `${Math.round(alert.currentValue)}${isPercentageMetric ? '%' : ''}`;
+ } else {
+ currentValueDisplay = alert.currentValue;
+ }
+
+ // Delivery indicators - Pulse UI is always present
+ const deliveryIndicators = `
+
+ Delivered to:
+
+ Pulse
+
+ ${alert.emailSent ? 'Email ' : ''}
+ ${alert.webhookSent ? 'Webhook ' : ''}
+
+ `;
+
+ return `
+
+
+
+
+
+ ${alert.guest?.name || 'Unknown'}
+
+
+ ${currentValueDisplay}
+
+ ${alert.escalated ? 'Escalated ' : ''}
+ ${acknowledged ? 'Acknowledged ' : ''}
+
+ ${deliveryIndicators}
+
+ ${alert.ruleName || 'Unknown Rule'}
+
+
+ Active for ${durationStr}
+ ${acknowledged ? ` • Acknowledged ${Math.round((Date.now() - alert.acknowledgedAt) / 60000)}m ago` : ''}
+
+
+
+ ${!acknowledged ? `
+
+ Acknowledge
+
+ ` : ''}
+
+ Details
+
+
+
+
+ `;
+ }
+
+ function preserveCurrentFormData() {
+ // TODO: Implement form data preservation
+ }
+
+ function restoreFormData(tabName) {
+ // TODO: Implement form data restoration
+ }
+
+ function openModal(targetTab = null) {
+ const modal = document.getElementById('alert-management-modal');
+ if (modal) {
+ modal.classList.remove('hidden');
+ modal.classList.add('flex');
+
+ // Load current configuration if needed
+ loadConfiguration();
+
+ // Switch to target tab if specified
+ if (targetTab && targetTab !== activeTab) {
+ switchTab(targetTab);
+ } else {
+ // Render initial tab content
+ renderTabContent();
+ }
+ }
+ }
+
+ function closeModal() {
+ const modal = document.getElementById('alert-management-modal');
+ if (modal) {
+ modal.classList.add('hidden');
+ modal.classList.remove('flex');
+ }
+ }
+
+ async function loadConfiguration() {
+ try {
+ console.log('Loading alert configuration...');
+
+ // Use the same configuration loading logic as settings
+ if (PulseApp.ui.settings && PulseApp.ui.settings.getCurrentConfig) {
+ currentConfig = await PulseApp.ui.settings.getCurrentConfig();
+ } else {
+ // Fallback: load configuration directly
+ const response = await fetch('/api/config');
+ if (response.ok) {
+ currentConfig = await response.json();
+ } else {
+ console.error('Failed to load configuration');
+ currentConfig = {};
+ }
+ }
+ } catch (error) {
+ console.error('Error loading configuration:', error);
+ currentConfig = {};
+ }
+ }
+
+ async function saveConfiguration() {
+ // TODO: Save alert configuration
+ console.log('Saving alert configuration...');
+ }
+
+ // Global functions that need to be accessible from HTML onclick handlers
+ window.toggleSystemAlert = function(alertId, enabled) {
+ console.log(`Toggling system alert ${alertId} to ${enabled ? 'enabled' : 'disabled'}`);
+ // TODO: Implement system alert toggle functionality
+ // This would update the configuration and save it
+ };
+
+ // Public API
+ return {
+ init,
+ openModal,
+ closeModal,
+ switchTab: switchTab,
+ openCustomAlertModal,
+ loadCurrentAlerts: () => {
+ if (activeTab === 'alerts') {
+ loadCurrentAlerts();
+ }
+ }
+ };
+})();
+
+// Auto-initialize when DOM is ready
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', PulseApp.ui.alertManagementModal.init);
+} else {
+ PulseApp.ui.alertManagementModal.init();
+}
\ No newline at end of file
diff --git a/src/public/js/ui/settings.js b/src/public/js/ui/settings.js
index 3790cd836..770d3585a 100644
--- a/src/public/js/ui/settings.js
+++ b/src/public/js/ui/settings.js
@@ -179,9 +179,6 @@ PulseApp.ui.settings = (() => {
case 'system':
content = renderSystemTab(advanced, safeConfig);
break;
- case 'alert-management':
- content = renderAlertManagementTab();
- break;
case 'diagnostics':
content = renderDiagnosticsTab();
break;
@@ -212,9 +209,6 @@ PulseApp.ui.settings = (() => {
} else if (activeTab === 'alerts') {
// Load threshold configurations when alerts tab is opened
loadThresholdConfigurations();
- } else if (activeTab === 'alert-management') {
- // Initialize alert management tab
- initializeAlertManagementTab();
}
}
@@ -4325,7 +4319,16 @@ PulseApp.ui.settings = (() => {
switchToChannel,
acknowledgeStableChoice,
proceedWithStableSwitch,
- clearUpdateCache
+ clearUpdateCache,
+ // Expose alert-related functions for the alert management modal
+ renderAlertsTab,
+ renderAlertManagementTab,
+ loadThresholdConfigurations,
+ setupEmailProviderSelection,
+ setupEmailTestButton,
+ setupWebhookTestButton,
+ initializeAlertManagementTab,
+ getCurrentConfig: () => currentConfig
};
})();
diff --git a/src/public/js/ui/thresholds.js b/src/public/js/ui/thresholds.js
index 59db36b04..f5d13e2d4 100644
--- a/src/public/js/ui/thresholds.js
+++ b/src/public/js/ui/thresholds.js
@@ -138,16 +138,21 @@ PulseApp.ui.thresholds = (() => {
return;
}
- // Show modal for creating alert rule
- _showCreateAlertRuleModal(activeThresholds);
+ // Open the custom alert creation modal with pre-populated threshold values
+ if (PulseApp.ui && PulseApp.ui.alertManagementModal && PulseApp.ui.alertManagementModal.openCustomAlertModal) {
+ PulseApp.ui.alertManagementModal.openCustomAlertModal(activeThresholds);
+ } else {
+ // Fallback - show legacy modal if alert management isn't available
+ _showCreateAlertRuleModal(activeThresholds);
+ }
}
function _handleViewAlertRules() {
- // Open the unified Alert Management interface in settings
- if (PulseApp.ui && PulseApp.ui.settings && PulseApp.ui.settings.openModalWithTab) {
- PulseApp.ui.settings.openModalWithTab('alert-management');
+ // Open the Alert Management modal directly to the Alert Rules tab
+ if (PulseApp.ui && PulseApp.ui.alertManagementModal && PulseApp.ui.alertManagementModal.openModal) {
+ PulseApp.ui.alertManagementModal.openModal('alert-rules');
} else {
- // Fallback to the old modal if settings isn't available
+ // Fallback to the old modal if alert management isn't available
_showViewAlertRulesModal();
}
}