Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 88 additions & 23 deletions src/components/ServiceStatus.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import React, { useState } from 'react'
import React, { useEffect, useMemo, useState } from 'react'

import { useStatus } from '../hooks/useStatus'
import { useBookings } from '../hooks/useBookings.jsx';
import { mdiInformation, mdiClose, mdiAlert } from '@mdi/js';
import { CCard, CCardTitle, CCardContent, CIcon, CButton } from '@cscfi/csc-ui-react';
import { StatusModal } from './StatusModal/StatusModal';
import { BookingModal } from './bookingCalendar.jsx';
import { set } from 'date-fns';
import { capitalizeFirstLetter } from '../utils/textUtils.js';

import { API_BASE_URL } from '../config/api';

const StatusCard = (props) => {
const isOnline = props.health;
const { onClick, ...rest } = props

const status_options = ["offline", "online", "healthy", "unknown"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the comment above


return (
<CCard onClick={onClick} className='border-[0.2px] border-gray-100 rounded-none shadow-md hover:shadow-xl col-span-1 h-[236px]'>
<CCardTitle className='font-bold text-on-white text-[18px]'>
Expand All @@ -24,14 +31,20 @@ const StatusCard = (props) => {

<div className='flex flex-col gap-0 text-[14px]'>
<strong>Service status:</strong>
{isOnline ? (
<div className='text-center text-[#204303] bg-[#B9DC9C] border-[0.5px] border-[#204303] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>Online</p>
{(props.device_status && !status_options.includes(props.device_status)) ? (
<div className='text-center text-[#ae4000] bg-[#ffb66d] border-[0.5px] border-[#ae4000] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>{capitalizeFirstLetter(props.device_status)}</p>
</div>
) : (
<div className='text-center text-[#7E0707] bg-[#F8CECE] border-[0.5px] border-[#7E0707] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>Offline</p>
</div>
isOnline ? (
<div className='text-center text-[#204303] bg-[#B9DC9C] border-[0.5px] border-[#204303] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>Online</p>
</div>
) : (
<div className='text-center text-[#7E0707] bg-[#F8CECE] border-[0.5px] border-[#7E0707] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>Offline</p>
</div>
)
)}
</div>
</CCardContent>
Expand All @@ -40,25 +53,70 @@ const StatusCard = (props) => {
}

export const ServiceStatus = (props) => {
const { status: statusList } = useStatus("https://fiqci-backend.2.rahtiapp.fi/devices/healthcheck");
const { bookingData: bookingData } = useBookings("https://fiqci-backend.2.rahtiapp.fi/bookings")
const qcs = props["quantum-computers"] || [];
const { status: statusList } = useStatus(`${API_BASE_URL}/devices/healthcheck`);
const { bookingData: bookingData } = useBookings(`${API_BASE_URL}/bookings`)
const qcs = Array.isArray(props["quantum-computers"]) ? props["quantum-computers"] : [];
const qcsKey = useMemo(
() => qcs.map(d => d?.device_id?.toLowerCase()).filter(Boolean).sort().join('|'),
Copy link
Collaborator

@Modupef Modupef Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is d in this case? Maybe device or dev instead of d. It's clearer

[qcs]
);

const [device_status_list, setDeviceStatusList] = useState([]);

const devicesWithStatus = (qcs.length === 0 || !Array.isArray(statusList))
? qcs
: qcs.map(device => {
const deviceStatus = statusList.find(({ name }) => name === device.device_id);
return {
...device,
health: deviceStatus?.health ?? false,
};
const deviceStatus = statusList.find(({ name }) => name === device.device_id);
return {
...device,
health: deviceStatus?.health ?? false,
};
});

useEffect(() => {
let cancelled = false;

if (!qcs.length) {
setDeviceStatusList({});
return;
}

// reset for current device set
setDeviceStatusList({});

qcs.forEach((device) => {
const id = device.device_id.toLowerCase();
fetch(`${API_BASE_URL}/device/${id}`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why fetch for each. why not fetch everything and the filter based on the id?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetching separately because the backend currently has no endpoint for fetching all the devices. Would probably be smart to add one tho

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's pretty easy to add since we are fetching the list once and then filtering on the backend. We can just add a route to return all or update the controller to return all if no device id is given, otherwise return info for the given device

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup

.then((resp) => resp.json())
.then((result) => result?.data?.status)
.then((status) => {
if (cancelled) return;
setDeviceStatusList((prev) =>
prev[id] === status ? prev : { ...prev, [id]: status }
);
})
.catch(() => {
if (cancelled) return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this catch block trying to do?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be redundant now. Lefover from testing and problems with the useEffect running infinitely

setDeviceStatusList((prev) =>
prev[id] === 'unknown' ? prev : { ...prev, [id]: 'unknown' }
);
});

});

return () => {
cancelled = true;
};
}, [qcsKey]);


const [bookingModalOpen, setBookingModalOpen] = useState(false)
const [modalOpen, setModalOpen] = useState(false);
const [modalProps, setModalProps] = useState({});
const handleCardClick = (qc) => {
setModalProps({ ...qc, devicesWithStatus });

const add_device_status = devicesWithStatus.map(d => d.device_id.toLowerCase() === qc.device_id.toLowerCase() ? { ...d, device_status: device_status_list?.[qc.device_id.toLowerCase()] || "unknown" } : d);
Copy link
Collaborator

@Modupef Modupef Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also maybe dev or device instead of d? dev for example immediately communicates that the variable is a device. e.g using var instead of using v


setModalProps({ ...qc, devicesWithStatus: add_device_status.find(d => d.device_id.toLowerCase() === qc.device_id.toLowerCase()) });
setModalOpen(true);
};
// Determine alert color based on props.alert.type
Expand Down Expand Up @@ -94,19 +152,26 @@ export const ServiceStatus = (props) => {
<div className='pt-[24px] flex flex-col gap-6 mb-6 justify-start'>
<h2 className='text-on-white'>Reservations</h2>
<p>
VTT devices can at times be reserved. At these times the queue will be paused.
VTT devices can at times be reserved. At these times the queue will be paused.
Reservations can be viewed from this calendar. Note that making reservations through FiQCI is not currently possible.
</p>
<CButton className='w-32' onClick={() => setBookingModalOpen(true)}>View Reservations</CButton>
</div>

<h2 className='text-on-white'>Devices</h2>
<div className='pb-[60px] grid grid-cols-1 min-[450px]:grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 min-[2600px]:grid-cols-4 w-full gap-[24px]'>
{devicesWithStatus.map((qc, index) => (
<StatusCard key={qc.device_id || index} {...qc} onClick={() => handleCardClick(qc)} />
<StatusCard
key={qc.device_id || index}
{...{
...qc,
device_status: device_status_list?.[qc.device_id.toLowerCase()] || "unknown"
}}
onClick={() => handleCardClick(qc)}
/>
))}


</div>
{bookingModalOpen && (
<BookingModal bookingData={bookingData} name={"Reservations"} isModalOpen={bookingModalOpen} setIsModalOpen={setBookingModalOpen} />
Expand All @@ -115,7 +180,7 @@ export const ServiceStatus = (props) => {
{modalOpen && (
<StatusModal {...modalProps} isModalOpen={modalOpen} setIsModalOpen={setModalOpen} />
)}

</div>
);
}
Expand Down
50 changes: 28 additions & 22 deletions src/components/StatusModal/DeviceStatus.jsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import React from 'react';
import { capitalizeFirstLetter } from '../../utils/textUtils.js';

export const DeviceStatus = (props) => {

const { deviceData, devicesWithStatus } = props;
const { deviceData, devicesWithStatus } = props;

return (
<>
<div className="flex flex-col justify-start text-[14px]">
<p className="pb-2"><strong>Qubits:</strong> {deviceData.qubits}</p>
<p className="pb-2"><strong>Basis gates:</strong> {deviceData.basis}</p>
<p className="pb-2"><strong>Topology:</strong> {deviceData.topology}</p>
</div>
const status_options = ["offline", "online", "healthy", "unknown"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what cases would the device be healthy and not online? Or what do these different status represent?

Copy link
Collaborator

@Modupef Modupef Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe :["offline", "online", "maintenance", "booked", "unknown"] instead?


<div className='flex flex-col gap-0 text-[14px] col-span-1 pb-8'>
<strong>Service status:</strong>
{devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.health ? (
<div className='text-center text-[#204303] bg-[#B9DC9C] border-[0.5px] border-[#204303] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>Online</p>
</div>
) : (
<div className='text-center text-[#7E0707] bg-[#F8CECE] border-[0.5px] border-[#7E0707] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>Offline</p>
</div>
)}
</div>
</>
)
return (
<>
<div className="flex flex-col justify-start text-[14px]">
<p className="pb-2"><strong>Qubits:</strong> {deviceData.qubits}</p>
<p className="pb-2"><strong>Basis gates:</strong> {deviceData.basis}</p>
<p className="pb-2"><strong>Topology:</strong> {deviceData.topology}</p>
</div>

{(devicesWithStatus.device_status && !status_options.includes(devicesWithStatus.device_status)) ? (
<div className='text-center text-[#ae4000] bg-[#ffb66d] border-[0.5px] border-[#ae4000] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>{capitalizeFirstLetter(devicesWithStatus.device_status)}</p>
</div>
) : (
devicesWithStatus.health ? (
<div className='text-center text-[#204303] bg-[#B9DC9C] border-[0.5px] border-[#204303] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>Online</p>
</div>
) : (
<div className='text-center text-[#7E0707] bg-[#F8CECE] border-[0.5px] border-[#7E0707] rounded-[100px] w-[88px] h-[25px]'>
<p className='font-bold text-[14px]'>Offline</p>
</div>
)
)}
</>
)

}
6 changes: 4 additions & 2 deletions src/components/StatusModal/StatusModalConent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import {
import { object } from 'framer-motion/client';
import { getDeviceMetricsConfig } from '../../config/deviceMetrics';

import { API_BASE_URL } from '../../config/api';

export const ModalContent = (props) => {

const { calibrationData: calibrationDataAll, calibrationError } = useCalibration(`https://fiqci-backend.2.rahtiapp.fi/device/${props.device_id.toLowerCase()}/calibration`)
const { deviceInfo: deviceInfoData, infoError } = useDeviceInfo(`https://fiqci-backend.2.rahtiapp.fi/device/${props.device_id.toLowerCase()}`)
const { calibrationData: calibrationDataAll, calibrationError } = useCalibration(`${API_BASE_URL}/device/${props.device_id.toLowerCase()}/calibration`)
const { deviceInfo: deviceInfoData, infoError } = useDeviceInfo(`${API_BASE_URL}/device/${props.device_id.toLowerCase()}`)

const [activeTab, setActiveTab] = useState('overview');

Expand Down
1 change: 1 addition & 0 deletions src/config/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const API_BASE_URL = "https://fiqci-backend.2.rahtiapp.fi";