diff --git a/Docker/lofar-ci/Dockerfile_ci_tmss b/Docker/lofar-ci/Dockerfile_ci_tmss index 5a3f8cabc1d54a339895213dace5ca7ff6721184..58473340971b00c153ca7de9d60701533fc5b7c0 100644 --- a/Docker/lofar-ci/Dockerfile_ci_tmss +++ b/Docker/lofar-ci/Dockerfile_ci_tmss @@ -38,7 +38,7 @@ RUN pip3 install astroplan cachetools comet coreapi coverage cx_Oracle cython dj -r tmss_lobster.txt -r tmss_ingest_tmss_adapter.txt -r tmss_scheduling.txt \ -r tmss_slack_webhook.txt -r tmss_websocket.txt \ -c tmss_constraints.txt --ignore-installed -RUN echo "This string is here to prevent Docker caching. It is 1 pm on Nov 3, 2023." +RUN echo "This string is here to prevent Docker caching. It is 3 pm on Nov 27, 2023." # Download and import the Nodesource GPG key - Requires curl (already installed by base) diff --git a/SAS/TMSS/deploy/nginx/default.conf b/SAS/TMSS/deploy/nginx/default.conf index a1d00801b50846b9e3b702f5a9f9338d1c00518f..a253d425cc6fd1c003c23934b7971c7b68ff9ee2 100644 --- a/SAS/TMSS/deploy/nginx/default.conf +++ b/SAS/TMSS/deploy/nginx/default.conf @@ -10,7 +10,7 @@ server { listen 8008; add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always; - add_header Content-Security-Policy "default-src 'self' data: https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://stackpath.bootstrapcdn.com https://fonts.googleapis.com https://cdnjs.cloudflare.com; font-src 'self' data: https://stackpath.bootstrapcdn.com https://fonts.gstatic.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; connect-src 'self' ws://localhost:5678 ws://tmss.lofar.eu:5678; img-src 'self' data: https://tile.openstreetmap.org"; + add_header Content-Security-Policy "default-src 'self' data: https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://stackpath.bootstrapcdn.com https://fonts.googleapis.com https://cdnjs.cloudflare.com; font-src 'self' data: https://stackpath.bootstrapcdn.com https://fonts.gstatic.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; connect-src 'self' ws://localhost:5678 ws://tmss.lofar.eu:5678; img-src 'self' data: blob: https://tile.openstreetmap.org"; add_header Referrer-Policy 'strict-origin'; location / { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.js b/SAS/TMSS/frontend/tmss_webapp/src/App.js index 698ee2caca3295e7807c6355ecadbd9f2bb2b2d6..fb832552ae9c5635adc0d3a780d2f6783256a311 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.js @@ -1,5 +1,5 @@ import { Component } from 'react'; -import { Redirect, BrowserRouter as Router, withRouter } from 'react-router-dom'; +import { Redirect, BrowserRouter as Router , withRouter } from 'react-router-dom'; import classNames from 'classnames'; import AppTopbar from './layout/components/AppTopbar'; @@ -70,7 +70,8 @@ class App extends Component { { label: 'Week View', icon: 'pi pi-fw pi-calendar-times', to: '/su/timelineview/week', section: 'su/timelineview/week', isBreadCrumbVisible: false ,isDateTimeVisible:true}, { label: 'Reports', icon: 'pi pi-fw pi-chart-bar', to: '/reports', section: 'reports', isBreadCrumbVisible: false ,isDateTimeVisible:false}, { label: 'System Events', icon: 'pi pi-fw pi-bolt', to: '/systemevent/list', section: 'system', isBreadCrumbVisible: false ,isDateTimeVisible:true}, - { label: 'Stations View', icon: 'pi pi-fw pi-wifi pi-rotate', to: '/station/list', section: 'system', isBreadCrumbVisible: false ,isDateTimeVisible:true}, + { label: 'Stations', icon: 'pi pi-fw pi-wifi pi-rotate', to: '/station/list', section: 'system', isBreadCrumbVisible: false ,isDateTimeVisible:true}, + { label: 'Daily Constraints', icon: 'pi pi-fw pi-sun', to: '/constraint/view', section: 'system', isBreadCrumbVisible: false ,isDateTimeVisible:true}, ]; } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageActionMenu.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageActionMenu.js index 3fa56ea0f3edbfbe7e3b90d510c5bba78621a9f9..4d25da3c2ad33a3658bf7eeea6eab42021c48d07 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageActionMenu.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageActionMenu.js @@ -74,19 +74,12 @@ const PageActionMenu = ({ actions, className }) => { ) case 'dropdown': - if (!action.options) { - return <div className={action.classes} key={action.title}> <span className="dropdown-title">{action.title} </span> <i className="pi pi-spin pi-hourglass"></i> </div> - } - if (action.optionsvalue) { return (<div className={action.classes} key={action.title}> <span className="dropdown-title">{action.title} </span><Dropdown value={action.selected} onChange={(e) => setSelected(e, action)} options={action.options} optionValue={action.optionvalue} optionLabel={action.optionlabel} className="w-full md:w-14rem" /></div>) } else { return (<div className={action.classes} key={action.title}> <span className="dropdown-title">{action.title} </span><Dropdown value={action.selected} onChange={(e) => setSelected(e, action)} options={action.options} optionLabel={action.optionlabel} className="w-full md:w-14rem" placeholder={action.optionSelectionLabel}/></div>) } - - case 'dropdownb': - case 'ext_link': return ( <a href={action.props.pathname} title={action.title || ''} key={action.title} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_pageheader.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_pageheader.scss index 8a43ae5b1578fdf2b941cb83000a40e559150a2d..6f036f305fddf6ee5ace33c80f19d66a3bb0d94e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_pageheader.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_pageheader.scss @@ -29,6 +29,8 @@ .page-actionsdefaultpageHeader{ display: flex; + margin-left: auto; + margin-right: auto; button { height: 30px; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/station.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/station.scss index d577a3dd70e7127ad3fe6ffde6834d272bb55793..8f6c53476535c956fa1de0f714e3a9504608162a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/station.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/station.scss @@ -4,8 +4,15 @@ .StationTable { } - - +.FlexColumn { + display: flex; + flex-direction: column; +} +.autowidthmargin { + + margin-left: auto; + margin-right:auto; +} .StationListWrap{ display: flex; @@ -16,6 +23,43 @@ justify-content: center; } +.TabNoScroll { + max-height: calc(#{$vh * 100} - 7.9em); + overflow-y: auto; + + .p-tabview-panels + { + padding-top:0px; + padding-right: 0px; + padding-left: 0px; + } + + .p-tabview-nav li + { + margin-right:10px; + } + .p-tabview-nav + { + margin-left:70px !important; + padding-left:5px; + } + } + +.HeaderBlock { + background-color: #E8EAF6; + padding:5px; + border-top:1px solid #1A237E; + border-bottom:1px solid #1A237E; + + button { + height: 30px; + margin-left:5px; + } + +} +.scheduletime { + margin-left: 60px; +} .Progress{ margin-left: 10px; } @@ -51,10 +95,16 @@ max-width: 100px;; + .p-listbox-list-wrapper{ height:calc(#{$vh * 100} - 11.5em); max-height:calc(#{$vh * 100} - 11.5em); + overflow-y: scroll; + } + .p-listbox-item { + padding :0px !important; + } } @@ -101,8 +151,9 @@ width: 20px; height: 20px; opacity: 50%; - background-color: #0a0d3a; /* Replace with your desired background color */ + background-color: grey; /* Replace with your desired background color */ border-radius: 50%; + border:2px solid black; margin-bottom: 5px; /* Adjust as needed for spacing between the circle and text */ } @@ -141,4 +192,21 @@ } } - \ No newline at end of file + .specified_station { + background-color: #1A237E; + color:white; +} + .used_station { + background-color: #1B5E20; + color:white; + } + + .missed_station { + background-color:#E65100 !important; + color:white; + } + + .stationoption{ + padding:5px; + border-bottom: 1px solid white; + } \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Constraint/DailyConstraintView.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Constraint/DailyConstraintView.js new file mode 100644 index 0000000000000000000000000000000000000000..0fdd890fe581cb597d9133a7e1018d17d4a3ead0 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Constraint/DailyConstraintView.js @@ -0,0 +1,72 @@ +import PropTypes from 'prop-types'; +import PageHeader from "../../layout/components/PageHeader"; +import { useEffect, useState } from "react"; +import moment from "moment"; +import ScheduleService from "../../services/schedule.service"; +import PageHeaderActions from "../../utils/pageheaderactions.util.js"; +import { ProgressSpinner } from 'primereact/progressspinner'; + + +import UIConstants from "../../utils/ui.constants"; +export default function DailyConstraintView(props) { + const { + location, + match, + + } = props + const parseTheDate =(match)=> { + if (match) + { + let incommingdate= match.params.date; + let truedate = moment(incommingdate).toDate(); + return truedate + } + return moment().startOf('day').toDate(); + }; + const [startTime, setStartTime] = useState( parseTheDate(match)) + const [constraintImage, setConstraintImage] = useState() + + const fetchImage = async () => { + setConstraintImage(null); + let constraintImage = await ScheduleService.getConstraintImgOnDate(moment(startTime).format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT)); + if (constraintImage!==undefined) setConstraintImage(constraintImage); +; + } + +useEffect(() => { + console.log("startTime", startTime); + fetchImage(); +}, [ startTime]); + + + + + + function navigateToDay(addingDays) { + let newStartTime = new Date(startTime); + newStartTime.setDate(startTime.getDate() + addingDays); + setStartTime(newStartTime); + } + + + + + return <> + <PageHeader location={location} title={'Daily Constraint View '} actions={PageHeaderActions.getOneDayNavigationActions(navigateToDay,setStartTime,startTime)} className="defaultpageHeader" /> + <div> + {constraintImage === null ? ( + <ProgressSpinner /> + ) : ( + + <img src={constraintImage} alt="Constraint" /> + + + )} + </div> + </> +} + +DailyConstraintView.propTypes = { + onDate: PropTypes.string, + + } \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationConstraintImage.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationConstraintImage.js index 719b1085d0c5e9982a0316a5c8f85eaa10a2b24a..db0377ca480a70b617efce7cee6788856f977846 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationConstraintImage.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationConstraintImage.js @@ -6,7 +6,8 @@ export default function StationConstraintImage(props) { const { station, startTime, - blueprintId + blueprintId, + children } = props @@ -15,25 +16,24 @@ export default function StationConstraintImage(props) { const fetchImage = async () => { setConstraintImage(null); let constraintImage = await ScheduleService.getConstraintImgForStationAndSchedulingUnitOnDate(station, blueprintId, startTime); - setConstraintImage(constraintImage); }; - - - - useEffect(() => { fetchImage(); }, [station, blueprintId, startTime]); return ( - <div> {constraintImage === null ? ( <ProgressSpinner /> ) : ( - <img src={constraintImage} alt="Constraint" /> + <div className='FlexColumn'> + {children} + <div> + <img src={constraintImage} alt="Constraint" /> + </div> + </div> )} </div> ) @@ -44,4 +44,5 @@ StationConstraintImage.propTypes = { station: PropTypes.string, startTime: PropTypes.string, blueprintId: PropTypes.string, + children: PropTypes.node }; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationConstraintView.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationConstraintView.js index 208fc9fd45f721ff42bd8b2049b99d0a4171ba60..13f38d66d044761016da24fff618e04cfd46df47 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationConstraintView.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationConstraintView.js @@ -1,8 +1,11 @@ +import { useRef, useState } from 'react'; import PropTypes from 'prop-types'; - - +import { Button } from 'primereact/button'; +import { ConfirmPopup } from 'primereact/confirmpopup'; +import { Calendar } from 'primereact/calendar'; import StationConstraintImage from './StationConstraintImage' - +import ScheduleService from "../../services/schedule.service"; +import { Toast } from 'primereact/toast'; export default function StationConstraintView(props) { const { station, @@ -10,9 +13,36 @@ export default function StationConstraintView(props) { selectedBluePrintId, startTime } = props + const toast = useRef(null); + const [visible, setVisible] = useState(false); + const [scheduletime, setScheduletime] = useState(new Date().now); + const buttonSchedule = useRef(null); + + + const accept = () => { + console.log(scheduletime) + + let CombinedTime = startTime+"T"+scheduletime.getHours()+":"+ scheduletime.getMinutes()+":00" + ScheduleService.ScheduleBluePrintOn(selectedBluePrintId,CombinedTime); + toast.current.show({ severity: 'info', summary: 'Confirmed', detail: 'Setting Schedule for ' + selectedBluePrint[1].name + ' at ' + startTime, life: 3000 }); + }; + return <div className='ConstraintsStart'> {selectedBluePrint ? ( - <StationConstraintImage blueprintId={selectedBluePrintId} station={station} startTime={startTime} ></StationConstraintImage>) + <StationConstraintImage blueprintId={selectedBluePrintId} station={station} startTime={startTime} > + + { selectedBluePrint[1].status==="scheduled" ||selectedBluePrint[1].status==="schedulable" ? ( + <> + <ConfirmPopup target={buttonSchedule.current} visible={visible} onHide={() => setVisible(false)} message="Are you sure you want set the blueprint to a fixed time ?" icon="pi pi-exclamation-triangle" accept={accept} /> + <div className='HeaderBlock'> + <span className="scheduletime"> Schedule time <Calendar value={scheduletime} onChange={(e) => setScheduletime(e.value)} timeOnly /> </span> + <Button ref={buttonSchedule} label={"Schedule " + selectedBluePrint[1].name + " at "+ startTime} className='autowidthmargin' onClick={() => setVisible(true)} disabled={!scheduletime}></Button> + + </div> + <Toast ref={toast} /> + </> + ):(<></>)} + </StationConstraintImage>) : (<div></div> )} </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationGeoView.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationGeoView.js index ee3e27f5e1a623581236f22ea708555ae8fd4839..b2a34208a3ae80afa53ef51747ce312f649d93aa 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationGeoView.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationGeoView.js @@ -57,8 +57,8 @@ function StationGeoView(props) { className: 'custom-marker-icon', html: ` <div > - <div class="marker-circle"></div> - <div class="marker-text">${station.id}</div> + <div class="marker-circle ${station.properties.className}"></div> + <div class="marker-text">${station.properties.name}</div> </div> `, }); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationView.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationView.js index 1ee557480775698cacbd8f51d66453b8e895ced2..30e2436ae5daf6e9f768017789fa8e3b516fc037 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationView.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Station/StationView.js @@ -17,6 +17,7 @@ export default function StationView(props) { location } = props + const [isLoading, setisLoading] = useState(true) const [stations, setStations] = useState() const [stationsGeo, setStationsGeo] = useState([]) const [selectedStation, setSelectedStation] = useState("CS002") @@ -24,24 +25,66 @@ export default function StationView(props) { const [flatBluePrints, setFlatBluePrints] = useState() const [selectedBluePrint, setSelectedBluePrint] = useState() const [selectedBluePrintId, setSelectedBluePrintId] = useState() - const [startTime, setStartTime] = useState(moment().startOf('day').toDate() ) + const [startTime, setStartTime] = useState(moment().startOf('day').toDate()) const [endTime, setEndTime] = useState(moment().endOf('day').add(UIConstants.WEEKVIEW_SHOW_NUMBER_OF_DAYS, 'days').toDate()) const [map, setMap] = useState() - + const isDebugLogging = false; async function fetchStations() { let stationlist = await UtilService.getStations(); + updateStations(stationlist); + } + + function updateStations(stationlist) { if (stationlist === null) return; let flatstationList = Object.entries(stationlist) setStations(flatstationList); - let geostations = MakeGeoJson(stationlist); + console.log(flatstationList) + let geostations = MakeGeoJson(flatstationList); setStationsGeo(geostations); } + + + function MakeGeoJson(stations) { + let StationgeojsonArray = [] + for (let station in stations) { + const geostationJson = generateGeoJson(station, stations[station]); + StationgeojsonArray.push(geostationJson); + } + return StationgeojsonArray; + } + + + function GetFlatBluePrints() { return flatBluePrints; } + function SetLogicalSelectedBluePrint(prints) { + for (const element of prints) { + if (element[1].status === "observing") { + setSelectedBluePrint(element); + return + } + } + + for (const element of prints) { + if (element[1].status === "finished") { + setSelectedBluePrint(element); + return + } + } + + for (const element of prints) { + if (element[1].status === "error") { + setSelectedBluePrint(element); + return + } + } + + } + function MakeLabel(prints) { for (const element of prints) { const print = element; @@ -55,8 +98,37 @@ export default function StationView(props) { }, [startTime, endTime]); + function MakeStationSelectionVisible(selectedBluePrint) { + if (isDebugLogging) console.log("Current Stations", stations); + const BluePrintNpde = selectedBluePrint[1]; + const specified_stations = BluePrintNpde.specified_stations; + const used_stations = BluePrintNpde.used_stations; + const missing_stations = BluePrintNpde.missing_stations; + + + + for (let counter = 0; counter < stations.length; counter++) { + const stationame = stations[counter][0]; + const isInSpecified = specified_stations.indexOf(stationame) > -1; + const isInUSing = used_stations.indexOf(stationame) > -1; + const isInMissing = missing_stations.indexOf(stationame) > -1; + + let className = "" + if (isInSpecified) className = className + " specified_station "; + if (isInUSing) className = className + " used_station "; + if (isInMissing) className = className + " missed_station "; + stations[counter][1].className = className + } + let geostations = MakeGeoJson(stations); + setStationsGeo(geostations); + setStations(stations); + + } + useEffect(() => { if (selectedBluePrint?.[1]?.id) { + console.log("Selected BluePrint", selectedBluePrint) + MakeStationSelectionVisible(selectedBluePrint); setSelectedBluePrintId("" + selectedBluePrint[1].id) } }, [selectedBluePrint]); @@ -77,6 +149,7 @@ export default function StationView(props) { } async function fetchBluePrints() { + setisLoading(true); const from = moment(startTime).format(UIConstants.CALENDAR_DATETIME_FORMAT); const until = moment(endTime).format(UIConstants.CALENDAR_DATETIME_FORMAT); const schedulingUnits = await ScheduleService.getTimelineSlimBlueprints(from, until); @@ -85,15 +158,23 @@ export default function StationView(props) { let flattedBluePrints = Object.entries(sortedbluePrints) MakeLabel(flattedBluePrints); setFlatBluePrints(flattedBluePrints); - + SetLogicalSelectedBluePrint(flattedBluePrints); + setisLoading(false); } - + function getBluePrintActions() { return [ + PageHeaderActions.actionButtonObject( + isLoading ? "System is already reloading the blueprint data" : "Reload the Blue print data", + (isLoading ? "pi-spin pi-hourglass " : "pi-sync"), + `subsystem subsystem--${isLoading ? "on" : "standard"}`, + { callback: () => fetchBluePrints(prevState => !prevState) } + ), PageHeaderActions.actionDropDownObject("Blue print ", GetFlatBluePrints(), { callback: (blueprint) => setSelectedBluePrint(blueprint) }, - selectedBluePrint, "station-blueprint-dropdown", undefined, "0","select Blue print" + selectedBluePrint, "station-blueprint-dropdown", undefined, "0", "select Blue print" ), + ] } @@ -101,44 +182,23 @@ export default function StationView(props) { let newStartTime = new Date(startTime); newStartTime.setDate(startTime.getDate() + addingDays); let NewEndTime = new Date(endTime) - NewEndTime.setDate(endTime + addingDays); + NewEndTime.setDate(endTime.getDate() + addingDays); setStartTime(newStartTime); setEndTime(NewEndTime); } - function getNavigationActions(AddDays) { - return [ - PageHeaderActions.actionButtonObject( - "previous day", - "pi pi-angle-left", - "", - { callback: () => AddDays(-1) } - ), - - PageHeaderActions.actionCalendarObject( - "", - { callback: (date) => setStartTime(date) }, - startTime, - "NoOverride" - ), - PageHeaderActions.actionButtonObject( - "next day", - "pi pi-angle-right", - "", - { callback: () => AddDays(1) } - ), - ]; - } - function generateGeoJson(station, coordinates) { - let name = station; - let longitude = coordinates.longitude; - let latitude = coordinates.latitude; + function generateGeoJson(stationid, station) { + let name = station[0]; + let longitude = station[1].longitude; + let latitude = station[1].latitude; return { type: "Feature", properties: { - name: name + name: name, + version: station[1].version, + className: station[1].className || "" }, "geometry": { "type": "Point", @@ -151,14 +211,6 @@ export default function StationView(props) { } } - function MakeGeoJson(stations) { - let StationgeojsonArray = [] - for (let station in stations) { - const geostationJson = generateGeoJson(station, stations[station]); - StationgeojsonArray.push(geostationJson); - } - return StationgeojsonArray; - } @@ -177,33 +229,38 @@ export default function StationView(props) { function constrainsSelect(station) { console.log(station); - setSelectedStation(...station) } - - return <> - - <PageHeader location={location} title={'Stations - List '} actions={getBluePrintActions().concat(getNavigationActions(navigateToDay))} className="defaultpageHeader" /> - <TabView> - <TabPanel header="Station Geo Map" className="TabStation"> - <div className="StationListWrap" > - <ListBox options={stations} optionLabel="0" className="StationListbox" onChange={(e) => ZoomTo(e.value)} /> - <StationGeoView stations={stationsGeo} selected={selectedStation} mapRef={setMap} ></StationGeoView > - </div> - </TabPanel> - <TabPanel header="Station Constraints Plot"> - <div className="StationListWrap" > - <ListBox options={stations} optionLabel="0" className="StationListbox" onChange={(e) => constrainsSelect(e.value)} selected={selectedStation} /> - <StationConstraintView station={selectedStation} stations={stations} selectedBluePrint={selectedBluePrint} selectedBluePrintId={selectedBluePrintId} startTime={moment(startTime).format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT)} ></StationConstraintView> - </div> - - </TabPanel> - </TabView> - </> + const stationTemplate = (option) => { + let classCombined = option?.[1]?.className || "" + classCombined = classCombined + " flex align-items-center stationoption" + return ( + <div className={classCombined} >{option[0]}</div> + ); + }; + + return <> + + <PageHeader location={location} title={'Stations - List '} actions={getBluePrintActions().concat(PageHeaderActions.getOneDayNavigationActions(navigateToDay, setStartTime, startTime))} className="defaultpageHeader" /> + <TabView className="TabNoScroll"> + <TabPanel header="Station Geo Map" className="TabStation"> + <div className="StationListWrap" > + <ListBox options={stations} optionLabel="0" className="StationListbox" onChange={(e) => ZoomTo(e.value)} itemTemplate={stationTemplate} emptyMessage="000"/> + <StationGeoView stations={stationsGeo} selected={selectedStation} mapRef={setMap} ></StationGeoView > + </div> + </TabPanel> + <TabPanel header="Station Constraints Plot"> + <div className="StationListWrap" > + <ListBox options={stations} optionLabel="0" className="StationListbox" onChange={(e) => constrainsSelect(e.value)} selected={selectedStation} itemTemplate={stationTemplate} emptyMessage="000"/> + <StationConstraintView station={selectedStation} stations={stations} selectedBluePrint={selectedBluePrint} selectedBluePrintId={selectedBluePrintId} startTime={moment(startTime).format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT)} ></StationConstraintView> + </div> + </TabPanel> + </TabView> + </> } - StationView.propTypes = { - title: PropTypes.string, - - } \ No newline at end of file +StationView.propTypes = { + title: PropTypes.string, + +} \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/helpers/timeline.renderer.helper.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/helpers/timeline.renderer.helper.js index 0692aedd8884a2a92b530c7f800c4074cc642a28..030d32265d29f9b5b156ff7eb570498c1153b2cf 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/helpers/timeline.renderer.helper.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/helpers/timeline.renderer.helper.js @@ -1,4 +1,4 @@ - +import UIConstants from '../../../utils/ui.constants'; function getItemDivStyle(itemContext, item, hasItemColors = false) { const style = { @@ -179,20 +179,21 @@ export function groupRenderer({ group }) { let primaryGroupTitle = null; let groupClassName = "group-renderer"; const doesContainHash = group.id.indexOf('#') !== -1; - + let weekdate = group.weekdate if (doesContainHash) { primaryGroupTitle = group.title groupClassName = groupClassName + ' ' + group.titleClass - - } + } + weekdate = ( <a href={"/constraint/view/" + group.date.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT)} target="_new"> {weekdate} </a> ) + return <div className={groupClassName} > {primaryGroupTitle ? <div className="group-primary-title">{primaryGroupTitle}</div> : null} <span className="week">{group.weeknr}</span> - {group.weekdate} + {weekdate} {group.cursorInfo ? <table className="cursor-information"><tbody> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index d39ec36ad2c87ff204a7b842adfc6505d39a276b..ac610d14b81906dec796d07778911d7e1fd249f2 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -14,6 +14,7 @@ import SchedulingUnitCreate from './Scheduling/create'; import UserOverView from './User/UserOverView'; import EditSchedulingUnit from './Scheduling/edit'; import StationView from './Station/StationView' +import DailyConstraintView from './Constraint/DailyConstraintView'; import StationGeoView from './Station/StationGeoView' import { CycleList, CycleCreate, CycleView, CycleEdit } from './Cycle'; import { ReservationCreate, ReservationList, ReservationView, ReservationEdit } from './Reservation'; @@ -267,6 +268,20 @@ export const routes = [ title: 'System Event - Edit', permissions: ['systemevent', 'edit'] }, + { + path: "/constraint/view", + component: DailyConstraintView, + name: 'Daily Constraints', + title: 'Daily Constraints', + permissions: ['scheduleunit_blueprint', 'list'] + }, + { + path: "/constraint/view/:date", + component: DailyConstraintView, + name: 'Daily Constraints', + title: 'Daily Constraints', + permissions: ['scheduleunit_blueprint', 'list'] + }, { path: "/station/list", component: StationView, diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js index 4b49d2ac503919b2673ca827621ed320937a2cf6..f761625b5304791830eae90a4851ac1105e94c36 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -95,6 +95,26 @@ const ScheduleService = { } }, + + /** + * Function to give back a scheduling constraint url + * @param {string} station - The Station + * @param {string} schedulingUnit - the Unit we are looking at + * @param {string} onDate - 2023-02-14 formatedf formated of the date + * @returns string with the url + */ + getConstraintImgOnDate : async function(onDate) { + if (onDate===undefined) return; + try { + let imageurl = "/api/daily_schedule_plot/" + onDate + const response = await axios.get( imageurl, { responseType: "blob", } ); + const dataUrl = URL.createObjectURL(response.data); + return dataUrl; + } catch (error) { + console.error("getConstraintImgOnDate Error fetching image:", error); + } + + }, getExpandedSchedulingUnit: async function (type, id, expand, fields) { let schedulingUnit = null; try { @@ -1137,6 +1157,17 @@ const ScheduleService = { console.error(error); } }, + ScheduleBluePrintOn: async function (id, startTime) { + const param = { + start_time:startTime + } + try { + return await axios.post(`/api/scheduling_unit_blueprint/${id}/schedule`, param); + } catch (error) { + console.error(error); + return error.response + } + }, } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/show.error.details.js b/SAS/TMSS/frontend/tmss_webapp/src/show.error.details.js index 7402e95cda574a268872c6b5dc608f75de0cb821..2150a77b135325a968bab11c9c85433a36708810 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/show.error.details.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/show.error.details.js @@ -53,6 +53,10 @@ export class ShowErrorDetails extends Component { const httpStatusMsg = UIConstants.httpStatusMessages[this.props.response.status]; const data = httpStatusMsg? httpStatusMsg.detail: ''; let message = '['+this.props.response.status+'] '+JSON.stringify(this.props.response.statusText)+ ' ['+data+']'; + let errorDetails = this.props.response?.request?.response; + if (errorDetails.size) { + errorDetails = "unable to download blob" + } return ( <div> <Toast ref={(ref) => this.growl =ref}/> @@ -71,7 +75,7 @@ export class ShowErrorDetails extends Component { <div style={{display : this.state.showDetailsCSS}} > <p style={{fontWeight: 'bold'}}> Error Details:</p> <div id="showError" className="show_error_details" > - {this.props.response.request.response} + {errorDetails} </div> </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/pageheaderactions.util.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/pageheaderactions.util.js index 0aa516fbe336e1aef8214fb77ad38e2ad949c0fd..9126213e931f2e2968b4009e940a73be0516abdf 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/utils/pageheaderactions.util.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/pageheaderactions.util.js @@ -26,11 +26,7 @@ actionButtonObject(title, icon, classes, callbackProp) { actionTagObject(title, content, className) { return this.actionObject(title, undefined, undefined, className, undefined, content, "tagv2", "click", undefined, undefined,undefined,undefined); }, - - actionDropDownObject(title, options, callbackProp, selected, classes,optionvalue ,optionlabel ,optionSelectionLabel) { - - return this.actionObject(title, undefined, classes, undefined, callbackProp, undefined, "dropdown", "select", options, selected,optionvalue,optionlabel,optionSelectionLabel); }, @@ -39,8 +35,34 @@ actionCalendarObject(title, callbackProp, selected, classes) { }, actionDivider(title) { return this.actionObject(title, undefined, undefined, undefined, undefined, undefined, "divider", undefined, undefined, undefined,undefined,undefined); +}, + getOneDayNavigationActions(AddDays,setStartTime,startTime) { + return [ + this.actionButtonObject( + "previous day", + "pi pi-angle-left", + "", + { callback: () => AddDays(-1) } + ), + + this.actionCalendarObject( + "", + { callback: (date) => setStartTime(date) }, + startTime, + "NoOverride" + ), + this.actionButtonObject( + "next day", + "pi pi-angle-right", + "", + { callback: () => AddDays(1) } + ), + ]; + +} } -}; + + export default PageHeaderActions; \ No newline at end of file