Add features box to tenant details (#738)

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Daniel Valdivia
2021-05-14 15:04:29 -07:00
committed by GitHub
parent 9c5a788dae
commit 2769ba145a
22 changed files with 688 additions and 372 deletions

View File

@@ -28,12 +28,14 @@ WORKDIR /go/src/github.com/minio/console/
ENV CGO_ENABLED=0
COPY --from=uilayer /app/build /go/src/github.com/minio/console/portal-ui/build
RUN go build -ldflags "-w -s" -a -o console ./cmd/console
FROM scratch
MAINTAINER MinIO Development "dev@min.io"
EXPOSE 9090
COPY --from=golayer /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=golayer /go/src/github.com/minio/console/console .

View File

@@ -50,12 +50,27 @@ type Tenant struct {
// enable prometheus
EnablePrometheus bool `json:"enable_prometheus,omitempty"`
// encryption enabled
EncryptionEnabled bool `json:"encryptionEnabled,omitempty"`
// endpoints
Endpoints *TenantEndpoints `json:"endpoints,omitempty"`
// idp ad enabled
IdpAdEnabled bool `json:"idpAdEnabled,omitempty"`
// idp oic enabled
IdpOicEnabled bool `json:"idpOicEnabled,omitempty"`
// image
Image string `json:"image,omitempty"`
// log enabled
LogEnabled bool `json:"logEnabled,omitempty"`
// monitoring enabled
MonitoringEnabled bool `json:"monitoringEnabled,omitempty"`
// name
Name string `json:"name,omitempty"`

View File

@@ -1,25 +1,25 @@
{
"files": {
"main.css": "/static/css/main.a19f3d53.chunk.css",
"main.js": "/static/js/main.06a632ee.chunk.js",
"main.js.map": "/static/js/main.06a632ee.chunk.js.map",
"main.js": "/static/js/main.914482b9.chunk.js",
"main.js.map": "/static/js/main.914482b9.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.f48e99e5.js",
"runtime-main.js.map": "/static/js/runtime-main.f48e99e5.js.map",
"static/css/2.76b14b73.chunk.css": "/static/css/2.76b14b73.chunk.css",
"static/js/2.a05a6b72.chunk.js": "/static/js/2.a05a6b72.chunk.js",
"static/js/2.a05a6b72.chunk.js.map": "/static/js/2.a05a6b72.chunk.js.map",
"static/js/2.0255e016.chunk.js": "/static/js/2.0255e016.chunk.js",
"static/js/2.0255e016.chunk.js.map": "/static/js/2.0255e016.chunk.js.map",
"index.html": "/index.html",
"static/css/2.76b14b73.chunk.css.map": "/static/css/2.76b14b73.chunk.css.map",
"static/css/main.a19f3d53.chunk.css.map": "/static/css/main.a19f3d53.chunk.css.map",
"static/js/2.a05a6b72.chunk.js.LICENSE.txt": "/static/js/2.a05a6b72.chunk.js.LICENSE.txt",
"static/js/2.0255e016.chunk.js.LICENSE.txt": "/static/js/2.0255e016.chunk.js.LICENSE.txt",
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",
"static/media/minio_operator_logo.1312b7c9.svg": "/static/media/minio_operator_logo.1312b7c9.svg"
},
"entrypoints": [
"static/js/runtime-main.f48e99e5.js",
"static/css/2.76b14b73.chunk.css",
"static/js/2.a05a6b72.chunk.js",
"static/js/2.0255e016.chunk.js",
"static/css/main.a19f3d53.chunk.css",
"static/js/main.06a632ee.chunk.js"
"static/js/main.914482b9.chunk.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.76b14b73.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.a05a6b72.chunk.js"></script><script src="/static/js/main.06a632ee.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.76b14b73.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.0255e016.chunk.js"></script><script src="/static/js/main.914482b9.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -289,7 +289,7 @@ const ListBuckets = ({
startIcon={<CreateIcon />}
onClick={() => {
addBucketOpen(true);
}}
}}
>
Create Bucket
</Button>

View File

@@ -568,3 +568,14 @@ export const wizardCommon = {
},
},
};
export const buttonsStyles = {
anchorButton: {
textDecoration: "underline" as const,
textTransform: "unset" as const,
fontWeight: "normal" as const,
padding: 0,
lineHeight: "unset" as const,
height: "unset" as const,
},
};

