diff --git a/dashboard/src/App.js b/dashboard/src/App.js index b4755fade3..b6d6e81a95 100644 --- a/dashboard/src/App.js +++ b/dashboard/src/App.js @@ -11,6 +11,7 @@ import { Routes, } from "react-router-dom"; import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; import { AuthForm } from "modules/components/AuthComponent/common-components"; import AuthLayout from "modules/containers/AuthLayout"; @@ -20,13 +21,12 @@ import MainLayout from "modules/containers/MainLayout"; import NoMatchingPage from "modules/components/EmptyPageComponent/NoMatchingPage"; import OverviewComponent from "modules/components/OverviewComponent"; import ProfileComponent from "modules/components/ProfileComponent"; +import { ReactKeycloakProvider } from "@react-keycloak/web"; import TableOfContent from "modules/components/TableOfContent"; import TableWithFavorite from "modules/components/TableComponent"; import favicon from "./assets/logo/favicon.ico"; import { fetchEndpoints } from "./actions/endpointAction"; import { showToast } from "actions/toastActions"; -import { useDispatch, useSelector } from "react-redux"; -import { ReactKeycloakProvider } from "@react-keycloak/web"; const ProtectedRoute = ({ redirectPath = APP_ROUTES.AUTH, children }) => { const loggedIn = Cookies.get("isLoggedIn"); diff --git a/dashboard/src/actions/relayActions.js b/dashboard/src/actions/relayActions.js new file mode 100644 index 0000000000..f2f6b5070e --- /dev/null +++ b/dashboard/src/actions/relayActions.js @@ -0,0 +1,42 @@ +import * as TYPES from "./types"; + +import { DANGER, SUCCESS } from "assets/constants/toastConstants"; + +import API from "../utils/axiosInstance"; +import { getDatasets } from "./overviewActions"; +import { showToast } from "./toastActions"; +import { uriTemplate } from "../utils/helper"; + +export const uploadFile = (fileURI) => async (dispatch, getState) => { + try { + dispatch({ type: TYPES.LOADING }); + const endpoints = getState().apiEndpoint.endpoints; + + const uri = uriTemplate(endpoints, "relay", { uri: fileURI }); + const response = await API.post(uri, null, null); + if (response.status >= 200 && response.status < 300) { + dispatch(showToast(SUCCESS, response.data.message)); + dispatch(setRelayModalState(false)); + dispatch(handleInputChange("")); + if (response.status === 201) { + // need to remove once response returns the uploaded dataset + dispatch(getDatasets()); + } + } + } catch (error) { + dispatch( + showToast(DANGER, error?.response?.data?.message ?? `Error: ${error}`) + ); + dispatch({ type: TYPES.NETWORK_ERROR }); + } + dispatch({ type: TYPES.COMPLETED }); +}; +export const setRelayModalState = (isOpen) => ({ + type: TYPES.TOGGLE_RELAY_MODAL, + payload: isOpen, +}); + +export const handleInputChange = (value) => ({ + type: TYPES.SET_RELAY_DATA, + payload: value, +}); diff --git a/dashboard/src/actions/types.js b/dashboard/src/actions/types.js index 543ba03456..bb732b55c5 100644 --- a/dashboard/src/actions/types.js +++ b/dashboard/src/actions/types.js @@ -40,6 +40,8 @@ export const EXPIRING_RUNS = "EXPIRING_RUNS"; export const SET_DASHBOARD_LOADING = "SET_DASHBOARD_LOADING"; export const SET_LOADING_FLAG = "SET_LOADING_FLAG"; export const SELECTED_SAVED_RUNS = "SELECTED_SAVED_RUNS"; +export const TOGGLE_RELAY_MODAL = "TOGGLE_RELAY_MODAL"; +export const SET_RELAY_DATA = "SET_RELAY_DATA"; /* TABLE OF CONTENT */ export const GET_TOC_DATA = "GET_TOC_DATA"; diff --git a/dashboard/src/modules/components/OverviewComponent/common-component.jsx b/dashboard/src/modules/components/OverviewComponent/common-component.jsx index b6f4518acb..0bce6273ce 100644 --- a/dashboard/src/modules/components/OverviewComponent/common-component.jsx +++ b/dashboard/src/modules/components/OverviewComponent/common-component.jsx @@ -33,6 +33,7 @@ import { getDatasets, updateMultipleDataset } from "actions/overviewActions"; import { useDispatch, useSelector } from "react-redux"; import { formatDateTime } from "utils/dateFunctions"; +import { setRelayModalState } from "actions/relayActions"; export const Heading = (props) => { return ( @@ -128,6 +129,14 @@ export const NewRunsHeading = () => { > Refresh results + { {savedRuns.length > 0 ? : } + )} diff --git a/dashboard/src/modules/components/OverviewComponent/index.less b/dashboard/src/modules/components/OverviewComponent/index.less index 56bfa2d351..a6efde36a5 100644 --- a/dashboard/src/modules/components/OverviewComponent/index.less +++ b/dashboard/src/modules/components/OverviewComponent/index.less @@ -44,6 +44,9 @@ display: flex; justify-content: flex-end; margin-bottom: 1.5vh; + .relay-button { + margin-right: 10px; + } } .newruns-table-container { height: 90%; diff --git a/dashboard/src/modules/components/RelayUIComponent/index.jsx b/dashboard/src/modules/components/RelayUIComponent/index.jsx new file mode 100644 index 0000000000..ad1a4ecaae --- /dev/null +++ b/dashboard/src/modules/components/RelayUIComponent/index.jsx @@ -0,0 +1,114 @@ +import "./index.less"; + +import { + ActionGroup, + Button, + Card, + CardBody, + Form, + FormGroup, + Modal, + ModalVariant, + Popover, + TextInput, + Title, +} from "@patternfly/react-core"; +import { + handleInputChange, + setRelayModalState, + uploadFile, +} from "actions/relayActions"; +import { useDispatch, useSelector } from "react-redux"; + +import { OutlinedQuestionCircleIcon } from "@patternfly/react-icons"; +import React from "react"; + +const AboutComponent = () => ( +
+

+ The Pbench Agent can push datasets to a Pbench Server directly, when the + server is accessible from the system where the Pbench Agent is being used. +

+

+ When a firewall prevents this direct access, the Pbench Agent can store a + dataset tarball and a manifest file on a file relay server which can be + reached by both the Pbench Agent and the Pbench Server. +

+

+ Enter the relay server URI reported by the Pbench Agent and press Submit + to begin the upload. +

+
+); +const PopoverComponent = () => ( + About} + bodyContent={} + maxWidth="4vw" + > + + +); + +const RelayComponent = () => { + const { isRelayModalOpen, relayInput } = useSelector( + (state) => state.overview + ); + const dispatch = useDispatch(); + + const handleClose = () => { + dispatch(handleInputChange("")); + dispatch(setRelayModalState(false)); + }; + + return ( + +
+ Relay + +
+
+ + +
+ + dispatch(handleInputChange(value))} + /> + + + + +
+
+
+
+
+ ); +}; + +export default RelayComponent; diff --git a/dashboard/src/modules/components/RelayUIComponent/index.less b/dashboard/src/modules/components/RelayUIComponent/index.less new file mode 100644 index 0000000000..f3da8d0df5 --- /dev/null +++ b/dashboard/src/modules/components/RelayUIComponent/index.less @@ -0,0 +1,23 @@ +.relay-ui-container { + padding: 2vw; + .divider { + margin-top: 5vh; + } + .modal-heading { + display: flex; + } + .card-wrapper { + height: 45vh; + display: flex; + align-items: center; + justify-content: center; + .pf-c-card { + width: 45vw; + } + } +} +.about-container { + p { + margin-bottom: 0.5vw; + } +} diff --git a/dashboard/src/reducers/overviewReducer.js b/dashboard/src/reducers/overviewReducer.js index c755c3ae3e..35fd537b19 100644 --- a/dashboard/src/reducers/overviewReducer.js +++ b/dashboard/src/reducers/overviewReducer.js @@ -10,6 +10,8 @@ const initialState = { selectedSavedRuns: [], expiringRuns: [], loadingDone: !!sessionStorage.getItem("loadingDone"), + isRelayModalOpen: false, + relayInput: "", }; const OverviewReducer = (state = initialState, action = {}) => { @@ -60,6 +62,16 @@ const OverviewReducer = (state = initialState, action = {}) => { ...state, loadingDone: payload, }; + case TYPES.TOGGLE_RELAY_MODAL: + return { + ...state, + isRelayModalOpen: payload, + }; + case TYPES.SET_RELAY_DATA: + return { + ...state, + relayInput: payload, + }; default: return state; }