Fix per-resource settings: tags saving, is_public bug, global setting inheritance

Bug fixes:
- tags field was missing from INSERT/UPDATE SQL — tags never persisted
- is_public: `0 !== false` is true in JS, so private resources (is_public=0)
  were silently flipped to public on every edit; fixed with explicit 0/false check

Global setting inheritance:
- One-time DB migration nulls out check_interval and timeout on all existing
  resources so they inherit from global monitoring settings going forward
- Scheduler reads global check_interval and timeout from DB each cycle and
  uses them as the fallback when per-resource values are null
- Create and edit modals: empty field = use global; placeholder shows the
  current global value so it's clear what will be used

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-06 17:01:03 -04:00
1 parent 3a3d32ecac
commit 59078362da
4 files changed
+49 -18

No files matched your search

+15 -7
View File
@@ -22,8 +22,8 @@ function Dashboard() {
name: '',
url: '',
type: 'http',
check_interval: 60000,
timeout: 5000,
check_interval: null,
timeout: null,
group_id: null,
http_keyword: '',
http_headers: '',
@@ -48,6 +48,8 @@ function Dashboard() {
const [sortKey, setSortKey] = useState('severity');
const [renderLimit, setRenderLimit] = useState(120);
const [refreshInterval, setRefreshInterval] = useState(15000);
const [globalCheckInterval, setGlobalCheckInterval] = useState(60000);
const [globalTimeout, setGlobalTimeout] = useState(5000);
const [loaded, setLoaded] = useState(false);
const [pendingDeleteId, setPendingDeleteId] = useState(null);
const [pendingBulkDelete, setPendingBulkDelete] = useState(false);
@@ -144,6 +146,8 @@ function Dashboard() {
if (s.items_per_page) setRenderLimit(parseInt(s.items_per_page));
if (s.default_sort) setSortKey(s.default_sort);
if (s.refresh_interval) setRefreshInterval(parseInt(s.refresh_interval));
if (s.check_interval) setGlobalCheckInterval(parseInt(s.check_interval));
if (s.timeout) setGlobalTimeout(parseInt(s.timeout));
}).catch(() => {});
}, []);
@@ -159,7 +163,7 @@ function Dashboard() {
}, [connectWebSocket, loadResources, refreshInterval]);
const emptyForm = {
name: '', url: '', type: 'http', check_interval: 60000, timeout: 5000,
name: '', url: '', type: 'http', check_interval: null, timeout: null,
group_id: null, http_keyword: '', http_headers: '', quiet_hours_start: '',
quiet_hours_end: '', cert_expiry_days: 30, sla_target: 99.9, email_to: '',
maintenance_mode: false, tags: '', consecutive_failures_threshold: 1,
@@ -825,13 +829,15 @@ function Dashboard() {
<div className="form-group">
<label>Check Interval (ms)</label>
<input type="number" value={formData.check_interval} onChange={(e) => setFormData({ ...formData, check_interval: parseInt(e.target.value) })} min="10000" />
<input type="number" value={formData.check_interval ?? ''} placeholder={`${globalCheckInterval} (global default)`} onChange={(e) => setFormData({ ...formData, check_interval: e.target.value ? parseInt(e.target.value) : null })} min="10000" />
<small>Leave empty to use global setting ({globalCheckInterval}ms)</small>
</div>
{formData.type !== 'heartbeat' && (
<div className="form-group">
<label>Timeout (ms)</label>
<input type="number" value={formData.timeout} onChange={(e) => setFormData({ ...formData, timeout: parseInt(e.target.value) })} min="1000" />
<input type="number" value={formData.timeout ?? ''} placeholder={`${globalTimeout} (global default)`} onChange={(e) => setFormData({ ...formData, timeout: e.target.value ? parseInt(e.target.value) : null })} min="1000" />
<small>Leave empty to use global setting ({globalTimeout}ms)</small>
</div>
)}
@@ -965,12 +971,14 @@ function Dashboard() {
<div className="form-group">
<label>Check Interval (ms)</label>
<input type="number" value={editData.check_interval || 60000} onChange={(e) => setEditData({ ...editData, check_interval: parseInt(e.target.value) })} min="10000" />
<input type="number" value={editData.check_interval ?? ''} placeholder={`${globalCheckInterval} (global default)`} onChange={(e) => setEditData({ ...editData, check_interval: e.target.value ? parseInt(e.target.value) : null })} min="10000" />
<small>Leave empty to use global setting ({globalCheckInterval}ms)</small>
</div>
<div className="form-group">
<label>Timeout (ms)</label>
<input type="number" value={editData.timeout || 5000} onChange={(e) => setEditData({ ...editData, timeout: parseInt(e.target.value) })} min="1000" />
<input type="number" value={editData.timeout ?? ''} placeholder={`${globalTimeout} (global default)`} onChange={(e) => setEditData({ ...editData, timeout: e.target.value ? parseInt(e.target.value) : null })} min="1000" />
<small>Leave empty to use global setting ({globalTimeout}ms)</small>
</div>
{(editData.type === 'http' || editData.type === 'https' || editData.type === 'health') && (