diff --git a/portal-ui/src/icons/PasswordKeyIcon.tsx b/portal-ui/src/icons/PasswordKeyIcon.tsx index 7ab0f67f3..4396909bd 100644 --- a/portal-ui/src/icons/PasswordKeyIcon.tsx +++ b/portal-ui/src/icons/PasswordKeyIcon.tsx @@ -26,19 +26,8 @@ const PasswordKeyIcon = (props: SVGProps) => { {...props} > - ); diff --git a/portal-ui/src/screens/Console/Common/UsageBar/UsageBar.tsx b/portal-ui/src/screens/Console/Common/UsageBar/UsageBar.tsx index cff7a19b9..a8141f047 100644 --- a/portal-ui/src/screens/Console/Common/UsageBar/UsageBar.tsx +++ b/portal-ui/src/screens/Console/Common/UsageBar/UsageBar.tsx @@ -45,10 +45,11 @@ const UsageBar = ({ overflow: "hidden", }} > - {sizeItems.map((sizeElement) => { + {sizeItems.map((sizeElement, index) => { const itemPercentage = (sizeElement.value * 100) / totalValue; return (
. + +import React, { useState, useEffect } from "react"; +import { connect } from "react-redux"; +import { Theme } from "@mui/material/styles"; +import { Button, Grid, IconButton } from "@mui/material"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import AddIcon from "@mui/icons-material/Add"; +import { + formFieldStyles, + modalStyleUtils, +} from "../../Common/FormComponents/common/styleLibrary"; +import { setModalErrorSnackMessage } from "../../../../actions"; +import { + ErrorResponseHandler, + IDomainsRequest, +} from "../../../../common/types"; +import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import api from "../../../../common/api"; +import RemoveIcon from "../../../../icons/RemoveIcon"; + +interface IEditDomains { + open: boolean; + closeModalAndRefresh: (update: boolean) => any; + namespace: string; + idTenant: string; + domains: IDomainsRequest | null; + setModalErrorSnackMessage: typeof setModalErrorSnackMessage; + classes: any; +} + +const styles = (theme: Theme) => + createStyles({ + buttonContainer: { + textAlign: "right", + }, + infoText: { + fontSize: 14, + }, + domainInline: { + display: "flex", + marginBottom: 15, + }, + overlayAction: { + marginLeft: 10, + display: "flex", + alignItems: "center", + "& svg": { + width: 15, + height: 15, + }, + "& button": { + background: "#EAEAEA", + }, + }, + ...formFieldStyles, + ...modalStyleUtils, + }); + +const EditDomains = ({ + open, + closeModalAndRefresh, + namespace, + idTenant, + domains, + setModalErrorSnackMessage, + classes, +}: IEditDomains) => { + const [isSending, setIsSending] = useState(false); + const [consoleDomain, setConsoleDomain] = useState(""); + const [minioDomains, setMinioDomains] = useState([""]); + const [consoleDomainValid, setConsoleDomainValid] = useState(true); + const [minioDomainValid, setMinioDomainValid] = useState([true]); + + useEffect(() => { + if (domains) { + const consoleDomainSet = domains.console || ""; + setConsoleDomain(consoleDomainSet); + + if (consoleDomainSet !== "") { + // We Validate console domain + const consoleRegExp = new RegExp( + /((http|https):\/\/)+[a-zA-Z0-9\-.]{3,}\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?(:[1-9]{1}([0-9]{1,4})?)?(\/[a-zA-Z0-9]{1,})*?$/ + ); + + setConsoleDomainValid(consoleRegExp.test(consoleDomainSet)); + } else { + setConsoleDomainValid(true); + } + + if (domains.minio && domains.minio.length > 0) { + setMinioDomains(domains.minio); + + const minioRegExp = new RegExp( + /((http|https):\/\/)+[a-zA-Z0-9\-.]{3,}\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?$/ + ); + + const initialValidations = domains.minio.map((domain) => { + if (domain.trim() !== "") { + return minioRegExp.test(domain); + } else { + return true; + } + }); + + setMinioDomainValid(initialValidations); + } + } + }, [domains]); + + const closeAction = () => { + closeModalAndRefresh(false); + }; + + const resetForm = () => { + setConsoleDomain(""); + setConsoleDomainValid(true); + setMinioDomains([""]); + setMinioDomainValid([true]); + }; + + const updateDomainsList = () => { + setIsSending(true); + + let payload = { + domains: { + console: consoleDomain, + minio: minioDomains.filter((minioDomain) => minioDomain.trim() !== ""), + }, + }; + api + .invoke( + "PUT", + `/api/v1/namespaces/${namespace}/tenants/${idTenant}/domains`, + payload + ) + .then(() => { + setIsSending(false); + closeModalAndRefresh(true); + }) + .catch((error: ErrorResponseHandler) => { + setModalErrorSnackMessage(error); + setIsSending(false); + }); + }; + + const updateMinIODomain = (value: string, index: number) => { + const cloneDomains = [...minioDomains]; + cloneDomains[index] = value; + + setMinioDomains(cloneDomains); + }; + + const addNewMinIODomain = () => { + const cloneDomains = [...minioDomains]; + const cloneValidations = [...minioDomainValid]; + + cloneDomains.push(""); + cloneValidations.push(true); + + setMinioDomains(cloneDomains); + setMinioDomainValid(cloneValidations); + }; + + const removeMinIODomain = (removeIndex: number) => { + const filteredDomains = minioDomains.filter( + (_, index) => index !== removeIndex + ); + + const filterValidations = minioDomainValid.filter( + (_, index) => index !== removeIndex + ); + + setMinioDomains(filteredDomains); + setMinioDomainValid(filterValidations); + }; + + const setMinioDomainValidation = (domainValid: boolean, index: number) => { + const cloneValidation = [...minioDomainValid]; + cloneValidation[index] = domainValid; + + setMinioDomainValid(cloneValidation); + }; + + return ( + + + + +
+ ) => { + setConsoleDomain(e.target.value); + + setConsoleDomainValid(e.target.validity.valid); + }} + label="Console Domain" + value={consoleDomain} + placeholder={ + "Eg. http://subdomain.domain:port/subpath1/subpath2" + } + pattern={ + "((http|https):\\/\\/)+[a-zA-Z0-9\\-.]{3,}\\.[a-zA-Z]{2,}(\\.[a-zA-Z]{2,})?(:[1-9]{1}([0-9]{1,4})?)?(\\/[a-zA-Z0-9]{1,})*?$" + } + error={ + !consoleDomainValid + ? "Domain format is incorrect (http|https://subdomain.domain:port/subpath1/subpath2)" + : "" + } + /> +
+
+

MinIO Domains

+
+ {minioDomains.map((domain, index) => { + return ( +
+ ) => { + updateMinIODomain(e.target.value, index); + setMinioDomainValidation( + e.target.validity.valid, + index + ); + }} + label={`MinIO Domain ${index + 1}`} + value={domain} + placeholder={"Eg. http://subdomain.domain"} + pattern={ + "((http|https):\\/\\/)+[a-zA-Z0-9\\-.]{3,}\\.[a-zA-Z]{2,}(\\.[a-zA-Z]{2,})?$" + } + error={ + !minioDomainValid[index] + ? "MinIO domain format is incorrect (http|https://subdomain.domain)" + : "" + } + /> +
+ + + +
+ +
+ removeMinIODomain(index)} + disabled={minioDomains.length <= 1} + > + + +
+
+ ); + })} +
+
+
+ + + + +
+
+
+ ); +}; +const connector = connect(null, { + setModalErrorSnackMessage, +}); + +export default withStyles(styles)(connector(EditDomains)); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx index 9c3f731fb..f5b6940b5 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx @@ -33,6 +33,10 @@ import SummaryUsageBar from "../../Common/UsageBarWrapper/SummaryUsageBar"; import LabelValuePair from "../../Common/UsageBarWrapper/LabelValuePair"; import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; import SectionTitle from "../../Common/SectionTitle"; +import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton"; +import { EditIcon } from "../../../../icons"; +import EditDomains from "./EditDomains"; +import { setTenantDetailsLoad } from "../actions"; interface ITenantsSummary { classes: any; @@ -46,6 +50,7 @@ interface ITenantsSummary { consoleEnabled: boolean; adEnabled: boolean; oidcEnabled: boolean; + setTenantDetailsLoad: typeof setTenantDetailsLoad; } const styles = (theme: Theme) => @@ -193,11 +198,13 @@ const TenantSummary = ({ minioTLS, adEnabled, oidcEnabled, + setTenantDetailsLoad, }: ITenantsSummary) => { const [poolCount, setPoolCount] = useState(0); const [instances, setInstances] = useState(0); const [volumes, setVolumes] = useState(0); const [updateMinioVersion, setUpdateMinioVersion] = useState(false); + const [editDomainsOpen, setEditDomainsOpen] = useState(false); const tenantName = match.params["tenantName"]; const tenantNamespace = match.params["tenantNamespace"]; @@ -210,6 +217,14 @@ const TenantSummary = ({ } }, [tenant]); + const closeEditDomainsModal = (refresh: boolean) => { + setEditDomainsOpen(false); + + if (refresh) { + setTenantDetailsLoad(true); + } + }; + return ( {updateMinioVersion && ( @@ -223,6 +238,16 @@ const TenantSummary = ({ /> )} + {editDomainsOpen && ( + + )} + Details @@ -253,46 +278,16 @@ const TenantSummary = ({ /> - - {!tenant?.domains?.minio && !tenant?.endpoints?.minio - ? "-" - : ""} - {tenant?.endpoints?.minio && ( - - - {tenant?.endpoints?.minio || "-"} - -
-
- )} - - {tenant?.domains?.minio && - tenant.domains.minio.map((domain) => { - return ( - - - {domain} - -
-
- ); - })} -
- } - /> +

+ Domains + } + title={""} + onClick={() => { + setEditDomainsOpen(true); + }} + /> +

+ + + {!tenant?.domains?.minio && !tenant?.endpoints?.minio + ? "-" + : ""} + {tenant?.endpoints?.minio && ( + + + {tenant?.endpoints?.minio || "-"} + +
+
+ )} + + {tenant?.domains?.minio && + tenant.domains.minio.map((domain) => { + return ( + + + {domain} + +
+
+ ); + })} + + } + /> +
@@ -477,6 +519,6 @@ const mapState = (state: AppState) => ({ ), }); -const connector = connect(mapState, null); +const connector = connect(mapState, { setTenantDetailsLoad }); export default withStyles(styles)(connector(TenantSummary));