refactor(naming): unify service abbreviations (cmk, semaphore)

Standardise CheckMK variables to the cmk prefix (checkMkUrl -> cmkUrl,
checkmk* -> cmk*) and resolve the ansible/semaphore split by renaming
all booking fields to semaphore*. Includes DB migrations 0001/0002 for
existing databases.
This commit is contained in:
Brückner
2026-06-10 17:06:17 +02:00
parent e0fd19f471
commit 150557ce2c
9 changed files with 95 additions and 87 deletions

View File

@ -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 (

View File

@ -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<typeof Database>): void {

View File

@ -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<string, string>();
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);
}

View File

@ -53,8 +53,8 @@ export default function App() {
const [remindedBookings, setRemindedBookings] = useState<Set<string>>(new Set());
const [inventoryHighlightDevice, setInventoryHighlightDevice] = useState<Device | null>(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' && (
<DeviceInventory
devices={devices}
checkmkEnabled={checkmkEnabled}
checkmkBaseUrl={checkmkBaseUrl}
cmkEnabled={cmkEnabled}
cmkBaseUrl={cmkBaseUrl}
onAddDevice={handleAddDevice}
onUpdateDevice={handleUpdateDevice}
onDeleteDevice={handleDeleteDevice}

View File

@ -17,7 +17,7 @@ interface BookingCalendarProps {
labs: LabTemplate[];
devices: Device[];
currentUser: User;
checkmkEnabled: boolean;
cmkEnabled: boolean;
onAddBooking: (booking: Omit<Booking, 'id' | 'notified' | 'emailSent'>) => 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({
</div>
);
}
if (checkmkEnabled && offline.length > 0) {
if (cmkEnabled && offline.length > 0) {
return (
<div className="bg-amber-950/40 p-2.5 rounded border border-amber-800/60 flex gap-2 text-amber-300 text-[11px] leading-normal">
<AlertTriangle className="w-4 h-4 text-amber-400 shrink-0" />

View File

@ -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);

View File

@ -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<Device, 'id'>) => 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 */}
<div className="flex items-center gap-3" onClick={(e) => e.stopPropagation()}>
{/* CheckMK Status Badge only when CheckMK is enabled */}
{checkmkEnabled && (() => { const m = statusMeta(effectiveStatus(device)); return (
{cmkEnabled && (() => { const m = statusMeta(effectiveStatus(device)); return (
<div className="flex flex-col items-end gap-1 font-sans">
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-medium border capitalize ${m.badge}`}>
<span className={`w-1.5 h-1.5 rounded-full ${m.dot}`} />
@ -402,7 +402,7 @@ export default function DeviceInventory({
</div>
{/* CheckMK Monitoring Panel only when CheckMK is enabled */}
{checkmkEnabled && (
{cmkEnabled && (
<div className="mt-4 pt-4 border-t border-slate-800 space-y-2.5">
<div className="flex items-center justify-between">
<span className="text-xs text-slate-400 font-sans font-medium flex items-center gap-1.5">

View File

@ -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) {
<div>
<div className="flex items-center gap-2">
<h2 className="text-sm font-semibold text-white">CheckMK</h2>
{checkmkEnabled && checkmkApiUrl && checkmkSecretSet && (
{cmkEnabled && cmkApiUrl && cmkSecretSet && (
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[9px] font-semibold bg-emerald-950/60 border border-emerald-900/50 text-emerald-400">
<span className="w-1 h-1 rounded-full bg-emerald-400 animate-pulse inline-block" />
ACTIVE
@ -717,25 +717,25 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
</div>
</div>
<div className="flex items-center gap-2.5">
<span className={`text-[10px] font-semibold font-mono ${checkmkEnabled ? 'text-emerald-400' : 'text-slate-600'}`}>
{checkmkEnabled ? 'ENABLED' : 'DISABLED'}
<span className={`text-[10px] font-semibold font-mono ${cmkEnabled ? 'text-emerald-400' : 'text-slate-600'}`}>
{cmkEnabled ? 'ENABLED' : 'DISABLED'}
</span>
<button
type="button"
onClick={() => setCheckmkEnabled(v => !v)}
className={`relative w-10 h-5 rounded-full transition-all duration-200 focus:outline-none ${checkmkEnabled ? 'bg-emerald-600 shadow-[0_0_10px_rgba(5,150,105,0.4)]' : 'bg-slate-800 border border-slate-700'}`}
onClick={() => setCmkEnabled(v => !v)}
className={`relative w-10 h-5 rounded-full transition-all duration-200 focus:outline-none ${cmkEnabled ? 'bg-emerald-600 shadow-[0_0_10px_rgba(5,150,105,0.4)]' : 'bg-slate-800 border border-slate-700'}`}
>
<span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-all duration-200 ${checkmkEnabled ? 'left-5' : 'left-0.5'}`} />
<span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-all duration-200 ${cmkEnabled ? 'left-5' : 'left-0.5'}`} />
</button>
</div>
</div>
<div className="h-px bg-slate-800/60" />
<div className={`space-y-5 transition-opacity duration-200 ${!checkmkEnabled ? 'opacity-40 pointer-events-none' : ''}`}>
<div className={`space-y-5 transition-opacity duration-200 ${!cmkEnabled ? 'opacity-40 pointer-events-none' : ''}`}>
<FieldRow label="API URL">
<Input
value={checkmkApiUrl}
value={cmkApiUrl}
onChange={setCheckmkApiUrl}
monospace
icon={<Globe className="w-3.5 h-3.5" />}
@ -744,7 +744,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
<FieldRow label="Automation User" hint="Setup > Users > Automation user">
<Input
value={checkmkApiUser}
value={cmkApiUser}
onChange={setCheckmkApiUser}
monospace
icon={<KeyRound className="w-3.5 h-3.5" />}
@ -753,25 +753,25 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
<FieldRow
label="Automation Secret"
hint="Setup > Users > Automation user > Automation secret"
badge={checkmkSecretSet ? <ConfiguredBadge /> : undefined}
badge={cmkSecretSet ? <ConfiguredBadge /> : undefined}
>
<SecretInput
value={checkmkApiSecret}
value={cmkApiSecret}
onChange={setCheckmkApiSecret}
show={showCheckmkSecret}
show={showCmkSecret}
onToggleShow={() => setShowCheckmkSecret(v => !v)}
/>
</FieldRow>
<FieldRow label="Sync Interval" hint="Milliseconds between status polls (default: 60000)">
<Input
value={checkmkSyncInterval}
value={cmkSyncInterval}
onChange={setCheckmkSyncInterval}
monospace
icon={<Clock className="w-3.5 h-3.5" />}
/>
</FieldRow>
</div>
{checkmkApiUrl && checkmkSecretSet && (
{cmkApiUrl && cmkSecretSet && (
<button
type="button"
onClick={runSync}

View File

@ -50,10 +50,10 @@ export interface Booking {
status: 'active' | 'upcoming' | 'completed' | 'cancelled';
notified: boolean;
emailSent?: boolean;
ansibleSetupTriggered?: boolean;
ansibleTeardownTriggered?: boolean;
ansibleSetupJobId?: string;
ansibleTeardownJobId?: string;
semaphoreSetupTriggered?: boolean;
semaphoreTeardownTriggered?: boolean;
semaphoreSetupJobId?: string;
semaphoreTeardownJobId?: string;
}
export interface LogEntry {