From ec3deed38e58f02295e161b712a1bec265d9de7c Mon Sep 17 00:00:00 2001 From: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com> Date: Fri, 14 Oct 2022 05:41:29 +0530 Subject: [PATCH] Site replication support for different accesskey and secretkey in different sites (#2350) --- models/session_response.go | 3 + .../SiteReplication/AddReplicationSites.tsx | 652 +++++++++--------- .../SiteReplication/EditSiteEndPoint.tsx | 172 +++++ .../SiteReplication/ReplicationSites.tsx | 131 +--- .../SiteReplication/SRSiteInputRow.tsx | 178 +++++ .../Configurations/SiteReplication/Types.tsx | 8 + portal-ui/src/screens/Console/consoleSlice.ts | 1 + portal-ui/src/screens/Console/types.ts | 1 + restapi/embedded_spec.go | 6 + restapi/user_session.go | 1 + swagger-console.yml | 2 + 11 files changed, 713 insertions(+), 442 deletions(-) create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/EditSiteEndPoint.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/SRSiteInputRow.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/Types.tsx diff --git a/models/session_response.go b/models/session_response.go index d68eb9911..e7f37b22d 100644 --- a/models/session_response.go +++ b/models/session_response.go @@ -59,6 +59,9 @@ type SessionResponse struct { // permissions Permissions map[string][]string `json:"permissions,omitempty"` + // server end point + ServerEndPoint string `json:"serverEndPoint,omitempty"` + // status // Enum: [ok] Status string `json:"status,omitempty"` diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSites.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSites.tsx index 1b2d702e7..3a03a2f60 100644 --- a/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSites.tsx +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSites.tsx @@ -20,8 +20,7 @@ import { Box, LinearProgress } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { Button } from "mds"; import useApi from "../../Common/Hooks/useApi"; -import { AddIcon, ClustersIcon, RemoveIcon } from "../../../../icons"; -import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import { ClustersIcon } from "../../../../icons"; import PageHeader from "../../Common/PageHeader/PageHeader"; import BackLink from "../../../../common/BackLink"; import { IAM_PAGES } from "../../../../common/SecureComponent/permissions"; @@ -33,12 +32,10 @@ import { setSnackBarMessage, } from "../../../../systemSlice"; import { useAppDispatch } from "../../../../store"; -import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper"; - -type SiteInputRow = { - name: string; - endpoint: string; -}; +import { useSelector } from "react-redux"; +import { selSession } from "../../consoleSlice"; +import SRSiteInputRow from "./SRSiteInputRow"; +import { SiteInputRow } from "./Types"; const isValidEndPoint = (ep: string) => { let isValidEndPointUrl = false; @@ -49,30 +46,74 @@ const isValidEndPoint = (ep: string) => { } catch (err) { isValidEndPointUrl = false; } - if (isValidEndPointUrl || ep === "") { + if (isValidEndPointUrl) { return ""; } else { return "Invalid Endpoint"; } }; + +const isEmptyValue = (value: string): boolean => { + return value?.trim() === ""; +}; + +const TableHeader = () => { + return ( + + + Site Name + + Endpoint {"*"} + Access Key {"*"} + Secret Key {"*"} + + + ); +}; + +const SiteTypeHeader = ({ title }: { title: string }) => { + return ( + + + {title} + + + ); +}; + const AddReplicationSites = () => { const dispatch = useAppDispatch(); const navigate = useNavigate(); + const { serverEndPoint = "" } = useSelector(selSession); + + const [currentSite, setCurrentSite] = useState([ + { + endpoint: serverEndPoint, + name: "", + accessKey: "", + secretKey: "", + }, + ]); + const [existingSites, setExistingSites] = useState([]); - const [accessKey, setAccessKey] = useState(""); - const [secretKey, setSecretKey] = useState(""); - const [siteConfig, setSiteConfig] = useState([]); - const setDefaultNewRows = () => { - const defaultNewSites = existingSites?.length - ? [{ endpoint: "", name: "" }] - : [ - { endpoint: "", name: "" }, - { endpoint: "", name: "" }, - ]; - setSiteConfig(defaultNewSites); + const defaultNewSites = [ + { endpoint: "", name: "", accessKey: "", secretKey: "" }, + ]; + setExistingSites(defaultNewSites); }; const [isSiteInfoLoading, invokeSiteInfoApi] = useApi( @@ -85,17 +126,34 @@ const AddReplicationSites = () => { curSite = { ...curSite, isCurrent: true, + isSaved: true, }; - siteList.splice(foundIdx, 1, curSite); + + setCurrentSite([curSite]); + siteList.splice(foundIdx, 1); } siteList.sort((x: any, y: any) => { return x.name === curSiteName ? -1 : y.name === curSiteName ? 1 : 0; }); - setExistingSites(siteList); + + let existingSiteList = siteList.map((si: any) => { + return { + ...si, + accessKey: "", + secretKey: "", + isSaved: true, + }; + }); + + if (existingSiteList.length) { + setExistingSites(existingSiteList); + } else { + setDefaultNewRows(); + } }, (err: any) => { - setExistingSites([]); + setDefaultNewRows(); } ); @@ -108,21 +166,36 @@ const AddReplicationSites = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - setDefaultNewRows(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [existingSites]); - - const isAllEndpointsValid = - siteConfig.reduce((acc: string[], cv, i) => { - const epValue = siteConfig[i].endpoint; + const existingEndPointsValidity = existingSites.reduce( + (acc: string[], cv, i) => { + const epValue = existingSites[i].endpoint; const isEpValid = isValidEndPoint(epValue); if (isEpValid === "" && epValue !== "") { acc.push(isEpValid); } return acc; - }, []).length === siteConfig.length; + }, + [] + ); + + const isExistingCredsValidity = existingSites + .map((site) => { + return !isEmptyValue(site.accessKey) && !isEmptyValue(site.secretKey); + }) + .filter(Boolean); + + const { accessKey: cAccessKey, secretKey: cSecretKey } = currentSite[0]; + + const isCurCredsValid = + !isEmptyValue(cAccessKey) && !isEmptyValue(cSecretKey); + const peerEndpointsValid = + existingEndPointsValidity.length === existingSites.length; + const peerCredsValid = + isExistingCredsValidity.length === existingSites.length; + + let isAllFieldsValid = + isCurCredsValid && peerEndpointsValid && peerCredsValid; const [isAdding, invokeSiteAddApi] = useApi( (res: any) => { @@ -146,37 +219,181 @@ const AddReplicationSites = () => { ); const resetForm = () => { - setAccessKey(""); - setSecretKey(""); setDefaultNewRows(); + setCurrentSite((prevItems) => { + return prevItems.map((item, ix) => ({ + ...item, + accessKey: "", + secretKey: "", + name: "", + })); + }); }; const addSiteReplication = () => { - const existingSitesToAdd = existingSites?.map((es, idx) => { + const curSite: any[] = currentSite?.map((es, idx) => { return { - accessKey: accessKey, - secretKey: secretKey, + accessKey: es.accessKey, + secretKey: es.secretKey, name: es.name, endpoint: es.endpoint, }; }); - const newSitesToAdd = siteConfig.reduce((acc: any, ns, idx) => { - if (ns.endpoint) { - acc.push({ - accessKey: accessKey, - secretKey: secretKey, - name: ns.name || `dr-site-${idx}`, - endpoint: ns.endpoint, - }); - } - return acc; - }, []); + const newOrExistingSitesToAdd = existingSites.reduce( + (acc: any, ns, idx) => { + if (ns.endpoint) { + acc.push({ + accessKey: ns.accessKey, + secretKey: ns.secretKey, + name: ns.name || `dr-site-${idx}`, + endpoint: ns.endpoint, + }); + } + return acc; + }, + [] + ); - invokeSiteAddApi("POST", `api/v1/admin/site-replication`, [ - ...(existingSitesToAdd || []), - ...(newSitesToAdd || []), - ]); + const sitesToAdd = curSite.concat(newOrExistingSitesToAdd); + + invokeSiteAddApi("POST", `api/v1/admin/site-replication`, sitesToAdd); + }; + + const renderCurrentSite = () => { + return ( + + + + + + {currentSite.map((cs, index) => { + const accessKeyError = isEmptyValue(cs.accessKey) + ? "AccessKey is required" + : ""; + const secretKeyError = isEmptyValue(cs.secretKey) + ? "SecretKey is required" + : ""; + return ( + { + const filedValue = e.target.value; + if (fieldName !== "") { + setCurrentSite((prevItems) => { + return prevItems.map((item, ix) => + ix === index + ? { ...item, [fieldName]: filedValue } + : item + ); + }); + } + }} + showRowActions={false} + /> + ); + })} + + + ); + }; + + const renderPeerSites = () => { + return ( + + + + + + {existingSites.map((ps, index) => { + const endPointError = isValidEndPoint(ps.endpoint); + + const accessKeyError = isEmptyValue(ps.accessKey) + ? "AccessKey is required" + : ""; + const secretKeyError = isEmptyValue(ps.secretKey) + ? "SecretKey is required" + : ""; + + return ( + { + const filedValue = e.target.value; + setExistingSites((prevItems) => { + return prevItems.map((item, ix) => + ix === index ? { ...item, [fieldName]: filedValue } : item + ); + }); + }} + canAdd={true} + canRemove={index > 0 && !ps.isSaved} + onAddClick={() => { + const newRows = [...existingSites]; + //add at the next index + newRows.splice(index + 1, 0, { + name: "", + endpoint: "", + accessKey: "", + secretKey: "", + }); + + setExistingSites(newRows); + }} + onRemoveClick={(index) => { + setExistingSites( + existingSites.filter((_, idx) => idx !== index) + ); + }} + /> + ); + })} + + + ); }; return ( @@ -195,10 +412,7 @@ const AddReplicationSites = () => { display: "grid", padding: "25px", gap: "25px", - gridTemplateColumns: { - md: "2fr 1.2fr", - xs: "1fr", - }, + gridTemplateColumns: "1fr", border: "1px solid #eaeaea", }} > @@ -208,6 +422,18 @@ const AddReplicationSites = () => { {isSiteInfoLoading || isAdding ? : null} + + + Note: AccessKey and SecretKey values for every site is required + while adding or editing peer sites + { return addSiteReplication(); }} > - - - Note:{" "} - - Access Key and Secret Key should be same on all sites. - - - - - ) => { - setAccessKey(event.target.value); - }} - label="Access Key" - required={true} - value={accessKey} - error={accessKey === "" ? "Access Key is required." : ""} - data-test-id={"add-site-rep-acc-key"} - /> - - - ) => { - setSecretKey(event.target.value); - }} - error={secretKey === "" ? "Secret Key is required." : ""} - label="Secret Key" - value={secretKey} - data-test-id={"add-site-rep-sec-key"} - /> - + {renderCurrentSite()} - - - Peer Sites - - - - - - Site Name - - - Endpoint {"*"} - - - {existingSites?.map((si, index) => { - return ( - - - {}} - /> - - - {}} - /> - - - {" "} - - - ); - })} - - {siteConfig.map((sci, index) => { - let isDelDisabled = false; - - if (existingSites?.length && index === 0) { - isDelDisabled = true; - } else if (!existingSites?.length && index < 2) { - isDelDisabled = true; - } - - return ( - - - { - const nameTxt = e.target.value; - setSiteConfig((prevItems) => { - return prevItems.map((item, ix) => - ix === index ? { ...item, name: nameTxt } : item - ); - }); - }} - data-test-id={`add-site-rep-peer-site-${index}`} - /> - - - { - const epTxt = e.target.value; - setSiteConfig((prevItems) => { - return prevItems.map((item, ix) => - ix === index - ? { ...item, endpoint: epTxt } - : item - ); - }); - }} - data-test-id={`add-site-rep-peer-ep-${index}`} - /> - - - - - } - onClick={(e) => { - e.preventDefault(); - const newRows = [...siteConfig]; - //add at the next index - newRows.splice(index + 1, 0, { - name: "", - endpoint: "", - }); - - setSiteConfig(newRows); - }} - style={{ - width: 25, - height: 25, - padding: 0, - }} - /> - - - } - onClick={(e) => { - e.preventDefault(); - setSiteConfig( - siteConfig.filter((_, idx) => idx !== index) - ); - }} - style={{ - width: 25, - height: 25, - padding: 0, - marginLeft: 8, - }} - /> - - - - - ); - })} - + {renderPeerSites()} { id={"save"} type="submit" variant="callAction" - disabled={ - isAdding || - !accessKey || - !secretKey || - !isAllEndpointsValid - } + disabled={isAdding || !isAllFieldsValid} label={"Save"} /> @@ -557,11 +533,12 @@ const AddReplicationSites = () => { borderRadius: "50%", }, - "& .step-row": { + "& li": { fontSize: "14px", display: "flex", marginTop: "15px", marginBottom: "15px", + width: "100%", "&.step-text": { fontWeight: 400, @@ -593,21 +570,52 @@ const AddReplicationSites = () => { owned by the root user) - Changes to Bucket features such as - - Bucket Policies - Bucket Tags - Bucket Object-Lock configurations - Bucket Encryption configuration - + + + Changes to Bucket features such as + + + Bucket Policies + Bucket Tags + Bucket Object-Lock configurations + Bucket Encryption configuration + + - The following Bucket features will NOT be replicated - - Bucket notification configuration - Bucket lifecycle (ILM) configuration - + + + The following Bucket features will NOT be replicated + + + + Bucket notification configuration + Bucket lifecycle (ILM) configuration + + diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/EditSiteEndPoint.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/EditSiteEndPoint.tsx new file mode 100644 index 000000000..b189d135b --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/EditSiteEndPoint.tsx @@ -0,0 +1,172 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { useState } from "react"; +import { EditIcon } from "../../../../icons"; +import { Box, DialogContentText } from "@mui/material"; +import Grid from "@mui/material/Grid"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import { Button } from "mds"; +import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper"; +import useApi from "../../Common/Hooks/useApi"; +import { + setErrorSnackMessage, + setSnackBarMessage, +} from "../../../../systemSlice"; +import { useAppDispatch } from "../../../../store"; +import withStyles from "@mui/styles/withStyles"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import { + formFieldStyles, + modalStyleUtils, + spacingUtils, +} from "../../Common/FormComponents/common/styleLibrary"; + +const styles = (theme: Theme) => + createStyles({ + ...modalStyleUtils, + ...formFieldStyles, + ...spacingUtils, + }); +const EditSiteEndPoint = ({ + editSite = {}, + onClose, + onComplete, + classes = {}, +}: { + editSite: any; + onClose: () => void; + onComplete: () => void; + classes: any; +}) => { + const dispatch = useAppDispatch(); + const [editEndPointName, setEditEndPointName] = useState(""); + + const [isEditing, invokeSiteEditApi] = useApi( + (res: any) => { + if (res.success) { + dispatch(setSnackBarMessage(res.status)); + } else { + dispatch( + setErrorSnackMessage({ + errorMessage: "Error", + detailedError: res.status, + }) + ); + } + onComplete(); + }, + (err: any) => { + dispatch(setErrorSnackMessage(err)); + onComplete(); + } + ); + + const updatePeerSite = () => { + invokeSiteEditApi("PUT", `api/v1/admin/site-replication`, { + endpoint: editEndPointName, + name: editSite.name, + deploymentId: editSite.deploymentID, // readonly + }); + }; + + let isValidEndPointUrl = false; + + try { + new URL(editEndPointName); + isValidEndPointUrl = true; + } catch (err) { + isValidEndPointUrl = false; + } + + return ( + } + onClose={onClose} + > + + + + Site: {" "} + {editSite.name} + + + Current Endpoint: {" "} + {editSite.endpoint} + + + + + New Endpoint: + ) => { + setEditEndPointName(event.target.value); + }} + label="" + value={editEndPointName} + /> + + + + Note:{" "} + + Access Key and Secret Key should be same on the new site/endpoint. + + + + + + + + + + + ); +}; +export default withStyles(styles)(EditSiteEndPoint); diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx index b14f0c623..43f1afce3 100644 --- a/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx @@ -23,11 +23,6 @@ import { ReplicationSite } from "./SiteReplication"; import TrashIcon from "../../../../icons/TrashIcon"; import { CircleIcon, ConfirmDeleteIcon, EditIcon } from "../../../../icons"; import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog"; -import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; -import Grid from "@mui/material/Grid"; -import useApi from "../../Common/Hooks/useApi"; - -import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper"; import withStyles from "@mui/styles/withStyles"; import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; @@ -36,12 +31,8 @@ import { modalStyleUtils, spacingUtils, } from "../../Common/FormComponents/common/styleLibrary"; -import { - setErrorSnackMessage, - setSnackBarMessage, -} from "../../../../systemSlice"; -import { useAppDispatch } from "../../../../store"; import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper"; +import EditSiteEndPoint from "./EditSiteEndPoint"; const styles = (theme: Theme) => createStyles({ @@ -54,54 +45,14 @@ const ReplicationSites = ({ sites, onDeleteSite, onRefresh, - classes, }: { sites: ReplicationSite[]; onDeleteSite: (isAll: boolean, sites: string[]) => void; onRefresh: () => void; classes: any; }) => { - const dispatch = useAppDispatch(); const [deleteSiteKey, setIsDeleteSiteKey] = useState(""); const [editSite, setEditSite] = useState(null); - const [editEndPointName, setEditEndPointName] = useState(""); - - const [isEditing, invokeSiteEditApi] = useApi( - (res: any) => { - if (res.success) { - setEditSite(null); - dispatch(setSnackBarMessage(res.status)); - } else { - dispatch( - setErrorSnackMessage({ - errorMessage: "Error", - detailedError: res.status, - }) - ); - } - onRefresh(); - }, - (err: any) => { - dispatch(setErrorSnackMessage(err)); - onRefresh(); - } - ); - const updatePeerSite = () => { - invokeSiteEditApi("PUT", `api/v1/admin/site-replication`, { - endpoint: editEndPointName, - name: editSite.name, - deploymentId: editSite.deploymentID, // readonly - }); - }; - - let isValidEndPointUrl = false; - - try { - new URL(editEndPointName); - isValidEndPointUrl = true; - } catch (err) { - isValidEndPointUrl = false; - } return ( @@ -229,17 +180,11 @@ const ReplicationSites = ({ }, }} > - + } onClick={(e) => { e.preventDefault(); @@ -256,6 +201,7 @@ const ReplicationSites = ({ } onClick={(e) => { e.preventDefault(); @@ -295,71 +241,16 @@ const ReplicationSites = ({ ) : null} {editSite?.name === key ? ( - } + { + setEditSite(null); + onRefresh(); + }} + editSite={editSite} onClose={() => { setEditSite(null); }} - > - - - - Site: {" "} - {editSite.name} - - - Current Endpoint: {" "} - {editSite.endpoint} - - - - - New Endpoint: - - ) => { - setEditEndPointName(event.target.value); - }} - label="" - value={editEndPointName} - /> - - - - - { - setEditSite(null); - }} - label={"Close"} - /> - { - updatePeerSite(); - }} - label={"Update"} - /> - - + /> ) : null} ); diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/SRSiteInputRow.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/SRSiteInputRow.tsx new file mode 100644 index 000000000..8d3c415b5 --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/SRSiteInputRow.tsx @@ -0,0 +1,178 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment } from "react"; +import { Box } from "@mui/material"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import Grid from "@mui/material/Grid"; +import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper"; +import { Button } from "mds"; +import { AddIcon, RemoveIcon } from "../../../../icons"; +import { SiteInputRow } from "./Types"; + +const SRSiteInputRow = ({ + rowData, + rowId: index, + onFieldChange, + onAddClick, + onRemoveClick, + canAdd = true, + canRemove = true, + showRowActions = true, + disabledFields = [], + fieldErrors = {}, +}: { + rowData: SiteInputRow; + rowId: number; + onFieldChange: (e: any, fieldName: string, index: number) => void; + onAddClick?: (index: number) => void; + onRemoveClick?: (index: number) => void; + canAdd?: boolean; + canRemove?: boolean; + showRowActions?: boolean; + disabledFields?: string[]; + fieldErrors?: Record; +}) => { + const { endpoint = "", accessKey = "", secretKey = "", name = "" } = rowData; + return ( + + + { + onFieldChange(e, "name", index); + }} + data-test-id={`add-site-rep-peer-site-${index}`} + /> + + + { + onFieldChange(e, "endpoint", index); + }} + data-test-id={`add-site-rep-peer-ep-${index}`} + /> + + + + { + onFieldChange(e, "accessKey", index); + }} + data-test-id={`add-rep-peer-site-ac-${index}`} + /> + + + { + onFieldChange(e, "secretKey", index); + }} + data-test-id={`add-rep-peer-site-sk-${index}`} + /> + + + + {showRowActions ? ( + + + } + onClick={(e) => { + e.preventDefault(); + onAddClick?.(index); + }} + style={{ + width: 25, + height: 25, + padding: 0, + }} + /> + + + } + onClick={(e) => { + e.preventDefault(); + onRemoveClick?.(index); + }} + style={{ + width: 25, + height: 25, + padding: 0, + marginLeft: 8, + }} + /> + + + ) : null} + + + + ); +}; + +export default SRSiteInputRow; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/Types.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/Types.tsx new file mode 100644 index 000000000..bc7478821 --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/Types.tsx @@ -0,0 +1,8 @@ +export type SiteInputRow = { + name: string; + endpoint: string; + accessKey: string; + secretKey: string; + isCurrent?: boolean; + isSaved?: boolean; +}; diff --git a/portal-ui/src/screens/Console/consoleSlice.ts b/portal-ui/src/screens/Console/consoleSlice.ts index ee6cf6a01..396554a35 100644 --- a/portal-ui/src/screens/Console/consoleSlice.ts +++ b/portal-ui/src/screens/Console/consoleSlice.ts @@ -32,6 +32,7 @@ const initialState: ConsoleState = { allowResources: null, customStyles: null, envConstants: null, + serverEndPoint: "", }, }; diff --git a/portal-ui/src/screens/Console/types.ts b/portal-ui/src/screens/Console/types.ts index 0dc97e85d..7452ae1e5 100644 --- a/portal-ui/src/screens/Console/types.ts +++ b/portal-ui/src/screens/Console/types.ts @@ -41,6 +41,7 @@ export interface ISessionResponse { allowResources: IAllowResources[] | null; customStyles?: string | null; envConstants?: IEnvironmentContants | null; + serverEndPoint?: string | undefined; } export interface ButtonProps { diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index b900ce6e1..59ee422d7 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -7289,6 +7289,9 @@ func init() { } } }, + "serverEndPoint": { + "type": "string" + }, "status": { "type": "string", "enum": [ @@ -15542,6 +15545,9 @@ func init() { } } }, + "serverEndPoint": { + "type": "string" + }, "status": { "type": "string", "enum": [ diff --git a/restapi/user_session.go b/restapi/user_session.go index b62124a64..01c2a39be 100644 --- a/restapi/user_session.go +++ b/restapi/user_session.go @@ -254,6 +254,7 @@ func getSessionResponse(ctx context.Context, session *models.Principal) (*models AllowResources: allowResources, CustomStyles: customStyles, EnvConstants: &envConstants, + ServerEndPoint: getMinIOServer(), } return sessionResp, nil } diff --git a/swagger-console.yml b/swagger-console.yml index e20599754..3ca0da32a 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -4143,6 +4143,8 @@ definitions: type: boolean distributedMode: type: boolean + serverEndPoint: + type: string permissions: type: object additionalProperties: