Migrated Site Replication Pages (#3011)

- Improved & Simplified UI elements

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2023-08-24 17:07:58 -06:00
committed by GitHub
parent f4a9420002
commit dbffc5fc22
10 changed files with 477 additions and 610 deletions

View File

@@ -32,6 +32,7 @@ import {
PageLayout,
RadioGroup,
Switch,
SectionTitle,
} from "mds";
import { k8sScalarUnitsExcluding } from "../../../../../common/utils";
import { AppState, useAppDispatch } from "../../../../../store";
@@ -44,7 +45,6 @@ import {
} from "../../../../../systemSlice";
import InputUnitMenu from "../../../Common/FormComponents/InputUnitMenu/InputUnitMenu";
import TooltipWrapper from "../../../Common/TooltipWrapper/TooltipWrapper";
import SectionTitle from "../../../Common/SectionTitle";
import {
resetForm,
setEnableObjectLocking,
@@ -277,7 +277,7 @@ const AddBucket = () => {
<Box sx={{ margin: "10px 0" }}>
<BucketNamingRules errorList={validationResult} />
</Box>
<SectionTitle>Features</SectionTitle>
<SectionTitle separator>Features</SectionTitle>
<Box sx={{ marginTop: 10 }}>
{!distributedSetup && (
<Fragment>

View File

@@ -15,13 +15,21 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import { Box, LinearProgress } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { BackLink, Button, ClustersIcon, HelpBox, PageLayout } from "mds";
import {
BackLink,
Button,
ClustersIcon,
HelpBox,
PageLayout,
Box,
Grid,
ProgressBar,
InputLabel,
SectionTitle,
} from "mds";
import useApi from "../../Common/Hooks/useApi";
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
import SectionTitle from "../../Common/SectionTitle";
import {
setErrorSnackMessage,
setHelpName,
@@ -58,17 +66,18 @@ const isEmptyValue = (value: string): boolean => {
const TableHeader = () => {
return (
<React.Fragment>
<Box
sx={{
fontSize: "14px",
marginLeft: "5px",
}}
>
Site Name
<Box>
<InputLabel>Site Name</InputLabel>
</Box>
<Box>
<InputLabel>Endpoint {"*"}</InputLabel>
</Box>
<Box>
<InputLabel>Access Key {"*"}</InputLabel>
</Box>
<Box>
<InputLabel>Secret Key {"*"}</InputLabel>
</Box>
<Box sx={{ fontSize: "14px", marginLeft: "5px" }}>Endpoint {"*"}</Box>
<Box sx={{ fontSize: "14px", marginLeft: "5px" }}>Access Key {"*"}</Box>
<Box sx={{ fontSize: "14px", marginLeft: "5px" }}>Secret Key {"*"}</Box>
<Box> </Box>
</React.Fragment>
);
@@ -272,10 +281,10 @@ const AddReplicationSites = () => {
>
<SiteTypeHeader title={"This Site"} />
<Box
withBorders
sx={{
display: "grid",
gridTemplateColumns: ".8fr 1.2fr .8fr .8fr .2fr",
border: "1px solid #eaeaea",
padding: "15px",
gap: "10px",
maxHeight: "430px",
@@ -330,10 +339,10 @@ const AddReplicationSites = () => {
>
<SiteTypeHeader title={"Peer Sites"} />
<Box
withBorders
sx={{
display: "grid",
gridTemplateColumns: ".8fr 1.2fr .8fr .8fr .2fr",
border: "1px solid #eaeaea",
padding: "15px",
gap: "10px",
maxHeight: "430px",
@@ -421,11 +430,11 @@ const AddReplicationSites = () => {
}}
>
<Box>
<SectionTitle icon={<ClustersIcon />}>
<SectionTitle separator icon={<ClustersIcon />}>
Add Sites for Replication
</SectionTitle>
{isSiteInfoLoading || isAdding ? <LinearProgress /> : null}
{isSiteInfoLoading || isAdding ? <ProgressBar /> : null}
<Box
sx={{
@@ -526,17 +535,6 @@ const AddReplicationSites = () => {
flexFlow: "column",
fontSize: "14px",
flex: "2",
"& .step-number": {
color: "#ffffff",
height: "25px",
width: "25px",
background: "#081C42",
marginRight: "10px",
textAlign: "center",
fontWeight: 600,
borderRadius: "50%",
},
"& li": {
fontSize: "14px",
display: "flex",
@@ -547,15 +545,6 @@ const AddReplicationSites = () => {
"&.step-text": {
fontWeight: 400,
},
"&:before": {
content: "' '",
height: "7px",
width: "7px",
backgroundColor: "#2781B0",
marginRight: "10px",
marginTop: "12px",
flexShrink: 0,
},
},
}}
>

View File

@@ -15,10 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useState } from "react";
import { Button, EditIcon } from "mds";
import { Box, DialogContentText } from "@mui/material";
import Grid from "@mui/material/Grid";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import { Box, Button, EditIcon, Grid, InputBox, InputLabel } from "mds";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import useApi from "../../Common/Hooks/useApi";
import {
@@ -26,31 +23,24 @@ import {
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";
import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
import styled from "styled-components";
import get from "lodash/get";
const SiteEndpointContainer = styled.div(({ theme }) => ({
"& .alertText": {
color: get(theme, "signalColors.danger", "#C51B3F"),
},
}));
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<string>("");
@@ -99,7 +89,7 @@ const EditSiteEndPoint = ({
titleIcon={<EditIcon />}
onClose={onClose}
>
<DialogContentText>
<SiteEndpointContainer>
<Box
sx={{
display: "flex",
@@ -118,8 +108,8 @@ const EditSiteEndPoint = ({
</Box>
<Grid item xs={12}>
<Box sx={{ marginBottom: "5px" }}> New Endpoint:</Box>
<InputBoxWrapper
<InputLabel sx={{ marginBottom: 5 }}>New Endpoint:</InputLabel>
<InputBox
id="edit-rep-peer-endpoint"
name="edit-rep-peer-endpoint"
placeholder={"https://dr.minio-storage:9000"}
@@ -130,31 +120,32 @@ const EditSiteEndPoint = ({
value={editEndPointName}
/>
</Grid>
<Grid item xs={12} marginBottom={"15px"}>
<Box
sx={{
fontStyle: "italic",
display: "flex",
alignItems: "center",
fontSize: "12px",
marginTop: 2,
}}
>
<Box sx={{ fontWeight: 600 }}>Note:</Box>{" "}
<Box sx={{ marginLeft: 1, color: "red" }}>
Access Key and Secret Key should be same on the new site/endpoint.
</Box>
</Box>
<Grid
item
xs={12}
sx={{
marginBottom: 15,
fontStyle: "italic",
display: "flex",
alignItems: "center",
fontSize: "12px",
marginTop: 2,
}}
>
<strong>Note:</strong>&nbsp;
<span className={"alertText"}>
Access Key and Secret Key should be same on the new site/endpoint.
</span>
</Grid>
</DialogContentText>
</SiteEndpointContainer>
<Grid item xs={12} className={classes.modalButtonBar}>
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"close"}
type="button"
variant="regular"
onClick={onClose}
label={"Close"}
label={"Cancel"}
/>
<Button
id={"update"}
@@ -168,4 +159,4 @@ const EditSiteEndPoint = ({
</ModalWrapper>
);
};
export default withStyles(styles)(EditSiteEndPoint);
export default EditSiteEndPoint;

View File

@@ -15,10 +15,16 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useState } from "react";
import { Box, Grid } from "@mui/material";
import { Button, ClustersIcon, Loader } from "mds";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import {
Box,
breakPoints,
Button,
ClustersIcon,
Grid,
Loader,
Select,
InputBox,
} from "mds";
import useApi from "../../Common/Hooks/useApi";
import { StatsResponseType } from "./SiteReplicationStatus";
import BucketEntityStatus from "./LookupStatus/BucketEntityStatus";
@@ -67,10 +73,12 @@ const EntityReplicationLookup = () => {
sx={{
display: "grid",
alignItems: "center",
gridTemplateColumns: {
md: ".7fr .9fr 1.2fr .3fr",
sm: "1.2fr .7fr .7fr .3fr",
xs: "1fr",
gridTemplateColumns: ".7fr .9fr 1.2fr .3fr",
[`@media (max-width: ${breakPoints.sm}px)`]: {
gridTemplateColumns: "1fr",
},
[`@media (max-width: ${breakPoints.md}px)`]: {
gridTemplateColumns: "1.2fr .7fr .7fr .3fr",
},
gap: "15px",
}}
@@ -80,17 +88,17 @@ const EntityReplicationLookup = () => {
</Box>
<Box
sx={{
marginLeft: {
md: "-25px",
xs: "0px",
marginLeft: -25,
[`@media (max-width: ${breakPoints.sm}px)`]: {
marginLeft: 0,
},
}}
>
<SelectWrapper
<Select
id="replicationEntityLookup"
name="replicationEntityLookup"
onChange={(e) => {
setEntityType(e.target.value);
onChange={(value) => {
setEntityType(value);
setStatsLoaded(false);
}}
label=""
@@ -122,7 +130,7 @@ const EntityReplicationLookup = () => {
flex: 2,
}}
>
<InputBoxWrapper
<InputBox
id="replicationLookupEntityValue"
name="replicationLookupEntityValue"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -159,10 +167,12 @@ const EntityReplicationLookup = () => {
<Grid
item
xs={12}
display={"flex"}
alignItems={"center"}
justifyContent={"center"}
marginTop={"45px"}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
marginTop: 45,
}}
>
<Loader style={{ width: 25, height: 25 }} />
</Grid>

View File

@@ -15,8 +15,56 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Box } from "@mui/material";
import { CircleIcon } from "mds";
import styled from "styled-components";
import get from "lodash/get";
import { Box, CircleIcon } from "mds";
const LookupTableBase = styled.div(({ theme }) => ({
marginTop: 15,
table: {
width: "100%",
borderCollapse: "collapse",
"& .feature-cell": {
fontWeight: 600,
fontSize: 14,
paddingLeft: 15,
},
"& .status-cell": {
textAlign: "center",
},
"& .header-cell": {
textAlign: "center",
},
"& tr": {
height: 38,
"& td": {
borderBottom: `1px solid ${get(theme, "borderColor", "#E2E2E2")}`,
},
"& th": {
borderBottom: `2px solid ${get(theme, "borderColor", "#E2E2E2")}`,
},
},
"& .indicator": {
display: "flex",
alignItems: "center",
justifyContent: "center",
"& .min-icon": {
height: 15,
width: 15,
},
"&.active": {
"& .min-icon": {
fill: get(theme, "signalColors.good", "#4CCB92"),
},
},
"&.deactivated": {
"& .min-icon": {
fill: get(theme, "signalColors.danger", "#C51B3F"),
},
},
},
},
}));
const LookupStatusTable = ({
matrixData = [],
@@ -32,9 +80,9 @@ const LookupStatusTable = ({
const tableHeader = header.map((hC: string, hcIdx: number) => {
return (
<td className="header-cell" key={`${0}${hcIdx}`}>
<th className="header-cell" key={`${0}${hcIdx}`}>
{hC}
</td>
</th>
);
});
@@ -51,35 +99,13 @@ const LookupStatusTable = ({
}
if (v === true) {
indicator = (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
"& .min-icon": {
fill: "#4CCB92",
height: "15px",
width: "15px",
},
}}
>
<Box className={`indicator active`}>
<CircleIcon />
</Box>
);
} else if (v === false) {
indicator = (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
"& .min-icon": {
fill: "#C83B51",
height: "15px",
width: "15px",
},
}}
>
<Box className={`indicator deactivated`}>
<CircleIcon />
</Box>
);
@@ -99,34 +125,8 @@ const LookupStatusTable = ({
});
return (
<Box
sx={{
marginTop: "15px",
table: {
width: "100%",
borderCollapse: "collapse",
"& .feature-cell": {
fontWeight: 600,
fontSize: "14px",
paddingLeft: "15px",
},
"& .status-cell": {
textAlign: "center",
},
"& .header-cell": {
textAlign: "center",
},
"& tr": {
height: "38px",
},
"tr td ": {
border: "1px solid #f1f1f1",
},
},
}}
>
<Box sx={{ marginTop: "15px", marginBottom: "15px" }}>
<LookupTableBase>
<Box sx={{ marginTop: 15, marginBottom: 15 }}>
Replication status for {entityType}: <strong>{entityName}</strong>.
</Box>
<table>
@@ -135,7 +135,7 @@ const LookupStatusTable = ({
</thead>
<tbody>{tableRowsToRender}</tbody>
</table>
</Box>
</LookupTableBase>
);
};

View File

@@ -14,36 +14,38 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useState } from "react";
import List from "@mui/material/List";
import ListItemButton from "@mui/material/ListItemButton";
import { Box, DialogContentText, Tooltip } from "@mui/material";
import React, { Fragment, useState } from "react";
import {
Button,
Box,
CircleIcon,
ConfirmDeleteIcon,
EditIcon,
TrashIcon,
DataTable,
IColumns,
ItemActions,
Tooltip,
} from "mds";
import styled from "styled-components";
import get from "lodash/get";
import { ReplicationSite } from "./SiteReplication";
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
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";
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
import EditSiteEndPoint from "./EditSiteEndPoint";
const styles = (theme: Theme) =>
createStyles({
...modalStyleUtils,
...formFieldStyles,
...spacingUtils,
});
const EndpointRender = styled.div(({ theme }) => ({
display: "flex",
gap: 10,
"& .currentIndicator": {
"& .min-icon": {
width: 12,
height: 12,
fill: get(theme, "signalColors.good", "#4CCB92"),
},
},
"& .endpointName": {
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
}));
const ReplicationSites = ({
sites,
@@ -53,215 +55,93 @@ const ReplicationSites = ({
sites: ReplicationSite[];
onDeleteSite: (isAll: boolean, sites: string[]) => void;
onRefresh: () => void;
classes: any;
}) => {
const [deleteSiteKey, setIsDeleteSiteKey] = useState<string>("");
const [editSite, setEditSite] = useState<any>(null);
const replicationColumns: IColumns[] = [
{ label: "Site Name", elementKey: "name" },
{
label: "Endpoint",
elementKey: "endpoint",
renderFullObject: true,
renderFunction: (siteInfo) => (
<EndpointRender>
{siteInfo.isCurrent ? (
<Tooltip tooltip={"This site/cluster"} placement="top">
<Box className={"currentIndicator"}>
<CircleIcon />
</Box>
</Tooltip>
) : null}
<Tooltip tooltip={siteInfo.endpoint}>
<Box className={"endpointName"}>{siteInfo.endpoint}</Box>
</Tooltip>
</EndpointRender>
),
},
];
const actions: ItemActions[] = [
{
type: "edit",
onClick: (valueToSend) => setEditSite(valueToSend),
tooltip: "Edit Endpoint",
},
{
type: "delete",
onClick: (valueToSend) => setIsDeleteSiteKey(valueToSend.name),
tooltip: "Delete Site",
},
];
return (
<Box>
<List
sx={{
width: "100%",
flex: 1,
padding: "0",
marginTop: "25px",
height: "calc( 100vh - 640px )",
minHeight: "250px",
border: "1px solid #eaeaea",
marginBottom: "25px",
overflowY: "auto",
}}
component="div"
aria-labelledby="nested-list-subheader"
>
<Box
sx={{
fontWeight: 600,
borderBottom: "1px solid #f1f1f1",
padding: "25px 25px 25px 20px",
<Fragment>
<DataTable
columns={replicationColumns}
records={sites}
itemActions={actions}
idField={"name"}
customPaperHeight={"calc(100vh - 660px)"}
sx={{ marginBottom: 20 }}
/>
{deleteSiteKey !== "" && (
<ConfirmDialog
title={`Delete Replication Site`}
confirmText={"Delete"}
isOpen={deleteSiteKey !== ""}
titleIcon={<ConfirmDeleteIcon />}
isLoading={false}
onConfirm={() => {
onDeleteSite(false, [deleteSiteKey]);
}}
>
List of Replicated Sites
</Box>
{sites.map((siteInfo, index) => {
const key = `${siteInfo.name}`;
onClose={() => {
setIsDeleteSiteKey("");
}}
confirmationContent={
<Fragment>
Are you sure you want to remove the replication site:{" "}
<strong>{deleteSiteKey}</strong>?
</Fragment>
}
/>
)}
return (
<React.Fragment key={`${key}-${index}`}>
<ListItemButton
disableRipple
sx={{
display: "flex",
alignItems: "center",
border: "1px solid #f1f1f1",
borderLeft: "0",
borderRight: "0",
borderTop: "0",
padding: "6px 10px 6px 20px",
"&:hover": {
background: "#bebbbb0d",
},
"&.expanded": {
marginBottom: "0",
},
}}
>
<Box
sx={{
flex: 2,
display: "grid",
gridTemplateColumns: {
sm: "1fr 1fr ",
},
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
overflow: "hidden",
}}
>
{siteInfo.name}
</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
overflow: "hidden",
}}
>
{siteInfo.isCurrent ? (
<Tooltip title={"This site/cluster"} placement="top">
<Box
sx={{
"& .min-icon": {
height: "12px",
fill: "green",
},
}}
>
<CircleIcon />
</Box>
</Tooltip>
) : null}
<Tooltip title={siteInfo.endpoint}>
<Box
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
marginLeft: siteInfo.isCurrent ? "" : "24px",
}}
>
{siteInfo.endpoint}
</Box>
</Tooltip>
</Box>
</Box>
<Box
sx={{
display: "flex",
marginLeft: "25px",
marginRight: "25px",
width: "60px",
flexShrink: 0,
"& button": {
borderRadius: "50%",
background: "#F8F8F8",
border: "none",
"&:hover": {
background: "#E2E2E2",
},
"& svg": {
fill: "#696565",
},
},
}}
>
<TooltipWrapper tooltip="Delete Site">
<Button
id={`delete-site-${key}-${index}`}
variant="secondary"
disabled={siteInfo.isCurrent}
icon={<TrashIcon />}
onClick={(e) => {
e.preventDefault();
setIsDeleteSiteKey(key);
}}
style={{
width: "25px",
height: "25px",
padding: "0",
}}
/>
</TooltipWrapper>
<TooltipWrapper tooltip={"Edit Endpoint"}>
<Button
id={`edit-icon-${key}-${index}`}
variant="regular"
disabled={siteInfo.isCurrent}
icon={<EditIcon />}
onClick={(e) => {
e.preventDefault();
setEditSite(siteInfo);
}}
style={{
width: "25px",
height: "25px",
padding: "0",
marginLeft: "8px",
}}
/>
</TooltipWrapper>
</Box>
</ListItemButton>
{deleteSiteKey === key ? (
<ConfirmDialog
title={`Delete Replication Site`}
confirmText={"Delete"}
isOpen={true}
titleIcon={<ConfirmDeleteIcon />}
isLoading={false}
onConfirm={() => {
onDeleteSite(false, [key]);
}}
onClose={() => {
setIsDeleteSiteKey("");
}}
confirmationContent={
<DialogContentText>
Are you sure you want to remove the replication site:{" "}
{key}.?
</DialogContentText>
}
/>
) : null}
{editSite?.name === key ? (
<EditSiteEndPoint
onComplete={() => {
setEditSite(null);
onRefresh();
}}
editSite={editSite}
onClose={() => {
setEditSite(null);
}}
/>
) : null}
</React.Fragment>
);
})}
</List>
</Box>
{editSite !== null && (
<EditSiteEndPoint
onComplete={() => {
setEditSite(null);
onRefresh();
}}
editSite={editSite}
onClose={() => {
setEditSite(null);
}}
/>
)}
</Fragment>
);
};
export default withStyles(styles)(ReplicationSites);
export default ReplicationSites;

View File

@@ -15,13 +15,23 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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 { AddIcon, Button, RemoveIcon } from "mds";
import { AddIcon, Box, Button, Grid, InputBox, RemoveIcon } from "mds";
import { SiteInputRow } from "./Types";
interface ISRSiteInputRowProps {
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<string, string>;
}
const SRSiteInputRow = ({
rowData,
rowId: index,
@@ -33,30 +43,17 @@ const SRSiteInputRow = ({
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<string, string>;
}) => {
}: ISRSiteInputRowProps) => {
const { endpoint = "", accessKey = "", secretKey = "", name = "" } = rowData;
return (
<Fragment key={`${index}`}>
<Box>
<InputBoxWrapper
<InputBox
id={`add-rep-peer-site-${index}`}
name={`add-rep-peer-site-${index}`}
placeholder={`site-name`}
label=""
extraInputProps={{
readOnly: disabledFields.includes("name"),
}}
readOnly={disabledFields.includes("name")}
value={name}
onChange={(e) => {
onFieldChange(e, "name", index);
@@ -65,14 +62,12 @@ const SRSiteInputRow = ({
/>
</Box>
<Box>
<InputBoxWrapper
<InputBox
id={`add-rep-peer-site-ep-${index}`}
name={`add-rep-peer-site-ep-${index}`}
placeholder={`https://dr.minio-storage:900${index}`}
label=""
extraInputProps={{
readOnly: disabledFields.includes("endpoint"),
}}
readOnly={disabledFields.includes("endpoint")}
error={fieldErrors["endpoint"]}
value={endpoint}
onChange={(e) => {
@@ -83,7 +78,7 @@ const SRSiteInputRow = ({
</Box>
<Box>
<InputBoxWrapper
<InputBox
id={`add-rep-peer-site-ac-${index}`}
name={`add-rep-peer-site-ac-${index}`}
label=""
@@ -98,7 +93,7 @@ const SRSiteInputRow = ({
/>
</Box>
<Box>
<InputBoxWrapper
<InputBox
id={`add-rep-peer-site-sk-${index}`}
name={`add-rep-peer-site-sk-${index}`}
label=""
@@ -113,7 +108,7 @@ const SRSiteInputRow = ({
data-test-id={`add-rep-peer-site-sk-${index}`}
/>
</Box>
<Grid item xs={12} alignItems={"center"} display={"flex"}>
<Grid item xs={12} sx={{ alignItems: "center", display: "flex" }}>
<Box
sx={{
display: "flex",

View File

@@ -16,31 +16,32 @@
import React, { Fragment, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Box, DialogContentText, Grid } from "@mui/material";
import useApi from "../../Common/Hooks/useApi";
import ReplicationSites from "./ReplicationSites";
import {
ActionLink,
AddIcon,
Box,
Button,
ClustersIcon,
ConfirmDeleteIcon,
Grid,
HelpBox,
Loader,
PageLayout,
RecoverIcon,
SectionTitle,
TrashIcon,
} from "mds";
import { ErrorResponseHandler } from "../../../../common/types";
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
import {
setErrorSnackMessage,
setHelpName,
setSnackBarMessage,
} from "../../../../systemSlice";
import AButton from "../../Common/AButton/AButton";
import { useAppDispatch } from "../../../../store";
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
import useApi from "../../Common/Hooks/useApi";
import ReplicationSites from "./ReplicationSites";
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../../HelpMenu";
@@ -120,59 +121,64 @@ const SiteReplication = () => {
return (
<Fragment>
<PageHeaderWrapper label={"Site Replication"} actions={<HelpMenu />} />
<PageLayout>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
"& button": {
marginLeft: "8px",
},
}}
>
{hasSites ? (
<Fragment>
<TooltipWrapper tooltip={"Delete All"}>
<Button
id={"delete-all"}
label={"Delete All"}
variant="secondary"
disabled={isRemoving}
icon={<TrashIcon />}
onClick={() => {
setIsDeleteAll(true);
}}
/>
</TooltipWrapper>
<TooltipWrapper tooltip={"Replication Status"}>
<Button
id={"replication-status"}
label={"Replication Status"}
variant="regular"
icon={<RecoverIcon />}
onClick={(e) => {
e.preventDefault();
navigate(IAM_PAGES.SITE_REPLICATION_STATUS);
}}
/>
</TooltipWrapper>
</Fragment>
) : null}
<TooltipWrapper tooltip={"Add Replication Sites"}>
<Button
id={"add-replication-site"}
label={"Add Sites"}
variant="callAction"
disabled={isRemoving}
icon={<AddIcon />}
onClick={() => {
navigate(IAM_PAGES.SITE_REPLICATION_ADD);
<SectionTitle
separator={!!hasSites}
sx={{ marginBottom: 15 }}
actions={
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
gap: 8,
}}
/>
</TooltipWrapper>
</Box>
>
{hasSites ? (
<Fragment>
<TooltipWrapper tooltip={"Delete All"}>
<Button
id={"delete-all"}
label={"Delete All"}
variant="secondary"
disabled={isRemoving}
icon={<TrashIcon />}
onClick={() => {
setIsDeleteAll(true);
}}
/>
</TooltipWrapper>
<TooltipWrapper tooltip={"Replication Status"}>
<Button
id={"replication-status"}
label={"Replication Status"}
variant="regular"
icon={<RecoverIcon />}
onClick={(e) => {
e.preventDefault();
navigate(IAM_PAGES.SITE_REPLICATION_STATUS);
}}
/>
</TooltipWrapper>
</Fragment>
) : null}
<TooltipWrapper tooltip={"Add Replication Sites"}>
<Button
id={"add-replication-site"}
label={"Add Sites"}
variant="callAction"
disabled={isRemoving}
icon={<AddIcon />}
onClick={() => {
navigate(IAM_PAGES.SITE_REPLICATION_ADD);
}}
/>
</TooltipWrapper>
</Box>
}
>
{hasSites ? "List of Replicated Sites" : ""}
</SectionTitle>
{hasSites ? (
<ReplicationSites
sites={sites}
@@ -193,12 +199,7 @@ const SiteReplication = () => {
</Box>
) : null}
{!hasSites && !isSiteInfoLoading ? (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid container>
<Grid item xs={8}>
<HelpBox
title={"Site Replication"}
@@ -211,13 +212,15 @@ const SiteReplication = () => {
<br />
<br />
To get started,{" "}
<AButton
<ActionLink
isLoading={false}
label={""}
onClick={() => {
navigate(IAM_PAGES.SITE_REPLICATION_ADD);
}}
>
Add a Replication Site
</AButton>
</ActionLink>
.
<br />
You can learn more at our{" "}
@@ -294,9 +297,9 @@ const SiteReplication = () => {
setIsDeleteAll(false);
}}
confirmationContent={
<DialogContentText>
<Fragment>
Are you sure you want to remove all the replication sites?.
</DialogContentText>
</Fragment>
}
/>
) : null}

View File

@@ -15,30 +15,31 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import { Box, Grid } from "@mui/material";
import {
BackLink,
Box,
breakPoints,
BucketsIcon,
Button,
Grid,
GroupsIcon,
IAMPoliciesIcon,
Loader,
PageLayout,
RefreshIcon,
UsersIcon,
SectionTitle,
} from "mds";
import useApi from "../../Common/Hooks/useApi";
import { useNavigate } from "react-router-dom";
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
import { useAppDispatch } from "../../../../store";
import { setHelpName } from "../../../../systemSlice";
import useApi from "../../Common/Hooks/useApi";
import StatusCountCard from "../../Dashboard/BasicDashboard/StatusCountCard";
import EntityReplicationLookup from "./EntityReplicationLookup";
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
import { useNavigate } from "react-router-dom";
import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../../HelpMenu";
import { useAppDispatch } from "../../../../store";
import { setHelpName } from "../../../../systemSlice";
export type StatsResponseType = {
maxBuckets?: number;
@@ -66,11 +67,11 @@ const SREntityStatus = ({
const statEntityLen = Object.keys(entityStatObj || {})?.length;
return (
<Box
withBorders
sx={{
border: "1px solid #f1f1f1",
padding: "25px",
maxWidth: {
sm: "100%",
[`@media (min-width: ${breakPoints.sm}px)`]: {
maxWidth: "100%",
},
}}
>
@@ -140,8 +141,7 @@ const SiteReplicationStatus = () => {
/>
<PageLayout>
<ScreenTitle
title={"Replication status from all Sites"}
<SectionTitle
actions={
<Fragment>
<TooltipWrapper tooltip={"Refresh"}>
@@ -158,17 +158,22 @@ const SiteReplicationStatus = () => {
</TooltipWrapper>
</Fragment>
}
/>
separator
>
Replication status from all Sites
</SectionTitle>
{!isStatsLoading ? (
<Box
sx={{
display: "grid",
marginTop: "25px",
gridTemplateColumns: {
md: "1fr 1fr 1fr 1fr",
sm: "1fr 1fr",
xs: "1fr",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
[`@media (max-width: ${breakPoints.md}px)`]: {
gridTemplateColumns: "1fr 1fr",
},
[`@media (max-width: ${breakPoints.sm}px)`]: {
gridTemplateColumns: "1fr",
},
gap: "30px",
}}
@@ -202,21 +207,23 @@ const SiteReplicationStatus = () => {
<Grid
item
xs={12}
display={"flex"}
alignItems={"center"}
justifyContent={"center"}
marginTop={"45px"}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
marginTop: 45,
}}
>
<Loader style={{ width: 25, height: 25 }} />
</Grid>
)}
<Box
withBorders
sx={{
border: "1px solid #eaeaea",
minHeight: {
sm: "450px",
xs: "250px",
minHeight: 450,
[`@media (max-width: ${breakPoints.sm}px)`]: {
minHeight: 250,
},
marginTop: "25px",
padding: "25px",

View File

@@ -15,8 +15,98 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Box } from "@mui/material";
import { CircleIcon } from "mds";
import styled from "styled-components";
import get from "lodash/get";
import { Box, breakPoints, CircleIcon } from "mds";
const StatusCountBase = styled.div(({ theme }) => ({
fontFamily: "Inter,sans-serif",
maxWidth: "321px",
display: "flex",
marginLeft: "auto",
marginRight: "auto",
cursor: "default",
color: get(theme, "signalColors.main", "#07193E"),
"& .mainBox": {
flex: 1,
display: "flex",
padding: "0 8px 0 8px",
[`@media (max-width: ${breakPoints.sm}px)`]: {
padding: "0 10px 0 10px",
},
"& .indicatorIcon": {
width: "20px",
height: "20px",
marginTop: "8px",
maxWidth: "26px",
"& .min-icon": {
width: "16px",
height: "16px",
},
},
"& .indicatorContainer": {
flex: 1,
display: "flex",
flexFlow: "column",
"& .indicatorLabel": {
fontSize: "16px",
fontWeight: 600,
},
"& .counterIndicator": {
display: "flex",
alignItems: "center",
gap: "5px",
justifyContent: "space-between",
paddingBottom: 0,
fontSize: "55px",
[`@media (max-width: ${breakPoints.sm}px)`]: {
paddingBottom: 10,
fontSize: "35px",
},
[`@media (max-width: ${breakPoints.lg}px)`]: {
fontSize: "45px",
},
[`@media (max-width: ${breakPoints.xl}px)`]: {
fontSize: "50px",
},
flexFlow: "row",
fontWeight: 600,
"& .stat-text": {
color: get(theme, "mutedText", "#87888D"),
fontSize: "12px",
marginTop: "8px",
},
"& .stat-value": {
textAlign: "center",
height: "50px",
},
"& .min-icon": {
marginRight: "8px",
marginTop: "8px",
height: "10px",
width: "10px",
},
},
"& .onlineCounter": {
display: "flex",
alignItems: "center",
marginTop: "5px",
"& .min-icon": {
fill: get(theme, "signalColors.good", "#4CCB92"),
},
},
"& .offlineCount": {
display: "flex",
alignItems: "center",
marginTop: "8px",
"& .min-icon": {
fill: get(theme, "signalColors.danger", "#C51B3F"),
},
},
},
},
}));
export const StatusCountCard = ({
onlineCount = 0,
@@ -34,91 +124,15 @@ export const StatusCountCard = ({
notOkStatusText?: string;
}) => {
return (
<Box
sx={{
fontFamily: "Inter,sans-serif",
color: "#07193E",
maxWidth: "321px",
display: "flex",
marginLeft: "auto",
marginRight: "auto",
cursor: "default",
}}
>
<Box
sx={{
flex: 1,
display: "flex",
padding: {
sm: "0 8px 0 8px",
xs: "0 10px 0 10px",
},
}}
>
<Box
sx={{
flex: 1,
display: "flex",
flexFlow: "column",
}}
>
<Box
sx={{
fontSize: "16px",
fontWeight: 600,
}}
>
{label}
</Box>
<StatusCountBase>
<Box className={"mainBox"}>
<Box className={"indicatorContainer"}>
<Box className={"indicatorLabel"}>{label}</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
justifyContent: "space-between",
paddingBottom: {
md: "0px",
xs: "10px",
},
fontSize: {
xl: "55px",
lg: "50px",
md: "45px",
xs: "35px",
},
flexFlow: "row",
fontWeight: 600,
"& .stat-text": {
color: "#696969",
fontSize: "12px",
marginTop: "8px",
},
"& .stat-value": {
textAlign: "center",
height: "50px",
},
"& .min-icon": {
marginRight: "8px",
marginTop: "8px",
height: "10px",
width: "10px",
},
}}
>
<Box className={"counterIndicator"}>
<Box>
<Box className="stat-value">{onlineCount}</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
marginTop: "5px",
"& .min-icon": {
fill: "#4CCB92",
},
}}
>
<Box className={"onlineCounter"}>
<CircleIcon />
<div className="stat-text">{okStatusText}</div>
</Box>
@@ -126,38 +140,16 @@ export const StatusCountCard = ({
<Box>
<Box className="stat-value">{offlineCount}</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
marginTop: "8px",
"& .min-icon": {
fill: "#C83B51",
},
}}
>
<Box className={"offlineCount"}>
<CircleIcon />{" "}
<div className="stat-text">{notOkStatusText}</div>
</Box>
</Box>
</Box>
</Box>
<Box
sx={{
width: "20px",
height: "20px",
marginTop: "8px",
maxWidth: "26px",
"& .min-icon": {
width: "16px",
height: "16px",
},
}}
>
{icon}
</Box>
<Box className={"indicatorIcon"}>{icon}</Box>
</Box>
</Box>
</StatusCountBase>
);
};