diff --git a/src/app.js b/src/app.js index 07207a9..9f1e67d 100644 --- a/src/app.js +++ b/src/app.js @@ -29,21 +29,20 @@ import Home from './common/home'; import Header from './common/header'; import Menu from './common/menu'; -import InfrastructureComponents from './ic/ics'; -import InfrastructureComponent from './ic/ic'; +import InfrastructureComponent from './pages/infrastructure/ic'; import Dashboard from './dashboard/dashboard'; import Scenarios from './scenario/scenarios'; import Scenario from './scenario/scenario'; import Users from './user/users'; import User from './user/user'; import APIBrowser from './common/api-browser'; -import LoginStore from './user/login-store' import './styles/app.css'; +import './styles/login.css'; import branding from './branding/branding'; - +import Infrastructure from './pages/infrastructure/infrastructure' class App extends React.Component { @@ -53,10 +52,6 @@ class App extends React.Component { this.state = {} } - static getStores() { - return [LoginStore] - } - componentDidMount() { NotificationsDataManager.setSystem(this.refs.notificationSystem); @@ -126,8 +121,12 @@ class App extends React.Component { : '' } { currentUser.role === "Admin" || pages.infrastructure ? <> - - + + + + + + : '' } { pages.account ? : '' } diff --git a/src/common/confirm-command.js b/src/common/confirm-command.js new file mode 100644 index 0000000..43b6885 --- /dev/null +++ b/src/common/confirm-command.js @@ -0,0 +1,48 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import React from 'react'; +import { Button, Modal} from 'react-bootstrap'; + +class ConfirmCommand extends React.Component { + onModalKeyPress = (event) => { + if (event.key === 'Enter') { + event.preventDefault(); + + this.props.onClose(false); + } + } + + render() { + return this.props.onClose(false)} onKeyPress={this.onModalKeyPress}> + + Confirm {this.props.command} + + + + Are you sure you want to {this.props.command} '{this.props.name}'? + + + + + + + ; + } +} + +export default ConfirmCommand; diff --git a/src/common/rawDataTable.js b/src/common/rawDataTable.js new file mode 100644 index 0000000..87ff5ba --- /dev/null +++ b/src/common/rawDataTable.js @@ -0,0 +1,23 @@ +import { isJSON } from "../utils/isJson"; +import ReactJson from "react-json-view"; + +const RawDataTable = (props) => { + if(props.rawData !== null && isJSON(props.rawData)){ + return ( + + ) + } else { + return ( +
No valid JSON raw data available.
+ ) + } +} + +export default RawDataTable; \ No newline at end of file diff --git a/src/localStorage.js b/src/localStorage.js new file mode 100644 index 0000000..6486af4 --- /dev/null +++ b/src/localStorage.js @@ -0,0 +1,20 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +//authentication +export const sessionToken = localStorage.getItem("token"); +export const currentUser = JSON.parse(localStorage.getItem("currentUser")); \ No newline at end of file diff --git a/src/pages/infrastructure/ic-category-table.js b/src/pages/infrastructure/ic-category-table.js new file mode 100644 index 0000000..dce214a --- /dev/null +++ b/src/pages/infrastructure/ic-category-table.js @@ -0,0 +1,225 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import { useSelector, useDispatch } from "react-redux"; + +import {Table, ButtonColumn, CheckboxColumn, DataColumn, LabelColumn, LinkColumn } from '../../common/table'; +import { Badge } from 'react-bootstrap'; +import FileSaver from 'file-saver'; +import _ from 'lodash'; +import moment from 'moment' +import IconToggleButton from "../../common/buttons/icon-toggle-button"; + +import { checkICsByCategory } from "../../store/icSlice"; +import { useState } from "react"; + +import { stateLabelStyle } from "./styles"; + +//a Table of IC components of specific category from props.category +//titled with props.title +const ICCategoryTable = (props) => { + + const ics = useSelector(state => state.infrastructure.ICsArray); + + let currentUser = JSON.parse(localStorage.getItem("currentUser")); + + const checkedICs = useSelector(state => state.infrastructure.checkedICsArray); + + const [isGenericDisplayed, setIsGenericDisplayed] = useState(false) + + const exportIC = (index) => { + // filter properties + let ic = Object.assign({}, ics[index]); + delete ic.id; + + // show save dialog + const blob = new Blob([JSON.stringify(ic, null, 2)], { type: 'application/json' }); + FileSaver.saveAs(blob, 'ic-' + (_.get(ic, 'name') || 'undefined') + '.json'); + } + + const modifyUptimeColumn = (uptime, component) => { + if (uptime >= 0) { + let momentDurationFormatSetup = require("moment-duration-format"); + momentDurationFormatSetup(moment) + + let timeString = moment.duration(uptime, "seconds").humanize(); + return {timeString} + } + else { + return unknown + } + } + + const stateUpdateModifier = (updatedAt, component) => { + let dateFormat = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; + let dateTime = moment(updatedAt, dateFormat); + return dateTime.fromNow() + } + + const statePrio = (state) => { + switch (state) { + case 'running': + case 'starting': + return 1; + case 'paused': + case 'pausing': + case 'resuming': + return 2; + case 'idle': + return 3; + case 'shutdown': + return 4; + case 'error': + return 10; + default: + return 99; + } + } + + + //if category of the table is manager we need to filter the generic-typed ics + //according to the state of IconToggleButton + let tableData = []; + + if(props.category == "manager"){ + tableData = ics.filter(ic=> (ic.category == props.category) && (isGenericDisplayed ** (ic.type == "generic"))) + }else{ + tableData = ics.filter(ic=> ic.category == props.category) + } + + tableData.sort((a, b) => { + if (a.state !== b.state) { + return statePrio(a.state) > statePrio(b.state); + } + else if (a.name !== b.name) { + return a.name < b.name; + } + else { + return a.stateUpdatedAt < b.stateUpdatedAt; + } + }) + + const isLocalIC = (index, ics) => { + let ic = ics[index] + return !ic.managedexternally + } + + const checkAllICs = (ics, title) => { + //TODO + } + + const isICchecked = (ic) => { + //TODO + return false + } + + const areAllChecked = (title) => { + //TODO + return false + } + + const onICChecked = (ic, event) => { + //TODO + } + + return (
+

+ {props.title} + { props.category == "manager" ? + + setIsGenericDisplayed(!isGenericDisplayed)} + checked={isGenericDisplayed} + checkedIcon='eye' + uncheckedIcon='eye-slash' + tooltipChecked='click to hide generic managers' + tooltipUnchecked='click to show generic managers' + /> + :<> + } +

+ + checkAllICs(ics, props.title)} + allChecked={areAllChecked(props.title)} + checkboxDisabled={(index) => isLocalIC(index, ics) === true} + checked={(ic) => isICchecked(ic)} + onChecked={(ic, event) => onICChecked(ic, event)} + width='30' + /> + {currentUser.role === "Admin" ? + + : <> + } + + stateLabelStyle(state, component)} + /> + + modifyUptimeColumn(uptime, component)} + /> + stateUpdateModifier(uptime, component)} + /> + + {currentUser.role === "Admin" ? + isLocalIC(index, ics)} + exportButton + deleteButton + showDeleteButton = {null} + onEdit={index => {}} + onExport={index => exportIC(index)} + onDelete={index => {}} + /> + : + exportIC(index)} + /> + } +
+
+ ); +} + +export default ICCategoryTable; \ No newline at end of file diff --git a/src/pages/infrastructure/ic-pages/default-ic-page.js b/src/pages/infrastructure/ic-pages/default-ic-page.js new file mode 100644 index 0000000..674f6fb --- /dev/null +++ b/src/pages/infrastructure/ic-pages/default-ic-page.js @@ -0,0 +1,75 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import React, { useEffect, useState } from 'react'; +import {Col, Row} from "react-bootstrap"; +import IconButton from '../../../common/buttons/icon-button'; +import ManagedICsTable from "./managed-ics-table"; + +import { useDispatch } from 'react-redux'; +import { loadICbyId } from '../../../store/icSlice'; +import { sessionToken, currentUser } from '../../../localStorage'; +import { loadConfig } from '../../../store/configSlice'; +import { useSelector } from 'react-redux'; +import {refresh, rawDataTable} from "../ic" + +import ICParamsTable from '../ic-params-table'; +import RawDataTable from '../../../common/rawDataTable'; + +import { iconStyle, buttonStyle } from "../styles"; + +const DefaultICPage = (props) => { + const ic = props.ic; + + const dispatch = useDispatch(); + + const refresh = () => { + dispatch(loadICbyId({token: sessionToken, id: ic.id})); + } + + useEffect(() => { + }, []); + + return (
+

{ic.name} + + refresh(ic, sessionToken)} + icon='sync-alt' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + +

+ + +

Properties

+ + + +

Raw Status

+ + +
+
+ ) + +} + +export default DefaultICPage; diff --git a/src/pages/infrastructure/ic-pages/default-manager-page.js b/src/pages/infrastructure/ic-pages/default-manager-page.js new file mode 100644 index 0000000..79324ad --- /dev/null +++ b/src/pages/infrastructure/ic-pages/default-manager-page.js @@ -0,0 +1,88 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import React, { useEffect, useState } from 'react'; +import {Col, Row} from "react-bootstrap"; +import IconButton from '../../../common/buttons/icon-button'; +import ManagedICsTable from "./managed-ics-table"; + +import { useDispatch } from 'react-redux'; +import { loadICbyId } from '../../../store/icSlice'; +import { sessionToken, currentUser } from '../../../localStorage'; +import { loadConfig } from '../../../store/configSlice'; +import { useSelector } from 'react-redux'; +import {refresh, rawDataTable} from "../ic" + +import ICParamsTable from '../ic-params-table'; +import RawDataTable from '../../../common/rawDataTable'; + +import { iconStyle, buttonStyle } from "../styles"; + +const DefaultManagerPage = (props) => { + const ic = props.ic; + + const ics = useSelector((state) => state.infrastructure.ICsArray); + + const dispatch = useDispatch(); + + const managedICs = ics.filter(managedIC => managedIC.category !== "manager" && managedIC.manager === ic.uuid); + const [jobName, setJobName] = useState(null); + + const refresh = () => { + dispatch(loadICbyId({token: sessionToken, id: ic.id})); + } + + useEffect(() => { + }, []); + + return (
+

{props.ic.name} + + refresh()} + icon='sync-alt' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + +

+ + +

Properties

+ + + + + +
+
+ + +

Raw Status

+ + +
+
) + +} + +export default DefaultManagerPage; diff --git a/src/pages/infrastructure/ic-pages/gateway-villas-node.js b/src/pages/infrastructure/ic-pages/gateway-villas-node.js new file mode 100644 index 0000000..8e78223 --- /dev/null +++ b/src/pages/infrastructure/ic-pages/gateway-villas-node.js @@ -0,0 +1,120 @@ +import { useState } from "react"; +import { useDispatch } from "react-redux"; + +import ICParamsTable from "../ic-params-table"; +import RawDataTable from '../../../common/rawDataTable'; + +import { restartIC, shutdownIC, loadICbyId } from "../../../store/icSlice"; +import { sessionToken, currentUser } from "../../../localStorage"; +import { buttonStyle, iconStyle } from "../styles"; + +import IconButton from "../../../common/buttons/icon-button"; +import {Button, Col, Container, Row} from "react-bootstrap"; +import ConfirmCommand from "../../../common/confirm-command"; + +const GatewayVillasNode = (props) => { + const dispatch = useDispatch(); + + const ic = props.ic; + + const [command, setCommand] = useState(""); + const [isCommandConfirmed, setIsCommandConfirmed] = useState(false); + + const sendControlCommand = () => { + switch(command){ + case "restart": + dispatch(restartIC({apiurl: ic.apiurl})) + case "shutdown": + dispatch(shutdownIC({apiurl: ic.apiurl})) + default: + console.log("Command not found"); + } + } + + const confirmCommand = (canceled) => { + if(!canceled){ + sendControlCommand(); + } + + setIsCommandConfirmed(false); + setCommand(""); + } + + return ( +
+

{ic.name} + + dispatch(loadICbyId({id: ic.id, token: sessionToken}))} + icon='sync-alt' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + +

+ + + +

Properties

+ + + + {currentUser.role === "Admin" ? +
+

Controls

+
+ + +
+
+ :
+ } + confirmCommand(c)} + /> + + +
+ + +

