diff --git a/public/api/resource-groups/1/announcements.json b/public/api/resource-groups/1/announcements.json new file mode 100644 index 0000000..a3d63d4 --- /dev/null +++ b/public/api/resource-groups/1/announcements.json @@ -0,0 +1,9 @@ +{ + "announcements": [ + { + "type": "outage", + "description": "Bridges-2 compute, storage, and virtual machines will be unavailable on September 1, 2024 from 8:00 a.m. to 5:00 p.m. EDT for scheduled filesystem maintenance.", + "announcementUri": "https://www.psc.edu/resources/bridges-2/" + } + ] +} diff --git a/public/api/resource-groups/1/events.json b/public/api/resource-groups/1/events.json new file mode 100644 index 0000000..3512c67 --- /dev/null +++ b/public/api/resource-groups/1/events.json @@ -0,0 +1,12 @@ +{ + "events": [ + { + "title": "Bridges-2 Webinar: Scaling Up Ecological Monitoring with AI: How Supercomputing Is Unlocking the Value of Autonomous Acoustic Sensing", + "startDateTime": "2024-08-23T13:00:00-04:00", + "endDateTime": "2024-08-23T14:00:00-04:00", + "eventUri": "https://www.psc.edu/events/bridges-2-webinar-series/scaling-up-ecological-monitoring-with-ai/", + "speaker": "Sam Lapp, University of Pittsburgh", + "description": "Automated acoustic recording devices monitor wildlife at an unprecedented scale. Using hundreds of sensors, bioacoustic monitoring transforms our understanding of birds, bats, and frogs, aiding biodiversity management." + } + ] +} diff --git a/src/alert.css b/src/alert.css new file mode 100644 index 0000000..1e3e47c --- /dev/null +++ b/src/alert.css @@ -0,0 +1,12 @@ +.alert { + align-items: center; + background-color: var(--yellow-200); + border-radius: 10px; + display: flex; + flex-direction: row; + margin-bottom: 20px; + padding: 20px; +} +.alert i { + margin-right: 20px; +} diff --git a/src/alert.jsx b/src/alert.jsx new file mode 100644 index 0000000..2c46bcb --- /dev/null +++ b/src/alert.jsx @@ -0,0 +1,10 @@ +import Icon from "./icon"; + +export default function Alert({ children, icon = "megaphone-fill" }) { + return ( +
+ {icon ? : null} +
{children}
+
+ ); +} diff --git a/src/resource-catalog.jsx b/src/resource-catalog.jsx index 3bf364e..75a3d67 100644 --- a/src/resource-catalog.jsx +++ b/src/resource-catalog.jsx @@ -1,5 +1,6 @@ import { ErrorBoundary, lazy, LocationProvider, Router } from "preact-iso"; import accordionStyle from "./accordion.css?inline"; +import alertStyle from "./alert.css?inline"; import baseStyle from "./base.css?inline"; import componentsStyle from "./components.css?inline"; import contentStyle from "./content.css?inline"; @@ -11,6 +12,7 @@ import gridStyle from "./grid.css?inline"; import carouselStyle from "./carousel.css?inline"; import resourceFiltersStyle from "./resource-filters.css?inline"; import resourceGroupStyle from "./resource-group.css?inline"; +import resourceGroupEventStyle from "./resource-group-event.css?inline"; import searchStyle from "./search.css?inline"; import tagsStyle from "./tags.css?inline"; import tippyStyle from "tippy.js/dist/tippy.css?inline"; @@ -49,6 +51,7 @@ export function ResourceCatalog({ + @@ -57,6 +60,7 @@ export function ResourceCatalog({ + diff --git a/src/resource-group-detail.jsx b/src/resource-group-detail.jsx index 861054a..49eef6b 100644 --- a/src/resource-group-detail.jsx +++ b/src/resource-group-detail.jsx @@ -1,4 +1,5 @@ import ResourceGroupDescription from "./resource-group-description"; +import ResourceGroupEvents from "./resource-group-events"; import ResourceGroupQueueMetrics from "./resource-group-queue-metrics"; import ResourceGroupResources from "./resource-group-resources"; import ResourceGroupSoftware from "./resource-group-software"; @@ -14,6 +15,10 @@ export default function ResourceGroupDetail({ baseUri, resourceGroupId }) { baseUri={baseUri} resourceGroupId={resourceGroupId} /> + {title} : title; + const metadata = []; + let icon = null; + + if (startDateTime) { + const start = new Date(startDateTime); + const end = new Date(endDateTime || startDateTime); + const [startDate, endDate] = [start, end].map((date) => + date.toLocaleString("en-US", { dateStyle: "long" }) + ); + const [startTime, endTime] = [start, end].map((date) => + date.toLocaleString("en-US", { timeStyle: "short" }) + ); + const tz = start + .toLocaleTimeString("en-US", { timeZoneName: "short" }) + .split(" ")[2]; + const parts = [`${startDate},`, startTime]; + if (endTime != startTime || endDate != startDate) { + parts.push("-"); + if (endDate != startDate) parts.push(`${endDate},`); + parts.push(endTime); + } + parts.push(`(${tz})`); + metadata.push(, parts.join(" ")); + + const iconContent = start + .toLocaleString("en-US", { dateStyle: "medium" }) + .split(",")[0] + .split(" ") + .map((part) => {part}); + icon = eventUri ? ( + + {iconContent} + + ) : ( +
{iconContent}
+ ); + } + + if (speaker) metadata.push(, speaker); + + return ( +
+ {icon} +
+

{headingContent}

+ {metadata.length ? : null} + {description ?

{description}

: null} +
+
+ ); +} diff --git a/src/resource-group-events.jsx b/src/resource-group-events.jsx new file mode 100644 index 0000000..76b16dd --- /dev/null +++ b/src/resource-group-events.jsx @@ -0,0 +1,37 @@ +import { useJSON } from "./utils"; + +import Alert from "./alert"; +import Icon from "./icon"; +import ResourceGroupEvent from "./resource-group-event"; + +export default function ResourceGroupEvents({ baseUri, resourceGroupId }) { + const announcementData = useJSON( + `${baseUri}/api/resource-groups/${resourceGroupId}/announcements.json`, + null + ); + const eventData = useJSON( + `${baseUri}/api/resource-groups/${resourceGroupId}/events.json`, + null + ); + if (!announcementData && !eventData) return; + return ( + <> +

+ + Announcements and Events +

+ {announcementData && + announcementData.announcements.map( + ({ description, announcementUri }) => ( + + {description}{" "} + {announcementUri ? ( + Learn more. + ) : null} + + ) + )} + {eventData && eventData.events.map((event) => ResourceGroupEvent(event))} + + ); +}