View File

@@ -1,7 +1,6 @@
import React from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { LinearProgress } from "@material-ui/core";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import CircularProgress from "@material-ui/core/CircularProgress";
import ErrorBlock from "../../../shared/ErrorBlock";
@@ -47,6 +46,9 @@ const BorderLinearProgress = withStyles((theme) => ({
borderRadius: 5,
backgroundColor: "#081C42",
},
padChart: {
padding: "5px",
},
}))(LinearProgress);
const UsageBarWrapper = ({
@@ -84,20 +86,18 @@ const UsageBarWrapper = ({
return (
<React.Fragment>
<Paper className={classes.paperContainer}>
{loading && (
<React.Fragment>
<Grid item xs={12} className={classes.centerItem}>
<CircularProgress
color="primary"
size={40}
variant="indeterminate"
/>
</Grid>
</React.Fragment>
)}
{renderComponent()}
</Paper>
{loading && (
<div className={classes.padChart}>
<Grid item xs={12} className={classes.centerItem}>
<CircularProgress
color="primary"
size={40}
variant="indeterminate"
/>
</Grid>
</div>
)}
{renderComponent()}
</React.Fragment>
);
};

View File

@@ -213,10 +213,6 @@ const ListTiersConfiguration = ({
<SlideOptions
slideOptions={[
<Fragment>
<Grid item xs={12} className={classes.customTitle}>
Tiers
</Grid>
<Grid item xs={12} className={classes.lambdaContainer}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField

View File

@@ -66,12 +66,11 @@ const styles = (theme: Theme) =>
syncButton: {
"&.MuiButton-root .MuiButton-iconSizeMedium > *:first-child": {
fontSize: 18,
}
},
},
actionsTray: {
...actionsTray.actionsTray,
padding: "0 10px"
padding: "0 10px",
},
});

View File

@@ -72,6 +72,11 @@ export interface ITenant {
volumes_per_server: number;
pools: IPool[];
endpoints: IEndpoints;
logEnabled: boolean;
monitoringEnabled: boolean;
encryptionEnabled: boolean;
idpAdEnabled: boolean;
idpOicEnabled: boolean;
// computed
capacity: string;
subnet_license: LicenseInfo;

View File

@@ -0,0 +1,255 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 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, { useEffect, useState } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
import { Button, Typography } from "@material-ui/core";
import { niceBytes } from "../../../../common/utils";
import Moment from "react-moment";
import { Link } from "react-router-dom";
import Paper from "@material-ui/core/Paper";
import { ITenant } from "../ListTenants/types";
import { LicenseInfo } from "../../License/types";
import api from "../../../../common/api";
interface ISubnetLicenseTenant {
classes: any;
tenant: ITenant | null;
loadingActivateProduct: any;
loadingLicenseInfo: boolean;
licenseInfo: LicenseInfo | undefined;
activateProduct: any;
}
const styles = (theme: Theme) =>
createStyles({
paperContainer: {
padding: "15px 15px 15px 50px",
},
licenseInfoValue: {
textTransform: "none",
fontSize: 14,
fontWeight: "bold",
},
licenseContainer: {
position: "relative",
padding: "20px 52px 0px 28px",
background: "#032F51",
boxShadow: "0px 3px 7px #00000014",
"& h2": {
color: "#FFF",
marginBottom: 67,
},
"& a": {
textDecoration: "none",
},
"& h3": {
color: "#FFFFFF",
marginBottom: "30px",
fontWeight: "bold",
},
"& h6": {
color: "#FFFFFF !important",
},
},
licenseInfo: { color: "#FFFFFF", position: "relative" },
licenseInfoTitle: {
textTransform: "none",
color: "#BFBFBF",
fontSize: 11,
},
verifiedIcon: {
width: 96,
position: "absolute",
right: 0,
bottom: 29,
},
noUnderLine: {
textDecoration: "none",
},
...containerForHeader(theme.spacing(4)),
});
const SubnetLicenseTenant = ({
classes,
tenant,
loadingActivateProduct,
loadingLicenseInfo,
licenseInfo,
activateProduct,
}: ISubnetLicenseTenant) => {
return (
<Paper
className={
tenant && tenant.subnet_license ? classes.licenseContainer : ""
}
>
{tenant && tenant.subnet_license ? (
<React.Fragment>
<Grid container className={classes.licenseInfo}>
<Grid item xs={6}>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
License
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
Commercial License
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Organization
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.organization}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Registered Capacity
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{niceBytes(
(tenant.subnet_license.storage_capacity * 1099511627776) // 1 Terabyte = 1099511627776 Bytes
.toString(10)
)}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Expiry Date
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
<Moment format="YYYY-MM-DD">
{tenant.subnet_license.expires_at}
</Moment>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Subscription Plan
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.plan}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Requester
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.email}
</Typography>
</Grid>
<img
className={classes.verifiedIcon}
src={"/verified.svg"}
alt="verified"
/>
</Grid>
</React.Fragment>
) : (
!loadingLicenseInfo && (
<Grid className={classes.paperContainer}>
{!licenseInfo && (
<Link
to={"/license"}
onClick={(e) => {
e.stopPropagation();
}}
className={classes.noUnderLine}
>
<Button
className={classes.licenseButton}
variant="contained"
color="primary"
>
Activate Product
</Button>
</Link>
)}
{licenseInfo && tenant && (
<Button
disabled={loadingActivateProduct}
className={classes.licenseButton}
variant="contained"
color="primary"
onClick={() => activateProduct(tenant.namespace, tenant.name)}
>
Attach License
</Button>
)}
</Grid>
)
)}
</Paper>
);
};
export default withStyles(styles)(SubnetLicenseTenant);

View File

@@ -19,8 +19,11 @@ import { connect } from "react-redux";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import {
actionsTray,
buttonsStyles,
containerForHeader,
modalBasic,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import Grid from "@material-ui/core/Grid";
import {
@@ -28,6 +31,7 @@ import {
IconButton,
Menu,
MenuItem,
TextField,
Typography,
} from "@material-ui/core";
import Tabs from "@material-ui/core/Tabs";
@@ -51,6 +55,10 @@ import { setErrorSnackMessage } from "../../../../actions";
import Moment from "react-moment";
import MoreVertIcon from "@material-ui/icons/MoreVert";
import TenantYAML from "./TenantYAML";
import Checkbox from "@material-ui/core/Checkbox";
import SubnetLicenseTenant from "./SubnetLicenseTenant";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
interface ITenantDetailsProps {
classes: any;
@@ -106,9 +114,6 @@ const styles = (theme: Theme) =>
margin: "5px 0px",
},
},
actionsTray: {
textAlign: "right",
},
updateButton: {
backgroundColor: "transparent",
border: 0,
@@ -121,55 +126,28 @@ const styles = (theme: Theme) =>
height: 12,
},
},
noUnderLine: {
textDecoration: "none",
},
poolLabel: {
color: "#666666",
},
licenseContainer: {
position: "relative",
padding: "20px 52px 0px 28px",
background: "#032F51",
boxShadow: "0px 3px 7px #00000014",
"& h2": {
color: "#FFF",
marginBottom: 67,
},
"& a": {
textDecoration: "none",
},
"& h3": {
color: "#FFFFFF",
marginBottom: "30px",
fontWeight: "bold",
},
"& h6": {
color: "#FFFFFF !important",
},
},
licenseInfo: { color: "#FFFFFF", position: "relative" },
licenseInfoTitle: {
textTransform: "none",
color: "#BFBFBF",
fontSize: 11,
},
licenseInfoValue: {
textTransform: "none",
fontSize: 14,
fontWeight: "bold",
},
verifiedIcon: {
width: 96,
position: "absolute",
right: 0,
bottom: 29,
},
breadcrumLink: {
textDecoration: "none",
color: "black",
},
hrClass: {
borderTop: 0,
borderLeft: 0,
borderRight: 0,
borderColor: "#999999",
backgroundColor: "transparent",
},
...modalBasic,
...actionsTray,
...buttonsStyles,
...searchField,
actionsTray: {
...actionsTray.actionsTray,
padding: "15px 0 0",
},
...containerForHeader(theme.spacing(4)),
});
@@ -199,6 +177,11 @@ const TenantDetails = ({
const [loadingActivateProduct, setLoadingActivateProduct] = useState<boolean>(
false
);
const [logEnabled, setLogEnabled] = useState<boolean>(false);
const [monitoringEnabled, setMonitoringEnabled] = useState<boolean>(false);
const [encryptionEnabled, setEncryptionEnabled] = useState<boolean>(false);
const [adEnabled, setAdEnabled] = useState<boolean>(false);
const [oicEnabled, setOicEnabled] = useState<boolean>(false);
const tenantName = match.params["tenantName"];
const tenantNamespace = match.params["tenantNamespace"];
@@ -280,6 +263,12 @@ const TenantDetails = ({
setPools(resPools);
setLogEnabled(res.logEnabled);
setMonitoringEnabled(res.monitoringEnabled);
setEncryptionEnabled(res.encryptionEnabled);
setAdEnabled(res.idpAdEnabled);
setOicEnabled(res.idpOicEnabled);
setTenant(res);
})
.catch((err) => {
@@ -434,74 +423,6 @@ const TenantDetails = ({
<Grid item xs={12} className={classes.container} />
<Grid container>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={9} className={classes.containerHeader}>
<Paper className={classes.paperContainer}>
<div className={classes.infoGrid}>
<div>Capacity:</div>
<div>{niceBytes(capacity.toString(10))}</div>
<div>Minio:</div>
<div>
{tenant ? tenant.image : ""}{" "}
<button
className={classes.updateButton}
onClick={() => {
setUpdateMinioVersion(true);
}}
>
<PencilIcon active={false} />
</button>
</div>
<div>Clusters:</div>
<div>{poolCount}</div>
<div>Console:</div>
<div>{tenant ? tenant.console_image : ""}</div>
<div>Instances:</div>
<div>{instances}</div>
<div>Volumes:</div>
<div>{volumes}</div>
{tenant?.endpoints && (
<React.Fragment>
<div>Endpoint:</div>
<div>
<a
href={tenant?.endpoints.minio}
target="_blank"
rel="noopener noreferrer"
>
{tenant?.endpoints.minio}
</a>
</div>
<div>Console:</div>
<div>
<a
href={tenant?.endpoints.console}
target="_blank"
rel="noopener noreferrer"
>
{tenant?.endpoints.console}
</a>
</div>
</React.Fragment>
)}
</div>
</Paper>
</Grid>
<Grid item xs={3}>
<UsageBarWrapper
currValue={usage}
maxValue={tenant ? tenant.total_size : 0}
label={"Storage"}
renderFunction={niceBytes}
error={usageError}
loading={loadingUsage}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={6}>
<Tabs
value={selectedTab}
indicatorColor="primary"
@@ -513,242 +434,284 @@ const TenantDetails = ({
variant="scrollable"
scrollButtons="auto"
>
<Tab label="Clusters" />
<Tab label="License" />
<Tab label="Summary" />
<Tab label="Pools" />
<Tab label="Pods" />
<Tab label="License" />
</Tabs>
</Grid>
<Grid item xs={6} className={classes.actionsTray}>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddPool(true);
}}
>
Expand Tenant
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
{selectedTab === 0 && (
<TableWrapper
itemActions={[
{
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={pools}
entityName="Servers"
idField="name"
/>
<React.Fragment>
<br />
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={8}>
<table width={"100%"}>
<tr>
<td colSpan={4}>
<h2>Details</h2>
<hr className={classes.hrClass} />
</td>
</tr>
<tr>
<td>Capacity:</td>
<td>{niceBytes(capacity.toString(10))}</td>
<td>MinIO:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setUpdateMinioVersion(true);
}}
>
{tenant ? tenant.image : ""}
</Button>
</td>
</tr>
<tr>
<td>Clusters:</td>
<td>{poolCount}</td>
<td>Console:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setUpdateMinioVersion(true);
}}
>
{tenant ? tenant.console_image : ""}
</Button>
</td>
</tr>
<tr>
<td>Instances:</td>
<td>{instances}</td>
<td>Volumes:</td>
<td>{volumes}</td>
{tenant?.endpoints && (
<React.Fragment>
<td>Endpoint:</td>
<td>
<a
href={tenant?.endpoints.minio}
target="_blank"
rel="noopener noreferrer"
>
{tenant?.endpoints.minio}
</a>
</td>
<td>Console:</td>
<td>
<a
href={tenant?.endpoints.console}
target="_blank"
rel="noopener noreferrer"
>
{tenant?.endpoints.console}
</a>
</td>
</React.Fragment>
)}
</tr>
</table>
</Grid>
<Grid item xs={4}>
<UsageBarWrapper
currValue={usage}
maxValue={tenant ? tenant.total_size : 0}
label={"Storage"}
renderFunction={niceBytes}
error={usageError}
loading={loadingUsage}
/>
</Grid>
</Grid>
</Paper>
<br />
<br />
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={12}>
<table width={"100%"}>
<tr>
<td colSpan={4}>
<h2>Features</h2>
<hr className={classes.hrClass} />
</td>
</tr>
<tr>
<td>Logs:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
>
{logEnabled ? "Enabled" : "Disabled"}
</Button>
</td>
<td>Monitoring:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
>
{monitoringEnabled ? "Enabled" : "Disabled"}
</Button>
</td>
</tr>
<tr>
<td>Encryption:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
>
{encryptionEnabled ? "Enabled" : "Disabled"}
</Button>
</td>
<td></td>
<td></td>
</tr>
<tr>
{adEnabled ||
(!adEnabled && !oicEnabled && (
<React.Fragment>
<td>Active Directory:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
>
{adEnabled ? "Enabled" : "Disabled"}
</Button>
</td>
</React.Fragment>
))}
{oicEnabled ||
(!oicEnabled && !adEnabled && (
<React.Fragment>
<td>OpenID:</td>
<td>
<Button
color="primary"
className={classes.anchorButton}
>
{oicEnabled ? "Enabled" : "Disabled"}
</Button>
</td>
</React.Fragment>
))}
</tr>
</table>
</Grid>
</Grid>
</Paper>
</React.Fragment>
)}
{selectedTab === 1 && (
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Filter"
className={classes.searchField}
id="search-resource"
label=""
onChange={(event) => {
// setFilter(event.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddPool(true);
}}
>
Expand Tenant
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={[
{
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={pools}
entityName="Servers"
idField="name"
/>
</Grid>
</Grid>
)}
{selectedTab === 2 && (
<React.Fragment>
<br />
<TableWrapper
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Status", elementKey: "status" },
{ label: "Age", elementKey: "time" },
{ label: "Pod IP", elementKey: "podIP" },
{
label: "Restarts",
elementKey: "restarts",
renderFunction: (input) => {
return input != null ? input : 0;
},
},
{ label: "Node", elementKey: "node" },
]}
isLoading={false}
records={pods}
entityName="Servers"
idField="name"
/>
</React.Fragment>
)}
{selectedTab === 3 && (
<React.Fragment>
<br />
<Grid container>
<Grid item xs={12}>
<Paper
className={
tenant && tenant.subnet_license
? classes.licenseContainer
: ""
}
>
{tenant && tenant.subnet_license ? (
<React.Fragment>
<Grid container className={classes.licenseInfo}>
<Grid item xs={6}>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
License
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
Commercial License
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Organization
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.organization}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Registered Capacity
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{niceBytes(
(
tenant.subnet_license.storage_capacity *
1099511627776
) // 1 Terabyte = 1099511627776 Bytes
.toString(10)
)}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Expiry Date
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
<Moment format="YYYY-MM-DD">
{tenant.subnet_license.expires_at}
</Moment>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Subscription Plan
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.plan}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Requester
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{tenant.subnet_license.email}
</Typography>
</Grid>
<img
className={classes.verifiedIcon}
src={"/verified.svg"}
alt="Verified"
/>
</Grid>
</React.Fragment>
) : (
!loadingLicenseInfo && (
<Grid className={classes.paperContainer}>
{!licenseInfo && (
<Link
to={"/license"}
onClick={(e) => {
e.stopPropagation();
}}
className={classes.noUnderLine}
>
<Button
className={classes.licenseButton}
variant="contained"
color="primary"
>
Activate Product
</Button>
</Link>
)}
{licenseInfo && tenant && (
<Button
disabled={loadingActivateProduct}
className={classes.licenseButton}
variant="contained"
color="primary"
onClick={() =>
activateProduct(tenant.namespace, tenant.name)
}
>
Attach License
</Button>
)}
</Grid>
)
)}
</Paper>
<SubnetLicenseTenant
tenant={tenant}
loadingLicenseInfo={loadingLicenseInfo}
loadingActivateProduct={loadingActivateProduct}
licenseInfo={licenseInfo}
activateProduct={activateProduct}
/>
</Grid>
</Grid>
</React.Fragment>
)}
{selectedTab === 2 && pods.length > 1 && (
<TableWrapper
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Status", elementKey: "status" },
{ label: "Age", elementKey: "time" },
{ label: "Pod IP", elementKey: "podIP" },
{
label: "Restarts",
elementKey: "restarts",
renderFunction: (input) => {
return input != null ? input : 0;
},
},
{ label: "Node", elementKey: "node" },
]}
isLoading={false}
records={pods}
entityName="Servers"
idField="name"
/>
)}
</Grid>
</Grid>
</React.Fragment>

View File

@@ -381,6 +381,15 @@ func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInf
info := getTenantInfo(minTenant)
// detect if AD is enabled
adEnabled := false
for _, env := range minTenant.Spec.Env {
if env.Name == "MINIO_IDENTITY_LDAP_SERVER_ADDR" && env.Value != "" {
adEnabled = true
}
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
@@ -390,6 +399,26 @@ func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInf
client: clientSet,
}
// detect if OpenID is enabled
oicEnabled := false
consoleSecret, err := clientSet.CoreV1().Secrets(minTenant.Namespace).Get(ctx, minTenant.Name, metav1.GetOptions{})
// we can tolerate not getting this secret
if err != nil {
log.Println(err)
}
if consoleSecret != nil {
if _, ok := consoleSecret.Data["CONSOLE_IDP_URL"]; ok {
oicEnabled = true
}
}
info.LogEnabled = minTenant.HasLogEnabled()
info.MonitoringEnabled = minTenant.HasPrometheusEnabled()
info.EncryptionEnabled = minTenant.HasKESEnabled()
info.IdpAdEnabled = adEnabled
info.IdpOicEnabled = oicEnabled
if minTenant.Spec.Console != nil {
// obtain current subnet license for tenant (if exists)
license, _ := getSubscriptionLicense(context.Background(), &k8sClient, params.Namespace, minTenant.Spec.Console.ConsoleSecret.Name)

View File

@@ -6442,6 +6442,9 @@ func init() {
"enable_prometheus": {
"type": "boolean"
},
"encryptionEnabled": {
"type": "boolean"
},
"endpoints": {
"type": "object",
"properties": {
@@ -6453,9 +6456,21 @@ func init() {
}
}
},
"idpAdEnabled": {
"type": "boolean"
},
"idpOicEnabled": {
"type": "boolean"
},
"image": {
"type": "string"
},
"logEnabled": {
"type": "boolean"
},
"monitoringEnabled": {
"type": "boolean"
},
"name": {
"type": "string"
},
@@ -13842,6 +13857,9 @@ func init() {
"enable_prometheus": {
"type": "boolean"
},
"encryptionEnabled": {
"type": "boolean"
},
"endpoints": {
"type": "object",
"properties": {
@@ -13853,9 +13871,21 @@ func init() {
}
}
},
"idpAdEnabled": {
"type": "boolean"
},
"idpOicEnabled": {
"type": "boolean"
},
"image": {
"type": "string"
},
"logEnabled": {
"type": "boolean"
},
"monitoringEnabled": {
"type": "boolean"
},
"name": {
"type": "string"
},

View File

@@ -3527,6 +3527,17 @@ definitions:
type: string
console:
type: string
logEnabled:
type: boolean
monitoringEnabled:
type: boolean
idpAdEnabled:
type: boolean
idpOicEnabled:
type: boolean
encryptionEnabled:
type: boolean
tenantUsage:
type: object