diff --git a/frontend-modern/src/pages/Alerts.tsx b/frontend-modern/src/pages/Alerts.tsx index b3dce25c3..a4d00af18 100644 --- a/frontend-modern/src/pages/Alerts.tsx +++ b/frontend-modern/src/pages/Alerts.tsx @@ -78,6 +78,24 @@ interface Override { } +// Local email config with UI-specific fields +interface UIEmailConfig { + enabled: boolean; + provider: string; + smtpHost: string; + smtpPort: number; + username: string; + password: string; + from: string; + to: string[]; + tls: boolean; + startTLS: boolean; + replyTo: string; + maxRetries: number; + retryDelay: number; + rateLimit: number; +} + export function Alerts() { const { state, activeAlerts } = useWebSocket(); const [activeTab, setActiveTab] = createSignal('overview'); @@ -89,6 +107,41 @@ export function Alerts() { const [overrides, setOverrides] = createSignal([]); + // Email configuration state moved to parent to persist across tab changes + const [emailConfig, setEmailConfig] = createSignal({ + enabled: false, + provider: '', + smtpHost: '', + smtpPort: 587, + username: '', + password: '', + from: '', + to: [] as string[], + tls: true, + startTLS: false, + replyTo: '', + maxRetries: 3, + retryDelay: 5, + rateLimit: 60 + }); + + // Set up destinationsRef.emailConfig function immediately + destinationsRef.emailConfig = () => { + const config = emailConfig(); + return { + enabled: config.enabled, + provider: config.provider, + server: config.smtpHost, + port: config.smtpPort, + username: config.username, + password: config.password, + from: config.from, + to: config.to, + tls: config.tls, + starttls: config.startTLS + } as EmailConfig; + }; + // Load existing alert configuration on mount (only once) onMount(async () => { try { @@ -173,6 +226,29 @@ export function Alerts() { }; scheduleRef.setScheduleConfig(scheduleConfig); } + + // Load email configuration + try { + const emailConfigData = await NotificationsAPI.getEmailConfig(); + setEmailConfig({ + enabled: emailConfigData.enabled, + provider: emailConfigData.provider, + smtpHost: emailConfigData.server, + smtpPort: emailConfigData.port, + username: emailConfigData.username, + password: emailConfigData.password || '', + from: emailConfigData.from, + to: emailConfigData.to, + tls: emailConfigData.tls, + startTLS: emailConfigData.starttls, + replyTo: '', + maxRetries: 3, + retryDelay: 5, + rateLimit: 60 + }); + } catch (emailErr) { + console.error('Failed to load email configuration:', emailErr); + } } catch (err) { console.error('Failed to load alert configuration:', err); } @@ -370,10 +446,10 @@ export function Alerts() { await AlertsAPI.updateConfig(alertConfig); - // Save email config if on destinations tab + // Save email config if it exists (regardless of active tab) console.log('Active tab:', activeTab()); console.log('Has emailConfig:', !!destinationsRef.emailConfig); - if (activeTab() === 'destinations' && destinationsRef.emailConfig) { + if (destinationsRef.emailConfig) { const emailData = destinationsRef.emailConfig(); console.log('Saving email config:', emailData); await NotificationsAPI.updateEmailConfig(emailData); @@ -453,6 +529,8 @@ export function Alerts() { ref={destinationsRef} hasUnsavedChanges={hasUnsavedChanges} setHasUnsavedChanges={setHasUnsavedChanges} + emailConfig={emailConfig} + setEmailConfig={setEmailConfig} /> @@ -1264,96 +1342,18 @@ interface DestinationsTabProps { ref: DestinationsRef; hasUnsavedChanges: () => boolean; setHasUnsavedChanges: (value: boolean) => void; -} - -// Local email config with UI-specific fields -interface UIEmailConfig { - enabled: boolean; - provider: string; - smtpHost: string; - smtpPort: number; - username: string; - password: string; - from: string; - to: string[]; - tls: boolean; - startTLS: boolean; - replyTo: string; - maxRetries: number; - retryDelay: number; - rateLimit: number; + emailConfig: () => UIEmailConfig; + setEmailConfig: (config: UIEmailConfig) => void; } function DestinationsTab(props: DestinationsTabProps) { - const [emailConfig, setEmailConfig] = createSignal({ - enabled: false, - provider: '', - smtpHost: '', - smtpPort: 587, - username: '', - password: '', - from: '', - to: [] as string[], - tls: true, - startTLS: false, - replyTo: '', - maxRetries: 3, - retryDelay: 5, - rateLimit: 60 - }); - - // Expose emailConfig to parent (convert to API format) - // Use createEffect instead of onMount to ensure it's always set - createEffect(() => { - if (props.ref) { - props.ref.emailConfig = () => { - const config = emailConfig(); - return { - enabled: config.enabled, - provider: config.provider, - server: config.smtpHost, - port: config.smtpPort, - username: config.username, - password: config.password, - from: config.from, - to: config.to, - tls: config.tls, - starttls: config.startTLS - } as EmailConfig; - }; - } - }); const [webhooks, setWebhooks] = createSignal([]); const [testingEmail, setTestingEmail] = createSignal(false); const [testingWebhook, setTestingWebhook] = createSignal(null); - // Load email config on mount + // Load webhooks on mount (email config is now loaded in parent) onMount(async () => { - try { - const config = await NotificationsAPI.getEmailConfig(); - // Map API config to local format - setEmailConfig({ - enabled: config.enabled, - provider: config.provider, - smtpHost: config.server, - smtpPort: config.port, - username: config.username, - password: config.password || '', - from: config.from, - to: config.to, - tls: config.tls, - startTLS: config.starttls, - replyTo: '', - maxRetries: 3, - retryDelay: 5, - rateLimit: 60 - }); - } catch (err) { - console.error('Failed to load email config:', err); - } - - // Load webhooks try { const hooks = await NotificationsAPI.getWebhooks(); // Map to local Webhook type @@ -1370,7 +1370,7 @@ function DestinationsTab(props: DestinationsTabProps) { setTestingEmail(true); try { // Get current form values and convert to backend format - const currentConfig = emailConfig(); + const currentConfig = props.emailConfig(); // If no recipients specified, use the from address as default recipient const recipients = currentConfig.to && currentConfig.to.length > 0 @@ -1430,9 +1430,9 @@ function DestinationsTab(props: DestinationsTabProps) { -
+
{ - setEmailConfig(config); + props.setEmailConfig(config); props.setHasUnsavedChanges(true); }} onTest={testEmailConfig} diff --git a/internal/notifications/notifications.go b/internal/notifications/notifications.go index acb4a162f..cc90e3bcd 100644 --- a/internal/notifications/notifications.go +++ b/internal/notifications/notifications.go @@ -249,10 +249,8 @@ func (n *NotificationManager) sendGroupedAlerts() { func (n *NotificationManager) sendGroupedEmail(alertList []*alerts.Alert) { config := n.emailConfig - if len(config.To) == 0 { - log.Warn().Msg("No email recipients configured") - return - } + // Don't check for recipients here - sendHTMLEmail handles empty recipients + // by using the From address as the recipient // Generate email using template subject, htmlBody, textBody := EmailTemplate(alertList, false) @@ -267,10 +265,8 @@ func (n *NotificationManager) sendEmail(alert *alerts.Alert) { config := n.emailConfig n.mu.RUnlock() - if len(config.To) == 0 { - log.Warn().Msg("No email recipients configured") - return - } + // Don't check for recipients here - sendHTMLEmail handles empty recipients + // by using the From address as the recipient // Generate email using template subject, htmlBody, textBody := EmailTemplate([]*alerts.Alert{alert}, true) @@ -283,9 +279,18 @@ func (n *NotificationManager) sendEmail(alert *alerts.Alert) { func (n *NotificationManager) sendHTMLEmail(subject, htmlBody, textBody string, config EmailConfig) { boundary := fmt.Sprintf("===============%d==", time.Now().UnixNano()) + // Use From address as recipient if To is empty + recipients := config.To + if len(recipients) == 0 && config.From != "" { + recipients = []string{config.From} + log.Info(). + Str("from", config.From). + Msg("Using From address as recipient since To is empty") + } + // Compose multipart message msg := fmt.Sprintf("From: %s\r\n", config.From) - msg += fmt.Sprintf("To: %s\r\n", strings.Join(config.To, ", ")) + msg += fmt.Sprintf("To: %s\r\n", strings.Join(recipients, ", ")) msg += fmt.Sprintf("Subject: %s\r\n", subject) msg += fmt.Sprintf("Date: %s\r\n", time.Now().Format(time.RFC1123Z)) msg += "MIME-Version: 1.0\r\n" @@ -316,16 +321,27 @@ func (n *NotificationManager) sendHTMLEmail(subject, htmlBody, textBody string, } addr := fmt.Sprintf("%s:%d", config.SMTPHost, config.SMTPPort) - err := smtp.SendMail(addr, auth, config.From, config.To, []byte(msg)) + + log.Info(). + Str("smtp", addr). + Str("from", config.From). + Strs("to", recipients). + Bool("hasAuth", auth != nil). + Msg("Attempting to send email via SMTP") + + err := smtp.SendMail(addr, auth, config.From, recipients, []byte(msg)) if err != nil { log.Error(). Err(err). + Str("smtp", addr). + Strs("recipients", recipients). Msg("Failed to send email notification") } else { log.Info(). - Strs("recipients", config.To). - Msg("Email notification sent") + Strs("recipients", recipients). + Int("recipientCount", len(recipients)). + Msg("Email notification sent successfully") } } diff --git a/testing-tools/check-saved-email.js b/testing-tools/check-saved-email.js new file mode 100644 index 000000000..feaefceb0 --- /dev/null +++ b/testing-tools/check-saved-email.js @@ -0,0 +1,68 @@ +const axios = require('axios'); +const fs = require('fs'); +const { exec } = require('child_process'); +const util = require('util'); +const execPromise = util.promisify(exec); + +async function checkSavedEmail() { + console.log('=== CHECKING SAVED EMAIL CONFIGURATION ===\n'); + + try { + // 1. Get via API (decrypted) + console.log('1. Getting email config via API...'); + const response = await axios.get('http://localhost:3000/api/notifications/email'); + const config = response.data; + + console.log('\nEmail Configuration:'); + console.log(' Enabled:', config.enabled); + console.log(' Provider:', config.provider); + console.log(' SMTP Host:', config.smtpHost); + console.log(' SMTP Port:', config.smtpPort); + console.log(' From:', config.from); + console.log(' Username:', config.username); + console.log(' Password:', config.password ? '[REDACTED]' : '(empty)'); + console.log(' Recipients:', config.to); + console.log(' Recipients count:', config.to ? config.to.length : 0); + + if (config.to && config.to.length > 0) { + console.log('\n Recipients list:'); + config.to.forEach((recipient, i) => { + console.log(` ${i + 1}. ${recipient}`); + }); + } else { + console.log('\n ❌ No recipients configured!'); + } + + // 2. Test what happens when we save with recipients + console.log('\n2. Testing save with recipients...'); + + const testConfig = { + ...config, + to: ['test1@example.com', 'test2@example.com'] + }; + + console.log(' Saving config with 2 test recipients...'); + await axios.put('http://localhost:3000/api/notifications/email', testConfig); + + // 3. Read back + console.log(' Reading back saved config...'); + const saved = await axios.get('http://localhost:3000/api/notifications/email'); + + console.log(' Recipients after save:', saved.data.to); + console.log(' Recipients count:', saved.data.to ? saved.data.to.length : 0); + + // 4. Restore original + console.log('\n3. Restoring original config...'); + await axios.put('http://localhost:3000/api/notifications/email', config); + console.log(' ✅ Original config restored'); + + } catch (error) { + console.error('Error:', error.response?.data || error.message); + } +} + +if (require.main === module) { + checkSavedEmail().catch(console.error); +} + +module.exports = { checkSavedEmail }; \ No newline at end of file diff --git a/testing-tools/debug-email-save.js b/testing-tools/debug-email-save.js new file mode 100644 index 000000000..913102b7e --- /dev/null +++ b/testing-tools/debug-email-save.js @@ -0,0 +1,84 @@ +const { chromium } = require('playwright'); + +const FRONTEND_URL = 'http://192.168.0.123:7655'; + +async function debugEmailSave() { + console.log('=== DEBUGGING EMAIL SAVE PROCESS ===\n'); + + const browser = await chromium.launch({ + headless: false, // Show browser + slowMo: 500 // Slow down actions + }); + + try { + const context = await browser.newContext(); + const page = await context.newPage(); + + // Monitor console logs + page.on('console', msg => { + if (msg.text().includes('email') || msg.text().includes('recipient')) { + console.log('[Browser]:', msg.text()); + } + }); + + // Navigate to alerts page + console.log('1. Navigating to alerts page...'); + await page.goto(`${FRONTEND_URL}/alerts`); + await page.waitForTimeout(2000); + + // Click on Destinations tab + console.log('2. Clicking Destinations tab...'); + await page.click('button:has-text("Destinations")'); + await page.waitForTimeout(1000); + + // Check current recipients + console.log('3. Checking recipients field...'); + const recipientsField = await page.locator('textarea').filter({ hasText: /leave empty|recipients/i }); + const currentRecipients = await recipientsField.inputValue(); + console.log(' Current recipients:', currentRecipients || '(empty)'); + + // Add a test recipient + console.log('4. Adding test recipient...'); + await recipientsField.fill('test@example.com'); + await page.waitForTimeout(500); + + // Save changes + console.log('5. Clicking Save Changes...'); + await page.click('button:has-text("Save Changes")'); + await page.waitForTimeout(2000); + + // Navigate away and back + console.log('6. Navigating to Overview tab...'); + await page.click('button:has-text("Overview")'); + await page.waitForTimeout(1000); + + console.log('7. Navigating back to Destinations...'); + await page.click('button:has-text("Destinations")'); + await page.waitForTimeout(1000); + + // Check if recipients persisted + console.log('8. Checking if recipients persisted...'); + const recipientsAfter = await recipientsField.inputValue(); + console.log(' Recipients after save:', recipientsAfter || '(empty)'); + + if (recipientsAfter.includes('test@example.com')) { + console.log(' ✅ Recipients persisted correctly!'); + } else { + console.log(' ❌ Recipients were not saved!'); + } + + console.log('\nPress Ctrl+C to close the browser...'); + await page.waitForTimeout(60000); // Keep browser open + + } catch (error) { + console.error('Error:', error.message); + } finally { + await browser.close(); + } +} + +if (require.main === module) { + debugEmailSave().catch(console.error); +} + +module.exports = { debugEmailSave }; \ No newline at end of file diff --git a/testing-tools/decrypt-check.js b/testing-tools/decrypt-check.js new file mode 100644 index 000000000..b5e548d4c --- /dev/null +++ b/testing-tools/decrypt-check.js @@ -0,0 +1,23 @@ +const fs = require('fs'); +const crypto = require('crypto'); +const path = require('path'); + +// This is a test to see if password is in the encrypted file +// We can't actually decrypt without the key, but we can check file size + +const encFile = '/etc/pulse/email.enc'; +const stats = fs.statSync(encFile); + +console.log('Encrypted email file info:'); +console.log(' Size:', stats.size, 'bytes'); +console.log(' Modified:', stats.mtime); + +// A config with password should be larger than one without +// Typical empty config ~200 bytes, with password ~250+ bytes +if (stats.size < 200) { + console.log(' ⚠️ File seems too small to contain full config'); +} else if (stats.size > 250) { + console.log(' ✅ File size suggests it may contain password'); +} else { + console.log(' 🤔 File size is in between - uncertain'); +} diff --git a/testing-tools/monitor-email-send.js b/testing-tools/monitor-email-send.js new file mode 100644 index 000000000..12af4b1e9 --- /dev/null +++ b/testing-tools/monitor-email-send.js @@ -0,0 +1,43 @@ +const axios = require('axios'); +const { spawn } = require('child_process'); + +async function monitorEmailSend() { + console.log('=== MONITORING EMAIL SEND ===\n'); + + // Start log monitoring + console.log('Starting log monitor...\n'); + const logMonitor = spawn('sudo', ['tail', '-f', '/opt/pulse/pulse.log']); + + logMonitor.stdout.on('data', (data) => { + const lines = data.toString().split('\n'); + lines.forEach(line => { + if (line.includes('email') || line.includes('notification') || line.includes('smtp') || line.includes('test')) { + console.log('[LOG]', line); + } + }); + }); + + // Wait a moment for log monitor to start + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Send test email + console.log('\n=== SENDING TEST EMAIL ===\n'); + try { + const response = await axios.post('http://localhost:3000/api/notifications/test', { + method: 'email' + }); + console.log('API Response:', response.data); + } catch (error) { + console.error('API Error:', error.response?.data || error.message); + } + + // Keep monitoring for a few seconds + console.log('\nMonitoring logs for 5 seconds...\n'); + await new Promise(resolve => setTimeout(resolve, 5000)); + + // Kill log monitor + logMonitor.kill(); + console.log('\nDone monitoring.'); +} + +monitorEmailSend().catch(console.error); \ No newline at end of file diff --git a/testing-tools/save-user-password.js b/testing-tools/save-user-password.js new file mode 100644 index 000000000..d27e02334 --- /dev/null +++ b/testing-tools/save-user-password.js @@ -0,0 +1,43 @@ +const axios = require('axios'); + +async function saveUserPassword() { + console.log('=== SAVING USER\'S EMAIL CONFIG WITH PASSWORD ===\n'); + + try { + // Get current config + const response = await axios.get('http://localhost:3000/api/notifications/email'); + const config = response.data; + + // Update with user's actual credentials + const userConfig = { + enabled: true, + smtpHost: 'smtp.gmail.com', + smtpPort: 587, + username: 'courtmanr@gmail.com', + password: 'zlff ruyk bxxf cxch', // User's app password + from: 'courtmanr@gmail.com', + to: [], // Empty as user wants + tls: true + }; + + console.log('Saving user config with password...'); + await axios.put('http://localhost:3000/api/notifications/email', userConfig); + console.log('✅ Config saved'); + + // Test sending + console.log('\nTesting email send...'); + try { + const testResponse = await axios.post('http://localhost:3000/api/notifications/test', { + method: 'email' + }); + console.log('✅ Test email sent:', testResponse.data); + } catch (testError) { + console.error('❌ Test email failed:', testError.response?.data?.error || testError.message); + } + + } catch (error) { + console.error('Error:', error.response?.data || error.message); + } +} + +saveUserPassword().catch(console.error); diff --git a/testing-tools/test-email-detailed.js b/testing-tools/test-email-detailed.js new file mode 100644 index 000000000..30fe7870d --- /dev/null +++ b/testing-tools/test-email-detailed.js @@ -0,0 +1,55 @@ +const axios = require('axios'); +const { spawn } = require('child_process'); + +async function testEmailDetailed() { + console.log('=== DETAILED EMAIL TEST ===\n'); + + // Start monitoring backend logs + console.log('Starting backend log monitor...\n'); + const logMonitor = spawn('sudo', ['tail', '-f', '/opt/pulse/pulse.log']); + + let logOutput = ''; + logMonitor.stdout.on('data', (data) => { + const output = data.toString(); + logOutput += output; + // Show all logs for debugging + process.stdout.write(output); + }); + + logMonitor.stderr.on('data', (data) => { + console.error('[ERROR]', data.toString()); + }); + + // Wait for log monitor to start + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Send test email + console.log('\n=== SENDING TEST EMAIL ===\n'); + try { + const response = await axios.post('http://localhost:3000/api/notifications/test', { + method: 'email' + }); + console.log('\nAPI Response:', JSON.stringify(response.data, null, 2)); + } catch (error) { + console.error('\nAPI Error:', error.response?.data || error.message); + } + + // Wait for any SMTP errors + console.log('\nWaiting for SMTP logs...\n'); + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Kill log monitor + logMonitor.kill(); + + // Check if we got any SMTP errors + if (logOutput.includes('Failed to send email')) { + console.log('\n❌ Email send failed - check logs above for details'); + } else if (logOutput.includes('Email notification sent')) { + console.log('\n✅ Email appears to have been sent successfully'); + console.log(' Check your email at: courtmanr@gmail.com'); + } else { + console.log('\n🤔 Could not determine email status from logs'); + } +} + +testEmailDetailed().catch(console.error); \ No newline at end of file diff --git a/testing-tools/test-email-direct.js b/testing-tools/test-email-direct.js new file mode 100644 index 000000000..3295657df --- /dev/null +++ b/testing-tools/test-email-direct.js @@ -0,0 +1,35 @@ +const axios = require('axios'); + +async function testEmailDirect() { + console.log('=== TESTING EMAIL SEND DIRECTLY ===\n'); + + try { + // Get current config + const response = await axios.get('http://localhost:3000/api/notifications/email'); + const config = response.data; + + console.log('Current email config:'); + console.log(' From:', config.from); + console.log(' Recipients:', config.to); + console.log(' Password:', config.password ? 'SET' : 'EMPTY'); + + // Try to send test email + console.log('\nSending test email...'); + const testResponse = await axios.post('http://localhost:3000/api/notifications/test', { + method: 'email' + }); + + console.log('Response:', testResponse.data); + + } catch (error) { + console.error('\nError details:'); + console.error(' Status:', error.response?.status); + console.error(' Message:', error.response?.data?.error || error.message); + + if (error.response?.data) { + console.error(' Full response:', JSON.stringify(error.response.data, null, 2)); + } + } +} + +testEmailDirect().catch(console.error); \ No newline at end of file diff --git a/testing-tools/test-email-send.js b/testing-tools/test-email-send.js new file mode 100644 index 000000000..7694db9c6 --- /dev/null +++ b/testing-tools/test-email-send.js @@ -0,0 +1,69 @@ +const axios = require('axios'); + +async function testEmailSend() { + console.log('=== TESTING EMAIL SEND FUNCTIONALITY ===\n'); + + try { + // 1. Get current email config + console.log('1. Getting current email configuration...'); + const configResponse = await axios.get('http://localhost:3000/api/notifications/email'); + const emailConfig = configResponse.data; + + console.log(' Email enabled:', emailConfig.enabled); + console.log(' SMTP Host:', emailConfig.smtpHost); + console.log(' SMTP Port:', emailConfig.smtpPort); + console.log(' From:', emailConfig.from); + console.log(' Recipients:', emailConfig.to); + + // 2. Test sending email + console.log('\n2. Testing email send...'); + + try { + const testResponse = await axios.post('http://localhost:3000/api/notifications/test', { + method: 'email' + }); + + console.log(' Response:', testResponse.status); + console.log(' Data:', testResponse.data); + + if (testResponse.data.success) { + console.log(' ✅ Test email sent successfully!'); + } else { + console.log(' ❌ Test email failed:', testResponse.data.message); + } + } catch (error) { + console.log(' ❌ Error sending test email:'); + console.log(' Status:', error.response?.status); + console.log(' Error:', error.response?.data || error.message); + + // If 400, might be missing config + if (error.response?.status === 400) { + console.log('\n3. Checking what the backend expects...'); + console.log(' The backend might be expecting different fields or format'); + } + } + + // 3. Try with full config in request + console.log('\n4. Testing with config included in request...'); + try { + const testWithConfig = await axios.post('http://localhost:3000/api/notifications/test', { + method: 'email', + config: emailConfig + }); + + console.log(' Response:', testWithConfig.status); + console.log(' Data:', testWithConfig.data); + } catch (error) { + console.log(' Error with config:', error.response?.data || error.message); + } + + } catch (error) { + console.error('Failed to get email config:', error.message); + } +} + +if (require.main === module) { + testEmailSend().catch(console.error); +} + +module.exports = { testEmailSend }; \ No newline at end of file diff --git a/testing-tools/test-save-with-password.js b/testing-tools/test-save-with-password.js new file mode 100644 index 000000000..ea69a6483 --- /dev/null +++ b/testing-tools/test-save-with-password.js @@ -0,0 +1,54 @@ +const axios = require('axios'); + +async function testSaveWithPassword() { + console.log('=== TESTING EMAIL SAVE WITH PASSWORD ===\n'); + + try { + // 1. Get current config + console.log('1. Getting current email config...'); + const response = await axios.get('http://localhost:3000/api/notifications/email'); + const currentConfig = response.data; + + console.log(' Current password:', currentConfig.password ? 'SET' : 'EMPTY'); + + // 2. Save with test password + console.log('\n2. Saving config with test password...'); + const testConfig = { + ...currentConfig, + password: 'test-password-123', + to: ['test@example.com'] + }; + + await axios.put('http://localhost:3000/api/notifications/email', testConfig); + console.log(' ✅ Saved successfully'); + + // 3. Read back + console.log('\n3. Reading back saved config...'); + const savedResponse = await axios.get('http://localhost:3000/api/notifications/email'); + const savedConfig = savedResponse.data; + + console.log(' Password after save:', savedConfig.password ? 'SET' : 'EMPTY'); + console.log(' Recipients:', savedConfig.to); + + // 4. Test email send + console.log('\n4. Testing email send...'); + try { + const testResponse = await axios.post('http://localhost:3000/api/notifications/test', { + method: 'email' + }); + console.log(' Response:', testResponse.data); + } catch (testError) { + console.error(' Test email failed:', testError.response?.data?.error || testError.message); + } + + // 5. Restore original (without password) + console.log('\n5. Restoring original config...'); + await axios.put('http://localhost:3000/api/notifications/email', currentConfig); + console.log(' ✅ Original config restored'); + + } catch (error) { + console.error('Error:', error.response?.data || error.message); + } +} + +testSaveWithPassword().catch(console.error); diff --git a/testing-tools/test-ui-email.js b/testing-tools/test-ui-email.js new file mode 100644 index 000000000..38f39df6b --- /dev/null +++ b/testing-tools/test-ui-email.js @@ -0,0 +1,68 @@ +const { chromium } = require('playwright'); + +const FRONTEND_URL = 'http://192.168.0.123:7655'; + +async function testUIEmail() { + console.log('=== TESTING EMAIL VIA UI ===\n'); + + const browser = await chromium.launch({ + headless: true + }); + + try { + const context = await browser.newContext(); + const page = await context.newPage(); + + // Navigate to alerts page + console.log('1. Navigating to alerts page...'); + await page.goto(`${FRONTEND_URL}/alerts`); + await page.waitForTimeout(2000); + + // Click on Notifications tab + console.log('2. Clicking Notifications tab...'); + await page.click('button:has-text("Notifications")'); + await page.waitForTimeout(1000); + + // Click Send Test Email + console.log('3. Clicking Send Test Email...'); + await page.click('button:has-text("Send Test Email")'); + + // Wait for response + await page.waitForTimeout(3000); + + // Check for success notification + const toasts = await page.locator('.toast-notification, .notification, [role="alert"]').allTextContents(); + if (toasts.length > 0) { + console.log('4. Notifications received:', toasts); + } + + // Check console for errors + const errors = []; + page.on('console', msg => { + if (msg.type() === 'error') { + errors.push(msg.text()); + } + }); + + await page.waitForTimeout(1000); + + if (errors.length > 0) { + console.log('\n❌ Console errors:', errors); + } else { + console.log('\n✅ No console errors detected'); + } + + console.log('\n✅ Test completed - check your email at courtmanr@gmail.com'); + + } catch (error) { + console.error('Error:', error.message); + } finally { + await browser.close(); + } +} + +if (require.main === module) { + testUIEmail().catch(console.error); +} + +module.exports = { testUIEmail }; \ No newline at end of file