Skip to content
Merged
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
15 changes: 15 additions & 0 deletions content/_data/constants.yml
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,27 @@ cookie_consent:
For estimating the runtime of a quantum job try out the FiQCI QPU resource estimator.
type: info
link: "https://fiqci.fi/resource-estimator"
- text: |-
Check out the new "Tools" section for FiQCI tools and example notebooks.
type: info
link: "#tools"

- text: |-
Aalto Q20 is currently down for maintenance.
type: warning
- text: |-
LUMI-Q VLQ will be down for scheduled maintenance from 4th to 25th of September.
type: warning
tools:
- name: FiQCI-EMS
description: Error mitigation service. Adds readout error mitigation, dynamical decoupling, Pauli twirling and zero-noise extrapolation to Qiskit workflows on IQM devices.
href: https://fiqci.fi/fiqci-ems
- name: QCut
description: Quantum circuit-knitting package. Cut large circuits into smaller subcircuits that fit on current devices and reconstruct the result.
href: https://fiqci.fi/QCut
- name: FiQCI examples
description: Example notebooks and scripts for running quantum jobs on FiQCI quantum computers through LUMI.
href: https://github.com/FiQCI/fiqci-examples
quantum-computers:
- name: VTT Q50
qubits: 53
Expand Down
117 changes: 95 additions & 22 deletions src/components/ServiceStatus.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import React, { useCallback, useMemo, useState } from 'react'

import { useStatus } from '../hooks/useStatus'
import { useBookings } from '../hooks/useBookings.jsx';
import { mdiInformation, mdiClose, mdiAlert, mdiRefresh } from '@mdi/js';
import { CCard, CCardTitle, CCardContent, CIcon, CButton, CSelect } from '@cscfi/csc-ui-react';
import { mdiInformation, mdiClose, mdiAlert, mdiRefresh, mdiArrowRight, mdiOpenInNew, mdiChevronLeft, mdiChevronRight } from '@mdi/js';
import { CCard, CCardTitle, CCardContent, CIcon, CIconButton, CButton, CSelect, CAccordion, CAccordionItem } from '@cscfi/csc-ui-react';
import { Swiper, SwiperSlide } from 'swiper/react';
import { A11y, Keyboard } from 'swiper';
import 'swiper/css';
import { prependBaseURL, isExternal } from '../utils/url';
import { StatusModal } from './StatusModal/StatusModal';
import { BookingModal } from './bookingCalendar.jsx';
import { API_BASE_URL } from '../config/api.js';
Expand Down Expand Up @@ -45,6 +49,63 @@ const StatusCard = (props) => {
)
}

const ToolCard = ({ name, description, href }) => (
<a href={prependBaseURL(href)} className='group block h-full'>
<CCard className='border-[0.2px] border-gray-100 rounded-none shadow-md group-hover:shadow-xl h-full'>
<CCardTitle className='font-bold text-on-white text-[18px]'>
<p>{name}</p>
</CCardTitle>
<CCardContent className='text-on-white flex flex-col justify-between gap-4 text-[14px]'>
<p>{description}</p>
<span className='text-sky-800 font-bold flex items-center gap-1 group-hover:underline'>
Documentation <CIcon path={isExternal(href) ? mdiOpenInNew : mdiArrowRight} />
</span>
</CCardContent>
</CCard>
</a>
)

