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:
1 parent
5330c8932f
commit
6a291e120d
10 files changed
+151
-45
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user