From 8e603b760d14acfa6d4bb2a1bee972b99514aedc Mon Sep 17 00:00:00 2001 From: Pulse Monitor Date: Sat, 2 Aug 2025 18:01:33 +0000 Subject: [PATCH] Fix email notifications to work with empty recipients - Backend now uses From address as recipient when To array is empty - Fixed sendEmail and sendGroupedEmail to not check for recipients - Added detailed logging for SMTP operations - Fixed recipient logging to show actual recipients sent This allows users to send test emails to themselves without having to enter their email address in the recipients field, as promised by the UI. --- frontend-modern/src/pages/Alerts.tsx | 178 +++++++++++------------ internal/notifications/notifications.go | 40 +++-- testing-tools/check-saved-email.js | 68 +++++++++ testing-tools/debug-email-save.js | 84 +++++++++++ testing-tools/decrypt-check.js | 23 +++ testing-tools/monitor-email-send.js | 43 ++++++ testing-tools/save-user-password.js | 43 ++++++ testing-tools/test-email-detailed.js | 55 +++++++ testing-tools/test-email-direct.js | 35 +++++ testing-tools/test-email-send.js | 69 +++++++++ testing-tools/test-save-with-password.js | 54 +++++++ testing-tools/test-ui-email.js | 68 +++++++++ 12 files changed, 659 insertions(+), 101 deletions(-) create mode 100644 testing-tools/check-saved-email.js create mode 100644 testing-tools/debug-email-save.js create mode 100644 testing-tools/decrypt-check.js create mode 100644 testing-tools/monitor-email-send.js create mode 100644 testing-tools/save-user-password.js create mode 100644 testing-tools/test-email-detailed.js create mode 100644 testing-tools/test-email-direct.js create mode 100644 testing-tools/test-email-send.js create mode 100644 testing-tools/test-save-with-password.js create mode 100644 testing-tools/test-ui-email.js 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