diff --git a/server-db.ts b/server-db.ts index 9d6c7e2..e6d2d95 100644 --- a/server-db.ts +++ b/server-db.ts @@ -30,7 +30,7 @@ db.exec(` status TEXT NOT NULL, emergencySheet TEXT NOT NULL, lastCheckedAt TEXT, - checkMkUrl TEXT NOT NULL DEFAULT '', + cmkUrl TEXT NOT NULL DEFAULT '', cmkHostname TEXT NOT NULL DEFAULT '' ); @@ -56,10 +56,10 @@ db.exec(` status TEXT NOT NULL, notified INTEGER NOT NULL DEFAULT 0, emailSent INTEGER NOT NULL DEFAULT 0, - ansibleSetupTriggered INTEGER NOT NULL DEFAULT 0, - ansibleTeardownTriggered INTEGER NOT NULL DEFAULT 0, - ansibleSetupJobId TEXT NOT NULL DEFAULT '', - ansibleTeardownJobId TEXT NOT NULL DEFAULT '' + semaphoreSetupTriggered INTEGER NOT NULL DEFAULT 0, + semaphoreTeardownTriggered INTEGER NOT NULL DEFAULT 0, + semaphoreSetupJobId TEXT NOT NULL DEFAULT '', + semaphoreTeardownJobId TEXT NOT NULL DEFAULT '' ); CREATE TABLE IF NOT EXISTS logs ( diff --git a/server-migrations.ts b/server-migrations.ts index dee4042..af966d8 100644 --- a/server-migrations.ts +++ b/server-migrations.ts @@ -8,13 +8,21 @@ interface Migration { // Append only. Never reorder or remove entries — that would corrupt tracking. // Each `up` function receives the open DB handle inside an already-open transaction. const migrations: Migration[] = [ - // Example: - // { - // id: '0001_bookings_add_color', - // up: (db) => { - // db.exec(`ALTER TABLE bookings ADD COLUMN color TEXT NOT NULL DEFAULT 'blue'`); - // }, - // }, + { + id: '0001_rename_device_checkMkUrl_to_cmkUrl', + up: (db) => { + db.exec(`ALTER TABLE devices RENAME COLUMN checkMkUrl TO cmkUrl`); + }, + }, + { + id: '0002_rename_booking_ansible_to_semaphore', + up: (db) => { + db.exec(`ALTER TABLE bookings RENAME COLUMN ansibleSetupTriggered TO semaphoreSetupTriggered`); + db.exec(`ALTER TABLE bookings RENAME COLUMN ansibleTeardownTriggered TO semaphoreTeardownTriggered`); + db.exec(`ALTER TABLE bookings RENAME COLUMN ansibleSetupJobId TO semaphoreSetupJobId`); + db.exec(`ALTER TABLE bookings RENAME COLUMN ansibleTeardownJobId TO semaphoreTeardownJobId`); + }, + }, ]; export function runMigrations(db: InstanceType): void { diff --git a/server.ts b/server.ts index 5b70d01..c196de2 100644 --- a/server.ts +++ b/server.ts @@ -271,9 +271,9 @@ async function startServer() { res.json({ azureEnabled: enabled && Boolean(clientId) && Boolean(tenantId) && Boolean(secret), effectiveRedirectUri, - checkmkEnabled: getSetting('checkmk_enabled') === 'true', + cmkEnabled: getSetting('checkmk_enabled') === 'true', semaphoreEnabled: getSetting('semaphore_enabled') === 'true', - checkmkBaseUrl: cmkApiUrl.replace(/\/api\/.*$/, ''), + cmkBaseUrl: cmkApiUrl.replace(/\/api\/.*$/, ''), isProduction: IS_PRODUCTION, }); }); @@ -655,10 +655,10 @@ async function startServer() { startDateTime: r.startDateTime, endDateTime: r.endDateTime, notes: r.notes || '', status: r.status as any, notified: r.notified === 1, emailSent: r.emailSent === 1, - ansibleSetupTriggered: r.ansibleSetupTriggered === 1, - ansibleTeardownTriggered: r.ansibleTeardownTriggered === 1, - ansibleSetupJobId: r.ansibleSetupJobId || '', - ansibleTeardownJobId: r.ansibleTeardownJobId || '', + semaphoreSetupTriggered: r.semaphoreSetupTriggered === 1, + semaphoreTeardownTriggered: r.semaphoreTeardownTriggered === 1, + semaphoreSetupJobId: r.semaphoreSetupJobId || '', + semaphoreTeardownJobId: r.semaphoreTeardownJobId || '', })); res.json(bookings); } catch (err: any) { @@ -714,14 +714,14 @@ async function startServer() { // Trigger teardown if booking had already started and teardown not yet triggered const now = new Date(); - if (new Date(booking.startDateTime) <= now && !booking.ansibleTeardownTriggered) { + if (new Date(booking.startDateTime) <= now && !booking.semaphoreTeardownTriggered) { const templateId = lab?.semaphoreTeardownTemplateId; if (templateId) { const jobId = await triggerSemaphoreTask(Number(templateId), { booking_id: booking.id, lab_name: lab?.name || '', user_id: booking.userId, start_time: booking.startDateTime, end_time: booking.endDateTime, }); - db.prepare('UPDATE bookings SET ansibleTeardownTriggered = 1, ansibleTeardownJobId = ? WHERE id = ?') + db.prepare('UPDATE bookings SET semaphoreTeardownTriggered = 1, semaphoreTeardownJobId = ? WHERE id = ?') .run(jobId !== null ? String(jobId) : '', booking.id); } } @@ -732,10 +732,10 @@ async function startServer() { id: r.id, labId: r.labId, userId: r.userId, startDateTime: r.startDateTime, endDateTime: r.endDateTime, notes: r.notes || '', status: r.status, notified: r.notified === 1, emailSent: r.emailSent === 1, - ansibleSetupTriggered: r.ansibleSetupTriggered === 1, - ansibleTeardownTriggered: r.ansibleTeardownTriggered === 1, - ansibleSetupJobId: r.ansibleSetupJobId || '', - ansibleTeardownJobId: r.ansibleTeardownJobId || '', + semaphoreSetupTriggered: r.semaphoreSetupTriggered === 1, + semaphoreTeardownTriggered: r.semaphoreTeardownTriggered === 1, + semaphoreSetupJobId: r.semaphoreSetupJobId || '', + semaphoreTeardownJobId: r.semaphoreTeardownJobId || '', }); } catch (err: any) { res.status(500).json({ error: err.message }); @@ -942,7 +942,7 @@ async function startServer() { // to 'unknown'. Runs on a self-scheduling setTimeout so that interval changes // in Settings take effect on the next cycle without a server restart. // ------------------------------------------------------------- - function checkmkHttpHint(status: number): string { + function cmkHttpHint(status: number): string { if (status === 401) return 'HTTP 401 Unauthorized - wrong automation user or secret (check Settings > CheckMK)'; if (status === 403) return 'HTTP 403 Forbidden - automation user lacks permission in CheckMK'; if (status === 404) return 'HTTP 404 Not Found - API URL incorrect or site name wrong'; @@ -974,7 +974,7 @@ async function startServer() { `${CHECKMK_API_URL}/domain-types/host_config/collections/all`, { headers } ); - if (!cfgRes.ok) throw new Error(checkmkHttpHint(cfgRes.status)); + if (!cfgRes.ok) throw new Error(cmkHttpHint(cfgRes.status)); const cfgData = await cfgRes.json(); ipToHostname = new Map(); for (const host of cfgData?.value ?? []) { @@ -1013,7 +1013,7 @@ async function startServer() { `${CHECKMK_API_URL}/objects/host/${encodeURIComponent(cmkHost)}?columns=state&columns=hard_state&columns=has_been_checked`, { headers } ); - if (!hostRes.ok) throw new Error(checkmkHttpHint(hostRes.status)); + if (!hostRes.ok) throw new Error(cmkHttpHint(hostRes.status)); const hostData = await hostRes.json(); const state: number = hostData?.extensions?.state ?? -1; @@ -1106,20 +1106,20 @@ async function startServer() { const setupPending = db.prepare( `SELECT b.*, l.semaphoreSetupTemplateId, l.name AS labName FROM bookings b LEFT JOIN labs l ON b.labId = l.id - WHERE b.startDateTime <= ? AND b.ansibleSetupTriggered = 0 AND b.status != 'cancelled'` + WHERE b.startDateTime <= ? AND b.semaphoreSetupTriggered = 0 AND b.status != 'cancelled'` ).all(now) as any[]; for (const row of setupPending) { const templateId = row.semaphoreSetupTemplateId; if (!templateId) { - db.prepare('UPDATE bookings SET ansibleSetupTriggered = 1 WHERE id = ?').run(row.id); + db.prepare('UPDATE bookings SET semaphoreSetupTriggered = 1 WHERE id = ?').run(row.id); continue; } const jobId = await triggerSemaphoreTask(Number(templateId), { booking_id: row.id, lab_name: row.labName || '', user_id: row.userId, start_time: row.startDateTime, end_time: row.endDateTime, }); - db.prepare('UPDATE bookings SET ansibleSetupTriggered = 1, ansibleSetupJobId = ? WHERE id = ?') + db.prepare('UPDATE bookings SET semaphoreSetupTriggered = 1, semaphoreSetupJobId = ? WHERE id = ?') .run(jobId !== null ? String(jobId) : '', row.id); } @@ -1127,20 +1127,20 @@ async function startServer() { const teardownPending = db.prepare( `SELECT b.*, l.semaphoreTeardownTemplateId, l.name AS labName FROM bookings b LEFT JOIN labs l ON b.labId = l.id - WHERE b.endDateTime <= ? AND b.ansibleTeardownTriggered = 0 AND b.status != 'cancelled'` + WHERE b.endDateTime <= ? AND b.semaphoreTeardownTriggered = 0 AND b.status != 'cancelled'` ).all(now) as any[]; for (const row of teardownPending) { const templateId = row.semaphoreTeardownTemplateId; if (!templateId) { - db.prepare('UPDATE bookings SET ansibleTeardownTriggered = 1 WHERE id = ?').run(row.id); + db.prepare('UPDATE bookings SET semaphoreTeardownTriggered = 1 WHERE id = ?').run(row.id); continue; } const jobId = await triggerSemaphoreTask(Number(templateId), { booking_id: row.id, lab_name: row.labName || '', user_id: row.userId, start_time: row.startDateTime, end_time: row.endDateTime, }); - db.prepare('UPDATE bookings SET ansibleTeardownTriggered = 1, ansibleTeardownJobId = ? WHERE id = ?') + db.prepare('UPDATE bookings SET semaphoreTeardownTriggered = 1, semaphoreTeardownJobId = ? WHERE id = ?') .run(jobId !== null ? String(jobId) : '', row.id); } } @@ -1190,10 +1190,10 @@ async function startServer() { }); if (type === 'setup') { - db.prepare('UPDATE bookings SET ansibleSetupTriggered = 1, ansibleSetupJobId = ? WHERE id = ?') + db.prepare('UPDATE bookings SET semaphoreSetupTriggered = 1, semaphoreSetupJobId = ? WHERE id = ?') .run(jobId !== null ? String(jobId) : '', bookingId); } else { - db.prepare('UPDATE bookings SET ansibleTeardownTriggered = 1, ansibleTeardownJobId = ? WHERE id = ?') + db.prepare('UPDATE bookings SET semaphoreTeardownTriggered = 1, semaphoreTeardownJobId = ? WHERE id = ?') .run(jobId !== null ? String(jobId) : '', bookingId); } diff --git a/src/App.tsx b/src/App.tsx index ffc7a4c..16f4e0d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -53,8 +53,8 @@ export default function App() { const [remindedBookings, setRemindedBookings] = useState>(new Set()); const [inventoryHighlightDevice, setInventoryHighlightDevice] = useState(null); - const [checkmkEnabled, setCheckmkEnabled] = useState(false); - const [checkmkBaseUrl, setCheckmkBaseUrl] = useState(''); + const [cmkEnabled, setCmkEnabled] = useState(false); + const [cmkBaseUrl, setCmkBaseUrl] = useState(''); const [isProduction, setIsProduction] = useState(false); const [semaphoreEnabled, setSemaphoreEnabled] = useState(false); @@ -145,7 +145,7 @@ export default function App() { if (bookingsRes.ok) setBookings(await bookingsRes.json()); if (logsRes.ok) setLogs(await logsRes.json()); if (linksRes.ok) setLinks(await linksRes.json()); - if (configRes.ok) { const cfg = await configRes.json(); setCheckmkEnabled(!!cfg.checkmkEnabled); setCheckmkBaseUrl(cfg.checkmkBaseUrl || ''); setIsProduction(!!cfg.isProduction); setSemaphoreEnabled(!!cfg.semaphoreEnabled); } + if (configRes.ok) { const cfg = await configRes.json(); setCmkEnabled(!!cfg.cmkEnabled); setCmkBaseUrl(cfg.cmkBaseUrl || ''); setIsProduction(!!cfg.isProduction); setSemaphoreEnabled(!!cfg.semaphoreEnabled); } } catch (err) { console.error('[App] Failed to load data:', err); } finally { @@ -588,7 +588,7 @@ export default function App() { labs={labs} devices={devices} currentUser={currentUser} - checkmkEnabled={checkmkEnabled} + cmkEnabled={cmkEnabled} onAddBooking={handleAddBooking} onCancelBooking={handleCancelBooking} onDeleteBooking={handleDeleteBooking} @@ -598,8 +598,8 @@ export default function App() { {activeTab === 'devices' && ( ) => void; onCancelBooking: (id: string) => void; onDeleteBooking: (id: string) => void; @@ -73,7 +73,7 @@ export default function BookingCalendar({ labs, devices, currentUser, - checkmkEnabled, + cmkEnabled, onAddBooking, onCancelBooking, onDeleteBooking, @@ -698,7 +698,7 @@ export default function BookingCalendar({ ); } - if (checkmkEnabled && offline.length > 0) { + if (cmkEnabled && offline.length > 0) { return (
diff --git a/src/components/BookingDetailsModal.tsx b/src/components/BookingDetailsModal.tsx index fe9137c..4acc714 100644 --- a/src/components/BookingDetailsModal.tsx +++ b/src/components/BookingDetailsModal.tsx @@ -47,10 +47,10 @@ export default function BookingDetailsModal({ const [localTeardownTriggered, setLocalTeardownTriggered] = useState(false); const [localTeardownJobId, setLocalTeardownJobId] = useState(''); - const setupTriggered = booking.ansibleSetupTriggered || localSetupTriggered; - const setupJobId = booking.ansibleSetupJobId || localSetupJobId; - const teardownTriggered = booking.ansibleTeardownTriggered || localTeardownTriggered; - const teardownJobId = booking.ansibleTeardownJobId || localTeardownJobId; + const setupTriggered = booking.semaphoreSetupTriggered || localSetupTriggered; + const setupJobId = booking.semaphoreSetupJobId || localSetupJobId; + const teardownTriggered = booking.semaphoreTeardownTriggered || localTeardownTriggered; + const teardownJobId = booking.semaphoreTeardownJobId || localTeardownJobId; async function manualTrigger(type: 'setup' | 'teardown') { setTriggering(true); diff --git a/src/components/DeviceInventory.tsx b/src/components/DeviceInventory.tsx index f6b4704..d5d8cde 100644 --- a/src/components/DeviceInventory.tsx +++ b/src/components/DeviceInventory.tsx @@ -15,8 +15,8 @@ const DEVICE_CLASS_PRESETS = ['Switch', 'Firewall', 'Access-Point', 'Controller' interface DeviceInventoryProps { devices: Device[]; - checkmkEnabled: boolean; - checkmkBaseUrl: string; + cmkEnabled: boolean; + cmkBaseUrl: string; onAddDevice: (device: Omit) => void; onUpdateDevice: (device: Device) => void; onDeleteDevice: (id: string) => void; @@ -24,8 +24,8 @@ interface DeviceInventoryProps { export default function DeviceInventory({ devices, - checkmkEnabled, - checkmkBaseUrl, + cmkEnabled, + cmkBaseUrl, onAddDevice, onUpdateDevice, onDeleteDevice, @@ -65,8 +65,8 @@ export default function DeviceInventory({ const effectiveStatus = (d: Device): 'online' | 'offline' | 'unknown' => d.status; const cmkHostUrl = (d: Device) => - checkmkEnabled && checkmkBaseUrl && d.cmkHostname - ? `${checkmkBaseUrl}/index.py?host=${encodeURIComponent(d.cmkHostname)}` + cmkEnabled && cmkBaseUrl && d.cmkHostname + ? `${cmkBaseUrl}/index.py?host=${encodeURIComponent(d.cmkHostname)}` : null; const statusMeta = (s: 'online' | 'offline' | 'unknown') => { @@ -324,7 +324,7 @@ export default function DeviceInventory({ {/* Right: Actions and Status */}
e.stopPropagation()}> {/* CheckMK Status Badge – only when CheckMK is enabled */} - {checkmkEnabled && (() => { const m = statusMeta(effectiveStatus(device)); return ( + {cmkEnabled && (() => { const m = statusMeta(effectiveStatus(device)); return (
@@ -402,7 +402,7 @@ export default function DeviceInventory({
{/* CheckMK Monitoring Panel – only when CheckMK is enabled */} - {checkmkEnabled && ( + {cmkEnabled && (
diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index bc0306e..52f61fa 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -171,13 +171,13 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) { const [azureAllowedGroup, setAzureAllowedGroup] = useState(''); const [showAzureSecret, setShowAzureSecret] = useState(false); - const [checkmkEnabled, setCheckmkEnabled] = useState(false); - const [checkmkApiUrl, setCheckmkApiUrl] = useState(''); - const [checkmkApiUser, setCheckmkApiUser] = useState(''); - const [checkmkApiSecret, setCheckmkApiSecret] = useState(''); - const [checkmkSecretSet, setCheckmkSecretSet] = useState(false); - const [checkmkSyncInterval, setCheckmkSyncInterval] = useState('60000'); - const [showCheckmkSecret, setShowCheckmkSecret] = useState(false); + const [cmkEnabled, setCmkEnabled] = useState(false); + const [cmkApiUrl, setCheckmkApiUrl] = useState(''); + const [cmkApiUser, setCheckmkApiUser] = useState(''); + const [cmkApiSecret, setCheckmkApiSecret] = useState(''); + const [cmkSecretSet, setCheckmkSecretSet] = useState(false); + const [cmkSyncInterval, setCheckmkSyncInterval] = useState('60000'); + const [showCmkSecret, setShowCheckmkSecret] = useState(false); const [syncing, setSyncing] = useState(false); const [semaphoreEnabled, setSemaphoreEnabled] = useState(false); @@ -251,7 +251,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) { setAzureClientSecret(''); setAzureRedirectUri(data.azure_redirect_uri || ''); setAzureAllowedGroup(data.azure_allowed_group || ''); - setCheckmkEnabled(data.checkmk_enabled === 'true'); + setCmkEnabled(data.checkmk_enabled === 'true'); setCheckmkApiUrl(data.checkmk_api_url || ''); setCheckmkApiUser(data.checkmk_api_user || ''); setCheckmkSecretSet(data.checkmk_api_secret === SECRET_SENTINEL); @@ -281,10 +281,10 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) { azure_tenant_id: azureTenantId, azure_redirect_uri: azureRedirectUri, azure_allowed_group: azureAllowedGroup, - checkmk_enabled: checkmkEnabled ? 'true' : 'false', - checkmk_api_url: checkmkApiUrl, - checkmk_api_user: checkmkApiUser, - checkmk_sync_interval_ms: checkmkSyncInterval, + checkmk_enabled: cmkEnabled ? 'true' : 'false', + checkmk_api_url: cmkApiUrl, + checkmk_api_user: cmkApiUser, + checkmk_sync_interval_ms: cmkSyncInterval, semaphore_enabled: semaphoreEnabled ? 'true' : 'false', semaphore_api_url: semaphoreApiUrl, semaphore_project_id: semaphoreProjectId, @@ -292,7 +292,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) { caddy_admin_url: caddyAdminUrl, }; if (azureClientSecret) payload.azure_client_secret = azureClientSecret; - if (checkmkApiSecret) payload.checkmk_api_secret = checkmkApiSecret; + if (cmkApiSecret) payload.checkmk_api_secret = cmkApiSecret; if (semaphoreApiToken) payload.semaphore_api_token = semaphoreApiToken; try { const res = await authFetch('/api/settings', { method: 'PUT', body: JSON.stringify(payload) }); @@ -706,7 +706,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {

CheckMK

- {checkmkEnabled && checkmkApiUrl && checkmkSecretSet && ( + {cmkEnabled && cmkApiUrl && cmkSecretSet && ( ACTIVE @@ -717,25 +717,25 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
- - {checkmkEnabled ? 'ENABLED' : 'DISABLED'} + + {cmkEnabled ? 'ENABLED' : 'DISABLED'}
-
+
} @@ -744,7 +744,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
} @@ -753,25 +753,25 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) { : undefined} + badge={cmkSecretSet ? : undefined} > setShowCheckmkSecret(v => !v)} /> } />
- {checkmkApiUrl && checkmkSecretSet && ( + {cmkApiUrl && cmkSecretSet && (