Raw Status

+ + + +

Raw Config

+ + + +

Raw Statistics

+ + +
+
+ ) + +} + +export default GatewayVillasNode; \ No newline at end of file diff --git a/src/pages/infrastructure/ic-pages/kubernetes-ic-page.js b/src/pages/infrastructure/ic-pages/kubernetes-ic-page.js new file mode 100644 index 0000000..18873b9 --- /dev/null +++ b/src/pages/infrastructure/ic-pages/kubernetes-ic-page.js @@ -0,0 +1,170 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import { useState, useEffect } from "react"; +import { Col, Row, Container, Table } from "react-bootstrap"; +import IconButton from "../../../common/buttons/icon-button"; +import ManagedICsTable from "./managed-ics-table"; +import FileSaver from 'file-saver'; +import RawDataTable from "../../../common/rawDataTable"; +import { downloadGraph } from "../../../utils/icUtils"; +import { sessionToken, currentUser } from "../../../localStorage"; +import { useDispatch, useSelector } from "react-redux"; +import { loadAllICs, loadICbyId } from "../../../store/icSlice"; + +import ICParamsTable from "../ic-params-table"; + +import { iconStyle, buttonStyle } from "../styles"; + +const KubernetesICPage = (props) => { + + const dispatch = useDispatch(); + + const ic = props.ic; + + const ics = useSelector((state) => state.infrastructure.ICsArray); + const config = useSelector((state) => state.config.config); + //const managedICs = ics.filter(managedIC => managedIC.category !== "manager" && managedIC.manager === ic.uuid); + + let namespace = null; + let jobName = null; + let podNames = []; + let clusterName = null; + let rancherURL = null; + + let managerIC = ics.find(manager => manager.uuid === ic.manager); + + // Take k8s cluster details from managers status update + if (managerIC != null) { + let managerProps = managerIC.statusupdateraw.properties; + + if (managerProps != null) { + rancherURL = managerProps.rancher_url; + clusterName = managerProps.cluster_name; + } + } + + // Fallback to backend config + if (config != null && config.kubernetes != null) { + let k8s = config.kubernetes; + if (k8s != null) { + if (rancherURL == null) { + rancherURL = k8s.rancher_url + } + + if (clusterName == null) { + clusterName = k8s.cluster_name + } + } + } + + if (rancherURL != null && clusterName != null) { + if (ic.statusupdateraw != null) { + let icProps = ic.statusupdateraw.properties + if (icProps != null && icProps.namespace != null) { + namespace = icProps.namespace; + jobName = icProps.job_name; + + if (icProps.pod_names != null) { + podNames = icProps.pod_names; + } + } + } + } + + let rancherTableRows = [] + + if (rancherURL != null && clusterName != null) { + let baseURL = rancherURL + "/dashboard/c/" + clusterName + "/explorer"; + + if (namespace != null) { + if (jobName != null) { + let url = baseURL + "/batch.job/" + namespace + "/" + jobName; + + rancherTableRows.push( + Job + {namespace} + {jobName} + ) + } + + for (const podName of podNames) { + let url = baseURL + "/pod/" + namespace + "/" + podName; + + rancherTableRows.push( + Pod + {namespace} + {podName} + ) + } + } + } + + const refresh = () => { + dispatch(loadICbyId({token: sessionToken, id: ic.id})); + } + + return ( +
+

{ic.name} + + + refresh()} + icon='sync-alt' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + +

