diff --git a/pkg/acl/endpoints.go b/pkg/acl/endpoints.go index 8f1aafd94..988566b76 100644 --- a/pkg/acl/endpoints.go +++ b/pkg/acl/endpoints.go @@ -36,7 +36,7 @@ var ( bucketsDetail = "/buckets/:bucketName" serviceAccounts = "/service-accounts" tenants = "/tenants" - tenantsDetail = "/tenants/:tenantName" + tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName" heal = "/heal" ) diff --git a/portal-ui/src/common/utils.ts b/portal-ui/src/common/utils.ts index 2b0ac970d..633ffddef 100644 --- a/portal-ui/src/common/utils.ts +++ b/portal-ui/src/common/utils.ts @@ -107,7 +107,7 @@ export const getBytes = ( const powFactor = unitsTake.findIndex((element) => element === unit); - if (powFactor == -1) { + if (powFactor === -1) { return "0"; } const factor = Math.pow(1024, powFactor); diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index 9161b6a67..be78deca4 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -311,7 +311,7 @@ const Console = ({ }, { component: TenantDetails, - path: "/tenants/:tenantName", + path: "/namespaces/:tenantNamespace/tenants/:tenantName", }, ]; const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]); diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/AddTenant.tsx b/portal-ui/src/screens/Console/Tenants/ListTenants/AddTenant.tsx index 023526aef..130e6230c 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/AddTenant.tsx +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/AddTenant.tsx @@ -40,7 +40,7 @@ import { } from "../../../../common/utils"; import { commonFormValidation, - IValidation, + IValidation } from "../../../../utils/validationFunctions"; import GenericWizard from "../../Common/GenericWizard/GenericWizard"; import { IWizardElement } from "../../Common/GenericWizard/types"; @@ -65,19 +65,19 @@ interface IAddTenantProps { const styles = (theme: Theme) => createStyles({ errorBlock: { - color: "red", + color: "red" }, buttonContainer: { - textAlign: "right", + textAlign: "right" }, multiContainer: { display: "flex", alignItems: "center" as const, - justifyContent: "flex-start" as const, + justifyContent: "flex-start" as const }, sizeFactorContainer: { marginLeft: 8, - alignSelf: "flex-start" as const, + alignSelf: "flex-start" as const }, headerElement: { position: "sticky", @@ -85,16 +85,16 @@ const styles = (theme: Theme) => paddingTop: 5, marginBottom: 10, backgroundColor: "#fff", - zIndex: 500, + zIndex: 500 }, tableTitle: { fontWeight: 700, - width: "30%", + width: "30%" }, zoneError: { color: "#dc1f2e", fontSize: "0.75rem", - paddingLeft: 120, + paddingLeft: 120 }, error: { color: "#dc1f2e", @@ -111,7 +111,7 @@ interface Opts { const AddTenant = ({ open, closeModalAndRefresh, - classes, + classes }: IAddTenantProps) => { // Fields const [addSending, setAddSending] = useState(false); @@ -301,8 +301,8 @@ const AddTenant = ({ setNameTenantValid( !("tenant-name" in commonValidation) && - !("namespace" in commonValidation) && - storageClasses.length > 0 + !("namespace" in commonValidation) && + storageClasses.length > 0 ); setValidationErrors(commonValidation); @@ -337,9 +337,9 @@ const AddTenant = ({ setConfigValid( !("nodes" in commonValidation) && - !("volume_size" in commonValidation) && - !("memory_per_node" in commonValidation) && - distribution.error === "" + !("volume_size" in commonValidation) && + !("memory_per_node" in commonValidation) && + distribution.error === "" ); setValidationErrors(commonValidation); @@ -356,8 +356,8 @@ const AddTenant = ({ required: true, value: imageName, pattern: /^((.*?)\/(.*?):(.+))$/, - customPatternMessage: "Format must be of form: 'minio/minio:VERSION'", - }, + customPatternMessage: "Format must be of form: 'minio/minio:VERSION'" + } ]; } @@ -620,7 +620,7 @@ const AddTenant = ({ enabled: true, action: () => { closeModalAndRefresh(false, null); - }, + } }; const wizardSteps: IWizardElement[] = [ @@ -690,7 +690,7 @@ const AddTenant = ({ id="adv_mode" name="adv_mode" checked={advancedMode} - onChange={(e) => { + onChange={e => { const targetD = e.target; const checked = targetD.checked; @@ -703,8 +703,8 @@ const AddTenant = ({ ), buttons: [ cancelButton, - { label: "Next", type: "next", enabled: nameTenantValid }, - ], + { label: "Next", type: "next", enabled: nameTenantValid } + ] }, { label: "Configure", @@ -1080,8 +1080,8 @@ const AddTenant = ({ buttons: [ cancelButton, { label: "Back", type: "back", enabled: true }, - { label: "Next", type: "next", enabled: true }, - ], + { label: "Next", type: "next", enabled: true } + ] }, { label: "Encryption", @@ -1504,8 +1504,8 @@ const AddTenant = ({ buttons: [ cancelButton, { label: "Back", type: "back", enabled: true }, - { label: "Next", type: "next", enabled: true }, - ], + { label: "Next", type: "next", enabled: true } + ] }, { label: "Tenant Size", @@ -1720,16 +1720,16 @@ const AddTenant = ({ enabled: !addSending, action: () => { setAddSending(true); - }, - }, - ], - }, + } + } + ] + } ]; let filteredWizardSteps = wizardSteps; if (!advancedMode) { - filteredWizardSteps = wizardSteps.filter((step) => !step.advancedOnly); + filteredWizardSteps = wizardSteps.filter(step => !step.advancedOnly); } return ( diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/DeleteTenant.tsx b/portal-ui/src/screens/Console/Tenants/ListTenants/DeleteTenant.tsx index e80d30dde..03bc89fd5 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/DeleteTenant.tsx +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/DeleteTenant.tsx @@ -27,11 +27,12 @@ import { import Typography from "@material-ui/core/Typography"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import api from "../../../../common/api"; +import { ITenant } from "./types"; interface IDeleteTenant { classes: any; deleteOpen: boolean; - selectedTenant: string; + selectedTenant: ITenant; closeDeleteModalAndRefresh: (refreshList: boolean) => any; } @@ -54,7 +55,10 @@ const DeleteTenant = ({ useEffect(() => { if (deleteLoading) { api - .invoke("DELETE", `/api/v1/tenants/${selectedTenant}`) + .invoke( + "DELETE", + `/api/v1/namespaces/${selectedTenant.namespace}/tenants/${selectedTenant.name}` + ) .then(() => { setDeleteLoading(false); setDeleteError(""); @@ -85,7 +89,7 @@ const DeleteTenant = ({ {deleteLoading && } - Are you sure you want to delete tenant {selectedTenant}? + Are you sure you want to delete tenant {selectedTenant.name}? {deleteError !== "" && (
diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx b/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx index cb2668667..9070a460c 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx @@ -32,6 +32,7 @@ import DeleteTenant from "./DeleteTenant"; import AddTenant from "./AddTenant"; import { NewServiceAccount } from "../../Common/CredentialsPrompt/types"; import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt"; +import history from "../../../../history"; interface ITenantsList { classes: any; @@ -122,11 +123,16 @@ const ListTenants = ({ classes }: ITenantsList) => { } }; - const confirmDeleteTenant = (tenant: string) => { + const confirmDeleteTenant = (tenant: ITenant) => { setSelectedTenant(tenant); setDeleteOpen(true); }; + const redirectToTenantDetails = (tenant: ITenant) => { + history.push(`/namespaces/${tenant.namespace}/tenants/${tenant.name}`); + return; + }; + const closeCredentialsModal = () => { setShowNewCredentials(false); setCreatedAccount(null); @@ -149,8 +155,8 @@ const ListTenants = ({ classes }: ITenantsList) => { }; const tableActions = [ - { type: "view", to: `/tenants`, sendOnlyId: true }, - { type: "delete", onClick: confirmDeleteTenant, sendOnlyId: true }, + { type: "view", onClick: redirectToTenantDetails }, + { type: "delete", onClick: confirmDeleteTenant }, ]; const filteredRecords = records @@ -187,9 +193,7 @@ const ListTenants = ({ classes }: ITenantsList) => { } for (let i = 0; i < resTenants.length; i++) { - const total = - resTenants[i].volume_count * resTenants[i].volume_size; - resTenants[i].capacity = niceBytes(total + ""); + resTenants[i].capacity = niceBytes(resTenants[i].total_size + ""); } setRecords(resTenants); diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/ZonesMultiSelector.tsx b/portal-ui/src/screens/Console/Tenants/ListTenants/ZonesMultiSelector.tsx index 81121de93..2b909a2b2 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/ZonesMultiSelector.tsx +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/ZonesMultiSelector.tsx @@ -86,7 +86,14 @@ const ZonesMultiSelector = ({ onChange, classes, }: IZonesMultiSelector) => { - const defaultZone: IZone = { name: "", servers: 0, capacity: "", volumes: 0 }; + const defaultZone: IZone = { + name: "", + servers: 0, + capacity: "", + volumes: 0, + volumes_per_server: 0, + volume_configuration: { size: 0, storage_class: "", labels: null }, + }; const [currentElements, setCurrentElements] = useState([]); const [internalCounter, setInternalCounter] = useState(1); diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts index 17ff24158..c26388cd8 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts @@ -17,18 +17,32 @@ export interface IZone { name: string; servers: number; + volumes_per_server: number; + volume_configuration: IVolumeConfiguration; // computed capacity: string; volumes: number; } +export interface IAddZoneRequest { + name: string; + servers: number; + volumes_per_server: number; + volume_configuration: IVolumeConfiguration; +} + export interface IVolumeConfiguration { size: number; storage_class: string; + labels: { [key: string]: any } | null; } export interface ITenant { + total_size: number; name: string; + namespace: string; + image: string; + console_image: string; zone_count: number; currentState: string; instance_count: 4; diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/AddZoneModal.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/AddZoneModal.tsx index 4db0140c9..ab3aac67c 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/AddZoneModal.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/AddZoneModal.tsx @@ -8,33 +8,34 @@ import Grid from "@material-ui/core/Grid"; import { factorForDropdown, getTotalSize, - niceBytes, + niceBytes } from "../../../../common/utils"; import { Button, LinearProgress } from "@material-ui/core"; +import api from "../../../../common/api"; +import { IAddZoneRequest, ITenant } from "../ListTenants/types"; interface IAddZoneProps { + tenant: ITenant; classes: any; open: boolean; onCloseZoneAndReload: (shouldReload: boolean) => void; - volumesPerInstance: number; - volumeSize: number; } const styles = (theme: Theme) => createStyles({ errorBlock: { - color: "red", + color: "red" }, buttonContainer: { - textAlign: "right", + textAlign: "right" }, multiContainer: { display: "flex", alignItems: "center" as const, - justifyContent: "flex-start" as const, + justifyContent: "flex-start" as const }, sizeFactorContainer: { - marginLeft: 8, + marginLeft: 8 }, bottomContainer: { display: "flex", @@ -42,39 +43,39 @@ const styles = (theme: Theme) => alignItems: "center", "& div": { flexGrow: 1, - width: "100%", - }, + width: "100%" + } }, factorElements: { display: "flex", - justifyContent: "flex-start", + justifyContent: "flex-start" }, sizeNumber: { fontSize: 35, fontWeight: 700, - textAlign: "center", + textAlign: "center" }, sizeDescription: { fontSize: 14, color: "#777", - textAlign: "center", + textAlign: "center" }, - ...modalBasic, + ...modalBasic }); const AddZoneModal = ({ + tenant, classes, open, - onCloseZoneAndReload, - volumesPerInstance, - volumeSize, + onCloseZoneAndReload }: IAddZoneProps) => { const [addSending, setAddSending] = useState(false); - const [zoneName, setZoneName] = useState(""); - const [numberOfInstances, setNumberOfInstances] = useState(0); + const [numberOfNodes, setNumberOfNodes] = useState(0); + const [volumeSize, setVolumeSize] = useState(0); + const [volumesPerServer, setVolumesPerSever] = useState(0); - const instanceCapacity: number = volumeSize * volumesPerInstance; - const totalCapacity: number = instanceCapacity * numberOfInstances; + const instanceCapacity: number = volumeSize * 1073741824 * volumesPerServer; + const totalCapacity: number = instanceCapacity * numberOfNodes; return ( ) => { e.preventDefault(); setAddSending(true); + const data: IAddZoneRequest = { + name: "", + servers: numberOfNodes, + volumes_per_server: volumesPerServer, + volume_configuration: { + size: volumeSize * 1073741824, + storage_class: "", + labels: null + } + }; + api + .invoke( + "POST", + `/api/v1/namespaces/${tenant.namespace}/tenants/${tenant.name}/zones`, + data + ) + .then(() => { + setAddSending(false); + onCloseZoneAndReload(true); + }) + .catch(err => { + setAddSending(false); + // setDeleteError(err); + }); }} > ) => { - setZoneName(e.target.value); + setNumberOfNodes(parseInt(e.target.value)); }} - label="Name" - value={zoneName} + label="Number o Nodes" + value={numberOfNodes.toString(10)} /> ) => { - setNumberOfInstances(parseInt(e.target.value)); + setVolumeSize(parseInt(e.target.value)); }} - label="Drives per Server" - value={numberOfInstances.toString(10)} + label="Volume Size (Gi)" + value={volumeSize.toString(10)} + /> + + + ) => { + setVolumesPerSever(parseInt(e.target.value)); + }} + label="Volumes per Server" + value={volumesPerServer.toString(10)} /> diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx index fd7de1227..b9a162ab6 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx @@ -42,25 +42,25 @@ interface ITenantDetailsProps { const styles = (theme: Theme) => createStyles({ errorBlock: { - color: "red", + color: "red" }, buttonContainer: { - textAlign: "right", + textAlign: "right" }, multiContainer: { display: "flex", alignItems: "center" as const, - justifyContent: "flex-start" as const, + justifyContent: "flex-start" as const }, sizeFactorContainer: { - marginLeft: 8, + marginLeft: 8 }, containerHeader: { display: "flex", - justifyContent: "space-between", + justifyContent: "space-between" }, paperContainer: { - padding: "15px 15px 15px 50px", + padding: "15px 15px 15px 50px" }, infoGrid: { display: "grid", @@ -68,27 +68,27 @@ const styles = (theme: Theme) => gridGap: 8, "& div": { display: "flex", - alignItems: "center", + alignItems: "center" }, "& div:nth-child(odd)": { justifyContent: "flex-end", - fontWeight: 700, + fontWeight: 700 }, "& div:nth-child(2n)": { - paddingRight: 35, - }, + paddingRight: 35 + } }, masterActions: { width: "25%", minWidth: "120px", "& div": { - margin: "5px 0px", - }, + margin: "5px 0px" + } }, actionsTray: { - textAlign: "right", + textAlign: "right" }, - ...modalBasic, + ...modalBasic }); const mainPagination = { @@ -99,9 +99,9 @@ const mainPagination = { page: 0, SelectProps: { inputProps: { "aria-label": "rows per page" }, - native: true, + native: true }, - ActionsComponent: MinTablePaginationActions, + ActionsComponent: MinTablePaginationActions }; const TenantDetails = ({ classes, match }: ITenantDetailsProps) => { @@ -142,31 +142,45 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => { const loadInfo = () => { const tenantName = match.params["tenantName"]; + const tenantNamespace = match.params["tenantNamespace"]; setLoading(true); api - .invoke("GET", `/api/v1/tenants/${tenantName}`) + .invoke( + "GET", + `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}` + ) .then((res: ITenant) => { - const total = res.volume_count * res.volume_size; - - setCapacity(total); - setZoneCount(res.zone_count); - setVolumes(res.volume_count); - setInstances(res.instance_count); const resZones = !res.zones ? [] : res.zones; + const total = res.volume_count * res.volume_size; + let totalInstances = 0; + let totalVolumes = 0; + let count = 1; for (let zone of resZones) { - zone.volumes = res.volumes_per_server; - const cap = res.volumes_per_server * res.volume_size * zone.servers; + const cap = + zone.volumes_per_server * + zone.servers * + zone.volume_configuration.size; + zone.name = `zone-${count}`; zone.capacity = niceBytes(cap + ""); + zone.volumes = zone.servers * zone.volumes_per_server; + totalInstances += zone.servers; + totalVolumes += zone.volumes; + count += 1; } + setCapacity(res.total_size); + setZoneCount(resZones.length); + setVolumes(totalVolumes); + setInstances(totalInstances); + setZones(resZones); setTenant(res); setError(""); setLoading(false); }) - .catch((err) => { + .catch(err => { setError(err); setLoading(false); }); @@ -182,8 +196,7 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => { )} {addBucketOpen && ( @@ -212,40 +225,18 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
Capacity:
{niceBytes(capacity.toString(10))}
+
Minio:
+
{tenant ? tenant.image : ""}
+
Console:
+
{tenant ? tenant.console_image : ""}
Zones:
{zoneCount}
-
External IDP:
-
- {externalIDP ? "Yes" : "No"}   - -
Instances:
{instances}
-
External KMS:
-
{externalKMS ? "Yes" : "No"}  
Volumes:
{volumes}
-
-
- -
-

@@ -261,185 +252,50 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => { aria-label="tenant-tabs" > - -
- {selectedTab === 0 && ( - - )} - - {selectedTab === 1 && ( - - )} - - {selectedTab === 2 && ( - - )} +
- {selectedTab === 0 && ( - { - console.log(element); - }, - sendOnlyId: true, + { + console.log(element); }, - { - type: "delete", - onClick: (element) => { - console.log(element); - }, - sendOnlyId: true, - }, - ]} - columns={[ - { label: "Name", elementKey: "name" }, - { label: "Capacity", elementKey: "capacity" }, - { label: "# of Instances", elementKey: "servers" }, - { label: "# of Drives", elementKey: "volumes" }, - ]} - isLoading={false} - records={zones} - entityName="Zones" - idField="name" - paginatorConfig={{ - ...mainPagination, - onChangePage: () => {}, - onChangeRowsPerPage: () => {}, - }} - /> - )} - - {selectedTab === 1 && ( - { - console.log(element); - }, - sendOnlyId: true, - }, - { - type: "replicate", - onClick: (element) => { - console.log(element); - }, - sendOnlyId: true, - }, - { - type: "mirror", - onClick: (element) => { - console.log(element); - }, - sendOnlyId: true, - }, - { - type: "delete", - onClick: (element) => { - console.log(element); - }, - sendOnlyId: true, - }, - ]} - columns={[ - { - label: "Status", - elementKey: "status", - }, - { label: "Name", elementKey: "name" }, - { label: "AccessPolicy", elementKey: "access_policy" }, - ]} - isLoading={false} - records={[]} - entityName="Buckets" - idField="name" - paginatorConfig={{ - ...mainPagination, - onChangePage: () => {}, - onChangeRowsPerPage: () => {}, - }} - /> - )} - - {selectedTab === 2 && ( - { - console.log(element); - }, - sendOnlyId: true, - }, - ]} - columns={[ - { - label: "Source", - elementKey: "source", - }, - { label: "Source Bucket", elementKey: "source_bucket" }, - { label: "Destination", elementKey: "destination" }, - { - label: "Destination Bucket", - elementKey: "destination_bucket", - }, - ]} - isLoading={false} - records={[]} - entityName="Replication" - idField="id" - paginatorConfig={{ - rowsPerPageOptions: [5, 10, 25], - colSpan: 3, - count: 0, - rowsPerPage: 0, - page: 0, - SelectProps: { - inputProps: { "aria-label": "rows per page" }, - native: true, - }, - onChangePage: () => {}, - onChangeRowsPerPage: () => {}, - ActionsComponent: MinTablePaginationActions, - }} - /> - )} + sendOnlyId: true + } + ]} + columns={[ + { label: "Name", elementKey: "name" }, + { label: "Capacity", elementKey: "capacity" }, + { label: "# of Instances", elementKey: "servers" }, + { label: "# of Drives", elementKey: "volumes" } + ]} + isLoading={false} + records={zones} + entityName="Zones" + idField="name" + paginatorConfig={{ + ...mainPagination, + onChangePage: () => {}, + onChangeRowsPerPage: () => {} + }} + />
diff --git a/restapi/admin_tenants.go b/restapi/admin_tenants.go index 02356246f..1fc16687e 100644 --- a/restapi/admin_tenants.go +++ b/restapi/admin_tenants.go @@ -1531,6 +1531,9 @@ func updateTenantZones( // replace zones array minInst.Spec.Zones = newZoneArray + minInst = minInst.DeepCopy() + minInst.EnsureDefaults() + payloadBytes, err := json.Marshal(minInst) if err != nil { return nil, err