const ToolCarousel = ({ tools }) => {
const [swiper, setSwiper] = useState(null);
const [edges, setEdges] = useState({ isBeginning: true, isEnd: true, isLocked: true });

const updateEdges = useCallback(({ isBeginning, isEnd, isLocked }) => {
setEdges({ isBeginning, isEnd, isLocked });
}, []);

return (
<div className='flex flex-col gap-2'>
<Swiper
modules={[A11y, Keyboard]}
// Padding leaves room for the card shadows (Swiper clips overflow). The negative side margins stay within the
// outlined accordion's 16px content padding so the Swiper doesn't cover its 2px outline.
className='!-mx-[14px] !px-5 !pt-2 !pb-10 !-mb-8'
spaceBetween={24}
slidesPerView={1}
breakpoints={{ 640: { slidesPerView: 2 }, 1024: { slidesPerView: 3 } }}
keyboard={{ enabled: true, onlyInViewport: true }}
onSwiper={setSwiper}
onAfterInit={updateEdges}
onSlideChange={updateEdges}
onResize={updateEdges}
>
{tools.map(tool => (
<SwiperSlide key={tool.name} className='!h-auto'>
<ToolCard {...tool} />
</SwiperSlide>
))}
</Swiper>
{!edges.isLocked && (
// relative z-10 keeps the buttons above the Swiper's shadow padding, which overlaps this row
<div className='relative z-10 flex justify-start gap-2'>
<CIconButton ghost size='small' path={mdiChevronLeft} aria-label='Previous tools' disabled={edges.isBeginning} onClick={() => swiper?.slidePrev()} />
<CIconButton ghost size='small' path={mdiChevronRight} aria-label='Next tools' disabled={edges.isEnd} onClick={() => swiper?.slideNext()} />
</div>
)}
</div>
);
}

export const ServiceStatus = (props) => {
const { status: statusList, loading: statusLoading, refetch: refetchStatus } = useStatus(`${API_BASE_URL}/devices/healthcheck`);
const { bookingData: bookingData } = useBookings(`${API_BASE_URL}/bookings`)
Expand All @@ -53,20 +114,20 @@ export const ServiceStatus = (props) => {
const devicesWithStatus = (qcs.length === 0 || !Array.isArray(statusList))
? qcs
: qcs.map(device => {
const deviceStatus = statusList.find(({ name }) => name === device.device_id);

if (device.name == "Aalto Q20") { // TEMP offline switch for Aalto Q20 until it is back online
return {
...device,
health: false,
};
}
const deviceStatus = statusList.find(({ name }) => name === device.device_id);

if (device.name == "Aalto Q20") { // TEMP offline switch for Aalto Q20 until it is back online
return {
...device,
health: deviceStatus?.health ?? false,
health: false,
};
});

}
return {
...device,
health: deviceStatus?.health ?? false,
};
});

const [bookingModalOpen, setBookingModalOpen] = useState(false)
const [modalOpen, setModalOpen] = useState(false);
const [modalProps, setModalProps] = useState({});
Expand Down Expand Up @@ -103,6 +164,7 @@ export const ServiceStatus = (props) => {
};
// Support both a single `alert` object and a list of `alerts`.
const alerts = props.alerts ?? (props.alert ? [props.alert] : []);
const tools = props.tools || [];

return (
<div className="flex gap-6 flex-col sm:flex-col items-stretch text-on-white">
Expand All @@ -120,15 +182,30 @@ export const ServiceStatus = (props) => {
))}
</div>
)}
<div className='pt-[24px] flex flex-col gap-6 mb-6 justify-start'>
<div className='pt-[24px] flex flex-col gap-6 mb-0 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>


<div className='pt-[24px] flex flex-col gap-6 mb-0 justify-start'>
<h2 className='text-on-white'>Tools</h2>
<p className='text-[16px] pb-0'>
FiQCI provides software to help you get more out of the quantum computers.
</p>
{tools.length > 0 && (
<CAccordion id="tools" outlined className='mb-6'>
<CAccordionItem heading={`Software tools (click to expand)`} value="tools">
<ToolCarousel tools={tools} />
</CAccordionItem>
</CAccordion>
)}
</div>


<div className='flex flex-col sm:flex-row gap-4 sm:gap-20'>
<h2 className='text-on-white'>Devices</h2>
<CSelect
Expand Down Expand Up @@ -162,10 +239,6 @@ export const ServiceStatus = (props) => {
{sortedDevices.map((qc, index) => (
<StatusCard key={qc.device_id || index} {...qc} statusLoading={statusLoading} onClick={() => handleCardClick(qc)} />
))}




</div>
{bookingModalOpen && (
<BookingModal bookingData={bookingData} name={"Reservations"} isModalOpen={bookingModalOpen} setIsModalOpen={setBookingModalOpen} />
Expand All @@ -180,8 +253,8 @@ export const ServiceStatus = (props) => {
setIsModalOpen={setModalOpen}
/>
)}


</div>
);
}
Expand Down
Loading