+
+ + +

Properties

+ + + +

Resources

+ + + + + + + + + + {rancherTableRows} + +
KindNamespaceName
+ +
+
+ + +

Raw Status

+ + +
+
+
+ ) +} + +export default KubernetesICPage; diff --git a/src/pages/infrastructure/ic-pages/managed-ics-table.js b/src/pages/infrastructure/ic-pages/managed-ics-table.js new file mode 100644 index 0000000..40c8414 --- /dev/null +++ b/src/pages/infrastructure/ic-pages/managed-ics-table.js @@ -0,0 +1,59 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import React from 'react'; +import { Table, LabelColumn, LinkColumn, DataColumn } from '../../../common/table'; +import { stateLabelStyle } from "../styles"; + + +class ManagedICsTable extends React.Component { + + render() { + + return (
+

Managed ICs:

+ + {this.props.currentUser.role === "Admin" ? + + : <> + } + + stateLabelStyle(state, component)} + /> + +
+
+ ) + } + +} + +export default ManagedICsTable; diff --git a/src/pages/infrastructure/ic-pages/manager-villas-node.js b/src/pages/infrastructure/ic-pages/manager-villas-node.js new file mode 100644 index 0000000..6221c17 --- /dev/null +++ b/src/pages/infrastructure/ic-pages/manager-villas-node.js @@ -0,0 +1,99 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import { useState, useEffect } from "react"; +import { Col, Row } from "react-bootstrap"; +import IconButton from "../../../common/buttons/icon-button"; +import ManagedICsTable from "./managed-ics-table"; +import FileSaver from 'file-saver'; +import RawDataTable from "../../../common/rawDataTable"; +import { downloadGraph } from "../../../utils/icUtils"; +import { sessionToken, currentUser } from "../../../localStorage"; +import { useDispatch, useSelector } from "react-redux"; +import { loadAllICs, loadICbyId } from "../../../store/icSlice"; + +import ICParamsTable from "../ic-params-table"; + +import { iconStyle, buttonStyle } from "../styles"; + +const ManagerVillasNode = (props) => { + + const dispatch = useDispatch(); + + const ic = props.ic; + + const ics = useSelector((state) => state.infrastructure.ICsArray); + const managedICs = ics.filter(managedIC => managedIC.category !== "manager" && managedIC.manager === ic.uuid); + const graphURL = ic.apiurl !== "" ? ic.apiurl + "/graph.svg" : ""; + + const refresh = () => { + dispatch(loadICbyId({token: sessionToken, id: ic.id})); + } + + return ( +
+

{ic.name} + + refresh()} + icon='sync-alt' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + +

+ + +

Properties

+ + + + + +
+
+ + +

Raw Status

+ + + +
+ downloadGraph(graphURL)} + icon='download' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> +
+

