// ========== NOTIFICATION SETTINGS ========== (function() { // Inject modal HTML injectModal('notifications-modal', `

🔔 Notification Settings

Notification Providers

Health Monitoring

Last check: Never

Events to Notify

Notification History

No notifications yet
`); const modal = document.getElementById('notifications-modal'); const openBtn = document.getElementById('manage-notifications'); const saveBtn = document.getElementById('notifications-save'); const cancelBtn = document.getElementById('notifications-cancel'); // Provider toggle handlers ['discord', 'telegram', 'ntfy', 'email'].forEach(provider => { const checkbox = document.getElementById(`${provider}-enabled`); const config = document.getElementById(`${provider}-config`); checkbox?.addEventListener('change', () => { config.style.display = checkbox.checked ? 'block' : 'none'; }); }); // Health check toggle const healthCheckEnabled = document.getElementById('health-check-enabled'); const healthCheckConfig = document.getElementById('health-check-config'); healthCheckEnabled?.addEventListener('change', () => { healthCheckConfig.style.opacity = healthCheckEnabled.checked ? '1' : '0.5'; }); // Load notification config from API async function loadNotificationConfig() { try { const response = await fetch('/api/v1/notifications/config'); const data = await response.json(); if (data.success) { const config = data.config; // Master toggle document.getElementById('notifications-enabled').checked = config.enabled; // Providers document.getElementById('discord-enabled').checked = config.providers?.discord?.enabled || false; document.getElementById('telegram-enabled').checked = config.providers?.telegram?.enabled || false; document.getElementById('ntfy-enabled').checked = config.providers?.ntfy?.enabled || false; document.getElementById('email-enabled').checked = config.providers?.email?.enabled || false; // Show/hide config sections document.getElementById('discord-config').style.display = config.providers?.discord?.enabled ? 'block' : 'none'; document.getElementById('telegram-config').style.display = config.providers?.telegram?.enabled ? 'block' : 'none'; document.getElementById('ntfy-config').style.display = config.providers?.ntfy?.enabled ? 'block' : 'none'; document.getElementById('email-config').style.display = config.providers?.email?.enabled ? 'block' : 'none'; // ntfy server URL if (config.providers?.ntfy?.serverUrl) { document.getElementById('ntfy-server').value = config.providers.ntfy.serverUrl; } // email fields if (config.providers?.email?.host) document.getElementById('email-host').value = config.providers.email.host; if (config.providers?.email?.from) document.getElementById('email-from').value = config.providers.email.from; // Health check document.getElementById('health-check-enabled').checked = config.healthCheck?.enabled || false; if (config.healthCheck?.intervalMinutes) { document.getElementById('health-check-interval').value = config.healthCheck.intervalMinutes; } if (config.healthCheck?.lastCheck) { document.getElementById('health-check-status').textContent = `Last check: ${new Date(config.healthCheck.lastCheck).toLocaleString()}`; } // Events document.getElementById('event-container-down').checked = config.events?.containerDown !== false; document.getElementById('event-container-up').checked = config.events?.containerUp !== false; document.getElementById('event-deploy-success').checked = config.events?.deploymentSuccess !== false; document.getElementById('event-deploy-failed').checked = config.events?.deploymentFailed !== false; } } catch (error) { console.error('Failed to load notification config:', error); } } // Load notification history async function loadNotificationHistory() { try { const response = await fetch('/api/v1/notifications/history?limit=10'); const data = await response.json(); const container = document.getElementById('notification-history'); if (data.success && data.history?.length > 0) { container.innerHTML = data.history.map(item => { const date = new Date(item.timestamp).toLocaleString(); const typeColors = { success: 'var(--ok-fg)', error: 'var(--bad-fg)', warning: '#f39c12', info: 'var(--accent)' }; return `
${item.type === 'success' ? '✓' : item.type === 'error' ? '✗' : 'ℹ'}
${escapeHtml(item.title)}
${date}
`; }).join(''); } else { container.innerHTML = '
No notifications yet
'; } } catch (error) { console.error('Failed to load notification history:', error); } } // Save notification config async function saveNotificationConfig() { try { const config = { enabled: document.getElementById('notifications-enabled').checked, providers: { discord: { enabled: document.getElementById('discord-enabled').checked, webhookUrl: document.getElementById('discord-webhook').value.trim() }, telegram: { enabled: document.getElementById('telegram-enabled').checked, botToken: document.getElementById('telegram-bot-token').value.trim(), chatId: document.getElementById('telegram-chat-id').value.trim() }, ntfy: { enabled: document.getElementById('ntfy-enabled').checked, serverUrl: document.getElementById('ntfy-server').value.trim() || 'https://ntfy.sh', topic: document.getElementById('ntfy-topic').value.trim() }, email: { enabled: document.getElementById('email-enabled').checked, host: document.getElementById('email-host').value.trim(), port: parseInt(document.getElementById('email-port').value) || 587, secure: document.getElementById('email-secure').checked, user: document.getElementById('email-user').value.trim(), pass: document.getElementById('email-pass').value.trim(), from: document.getElementById('email-from').value.trim(), to: document.getElementById('email-to').value.trim() } }, events: { containerDown: document.getElementById('event-container-down').checked, containerUp: document.getElementById('event-container-up').checked, deploymentSuccess: document.getElementById('event-deploy-success').checked, deploymentFailed: document.getElementById('event-deploy-failed').checked }, healthCheck: { enabled: document.getElementById('health-check-enabled').checked, intervalMinutes: parseInt(document.getElementById('health-check-interval').value) || 5 } }; const response = await secureFetch('/api/v1/notifications/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const data = await response.json(); if (data.success) { showNotification('Notification settings saved', 'success', 3000); modal.classList.remove('show'); } else { showNotification(`Failed to save: ${data.error}`, 'error', 3000); } } catch (error) { showNotification(`Error: ${error.message}`, 'error', 3000); } } // Test notification handlers async function testProvider(provider) { try { const response = await secureFetch('/api/v1/notifications/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider }) }); const data = await response.json(); if (data.success) { showNotification(`Test ${provider} notification sent!`, 'success', 3000); } else { showNotification(`Test failed: ${data.error}`, 'error', 3000); } } catch (error) { showNotification(`Error: ${error.message}`, 'error', 3000); } } document.getElementById('discord-test')?.addEventListener('click', () => testProvider('discord')); document.getElementById('telegram-test')?.addEventListener('click', () => testProvider('telegram')); document.getElementById('ntfy-test')?.addEventListener('click', () => testProvider('ntfy')); document.getElementById('email-test')?.addEventListener('click', () => testProvider('email')); // Health check now button document.getElementById('health-check-now')?.addEventListener('click', async () => { try { const response = await secureFetch('/api/v1/notifications/health-check', { method: 'POST' }); const data = await response.json(); if (data.success) { document.getElementById('health-check-status').textContent = `Last check: ${new Date(data.lastCheck).toLocaleString()} (${data.containersMonitored} containers)`; showNotification('Health check completed', 'success', 2000); } } catch (error) { showNotification(`Error: ${error.message}`, 'error', 3000); } }); // Modal handlers openBtn?.addEventListener('click', () => { modal.classList.add('show'); loadNotificationConfig(); loadNotificationHistory(); }); saveBtn?.addEventListener('click', saveNotificationConfig); wireModal(modal, cancelBtn); })();