mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-10 03:51:54 +00:00
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.
This commit is contained in:
parent
740ec400a6
commit
8e603b760d
12 changed files with 659 additions and 101 deletions
|
|
@ -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<AlertTab>('overview');
|
||||
|
|
@ -89,6 +107,41 @@ export function Alerts() {
|
|||
|
||||
const [overrides, setOverrides] = createSignal<Override[]>([]);
|
||||
|
||||
// Email configuration state moved to parent to persist across tab changes
|
||||
const [emailConfig, setEmailConfig] = createSignal<UIEmailConfig>({
|
||||
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}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
|
|
@ -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<UIEmailConfig>({
|
||||
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<Webhook[]>([]);
|
||||
const [testingEmail, setTestingEmail] = createSignal(false);
|
||||
const [testingWebhook, setTestingWebhook] = createSignal<string | null>(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) {
|
|||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={emailConfig().enabled}
|
||||
checked={props.emailConfig().enabled}
|
||||
onChange={(e) => {
|
||||
setEmailConfig({...emailConfig(), enabled: e.currentTarget.checked});
|
||||
props.setEmailConfig({...props.emailConfig(), enabled: e.currentTarget.checked});
|
||||
props.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="sr-only peer"
|
||||
|
|
@ -1441,11 +1441,11 @@ function DestinationsTab(props: DestinationsTabProps) {
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<div class={`${!emailConfig().enabled ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||
<div class={`${!props.emailConfig().enabled ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||
<EmailProviderSelect
|
||||
config={emailConfig()}
|
||||
config={props.emailConfig()}
|
||||
onChange={(config) => {
|
||||
setEmailConfig(config);
|
||||
props.setEmailConfig(config);
|
||||
props.setHasUnsavedChanges(true);
|
||||
}}
|
||||
onTest={testEmailConfig}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
68
testing-tools/check-saved-email.js
Normal file
68
testing-tools/check-saved-email.js
Normal file
|
|
@ -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 };
|
||||
84
testing-tools/debug-email-save.js
Normal file
84
testing-tools/debug-email-save.js
Normal file
|
|
@ -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 };
|
||||
23
testing-tools/decrypt-check.js
Normal file
23
testing-tools/decrypt-check.js
Normal file
|
|
@ -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');
|
||||
}
|
||||
43
testing-tools/monitor-email-send.js
Normal file
43
testing-tools/monitor-email-send.js
Normal file
|
|
@ -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);
|
||||
43
testing-tools/save-user-password.js
Normal file
43
testing-tools/save-user-password.js
Normal file
|
|
@ -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);
|
||||
55
testing-tools/test-email-detailed.js
Normal file
55
testing-tools/test-email-detailed.js
Normal file
|
|
@ -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);
|
||||
35
testing-tools/test-email-direct.js
Normal file
35
testing-tools/test-email-direct.js
Normal file
|
|
@ -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);
|
||||
69
testing-tools/test-email-send.js
Normal file
69
testing-tools/test-email-send.js
Normal file
|
|
@ -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 };
|
||||
54
testing-tools/test-save-with-password.js
Normal file
54
testing-tools/test-save-with-password.js
Normal file
|
|
@ -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);
|
||||
68
testing-tools/test-ui-email.js
Normal file
68
testing-tools/test-ui-email.js
Normal file
|
|
@ -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 };
|
||||
Loading…
Add table
Add a link
Reference in a new issue