Added UI for Domains edit (#1897)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -26,19 +26,8 @@ const PasswordKeyIcon = (props: SVGProps<SVGSVGElement>) => {
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
id="Trazado_7179"
|
||||
data-name="Trazado 7179"
|
||||
d="M141.421,148.182a4.5,4.5,0,0,0-4.3,5.805l-5.188,5.195v3h3l5.187-5.2a4.5,4.5,0,0,0,5.8-3.936,4.39,4.39,0,0,0-.823-3A4.492,4.492,0,0,0,141.421,148.182Zm.5,5a1,1,0,1,1,1-1A1,1,0,0,1,141.92,153.182Z"
|
||||
transform="translate(-131.934 -148.182)"
|
||||
//fill="#5e5e5e"
|
||||
/>
|
||||
<rect
|
||||
id="Rectángulo_1090"
|
||||
data-name="Rectángulo 1090"
|
||||
width="13.764"
|
||||
height="13.764"
|
||||
transform="translate(0.118 0.118)"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -45,10 +45,11 @@ const UsageBar = ({
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{sizeItems.map((sizeElement) => {
|
||||
{sizeItems.map((sizeElement, index) => {
|
||||
const itemPercentage = (sizeElement.value * 100) / totalValue;
|
||||
return (
|
||||
<div
|
||||
key={`itemSize-${index.toString()}`}
|
||||
style={{
|
||||
width: `${itemPercentage}%`,
|
||||
height: "100%",
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<boolean>(false);
|
||||
const [consoleDomain, setConsoleDomain] = useState<string>("");
|
||||
const [minioDomains, setMinioDomains] = useState<string[]>([""]);
|
||||
const [consoleDomainValid, setConsoleDomainValid] = useState<boolean>(true);
|
||||
const [minioDomainValid, setMinioDomainValid] = useState<boolean[]>([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 (
|
||||
<ModalWrapper
|
||||
title={`Edit Tenant Domains - ${idTenant}`}
|
||||
modalOpen={open}
|
||||
onClose={closeAction}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.modalFormScrollable}>
|
||||
<Grid item xs={12} className={`${classes.configSectionItem}`}>
|
||||
<div className={classes.containerItem}>
|
||||
<InputBoxWrapper
|
||||
id="console_domain"
|
||||
name="console_domain"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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)"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4>MinIO Domains</h4>
|
||||
<div>
|
||||
{minioDomains.map((domain, index) => {
|
||||
return (
|
||||
<div
|
||||
className={`${classes.domainInline}`}
|
||||
key={`minio-domain-key-${index.toString()}`}
|
||||
>
|
||||
<InputBoxWrapper
|
||||
id={`minio-domain-${index.toString()}`}
|
||||
name={`minio-domain-${index.toString()}`}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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)"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
<div className={classes.overlayAction}>
|
||||
<IconButton
|
||||
size={"small"}
|
||||
onClick={addNewMinIODomain}
|
||||
disabled={index !== minioDomains.length - 1}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<div className={classes.overlayAction}>
|
||||
<IconButton
|
||||
size={"small"}
|
||||
onClick={() => removeMinIODomain(index)}
|
||||
disabled={minioDomains.length <= 1}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.modalButtonBar}>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={resetForm}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={
|
||||
isSending ||
|
||||
!consoleDomainValid ||
|
||||
minioDomainValid.filter((domain) => !domain).length > 0
|
||||
}
|
||||
onClick={updateDomainsList}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
const connector = connect(null, {
|
||||
setModalErrorSnackMessage,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(EditDomains));
|
||||
@@ -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<number>(0);
|
||||
const [instances, setInstances] = useState<number>(0);
|
||||
const [volumes, setVolumes] = useState<number>(0);
|
||||
const [updateMinioVersion, setUpdateMinioVersion] = useState<boolean>(false);
|
||||
const [editDomainsOpen, setEditDomainsOpen] = useState<boolean>(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 (
|
||||
<Fragment>
|
||||
{updateMinioVersion && (
|
||||
@@ -223,6 +238,16 @@ const TenantSummary = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{editDomainsOpen && (
|
||||
<EditDomains
|
||||
open={editDomainsOpen}
|
||||
idTenant={tenantName}
|
||||
namespace={tenantNamespace}
|
||||
domains={tenant?.domains || null}
|
||||
closeModalAndRefresh={closeEditDomainsModal}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SectionTitle separator={false}>Details</SectionTitle>
|
||||
|
||||
<StorageSummary tenant={tenant} classes={classes} />
|
||||
@@ -253,46 +278,16 @@ const TenantSummary = ({
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<LabelValuePair
|
||||
label={"Endpoint:"}
|
||||
value={
|
||||
<Fragment>
|
||||
{!tenant?.domains?.minio && !tenant?.endpoints?.minio
|
||||
? "-"
|
||||
: ""}
|
||||
{tenant?.endpoints?.minio && (
|
||||
<Fragment>
|
||||
<a
|
||||
href={tenant?.endpoints?.minio}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`${classes.linkedSection} ${classes.autoGeneratedLink}`}
|
||||
>
|
||||
{tenant?.endpoints?.minio || "-"}
|
||||
</a>
|
||||
<br />
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{tenant?.domains?.minio &&
|
||||
tenant.domains.minio.map((domain) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<a
|
||||
href={domain}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={classes.linkedSection}
|
||||
>
|
||||
{domain}
|
||||
</a>
|
||||
<br />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
<h3>
|
||||
Domains
|
||||
<RBIconButton
|
||||
icon={<EditIcon />}
|
||||
title={""}
|
||||
onClick={() => {
|
||||
setEditDomainsOpen(true);
|
||||
}}
|
||||
/>
|
||||
</h3>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<LabelValuePair
|
||||
@@ -333,6 +328,53 @@ const TenantSummary = ({
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<LabelValuePair
|
||||
label={`MinIO Endpoint${
|
||||
tenant?.endpoints?.minio &&
|
||||
tenant?.endpoints?.minio.length === 1
|
||||
? ""
|
||||
: "s"
|
||||
}:`}
|
||||
value={
|
||||
<Fragment>
|
||||
{!tenant?.domains?.minio && !tenant?.endpoints?.minio
|
||||
? "-"
|
||||
: ""}
|
||||
{tenant?.endpoints?.minio && (
|
||||
<Fragment>
|
||||
<a
|
||||
href={tenant?.endpoints?.minio}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`${classes.linkedSection} ${classes.autoGeneratedLink}`}
|
||||
>
|
||||
{tenant?.endpoints?.minio || "-"}
|
||||
</a>
|
||||
<br />
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{tenant?.domains?.minio &&
|
||||
tenant.domains.minio.map((domain) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<a
|
||||
href={domain}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={classes.linkedSection}
|
||||
>
|
||||
{domain}
|
||||
</a>
|
||||
<br />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12} md={4}>
|
||||
<Grid item xs={12}>
|
||||
@@ -477,6 +519,6 @@ const mapState = (state: AppState) => ({
|
||||
),
|
||||
});
|
||||
|
||||
const connector = connect(mapState, null);
|
||||
const connector = connect(mapState, { setTenantDetailsLoad });
|
||||
|
||||
export default withStyles(styles)(connector(TenantSummary));
|
||||
|
||||
Reference in New Issue
Block a user