From 95255a7470fde5cb95aba7f86f446f30d55f962a Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 23 Feb 2026 16:18:31 +0200 Subject: [PATCH 1/7] move base url to config --- src/config/api.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/config/api.js diff --git a/src/config/api.js b/src/config/api.js new file mode 100644 index 000000000000..d085c2efa315 --- /dev/null +++ b/src/config/api.js @@ -0,0 +1 @@ +export const API_BASE_URL = "http://localhost:3000" //"https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file From 5cdc68f1d4f94cbd8e58be2e1bfbd12544666707 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 23 Feb 2026 16:18:54 +0200 Subject: [PATCH 2/7] add state "booked" to status check --- src/components/ServiceStatus.jsx | 75 +++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index d21af3be8158..74f1ac9bbf39 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { useStatus } from '../hooks/useStatus' import { useBookings } from '../hooks/useBookings.jsx'; @@ -6,6 +6,9 @@ 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 { API_BASE_URL } from '../config/api'; const StatusCard = (props) => { const isOnline = props.health; @@ -24,14 +27,20 @@ const StatusCard = (props) => {
Service status: - {isOnline ? ( -
-

Online

+ {(props.device_status === "booked") ? ( +
+

Booked

) : ( -
-

Offline

-
+ isOnline ? ( +
+

Online

+
+ ) : ( +
+

Offline

+
+ ) )}
@@ -40,25 +49,51 @@ 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 { status: statusList } = useStatus(`${API_BASE_URL}/devices/healthcheck`); + const { bookingData: bookingData } = useBookings(`${API_BASE_URL}/bookings`) const qcs = props["quantum-computers"] || []; - const devicesWithStatus = (qcs.length === 0 || !Array.isArray(statusList)) - ? qcs - : qcs.map(device => { + const [device_status_list, setDeviceStatusList] = useState([]); + const [devicesWithStatus, setDevicesWithStatus] = useState([]); + + useEffect(() => { + // Compute devicesWithStatus inside the effect + 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, }; }); - + + setDevicesWithStatus(devicesWithStatus); + + if (!devicesWithStatus || devicesWithStatus.length === 0) { + setDeviceStatusList([]); + return; + } + + const deviceStatusList = devicesWithStatus.map(async device => { + const url = `${API_BASE_URL}/device/${device.device_id.toLowerCase()}`; + const data = await fetch(url) + .then(resp => resp.json()) + .then(result => result?.data || {}) + return { ...device, device_status: data.status }; + }); + Promise.all(deviceStatusList).then(results => { + setDeviceStatusList(results); + }); + }, [qcs, statusList]); + + + const [bookingModalOpen, setBookingModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false); const [modalProps, setModalProps] = useState({}); const handleCardClick = (qc) => { - setModalProps({ ...qc, devicesWithStatus }); + setModalProps({ ...qc, devicesWithStatus: device_status_list }); setModalOpen(true); }; // Determine alert color based on props.alert.type @@ -94,19 +129,19 @@ export const ServiceStatus = (props) => {

Reservations

- 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.

setBookingModalOpen(true)}>View Reservations
- +

Devices

- {devicesWithStatus.map((qc, index) => ( + {(device_status_list.length > 0 ? device_status_list : devicesWithStatus).map((qc, index) => ( handleCardClick(qc)} /> ))} - - + +
{bookingModalOpen && ( @@ -115,7 +150,7 @@ export const ServiceStatus = (props) => { {modalOpen && ( )} - +
); } From 61e76e8e51837f674c93fda26f8dadabb8494494 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 23 Feb 2026 16:19:06 +0200 Subject: [PATCH 3/7] add "booked" state --- src/components/StatusModal/DeviceStatus.jsx | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/components/StatusModal/DeviceStatus.jsx b/src/components/StatusModal/DeviceStatus.jsx index d14eefc742fe..a6bdc92ad509 100644 --- a/src/components/StatusModal/DeviceStatus.jsx +++ b/src/components/StatusModal/DeviceStatus.jsx @@ -12,18 +12,21 @@ export const DeviceStatus = (props) => {

Topology: {deviceData.topology}

-
- Service status: - {devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.health ? ( -
-

Online

-
- ) : ( -
-

Offline

-
- )} + {(devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.device_status === "booked") ? ( +
+

Booked

+ ) : ( + devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.health ? ( +
+

Online

+
+ ) : ( +
+

Offline

+
+ ) + )} ) From dab31eee3765afdebb3a3ec58c592aa22ae8a88d Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 23 Feb 2026 16:19:41 +0200 Subject: [PATCH 4/7] use cntral api base url --- src/components/StatusModal/StatusModalConent.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/StatusModal/StatusModalConent.jsx b/src/components/StatusModal/StatusModalConent.jsx index 7683e2f16a6c..d8cd514b429c 100644 --- a/src/components/StatusModal/StatusModalConent.jsx +++ b/src/components/StatusModal/StatusModalConent.jsx @@ -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'); From cccba6f01f35442ac6663e502c9c2e7565916752 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 23 Feb 2026 16:38:41 +0200 Subject: [PATCH 5/7] actual url --- src/config/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/api.js b/src/config/api.js index d085c2efa315..eb4263c5bc0c 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -1 +1 @@ -export const API_BASE_URL = "http://localhost:3000" //"https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file +export const API_BASE_URL = "https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file From de1aca530208f2f943d643582acba097568ccc27 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 25 Feb 2026 12:09:37 +0200 Subject: [PATCH 6/7] streamline new status options --- src/components/ServiceStatus.jsx | 100 +++++++++++++------- src/components/StatusModal/DeviceStatus.jsx | 53 ++++++----- 2 files changed, 93 insertions(+), 60 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index 74f1ac9bbf39..9a66d9b36ea2 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import { useStatus } from '../hooks/useStatus' import { useBookings } from '../hooks/useBookings.jsx'; @@ -7,12 +7,16 @@ import { CCard, CCardTitle, CCardContent, CIcon, CButton } from '@cscfi/csc-ui-r 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"] + return ( @@ -27,9 +31,9 @@ const StatusCard = (props) => {
Service status: - {(props.device_status === "booked") ? ( + {(props.device_status && !status_options.includes(props.device_status)) ? (
-

Booked

+

{capitalizeFirstLetter(props.device_status)}

) : ( isOnline ? ( @@ -51,49 +55,68 @@ const StatusCard = (props) => { export const ServiceStatus = (props) => { const { status: statusList } = useStatus(`${API_BASE_URL}/devices/healthcheck`); const { bookingData: bookingData } = useBookings(`${API_BASE_URL}/bookings`) - const qcs = props["quantum-computers"] || []; + const qcs = Array.isArray(props["quantum-computers"]) ? props["quantum-computers"] : []; + const qcsKey = useMemo( + () => qcs.map(d => d?.device_id?.toLowerCase()).filter(Boolean).sort().join('|'), + [qcs] + ); const [device_status_list, setDeviceStatusList] = useState([]); - const [devicesWithStatus, setDevicesWithStatus] = useState([]); - useEffect(() => { - // Compute devicesWithStatus inside the effect - 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 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, + }; + }); - setDevicesWithStatus(devicesWithStatus); + useEffect(() => { + let cancelled = false; - if (!devicesWithStatus || devicesWithStatus.length === 0) { - setDeviceStatusList([]); - return; - } + if (!qcs.length) { + setDeviceStatusList({}); + return; + } - const deviceStatusList = devicesWithStatus.map(async device => { - const url = `${API_BASE_URL}/device/${device.device_id.toLowerCase()}`; - const data = await fetch(url) - .then(resp => resp.json()) - .then(result => result?.data || {}) - return { ...device, device_status: data.status }; - }); - Promise.all(deviceStatusList).then(results => { - setDeviceStatusList(results); - }); - }, [qcs, statusList]); + // reset for current device set + setDeviceStatusList({}); + + qcs.forEach((device) => { + const id = device.device_id.toLowerCase(); + fetch(`${API_BASE_URL}/device/${id}`) + .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; + 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: device_status_list }); + + 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()] } : d); + + 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 @@ -137,8 +160,15 @@ export const ServiceStatus = (props) => {

Devices

- {(device_status_list.length > 0 ? device_status_list : devicesWithStatus).map((qc, index) => ( - handleCardClick(qc)} /> + {devicesWithStatus.map((qc, index) => ( + handleCardClick(qc)} + /> ))} diff --git a/src/components/StatusModal/DeviceStatus.jsx b/src/components/StatusModal/DeviceStatus.jsx index a6bdc92ad509..7172d7a641d0 100644 --- a/src/components/StatusModal/DeviceStatus.jsx +++ b/src/components/StatusModal/DeviceStatus.jsx @@ -1,33 +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 ( - <> -
-

Qubits: {deviceData.qubits}

-

Basis gates: {deviceData.basis}

-

Topology: {deviceData.topology}

-
+ const status_options = ["offline", "online", "healthy"] - {(devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.device_status === "booked") ? ( -
-

Booked

-
- ) : ( - devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.health ? ( -
-

Online

-
- ) : ( -
-

Offline

-
- ) - )} - - ) + return ( + <> +
+

Qubits: {deviceData.qubits}

+

Basis gates: {deviceData.basis}

+

Topology: {deviceData.topology}

+
+ + {(devicesWithStatus.device_status && !status_options.includes(devicesWithStatus.device_status)) ? ( +
+

{capitalizeFirstLetter(devicesWithStatus.device_status)}

+
+ ) : ( + devicesWithStatus.health ? ( +
+

Online

+
+ ) : ( +
+

Offline

+
+ ) + )} + + ) } \ No newline at end of file From 3402159ab80d45247901c05e106e69ea32b8d732 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 25 Feb 2026 12:14:40 +0200 Subject: [PATCH 7/7] handle undefined --- src/components/ServiceStatus.jsx | 6 +++--- src/components/StatusModal/DeviceStatus.jsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index 9a66d9b36ea2..b692f355d295 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -15,7 +15,7 @@ const StatusCard = (props) => { const isOnline = props.health; const { onClick, ...rest } = props - const status_options = ["offline", "online", "healthy"] + const status_options = ["offline", "online", "healthy", "unknown"] return ( @@ -114,7 +114,7 @@ export const ServiceStatus = (props) => { const [modalProps, setModalProps] = useState({}); const handleCardClick = (qc) => { - 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()] } : d); + 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); setModalProps({ ...qc, devicesWithStatus: add_device_status.find(d => d.device_id.toLowerCase() === qc.device_id.toLowerCase()) }); setModalOpen(true); @@ -165,7 +165,7 @@ export const ServiceStatus = (props) => { key={qc.device_id || index} {...{ ...qc, - device_status: device_status_list[qc.device_id.toLowerCase()] + device_status: device_status_list?.[qc.device_id.toLowerCase()] || "unknown" }} onClick={() => handleCardClick(qc)} /> diff --git a/src/components/StatusModal/DeviceStatus.jsx b/src/components/StatusModal/DeviceStatus.jsx index 7172d7a641d0..45b9c3a182cf 100644 --- a/src/components/StatusModal/DeviceStatus.jsx +++ b/src/components/StatusModal/DeviceStatus.jsx @@ -5,7 +5,7 @@ export const DeviceStatus = (props) => { const { deviceData, devicesWithStatus } = props; - const status_options = ["offline", "online", "healthy"] + const status_options = ["offline", "online", "healthy", "unknown"] return ( <>