diff --git a/src/components/BookingCalendar.tsx b/src/components/BookingCalendar.tsx index 1c77e20..17a8bfb 100644 --- a/src/components/BookingCalendar.tsx +++ b/src/components/BookingCalendar.tsx @@ -5,11 +5,10 @@ import { X, Layers, Server, Clock, ChevronDown } from 'lucide-react'; -/** A device can only be reserved when CheckMK reports it online. */ function effectiveStatus(d: Device): 'online' | 'offline' | 'unknown' { return d.checkMkUrl ? (d.status === 'online' || d.status === 'offline' ? d.status : 'unknown') : 'unknown'; } -function isBookable(d: Device): boolean { +function isOnline(d: Device): boolean { return effectiveStatus(d) === 'online'; } @@ -156,11 +155,11 @@ export default function BookingCalendar({ return { hasConflict: false }; } - // Devices in the current selection that CheckMK does not report as online - these block the booking. - function blockingDevices(deviceIds: string[]): Device[] { + // Devices in the current selection that CheckMK does not report as online - shown as a warning only. + function offlineDevices(deviceIds: string[]): Device[] { return deviceIds .map(id => devices.find(d => d.id === id)) - .filter((d): d is Device => !!d && !isBookable(d)); + .filter((d): d is Device => !!d && !isOnline(d)); } // ── available-now helpers for Quick Booking ──────────────────────────── @@ -171,17 +170,14 @@ export default function BookingCalendar({ return { startMs: start.getTime(), endMs: end.getTime(), start, end }; }, [quickDuration]); - // A lab is quick-bookable only when every device is free AND reported online by CheckMK. + // A lab is quick-bookable when every device is free (regardless of online status). const availableLabs = useMemo(() => labs.filter(lab => lab.deviceIds.length > 0 && - lab.deviceIds.every(dId => { - const dev = devices.find(d => d.id === dId); - return !!dev && isBookable(dev) && !isDeviceBooked(dId, quickWindow.startMs, quickWindow.endMs); - }) + lab.deviceIds.every(dId => !isDeviceBooked(dId, quickWindow.startMs, quickWindow.endMs)) ), [labs, devices, bookings, quickWindow]); const availableDevices = useMemo(() => devices.filter(dev => - isBookable(dev) && !isDeviceBooked(dev.id, quickWindow.startMs, quickWindow.endMs) + !isDeviceBooked(dev.id, quickWindow.startMs, quickWindow.endMs) ), [devices, bookings, quickWindow]); // ── booking actions ──────────────────────────────────────────────────── @@ -191,12 +187,6 @@ export default function BookingCalendar({ const deviceIds = targetDeviceIds(); if (deviceIds.length === 0) { alert('Please select a resource to reserve.'); return; } - const blocked = blockingDevices(deviceIds); - if (blocked.length > 0) { - alert(`Not bookable: ${blocked.map(d => `"${d.hostname}" (${effectiveStatus(d)})`).join(', ')} ${blocked.length === 1 ? 'is' : 'are'} not online in CheckMK.`); - return; - } - const conflict = checkConflict(deviceIds, startDate, startTime, endDate, endTime); if (conflict.hasConflict) { alert(conflict.message); return; } @@ -225,10 +215,6 @@ export default function BookingCalendar({ }; const handleQuickBookDevice = (device: Device) => { - if (!isBookable(device)) { - alert(`"${device.hostname}" is ${effectiveStatus(device)} in CheckMK and cannot be reserved.`); - return; - } // Find or pick a lab that contains this device; fall back to device ID as labId marker const hostLab = labs.find(l => l.deviceIds.includes(device.id)); onAddBooking({ @@ -312,16 +298,22 @@ export default function BookingCalendar({
{quickTab === 'labs' ? ( availableLabs.length === 0 ? ( -

Nothing free and fully online for {quickDuration}h right now. all boxes either leased or not reporting in.

+

Nothing free for {quickDuration}h right now. All slots leased.

) : ( availableLabs.map(lab => { const labDevices = lab.deviceIds.map(id => devices.find(d => d.id === id)).filter(Boolean) as Device[]; + const offlineCount = labDevices.filter(d => !isOnline(d)).length; return (

{lab.name}

{lab.location} · {labDevices.length} device{labDevices.length !== 1 ? 's' : ''}

{labDevices.map(d => d.hostname).join(', ')}

+ {offlineCount > 0 && ( +

+ {offlineCount} device{offlineCount !== 1 ? 's' : ''} not reachable +

+ )}
- ) : !online ? ( - - {status} - ) : ( Busy )} @@ -597,7 +589,7 @@ export default function BookingCalendar({ > {devices.map((d) => ( ))} @@ -671,28 +663,31 @@ export default function BookingCalendar({ {(() => { const deviceIds = targetDeviceIds(); - const blocked = blockingDevices(deviceIds); + const offline = offlineDevices(deviceIds); const conflict = checkConflict(deviceIds, startDate, startTime, endDate, endTime); - if (blocked.length > 0) { + if (conflict.hasConflict) { + return ( +
+ + {conflict.message} +
+ ); + } + if (offline.length > 0) { return (
- Not bookable - {blocked.map(d => `${d.hostname} (${effectiveStatus(d)})`).join(', ')} {blocked.length === 1 ? 'is' : 'are'} not online in CheckMK. Hardware must be reachable before it can be reserved. + Warning – {offline.map(d => `${d.hostname} (${effectiveStatus(d)})`).join(', ')} {offline.length === 1 ? 'is' : 'are'} not reachable in CheckMK. Booking will still be created.
); } - return conflict.hasConflict ? ( -
- - {conflict.message} -
- ) : ( + return (
- Online & free. Timeframe is available. + Timeframe is available.
); })()} @@ -700,7 +695,6 @@ export default function BookingCalendar({ {(() => { const deviceIds = targetDeviceIds(); const disabled = deviceIds.length === 0 - || blockingDevices(deviceIds).length > 0 || checkConflict(deviceIds, startDate, startTime, endDate, endTime).hasConflict; return (