Graph

+
+ {"Graph +
+ +
+
) +} + +export default ManagerVillasNode; diff --git a/src/pages/infrastructure/ic-pages/manager-villas-relay.js b/src/pages/infrastructure/ic-pages/manager-villas-relay.js new file mode 100644 index 0000000..92f1d6f --- /dev/null +++ b/src/pages/infrastructure/ic-pages/manager-villas-relay.js @@ -0,0 +1,81 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import { useState, useEffect } from "react"; +import { Col, Row, Container } from "react-bootstrap"; +import IconButton from "../../../common/buttons/icon-button"; +import ManagedICsTable from "./managed-ics-table"; +import RawDataTable from "../../../common/rawDataTable"; +import { sessionToken, currentUser } from "../../../localStorage"; +import { useDispatch, useSelector } from "react-redux"; +import { loadAllICs, loadICbyId } from "../../../store/icSlice"; + +import ICParamsTable from "../ic-params-table"; + +import { iconStyle, buttonStyle } from "../styles"; + +const ManagerVillasRelay = (props) => { + + const dispatch = useDispatch(); + + const ic = props.ic; + + const ics = useSelector((state) => state.infrastructure.ICsArray); + const managedICs = ics.filter(managedIC => managedIC.category !== "manager" && managedIC.manager === ic.uuid); + + const refresh = () => { + dispatch(loadICbyId({token: sessionToken, id: ic.id})); + } + + return (
+ +

{ic.name} + + refresh()} + icon='sync-alt' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + +

+ + + +

Properties

+ + + + + +
+ + +

Raw Status

+ + +
+
+
) +} + +export default ManagerVillasRelay; diff --git a/src/pages/infrastructure/ic-params-table.js b/src/pages/infrastructure/ic-params-table.js new file mode 100644 index 0000000..a134c5b --- /dev/null +++ b/src/pages/infrastructure/ic-params-table.js @@ -0,0 +1,74 @@ +import { Table, } from 'react-bootstrap'; +import moment from 'moment'; +import { isJSON } from '../../utils/isJson'; +import ReactJson from 'react-json-view'; + +const ICParamsTable = (props) => { + const ic = props.ic; + + return( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Name{ic.name}
Description{ic.description}
UUID{ic.uuid}
State{ic.state}
Category{ic.category}
Type{ic.type}
Uptime{moment.duration(ic.uptime, "seconds").humanize()}
Location{ic.location}
Websocket URL{ic.websocketurl}
API URL{ic.apiurl}
Start parameter schema + {isJSON(ic.startparameterschema) ? + :
No Start parameter schema JSON available.
} +
+ ) +} + +export default ICParamsTable; \ No newline at end of file diff --git a/src/pages/infrastructure/ic.js b/src/pages/infrastructure/ic.js new file mode 100644 index 0000000..d7b3d2a --- /dev/null +++ b/src/pages/infrastructure/ic.js @@ -0,0 +1,63 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import { useEffect, useRef } from "react"; +import { sessionToken, currentUser } from "../../localStorage"; +import { useDispatch, useSelector } from "react-redux"; +import { useParams } from 'react-router-dom'; +import { loadAllICs, loadICbyId } from "../../store/icSlice"; +import { loadConfig } from "../../store/configSlice"; + +import DefaultManagerPage from "./ic-pages/default-manager-page"; +import GatewayVillasNode from "./ic-pages/gateway-villas-node"; +import ManagerVillasNode from "./ic-pages/manager-villas-node"; +import ManagerVillasRelay from "./ic-pages/manager-villas-relay"; +import KubernetesICPage from "./ic-pages/kubernetes-ic-page"; +import DefaultICPage from "./ic-pages/default-ic-page"; + +const InfrastructureComponent = (props) => { + const params = useParams(); + const id = params.ic; + const dispatch = useDispatch(); + + const ic = useSelector(state => state.infrastructure.currentIC); + const isICLoading = useSelector(state => state.infrastructure.isCurrentICLoading); + + useEffect(() => { + dispatch(loadAllICs({token: sessionToken})); + dispatch(loadICbyId({token: sessionToken, id: id})); + dispatch(loadConfig({token: sessionToken})); + }, []); + + if(ic == null || ic === undefined){ + return
Loading...
+ } else if(ic.category ==="gateway" && ic.type === "villas-node"){ + return + } else if(ic.category ==="manager" && ic.type === "villas-node"){ + return + } else if(ic.category ==="manager" && ic.type === "villas-relay"){ + return + } else if(ic.category ==="manager") { + return + } else if(ic.category === "simulator" && ic.type === "kubernetes"){ + return + } else { + return + } +} + +export default InfrastructureComponent; \ No newline at end of file diff --git a/src/pages/infrastructure/infrastructure.js b/src/pages/infrastructure/infrastructure.js new file mode 100644 index 0000000..a69211d --- /dev/null +++ b/src/pages/infrastructure/infrastructure.js @@ -0,0 +1,140 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import { useEffect, useState } from "react" +import { useDispatch } from "react-redux"; +import { useSelector } from "react-redux"; +import { Badge } from 'react-bootstrap'; + +import { loadAllICs, loadICbyId } from "../../store/icSlice"; +import { set } from "lodash"; + +import IconButton from "../../common/buttons/icon-button"; + +import ICCategoryTable from "./ic-category-table"; + +import { sessionToken, currentUser } from "../../localStorage"; + +const Infrastructure = (props) => { + const dispatch = useDispatch(); + + const ICsArray = useSelector(state => state.infrastructure.ICsArray); + + //track status of the modals + const [isEditModalOpened, setIsEditModalOpened] = useState(false); + const [isNewModalOpened, setIsNewModalOpened] = useState(false); + const [isImportModalOpened, setIsImportModalOpened] = useState(false); + const [isDeleteModalOpened, setIsDeleteModalOpened] = useState(false); + const [isICModalOpened, setIsICModalOpened] = useState(false); + + const [checkedICs, setCheckedICs] = useState([]); + + const currentUser = JSON.parse(localStorage.getItem("currentUser")); + + useEffect(() => { + //load array of ics and start a timer for periodic refresh + dispatch(loadAllICs({token: sessionToken})); + let timer = window.setInterval(() => refresh(), 10000); + + return () => { + window.clearInterval(timer); + } + }, []); + + const refresh = () => { + //if none of the modals are currently opened, we reload ics array + if(!(isEditModalOpened || isDeleteModalOpened || isICModalOpened)){ + dispatch(loadAllICs({token: sessionToken})); + } + } + + const buttonStyle = { + marginLeft: '10px', + } + + const iconStyle = { + height: '30px', + width: '30px' + } + + return ( +
+
+

Infrastructure + {//TODO + /* {currentUser.role === "Admin" ? + + setIsNewModalOpened(true)} + icon='plus' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + setIsImportModalOpened(true)} + icon='upload' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + + : + + } */} +

+ + + + + + + + + + + +
+ + {//TODO + /* this.closeNewModal(data)} managers={this.state.managers} /> + this.closeEditModal(data)} ic={this.state.modalIC} /> + this.closeImportModal(data)} /> + this.closeDeleteModal(e)} /> */} + +
+ ); +} + +export default Infrastructure; \ No newline at end of file diff --git a/src/pages/infrastructure/styles.js b/src/pages/infrastructure/styles.js new file mode 100644 index 0000000..eb135a5 --- /dev/null +++ b/src/pages/infrastructure/styles.js @@ -0,0 +1,58 @@ +import { isICOutdated } from "../../utils/icUtils"; + +export const buttonStyle = { + marginLeft: '5px', +} + +export const iconStyle = { + height: '25px', + width: '25px' +} + +//outputs a corresponding style for a LabelColumn Component +export const stateLabelStyle = (state, component) => { + let style = []; + + switch (state) { + case 'error': + style[0] = 'danger'; + break; + case 'idle': + style[0] = 'primary'; + break; + case 'starting': + style[0] = 'info'; + break; + case 'running': + style[0] = 'success'; + break; + case 'pausing': + style[0] = 'info'; + break; + case 'paused': + style[0] = 'info'; + break; + case 'resuming': + style[0] = 'warning'; + break; + case 'stopping': + style[0] = 'warning'; + break; + case 'resetting': + style[0] = 'warning'; + break; + case 'shuttingdown': + style[0] = 'warning'; + break; + case 'shutdown': + style[0] = 'warning'; + break; + + default: + style[0] = 'secondary'; + } + + style[1] = isICOutdated(component) && state !== 'shutdown' ? 'badge-outdated' : '' + + return style; + } \ No newline at end of file diff --git a/src/store/configSlice.js b/src/store/configSlice.js new file mode 100644 index 0000000..920fef4 --- /dev/null +++ b/src/store/configSlice.js @@ -0,0 +1,54 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import {createSlice, createAsyncThunk} from '@reduxjs/toolkit' +import RestAPI from '../common/api/rest-api'; + +const configSlice = createSlice({ + name: 'config', + initialState: { + config: {}, + isLoading: false + }, + extraReducers: builder => { + builder + .addCase(loadConfig.pending, (state, action) => { + state.isLoading = true + }) + .addCase(loadConfig.fulfilled, (state, action) => { + state.isLoading = false + state.config = action.payload; + console.log("fetched config", action.payload) + }) + } +}); + +//loads all ICs and saves them in the store +export const loadConfig = createAsyncThunk( + 'config/loadConfig', + async (data) => { + try { + const res = await RestAPI.get("/api/v2/config", null); + console.log("response:", res) + return res; + } catch (error) { + console.log("Error loading config", error); + } + } +); + +export default configSlice.reducer; \ No newline at end of file diff --git a/src/store/icSlice.js b/src/store/icSlice.js new file mode 100644 index 0000000..a94d0c2 --- /dev/null +++ b/src/store/icSlice.js @@ -0,0 +1,131 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import {createSlice, createAsyncThunk} from '@reduxjs/toolkit' +import RestAPI from '../common/api/rest-api'; + +import { sessionToken } from '../localStorage'; + +const icSlice = createSlice({ + name: 'infrastructure', + initialState: { + ICsArray: [], + checkedICsArray: [], + isLoading: false, + currentIC: {}, + isCurrentICLoading: false + + }, + reducers: { + checkICsByCategory: (state, args) => { + const category = args.payload; + + for(const ic in state.ICsArray){ + if (ic.category == category) state.checkedICsArray.push(ic) + } + } + }, + extraReducers: builder => { + builder + .addCase(loadAllICs.pending, (state, action) => { + state.isLoading = true + }) + .addCase(loadAllICs.fulfilled, (state, action) => { + state.ICsArray = action.payload; + console.log("fetched ICs") + }) + .addCase(loadICbyId.pending, (state, action) => { + state.isCurrentICLoading = true + }) + .addCase(loadICbyId.fulfilled, (state, action) => { + state.isCurrentICLoading = false + state.currentIC = action.payload; + console.log("fetched IC", state.currentIC.name) + }) + //TODO + // .addCase(restartIC.fullfilled, (state, action) => { + // console.log("restart fullfilled") + // //loadAllICs({token: sessionToken}) + // }) + // .addCase(shutdownIC.fullfilled, (state, action) => { + // console.log("shutdown fullfilled") + // //loadAllICs({token: sessionToken}) + // }) + } +}); + +//loads all ICs and saves them in the store +export const loadAllICs = createAsyncThunk( + 'infrastructure/loadAllICs', + async (data) => { + try { + const res = await RestAPI.get('/api/v2/ic', data.token); + return res.ics; + } catch (error) { + console.log("Error loading ICs data: ", error); + } + } +); + +//loads one IC by its id +export const loadICbyId = createAsyncThunk( + 'infrastructure/loadICbyId', + async (data) => { + try { + const res = await RestAPI.get('/api/v2/ic/' + data.id, data.token); + return res.ic; + } catch (error) { + console.log("Error loading IC (id=" + data.id + ") : ", error); + } + } +) + +//TODO + +//restarts ICs +export const restartIC = createAsyncThunk( + 'infrastructure/restartIC', + async (data) => { + try { + const url = data.apiurl + '/restart' + const res = await RestAPI.post(data.apiurl, null); + console.log(res) + return res; + } catch (error) { + console.log("Error restarting IC: ", error); + } + } +) + +//restarts ICs +export const shutdownIC = createAsyncThunk( + 'infrastructure/shutdownIC', + async (data) => { + try { + const url = data.apiurl + '/shutdown' + const res = await RestAPI.post(data.apiurl, null); + console.log(res) + return res; + } catch (error) { + console.log("Error shutting IC down: ", error); + } + } +) + +export const {checkICsByCategory} = icSlice.actions; + +export default icSlice.reducer; \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index 106544b..9867d04 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -15,12 +15,17 @@ * along with VILLASweb. If not, see . ******************************************************************************/ -import { configureStore } from "@reduxjs/toolkit" -import userReducer from './userSlice' +import { configureStore } from "@reduxjs/toolkit"; + +import userReducer from './userSlice'; +import icReducer from './icSlice'; +import configReducer from './configSlice' export const store = configureStore({ reducer: { - user: userReducer + user: userReducer, + infrastructure: icReducer, + config: configReducer }, devTools: true }) \ No newline at end of file diff --git a/src/store/userSlice.js b/src/store/userSlice.js index ce346d3..18f6117 100644 --- a/src/store/userSlice.js +++ b/src/store/userSlice.js @@ -21,8 +21,12 @@ import RestAPI from '../common/api/rest-api'; import ICDataDataManager from '../ic/ic-data-data-manager'; const userSlice = createSlice({ - name: 'login', - initialState: {currentUser: null, currentToken: null, isLoading: false, loginMessage: ''}, + name: 'user', + initialState: { + currentUser: null, + currentToken: null, + isLoading: false, + loginMessage: ''}, extraReducers: (builder) => { builder .addCase(login.pending, (state, action) => { @@ -62,7 +66,6 @@ export const login = createAsyncThunk( async (userData, thunkAPI) => { try { const res = await RestAPI.post('/api/v2/authenticate/internal', userData) - return {user: res.user, token: res.token} } catch(error) { console.log('Error while trying to log in: ', error) diff --git a/src/styles/login.css b/src/styles/login.css new file mode 100644 index 0000000..74b8e69 --- /dev/null +++ b/src/styles/login.css @@ -0,0 +1,42 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +.login-parent { + display: flex; + max-width: 800px; + margin: 30px auto; +} + +.login-welcome { + float: right; + max-width: 400px; + padding: 15px 20px; + border-radius: var(--borderradius) 0px 0px var(--borderradius); + background-color: #fff; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + 0 9px 18px 0 rgba(0, 0, 0, 0.1); +} + +.login-container { + float: left; + max-width: 400px; + border-radius: 0px var(--borderradius) var(--borderradius) 0px; + padding: 15px 20px; + background-color: #fff; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + 0 9px 18px 0 rgba(0, 0, 0, 0.1); +} \ No newline at end of file diff --git a/src/utils/icUtils.js b/src/utils/icUtils.js new file mode 100644 index 0000000..a74149b --- /dev/null +++ b/src/utils/icUtils.js @@ -0,0 +1,13 @@ +export const isICOutdated = (component) => { + if (!component.stateUpdateAt) + return true; + + const fiveMinutes = 5 * 60 * 1000; + + return Date.now() - new Date(component.stateUpdateAt) > fiveMinutes; +} + +export const downloadGraph = async (url) => { + let blob = await fetch(url).then(r => r.blob()) + FileSaver.saveAs(blob, this.props.ic.name + ".svg"); +} \ No newline at end of file diff --git a/src/utils/isJson.js b/src/utils/isJson.js new file mode 100644 index 0000000..14a50d0 --- /dev/null +++ b/src/utils/isJson.js @@ -0,0 +1,13 @@ +export const isJSON = (data) => { + if (data === undefined || data === null) { + return false; + } + let str = JSON.stringify(data); + try { + JSON.parse(str) + } + catch (ex) { + return false + } + return true + } \ No newline at end of file