Feature: Independent settings section saving - no password re-entry required

- Refactored SettingsWizard to allow saving each section separately (Email, Webhook, Monitoring)
- Each section now has its own Save button
- Removed global Save button - sections save independently
- No longer need to re-enter email passwords when changing other settings
- Added success/error status messages that appear for 3-5 seconds
- Status messages appear as toast notifications in top-right corner
- Updated CSS with save-status styling and slideIn animation
- handleSaveSection() only POSTs the relevant fields for that section
This commit is contained in:
nmemmert committed 2025-12-16 13:27:53 -05:00
1 parent 5330c8932f
commit 6a291e120d
10 files changed
+151 -45

No files matched your search

+46
View File
@@ -187,3 +187,49 @@
padding: 1rem 3rem;
font-size: 1.1rem;
}
.save-status {
position: fixed;
top: 20px;
right: 20px;
padding: 1rem 1.5rem;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
animation: slideIn 0.3s ease-out;
max-width: 300px;
}
.save-status.success {
background: #4caf50;
color: white;
border-left: 4px solid #388e3c;
}
.save-status.error {
background: #f44336;
color: white;
border-left: 4px solid #d32f2f;
}
.save-status strong {
display: block;
margin-bottom: 0.5rem;
font-size: 1rem;
}
.save-status p {
margin: 0;
font-size: 0.9rem;
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
+91 -31
View File
@@ -35,8 +35,9 @@ function SettingsWizard() {
});
const [testResult, setTestResult] = useState(null);
const [saving, setSaving] = useState(false);
const [savingSection, setSavingSection] = useState(null); // Track which section is saving
const [testing, setTesting] = useState(false);
const [savedMessage, setSavedMessage] = useState(null);
useEffect(() => {
loadSettings();
@@ -51,16 +52,21 @@ function SettingsWizard() {
}
};
const handleSave = async () => {
setSaving(true);
// Save specific section without requiring all settings
const handleSaveSection = async (sectionName, sectionData) => {
setSavingSection(sectionName);
setSavedMessage(null);
try {
await axios.post('/api/settings', settings);
alert('Settings saved successfully! Changes apply immediately.');
await axios.post('/api/settings', sectionData);
setSavedMessage({ section: sectionName, success: true });
// Clear message after 3 seconds
setTimeout(() => setSavedMessage(null), 3000);
} catch (error) {
console.error('Error saving settings:', error);
alert('Error saving settings');
setSavedMessage({ section: sectionName, success: false, error: error.response?.data?.error || 'Failed to save' });
setTimeout(() => setSavedMessage(null), 5000);
} finally {
setSaving(false);
setSavingSection(null);
}
};
@@ -230,13 +236,30 @@ function SettingsWizard() {
</ol>
</div>
<button
className="btn btn-secondary"
onClick={handleTestEmail}
disabled={testing || !settings.email_user || !settings.email_pass}
>
{testing ? 'Sending...' : '📨 Send Test Email'}
</button>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
<button
className="btn btn-secondary"
onClick={handleTestEmail}
disabled={testing || !settings.email_user || !settings.email_pass}
>
{testing ? 'Sending...' : '📨 Send Test Email'}
</button>
<button
className="btn btn-primary"
onClick={() => handleSaveSection('Email', {
email_enabled: settings.email_enabled,
email_host: settings.email_host,
email_port: settings.email_port,
email_user: settings.email_user,
email_pass: settings.email_pass,
email_from: settings.email_from,
email_to: settings.email_to,
})}
disabled={savingSection === 'Email'}
>
{savingSection === 'Email' ? 'Saving...' : '💾 Save Email Settings'}
</button>
</div>
</div>
)}
</div>
@@ -279,13 +302,25 @@ function SettingsWizard() {
}, null, 2)}</pre>
</div>
<button
className="btn btn-secondary"
onClick={handleTestWebhook}
disabled={testing || !settings.webhook_url}
>
{testing ? 'Sending...' : '🔔 Send Test Webhook'}
</button>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
<button
className="btn btn-secondary"
onClick={handleTestWebhook}
disabled={testing || !settings.webhook_url}
>
{testing ? 'Sending...' : '🔔 Send Test Webhook'}
</button>
<button
className="btn btn-primary"
onClick={() => handleSaveSection('Webhook', {
webhook_enabled: settings.webhook_enabled,
webhook_url: settings.webhook_url,
})}
disabled={savingSection === 'Webhook'}
>
{savingSection === 'Webhook' ? 'Saving...' : '💾 Save Webhook Settings'}
</button>
</div>
</div>
)}
</div>
@@ -368,19 +403,44 @@ function SettingsWizard() {
<small>Number of consecutive failed checks before automatically creating an incident (default: 10)</small>
</div>
</div>
<button
className="btn btn-primary"
onClick={() => handleSaveSection('Monitoring', {
check_interval: settings.check_interval,
timeout: settings.timeout,
timezone: settings.timezone,
retention_days: settings.retention_days,
auto_cleanup_enabled: settings.auto_cleanup_enabled,
consecutive_failures: settings.consecutive_failures,
grace_period: settings.grace_period,
downtime_threshold: settings.downtime_threshold,
alert_retry_count: settings.alert_retry_count,
alert_retry_delay: settings.alert_retry_delay,
fallback_webhook: settings.fallback_webhook,
global_quiet_hours_start: settings.global_quiet_hours_start,
global_quiet_hours_end: settings.global_quiet_hours_end,
escalation_hours: settings.escalation_hours,
default_sort: settings.default_sort,
items_per_page: settings.items_per_page,
refresh_interval: settings.refresh_interval,
theme: settings.theme,
incident_failure_threshold: settings.incident_failure_threshold,
})}
disabled={savingSection === 'Monitoring'}
>
{savingSection === 'Monitoring' ? 'Saving...' : '💾 Save Monitoring Settings'}
</button>
</div>
</div>
{/* Save Button */}
<div className="settings-actions">
<button
className="btn btn-primary btn-large"
onClick={handleSave}
disabled={saving}
>
{saving ? 'Saving...' : '💾 Save Settings'}
</button>
</div>
{/* Status Messages */}
{savedMessage && (
<div className={`save-status ${savedMessage.success ? 'success' : 'error'}`}>
<strong>{savedMessage.success ? '✅ Success!' : '❌ Error:'}</strong>
<p>{savedMessage.success ? `${savedMessage.section} settings saved!` : savedMessage.error}</p>
</div>
)}
</div>
);
}