From 966cfee44e974bef429fa189d697bbd11957d9af Mon Sep 17 00:00:00 2001 From: Yaxue Guo <37635744+yaxue1123@users.noreply.github.com> Date: Wed, 22 Feb 2023 13:24:28 -0500 Subject: [PATCH] Mockup/site page (#247) * #238: added mock up page for site details * #244: updated the yellow tera core ring * #244: added site HAWI and connections * #244: updated site name mapping * #233: updated map and detail table to show Maintenance node * #233: completed site maintenance info display on resources page and site detail page * #233: added site detail page link to Slice Builder * #233: updated portal release number * #233: updated link to resources page on Slice Builder with site param * #244: updated international links --- package-lock.json | 2 +- package.json | 2 +- src/App.js | 2 + src/components/Experiment/Slices.jsx | 4 +- src/components/Resource/DetailTable.jsx | 88 +++++++++++-- src/components/Resource/SiteDetailPage.jsx | 124 ++++++++++++++++++ src/components/Resource/SummaryTable.jsx | 31 ++++- src/components/Resource/Topomap.jsx | 24 ++-- src/components/Slice/SlicesTable.jsx | 6 +- src/components/SliceViewer/SideNodes.jsx | 77 +++++++---- src/components/common/ProgressBar.jsx | 10 +- src/data/sites.js | 12 +- src/data/topomap.js | 32 +++-- src/pages/Home.jsx | 14 +- src/pages/Resources.jsx | 32 ++++- src/pages/SliceViewer.jsx | 4 +- src/services/mockData/fakeResources.js | 14 +- src/services/parser/sitesParser.js | 34 ++++- src/services/portalData.json | 3 +- src/styles/custom.scss | 3 +- ...eTimeParser.js => utcToLocalTimeParser.js} | 2 +- 21 files changed, 438 insertions(+), 82 deletions(-) create mode 100644 src/components/Resource/SiteDetailPage.jsx rename src/utils/{sliceTimeParser.js => utcToLocalTimeParser.js} (86%) diff --git a/package-lock.json b/package-lock.json index 85ab0cfc..d566e205 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12469,7 +12469,7 @@ "fast-json-stable-stringify": "^2.1.0", "pretty-bytes": "^5.4.1", "upath": "^1.2.0", - "webpack-sources": "^1.4.3", + "webpack-sources": "^1.4.4", "workbox-build": "6.5.2" }, "dependencies": { diff --git a/package.json b/package.json index c1683835..3379b4f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fabric-portal", - "version": "1.4.3", + "version": "1.4.4", "private": true, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.32", diff --git a/src/App.js b/src/App.js index 9cce0150..19743132 100644 --- a/src/App.js +++ b/src/App.js @@ -16,6 +16,7 @@ import SliceViewer from "./pages/SliceViewer"; import NewSliceForm from "./pages/NewSliceForm"; import User from "./pages/User"; import PublicUserProfile from "./components/UserProfile/PublicUserProfile.jsx"; +import SiteDetailPage from "./components/Resource/SiteDetailPage.jsx"; import NotFound from "./pages/NotFound"; import Help from "./pages/Help"; import Header from "./components/Header"; @@ -123,6 +124,7 @@ class App extends React.Component { + diff --git a/src/components/Experiment/Slices.jsx b/src/components/Experiment/Slices.jsx index 25c2114d..e0c3317c 100644 --- a/src/components/Experiment/Slices.jsx +++ b/src/components/Experiment/Slices.jsx @@ -190,7 +190,7 @@ class Slices extends React.Component {

- You have no slice in this project. Please create slices in Portal or  + You have no slices in this project. Please create slices in Portal or  :

- We couldn't find your slice. Please create slices in Portal or   + We couldn't find your slices. Please create slices in Portal or   { +const generateProgressBar = (total, free, color, labelColor) => { return ( 0 ? Math.round(free * 100/ total) : 0} label={`${free}/${total}`} + color={color} + labelColor={labelColor} /> ) } const DetailTable = props => { - const {name, resource} = props; + const {name, resource, parent} = props; const rows = [ ["Cores", "totalCore", "freeCore"], ["Disk (GB)", "totalDisk", "freeDisk"], @@ -22,13 +25,69 @@ const DetailTable = props => { ["SharedNIC", "totalSharedNIC", "freeSharedNIC"], ["FPGA", "totalFPGA", "freeFPGA"], ] + + const statusMapping = { + "Maint": { + state: "Maintenance", + colorName: "danger", + colorHex: "#b00020", + labelColorHex: "#fff" + }, + "PreMaint": { + state: "Pre-Maintenance", + colorName: "info", + colorHex: "#ffb670", + labelColorHex: "#212529" + }, + "Active": { + state: "Active", + colorName: "primary", + colorHex: "#68b3d1", + labelColorHex: "#212529" + } + } + return (

@@ -37,11 +96,19 @@ const DetailTable = props => { { @@ -50,7 +117,12 @@ const DetailTable = props => { ) diff --git a/src/components/Resource/SiteDetailPage.jsx b/src/components/Resource/SiteDetailPage.jsx new file mode 100644 index 00000000..2f255fb2 --- /dev/null +++ b/src/components/Resource/SiteDetailPage.jsx @@ -0,0 +1,124 @@ +import React from "react"; +import DetailTable from "./DetailTable"; +import { sitesNameMapping } from "../../data/sites"; +import utcToLocalTimeParser from "../../utils/utcToLocalTimeParser.js"; +import { default as portalData } from "../../services/portalData.json"; +import { Link } from "react-router-dom"; + +const SiteDetailPage = props => { + const statusMapping = { + "Maint": { + state: "Maintenance", + color: "warning", + explanation: "no requests would be accepted" + }, + "PreMaint": { + state: "Pre-Maintenance", + color: "warning", + explanation: "requests will be expected until the deadline" + }, + "Active": { + state: "Active", + color: "primary", + explanation: "" + + } + } + + const { state } = props.location; + + return ( +
+
+

Site - {state.siteData.name}

+ + + +
+ { + ["Maint", "PreMaint"].includes(state.siteData.status["state"]) && +
+ + Please check the + + FABRIC Announcements Forum + for more detailed site maintenance information. +
+ } +
+

Basic Information

+
- {`${sitesNameMapping.shortNameToAcronym[name]} (${sitesNameMapping.shortToLongName[name]})`} + { + !resource && parent !== "sitepage" && + `${sitesNameMapping.shortNameToAcronym[name]} (${sitesNameMapping.shortToLongName[name]})` + } + { + !resource && parent === "sitepage" && + `${sitesNameMapping.acronymToShortName[name]} (${sitesNameMapping.acronymToLongName[name]})` + } + { + resource && parent !== "sitepage" && + + {`${sitesNameMapping.shortNameToAcronym[name]} (${sitesNameMapping.shortToLongName[name]})`} + + } + { + resource && parent === "sitepage" && sitesNameMapping.acronymToShortName[name] && + + {`${sitesNameMapping.acronymToShortName[name]} (${sitesNameMapping.acronymToLongName[name]})`} + + } + { + resource && parent === "sitepage" && !sitesNameMapping.acronymToShortName[name] && name + }
Status { - resource ? ( - Up - ) : (Down) + !resource && + + Down + + } + { + resource && + + { statusMapping[resource.status.state].state } + } -
{row[0]} - {generateProgressBar(resource[row[1]], resource[row[2]])} + { + generateProgressBar(resource[row[1]], + resource[row[2]], + statusMapping[resource.status.state].colorHex, + statusMapping[resource.status.state].labelColorHex) + }
+ + { + sitesNameMapping.acronymToShortName[state.siteData.name] && + + + + + } + + + + + + + + + { + state.siteData.status["state"] === "Maint" && + + + + + } + { + state.siteData.status["state"] === "PreMaint" && + + + + + } + { + state.siteData.location && + + + + + } + +
Name{ sitesNameMapping.acronymToShortName[state.siteData.name] }
Acronym{ state.siteData.name }
Status + { + state.siteData.status["state"] !== "Active" ? + `${statusMapping[state.siteData.status["state"]].state} (${statusMapping[state.siteData.status["state"]].explanation})` : + statusMapping[state.siteData.status["state"]].state + } +
Expected End Time + { + state.siteData.status["expected_end"] ? + utcToLocalTimeParser(state.siteData.status["expected_end"]) : "Unknown" + } +
Deadline + { + state.siteData.status["deadline"] ? + utcToLocalTimeParser(state.siteData.status["deadline"]) : "Unknown" + } +
Rack Location{ JSON.parse(state.siteData.location).postal }
+
+
+

Resource Availabilities

+ +
+
+ ) +} + +export default SiteDetailPage; \ No newline at end of file diff --git a/src/components/Resource/SummaryTable.jsx b/src/components/Resource/SummaryTable.jsx index 04ffa2e9..550820f2 100644 --- a/src/components/Resource/SummaryTable.jsx +++ b/src/components/Resource/SummaryTable.jsx @@ -1,10 +1,39 @@ import React, { Component } from "react"; import Table from "../common/Table"; +import { Link } from "react-router-dom"; class SummaryTable extends Component { columns = [ { - path: "name", + content: (resource) => ( +
+ + {resource.name} + + { + resource.status && resource.status.state === "Maint" && +
+ + Maintenance + +
+ } + { + resource.status && resource.status.state === "PreMaint" && +
+ + Pre-Maintenance + +
+ } +
+ ), label: "Site", }, { path: ["freeCore", "totalCore"], label: "Cores" }, diff --git a/src/components/Resource/Topomap.jsx b/src/components/Resource/Topomap.jsx index a5240281..286dd8df 100644 --- a/src/components/Resource/Topomap.jsx +++ b/src/components/Resource/Topomap.jsx @@ -7,7 +7,7 @@ import { Line, Marker, } from "react-simple-maps"; - +import { sitesNameMapping } from "../../data/sites"; import { topomap } from "../../data/topomap.js" const geoUrl = "https://raw.githubusercontent.com/deldersveld/topojson/master/world-countries.json"; @@ -29,11 +29,6 @@ const Topomap = props => { setPosition(position); } - function checkStatus(name) { - // return "up" or "down" - return props.sites.includes(name) ? "up" : "down"; - } - return (
{ } - {topomap.fab_lines.map(({ from, to }) => ( + {topomap.lines.map(({ from, to }) => ( { /> ))} + {topomap.international_lines.map(({ from, to }) => ( + { + }} + /> + ))} + {topomap.nodes.map(({ name, markerOffset, type }) => ( { > ( - {sliceTimeParser(slice.lease_end_time)} + {utcToLocalTimeParser(slice.lease_end_time)} ) }, { @@ -53,7 +53,7 @@ class SlicesTable extends Component { path: "lease_end_time", label: "Lease End", content: (slice) => ( - {sliceTimeParser(slice.lease_end_time)} + {utcToLocalTimeParser(slice.lease_end_time)} ) }, { diff --git a/src/components/SliceViewer/SideNodes.jsx b/src/components/SliceViewer/SideNodes.jsx index 2bc57d9f..74003798 100644 --- a/src/components/SliceViewer/SideNodes.jsx +++ b/src/components/SliceViewer/SideNodes.jsx @@ -5,11 +5,13 @@ import SingleComponent from './SingleComponent'; import _ from "lodash"; import validator from "../../utils/sliceValidator"; import { sitesNameMapping } from "../../data/sites"; +import { Link } from "react-router-dom"; import { default as portalData } from "../../services/portalData.json"; class SideNodes extends React.Component { state = { - selectedSite: "", + selectedSiteName: "", + selectedSite: {}, nodeName: "", core: 2, ram: 6, @@ -37,13 +39,13 @@ class SideNodes extends React.Component { handleAddNode = () => { // type: currently only support 'VM' - const { selectedSite, nodeName, nodeType, core, ram, disk, + const { selectedSiteName, nodeName, nodeType, core, ram, disk, imageType, selectedImageRef, nodeComponents, BootScript } = this.state; const image = `${selectedImageRef},${imageType}`; - this.props.onNodeAdd(nodeType, selectedSite, nodeName, Number(core), + this.props.onNodeAdd(nodeType, selectedSiteName, nodeName, Number(core), Number(ram), Number(disk), image, nodeComponents, BootScript); this.setState({ - selectedSite: "", + selectedSiteName: "", nodeName: "", core: 2, ram: 6, @@ -74,15 +76,29 @@ class SideNodes extends React.Component { this.setState({ nodeComponents: updated_nodeComponents }); } + getSiteResource = (name) => { + for (const site of this.props.resources.parsedSites) { + if (site.name === name) { + return site; + } + } + } + handleSiteChange = (e) => { if (e.target.value === "") { - this.setState({ selectedSite: ""}); + this.setState({ selectedSiteName: ""}); } else if (e.target.value === "Random") { const sites = this.props.resources.parsedSites; const random_site = sites[Math.floor(Math.random() * sites.length)].name; - this.setState({selectedSite: random_site}); + this.setState({ + selectedSiteName: random_site, + selectedSite: this.getSiteResource(random_site) + }); } else { - this.setState({ selectedSite: e.target.value }); + this.setState({ + selectedSiteName: e.target.value, + selectedSite: this.getSiteResource(e.target.value) + }); } } @@ -114,14 +130,6 @@ class SideNodes extends React.Component { this.setState({ BootScript: e.target.value }); } - getSiteResource = () => { - for (const site of this.props.resources.parsedSites) { - if (site.name === this.state.selectedSite) { - return site; - } - } - } - getResourcesSum = () => { const selectedLabels = [ "freeCore", @@ -146,9 +154,9 @@ class SideNodes extends React.Component { } render() { - const { selectedSite, nodeName, imageType, selectedImageRef, core, ram, + const { selectedSiteName, selectedSite, nodeName, imageType, selectedImageRef, core, ram, disk, BootScript, nodeComponents } = this.state; - const validationResult = validator.validateNodeComponents(selectedSite, nodeName, this.props.nodes, core, ram, disk, nodeComponents, BootScript); + const validationResult = validator.validateNodeComponents(selectedSiteName, nodeName, this.props.nodes, core, ram, disk, nodeComponents, BootScript); const renderTooltip = (id, content) => ( {content} @@ -159,19 +167,38 @@ class SideNodes extends React.Component { {this.props.resources !== null &&
{ - selectedSite !== "" && + selectedSiteName !== "" &&
- Available Site Resources - - - {this.state.selectedSite} ({sitesNameMapping.acronymToLongName[this.state.selectedSite]}) - + + {selectedSiteName} { sitesNameMapping.acronymToLongName[selectedSiteName] && + `(${sitesNameMapping.acronymToLongName[selectedSiteName]})` + } + + { + selectedSite.status.state === "Maint" && + Maintenance + + } + { + selectedSite.status.state === "PreMaint" && + Pre-Maintenance + + }
- +
} { - selectedSite === "" && + selectedSiteName === "" &&
Available FABRIC Testbed Resources @@ -196,7 +223,7 @@ class SideNodes extends React.Component {