Added loading validation to avoid flashing empty components (#1177)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2021-11-01 21:46:45 -06:00
committed by GitHub
parent e7f1aeff94
commit 684f089f61
10 changed files with 575 additions and 543 deletions

View File

@@ -168,31 +168,33 @@ const BucketEventsPanel = ({
customPaperHeight={classes.twHeight} customPaperHeight={classes.twHeight}
/> />
</Grid> </Grid>
<Grid item xs={12}> {!loadingEvents && (
<HelpBox <Grid item xs={12}>
title={"Lambda Notifications"} <HelpBox
iconComponent={<LambdaIcon />} title={"Lambda Notifications"}
help={ iconComponent={<LambdaIcon />}
<Fragment> help={
MinIO bucket notifications allow administrators to send <Fragment>
notifications to supported external services on certain object MinIO bucket notifications allow administrators to send
or bucket events. MinIO supports bucket and object-level S3 notifications to supported external services on certain object
events similar to the Amazon S3 Event Notifications. or bucket events. MinIO supports bucket and object-level S3
<br /> events similar to the Amazon S3 Event Notifications.
<br /> <br />
You can learn more at our{" "} <br />
<a You can learn more at our{" "}
href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con" <a
target="_blank" href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con"
rel="noreferrer" target="_blank"
> rel="noreferrer"
documentation >
</a> documentation
. </a>
</Fragment> .
} </Fragment>
/> }
</Grid> />
</Grid>
)}
</Grid> </Grid>
</Fragment> </Fragment>
); );

View File

@@ -208,31 +208,33 @@ const BucketLifecyclePanel = ({
customPaperHeight={classes.twHeight} customPaperHeight={classes.twHeight}
/> />
</Grid> </Grid>
<Grid item xs={12}> {!loadingLifecycle && (
<HelpBox <Grid item xs={12}>
title={"Lifecycle Rules"} <HelpBox
iconComponent={<TiersIcon />} title={"Lifecycle Rules"}
help={ iconComponent={<TiersIcon />}
<Fragment> help={
MinIO Object Lifecycle Management allows creating rules for time <Fragment>
or date based automatic transition or expiry of objects. For MinIO Object Lifecycle Management allows creating rules for
object transition, MinIO automatically moves the object to a time or date based automatic transition or expiry of objects.
configured remote storage tier. For object transition, MinIO automatically moves the object to
<br /> a configured remote storage tier.
<br /> <br />
You can learn more at our{" "} <br />
<a You can learn more at our{" "}
href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html?ref=con" <a
target="_blank" href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html?ref=con"
rel="noreferrer" target="_blank"
> rel="noreferrer"
documentation >
</a> documentation
. </a>
</Fragment> .
} </Fragment>
/> }
</Grid> />
</Grid>
)}
</Grid> </Grid>
</Fragment> </Fragment>
); );

View File

@@ -19,7 +19,7 @@ import { connect } from "react-redux";
import { Theme } from "@mui/material/styles"; import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles"; import withStyles from "@mui/styles/withStyles";
import { Box, Button } from "@mui/material"; import { Box, Button, LinearProgress } from "@mui/material";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment"; import InputAdornment from "@mui/material/InputAdornment";
@@ -28,13 +28,12 @@ import { Bucket, BucketList, HasPermissionResponse } from "../types";
import { import {
AddIcon, AddIcon,
BucketsIcon, BucketsIcon,
TenantsIcon,
WatchIcon, WatchIcon,
} from "../../../../icons"; } from "../../../../icons";
import { AppState } from "../../../../store"; import { AppState } from "../../../../store";
import { addBucketOpen, addBucketReset } from "../actions"; import { addBucketOpen, addBucketReset } from "../actions";
import { setErrorSnackMessage } from "../../../../actions"; import { setErrorSnackMessage } from "../../../../actions";
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary"; import { containerForHeader, linkStyles } from "../../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../../common/types"; import { ErrorResponseHandler } from "../../../../common/types";
import api from "../../../../common/api"; import api from "../../../../common/api";
import AddBucket from "./AddBucket"; import AddBucket from "./AddBucket";
@@ -127,10 +126,7 @@ const styles = (theme: Theme) =>
constrainedContainer: { constrainedContainer: {
maxWidth: 1180, maxWidth: 1180,
}, },
link: { ...linkStyles(theme.palette.info.main),
textDecoration: "underline",
color: theme.palette.info.main,
},
}); });
interface IListBucketsProps { interface IListBucketsProps {
@@ -408,53 +404,57 @@ const ListBuckets = ({
<Grid item xs={12}> <Grid item xs={12}>
<br /> <br />
</Grid> </Grid>
<Grid item xs={12}> {loading && <LinearProgress />}
{filteredRecords.map((bucket, index) => { {!loading && (
return ( <Grid item xs={12}>
<BucketListItem {filteredRecords.map((bucket, index) => {
bucket={bucket} return (
key={`bucketListItem-${index.toString()}`} <BucketListItem
onDelete={confirmDeleteBucket} bucket={bucket}
onSelect={selectListBuckets} key={`bucketListItem-${index.toString()}`}
selected={selectedBuckets.includes(bucket.name)} onDelete={confirmDeleteBucket}
bulkSelect={bulkSelect} onSelect={selectListBuckets}
/> selected={selectedBuckets.includes(bucket.name)}
); bulkSelect={bulkSelect}
})}
{filteredRecords.length == 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
iconComponent={<BucketsIcon />}
title={"Buckets"}
help={
<Fragment>
MinIO uses buckets to organize objects. A bucket is
similar to a folder or directory in a filesystem, where
each bucket can hold an arbitrary number of objects.
<br />
<br />
To get started,&nbsp;
<a
className={classes.link}
onClick={() => {
addBucketOpen(true);
}}
>
Create a Bucket.
</a>
</Fragment>
}
/> />
);
})}
{filteredRecords.length === 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
iconComponent={<BucketsIcon />}
title={"Buckets"}
help={
<Fragment>
MinIO uses buckets to organize objects. A bucket is
similar to a folder or directory in a filesystem,
where each bucket can hold an arbitrary number of
objects.
<br />
<br />
To get started,&nbsp;
<button
className={classes.link}
onClick={() => {
addBucketOpen(true);
}}
>
Create a Bucket.
</button>
</Fragment>
}
/>
</Grid>
</Grid> </Grid>
</Grid> )}
)} </Grid>
</Grid> )}
</Grid> </Grid>
</Grid> </Grid>
</Fragment> </Fragment>

View File

@@ -965,3 +965,13 @@ export const commonDashboardInfocard = {
}, },
}, },
}; };
export const linkStyles = (color: string) => ({
link: {
textDecoration: "underline",
color,
backgroundColor: "transparent",
border: 0,
cursor: "pointer",
},
});

View File

@@ -20,13 +20,14 @@ import { connect } from "react-redux";
import { Theme } from "@mui/material/styles"; import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles"; import withStyles from "@mui/styles/withStyles";
import { IconButton, TextField } from "@mui/material"; import { IconButton, LinearProgress, TextField } from "@mui/material";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import InputAdornment from "@mui/material/InputAdornment"; import InputAdornment from "@mui/material/InputAdornment";
import { import {
actionsTray, actionsTray,
containerForHeader, containerForHeader,
linkStyles,
searchField, searchField,
settingsCommon, settingsCommon,
typesSelection, typesSelection,
@@ -76,10 +77,7 @@ const styles = (theme: Theme) =>
...settingsCommon.customTitle, ...settingsCommon.customTitle,
marginTop: 0, marginTop: 0,
}, },
link: { ...linkStyles(theme.palette.info.main),
textDecoration: "underline",
color: theme.palette.info.main,
},
}); });
const ListTiersConfiguration = ({ const ListTiersConfiguration = ({
@@ -235,122 +233,127 @@ const ListTiersConfiguration = ({
<Grid item xs={12}> <Grid item xs={12}>
<br /> <br />
</Grid> </Grid>
{records.length > 0 && ( {isLoading && <LinearProgress />}
{!isLoading && (
<Fragment> <Fragment>
<Grid item xs={12}> {records.length > 0 && (
<TableWrapper <Fragment>
itemActions={[ <Grid item xs={12}>
{ <TableWrapper
type: "edit", itemActions={[
onClick: (tierData: ITierElement) => { {
setSelectedTier(tierData); type: "edit",
setUpdateCredentialsOpen(true); onClick: (tierData: ITierElement) => {
}, setSelectedTier(tierData);
}, setUpdateCredentialsOpen(true);
]} },
columns={[ },
{ ]}
label: "Tier Name", columns={[
elementKey: "type", {
renderFunction: renderTierName, label: "Tier Name",
renderFullObject: true, elementKey: "type",
}, renderFunction: renderTierName,
{ renderFullObject: true,
label: "Type", },
elementKey: "type", {
width: 150, label: "Type",
}, elementKey: "type",
{ width: 150,
label: "Endpoint", },
elementKey: "type", {
renderFunction: renderTierEndpoint, label: "Endpoint",
renderFullObject: true, elementKey: "type",
}, renderFunction: renderTierEndpoint,
{ renderFullObject: true,
label: "Bucket", },
elementKey: "type", {
renderFunction: renderTierBucket, label: "Bucket",
renderFullObject: true, elementKey: "type",
}, renderFunction: renderTierBucket,
{ renderFullObject: true,
label: "Prefix", },
elementKey: "type", {
renderFunction: renderTierPrefix, label: "Prefix",
renderFullObject: true, elementKey: "type",
}, renderFunction: renderTierPrefix,
{ renderFullObject: true,
label: "Region", },
elementKey: "type", {
renderFunction: renderTierRegion, label: "Region",
renderFullObject: true, elementKey: "type",
}, renderFunction: renderTierRegion,
]} renderFullObject: true,
isLoading={isLoading} },
records={filteredRecords} ]}
entityName="Tiers" isLoading={isLoading}
idField="service_name" records={filteredRecords}
customPaperHeight={classes.customConfigurationPage} entityName="Tiers"
/> idField="service_name"
</Grid> customPaperHeight={classes.customConfigurationPage}
<Grid item xs={12}> />
<HelpBox </Grid>
title={"Learn more about TIERS"} <Grid item xs={12}>
iconComponent={<TiersIcon />} <HelpBox
help={ title={"Learn more about TIERS"}
<Fragment> iconComponent={<TiersIcon />}
Tiers are used by the MinIO Object Lifecycle Management help={
which allows creating rules for time or date based automatic <Fragment>
transition or expiry of objects. For object transition, Tiers are used by the MinIO Object Lifecycle Management
MinIO automatically moves the object to a configured remote which allows creating rules for time or date based
storage tier. automatic transition or expiry of objects. For object
<br /> transition, MinIO automatically moves the object to a
<br /> configured remote storage tier.
You can learn more at our{" "} <br />
<a <br />
href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html?ref=con" You can learn more at our{" "}
target="_blank" <a
rel="noreferrer" href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html?ref=con"
> target="_blank"
documentation rel="noreferrer"
</a> >
. documentation
</Fragment> </a>
} .
/> </Fragment>
</Grid> }
/>
</Grid>
</Fragment>
)}
{records.length === 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Tiers"}
iconComponent={<TiersIcon />}
help={
<Fragment>
Tiers are used by the MinIO Object Lifecycle Management
which allows creating rules for time or date based
automatic transition or expiry of objects. For object
transition, MinIO automatically moves the object to a
configured remote storage tier.
<br />
<br />
To get started,{" "}
<button onClick={addTier} className={classes.link}>
Add Tier
</button>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Fragment> </Fragment>
)} )}
{records.length == 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Tiers"}
iconComponent={<TiersIcon />}
help={
<Fragment>
Tiers are used by the MinIO Object Lifecycle Management
which allows creating rules for time or date based automatic
transition or expiry of objects. For object transition,
MinIO automatically moves the object to a configured remote
storage tier.
<br />
<br />
To get started,{" "}
<a onClick={addTier} className={classes.link}>
Add Tier
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid> </Grid>
</Fragment> </Fragment>
); );

View File

@@ -22,7 +22,7 @@ import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment"; import InputAdornment from "@mui/material/InputAdornment";
import { Button } from "@mui/material"; import { Button, LinearProgress } from "@mui/material";
import { AddIcon, GroupsIcon, UsersIcon } from "../../../icons"; import { AddIcon, GroupsIcon, UsersIcon } from "../../../icons";
import { setErrorSnackMessage } from "../../../actions"; import { setErrorSnackMessage } from "../../../actions";
import { GroupsList } from "./types"; import { GroupsList } from "./types";
@@ -30,6 +30,7 @@ import { stringSort } from "../../../utils/sortFunctions";
import { import {
actionsTray, actionsTray,
containerForHeader, containerForHeader,
linkStyles,
searchField, searchField,
} from "../Common/FormComponents/common/styleLibrary"; } from "../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../common/types"; import { ErrorResponseHandler } from "../../../common/types";
@@ -80,10 +81,7 @@ const styles = (theme: Theme) =>
}, },
}, },
}, },
link: { ...linkStyles(theme.palette.info.main),
textDecoration: "underline",
color: theme.palette.info.main,
},
...actionsTray, ...actionsTray,
...searchField, ...searchField,
...containerForHeader(theme.spacing(4)), ...containerForHeader(theme.spacing(4)),
@@ -230,81 +228,86 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
<Grid item xs={12}> <Grid item xs={12}>
<br /> <br />
</Grid> </Grid>
{records.length > 0 && ( {loading && <LinearProgress />}
{!loading && (
<Fragment> <Fragment>
<Grid item xs={12}> {records.length > 0 && (
<TableWrapper <Fragment>
itemActions={tableActions} <Grid item xs={12}>
columns={[{ label: "Name", elementKey: "" }]} <TableWrapper
isLoading={loading} itemActions={tableActions}
records={filteredRecords} columns={[{ label: "Name", elementKey: "" }]}
entityName="Groups" isLoading={loading}
idField="" records={filteredRecords}
/> entityName="Groups"
</Grid> idField=""
<Grid item xs={12}> />
<HelpBox </Grid>
title={"Groups"} <Grid item xs={12}>
iconComponent={<GroupsIcon />} <HelpBox
help={ title={"Groups"}
<Fragment> iconComponent={<GroupsIcon />}
A group can have one attached IAM policy, where all users help={
with membership in that group inherit that policy. Groups <Fragment>
support more simplified management of user permissions on A group can have one attached IAM policy, where all
the MinIO Tenant. users with membership in that group inherit that
<br /> policy. Groups support more simplified management of
<br /> user permissions on the MinIO Tenant.
You can learn more at our{" "} <br />
<a <br />
href="https://docs.min.io/minio/k8s/tutorials/group-management.html?ref=con" You can learn more at our{" "}
target="_blank" <a
rel="noreferrer" href="https://docs.min.io/minio/k8s/tutorials/group-management.html?ref=con"
> target="_blank"
documentation rel="noreferrer"
</a> >
. documentation
</Fragment> </a>
} .
/> </Fragment>
</Grid> }
/>
</Grid>
</Fragment>
)}
{records.length === 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Groups"}
iconComponent={<UsersIcon />}
help={
<Fragment>
A group can have one attached IAM policy, where all
users with membership in that group inherit that
policy. Groups support more simplified management of
user permissions on the MinIO Tenant.
<br />
<br />
To get started,{" "}
<button
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
className={classes.link}
>
Create a Group
</button>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Fragment> </Fragment>
)} )}
{records.length == 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Groups"}
iconComponent={<UsersIcon />}
help={
<Fragment>
A group can have one attached IAM policy, where all users
with membership in that group inherit that policy. Groups
support more simplified management of user permissions on
the MinIO Tenant.
<br />
<br />
To get started,{" "}
<a
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
className={classes.link}
>
Create a Group
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid> </Grid>
</Grid> </Grid>
</React.Fragment> </React.Fragment>

View File

@@ -37,7 +37,6 @@ import { ErrorResponseHandler } from "../../../../common/types";
import api from "../../../../common/api"; import api from "../../../../common/api";
import TableWrapper from "../../Common/TableWrapper/TableWrapper"; import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import FilterInputWrapper from "../../Common/FormComponents/FilterInputWrapper/FilterInputWrapper"; import FilterInputWrapper from "../../Common/FormComponents/FilterInputWrapper/FilterInputWrapper";
import DateTimePickerWrapper from "../../Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper";
import LogSearchFullModal from "./LogSearchFullModal"; import LogSearchFullModal from "./LogSearchFullModal";
import { LogSearchColumnLabels } from "./utils"; import { LogSearchColumnLabels } from "./utils";
import DateRangeSelector from "../../Common/FormComponents/DateRangeSelector/DateRangeSelector"; import DateRangeSelector from "../../Common/FormComponents/DateRangeSelector/DateRangeSelector";

View File

@@ -19,7 +19,7 @@ import { connect } from "react-redux";
import { Theme } from "@mui/material/styles"; import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles"; import withStyles from "@mui/styles/withStyles";
import { IconButton, TextField } from "@mui/material"; import { IconButton, LinearProgress, TextField } from "@mui/material";
import { red } from "@mui/material/colors"; import { red } from "@mui/material/colors";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord"; import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
@@ -37,6 +37,7 @@ import { setErrorSnackMessage } from "../../../actions";
import { import {
actionsTray, actionsTray,
containerForHeader, containerForHeader,
linkStyles,
searchField, searchField,
settingsCommon, settingsCommon,
} from "../Common/FormComponents/common/styleLibrary"; } from "../Common/FormComponents/common/styleLibrary";
@@ -73,10 +74,7 @@ const styles = (theme: Theme) =>
lambdaContainer: { lambdaContainer: {
padding: "15px 0", padding: "15px 0",
}, },
link: { ...linkStyles(theme.palette.info.main),
textDecoration: "underline",
color: theme.palette.info.main,
},
}); });
const ListNotificationEndpoints = ({ const ListNotificationEndpoints = ({
@@ -186,91 +184,96 @@ const ListNotificationEndpoints = ({
<Grid item xs={12}> <Grid item xs={12}>
<br /> <br />
</Grid> </Grid>
{records.length > 0 && ( {isLoading && <LinearProgress />}
{!isLoading && (
<Fragment> <Fragment>
<Grid item xs={12}> {records.length > 0 && (
<TableWrapper <Fragment>
itemActions={[]} <Grid item xs={12}>
columns={[ <TableWrapper
{ itemActions={[]}
label: "Status", columns={[
elementKey: "status", {
renderFunction: statusDisplay, label: "Status",
width: 150, elementKey: "status",
}, renderFunction: statusDisplay,
{ label: "Service", elementKey: "service_name" }, width: 150,
]} },
isLoading={isLoading} { label: "Service", elementKey: "service_name" },
records={filteredRecords} ]}
entityName="Notification Endpoints" isLoading={isLoading}
idField="service_name" records={filteredRecords}
customPaperHeight={classes.twHeight} entityName="Notification Endpoints"
/> idField="service_name"
</Grid> customPaperHeight={classes.twHeight}
<Grid item xs={12}> />
<HelpBox </Grid>
title={"Notification Endpoints"} <Grid item xs={12}>
iconComponent={<LambdaIcon />} <HelpBox
help={ title={"Notification Endpoints"}
<Fragment> iconComponent={<LambdaIcon />}
MinIO bucket notifications allow administrators to send help={
notifications to supported external services on certain <Fragment>
object or bucket events. MinIO supports bucket and MinIO bucket notifications allow administrators to send
object-level S3 events similar to the Amazon S3 Event notifications to supported external services on certain
Notifications. object or bucket events. MinIO supports bucket and
<br /> object-level S3 events similar to the Amazon S3 Event
<br /> Notifications.
You can learn more at our{" "} <br />
<a <br />
href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con" You can learn more at our{" "}
target="_blank" <a
rel="noreferrer" href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con"
> target="_blank"
documentation rel="noreferrer"
</a> >
. documentation
</Fragment> </a>
} .
/> </Fragment>
</Grid> }
/>
</Grid>
</Fragment>
)}
{records.length === 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Notification Targets"}
iconComponent={<LambdaIcon />}
help={
<Fragment>
MinIO bucket notifications allow administrators to send
notifications to supported external services on certain
object or bucket events. MinIO supports bucket and
object-level S3 events similar to the Amazon S3 Event
Notifications.
<br />
<br />
To get started,{" "}
<button
onClick={() => {
history.push("/notification-endpoints/add");
}}
className={classes.link}
>
Add a Notification Target
</button>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Fragment> </Fragment>
)} )}
{records.length == 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Notification Targets"}
iconComponent={<LambdaIcon />}
help={
<Fragment>
MinIO bucket notifications allow administrators to send
notifications to supported external services on certain
object or bucket events. MinIO supports bucket and
object-level S3 events similar to the Amazon S3 Event
Notifications.
<br />
<br />
To get started,{" "}
<a
onClick={() => {
history.push("/notification-endpoints/add");
}}
className={classes.link}
>
Add a Notification Target
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid> </Grid>
</Fragment> </Fragment>
); );

View File

@@ -19,7 +19,7 @@ import { connect } from "react-redux";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment"; import InputAdornment from "@mui/material/InputAdornment";
import { Box, Button, IconButton } from "@mui/material"; import { Box, Button, IconButton, LinearProgress } from "@mui/material";
import { Theme } from "@mui/material/styles"; import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles"; import withStyles from "@mui/styles/withStyles";
@@ -29,6 +29,7 @@ import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
import { import {
actionsTray, actionsTray,
containerForHeader, containerForHeader,
linkStyles,
searchField, searchField,
} from "../../Common/FormComponents/common/styleLibrary"; } from "../../Common/FormComponents/common/styleLibrary";
import { setErrorSnackMessage } from "../../../../actions"; import { setErrorSnackMessage } from "../../../../actions";
@@ -104,10 +105,7 @@ const styles = (theme: Theme) =>
paddingTop: 30, paddingTop: 30,
paddingBottom: 30, paddingBottom: 30,
}, },
link: { ...linkStyles(theme.palette.info.main),
textDecoration: "underline",
color: theme.palette.info.main,
},
}); });
const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => { const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
@@ -273,43 +271,49 @@ const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
<RefreshIcon /> <RefreshIcon />
</IconButton> </IconButton>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
{filteredRecords.map((t) => { {isLoading && <LinearProgress />}
return <TenantListItem tenant={t} />; {!isLoading && (
})} <Fragment>
{filteredRecords.length == 0 && ( {filteredRecords.map((t) => {
<Grid return <TenantListItem tenant={t} />;
container })}
justifyContent={"center"} {filteredRecords.length === 0 && (
alignContent={"center"} <Grid
alignItems={"center"} container
> justifyContent={"center"}
<Grid item xs={8}> alignContent={"center"}
<HelpBox alignItems={"center"}
iconComponent={<TenantsIcon />} >
title={"Tenants"} <Grid item xs={8}>
help={ <HelpBox
<Fragment> iconComponent={<TenantsIcon />}
Tenant is the logical structure to represent a MinIO title={"Tenants"}
deployment. A tenant can have different size and help={
configurations from other tenants, even a different <Fragment>
storage class. Tenant is the logical structure to represent a
<br /> MinIO deployment. A tenant can have different size
<br /> and configurations from other tenants, even a
To get started,&nbsp; different storage class.
<a <br />
className={classes.link} <br />
onClick={() => { To get started,&nbsp;
history.push("/tenants/add"); <button
}} className={classes.link}
> onClick={() => {
Create a Tenant. history.push("/tenants/add");
</a> }}
</Fragment> >
} Create a Tenant.
/> </button>
</Grid> </Fragment>
</Grid> }
/>
</Grid>
</Grid>
)}
</Fragment>
)} )}
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -14,13 +14,19 @@
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react"; import React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Theme } from "@mui/material/styles"; import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles"; import withStyles from "@mui/styles/withStyles";
import api from "../../../common/api"; import api from "../../../common/api";
import { Button, Grid, InputAdornment, TextField } from "@mui/material"; import {
Button,
Grid,
InputAdornment,
LinearProgress,
TextField,
} from "@mui/material";
import GroupIcon from "@mui/icons-material/Group"; import GroupIcon from "@mui/icons-material/Group";
import { User, UsersList } from "./types"; import { User, UsersList } from "./types";
import { usersSort } from "../../../utils/sortFunctions"; import { usersSort } from "../../../utils/sortFunctions";
@@ -28,6 +34,7 @@ import { AddIcon, UsersIcon } from "../../../icons";
import { import {
actionsTray, actionsTray,
containerForHeader, containerForHeader,
linkStyles,
searchField, searchField,
} from "../Common/FormComponents/common/styleLibrary"; } from "../Common/FormComponents/common/styleLibrary";
import { setErrorSnackMessage } from "../../../actions"; import { setErrorSnackMessage } from "../../../actions";
@@ -80,10 +87,7 @@ const styles = (theme: Theme) =>
...actionsTray, ...actionsTray,
...searchField, ...searchField,
...containerForHeader(theme.spacing(4)), ...containerForHeader(theme.spacing(4)),
link: { ...linkStyles(theme.palette.info.main),
textDecoration: "underline",
color: theme.palette.info.main,
},
}); });
interface IUsersProps { interface IUsersProps {
@@ -94,7 +98,7 @@ interface IUsersProps {
const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => { const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
const [records, setRecords] = useState<User[]>([]); const [records, setRecords] = useState<User[]>([]);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(true);
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false); const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
const [deleteOpen, setDeleteOpen] = useState<boolean>(false); const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null); const [selectedUser, setSelectedUser] = useState<User | null>(null);
@@ -103,31 +107,15 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
const [checkedUsers, setCheckedUsers] = useState<string[]>([]); const [checkedUsers, setCheckedUsers] = useState<string[]>([]);
const [policyOpen, setPolicyOpen] = useState<boolean>(false); const [policyOpen, setPolicyOpen] = useState<boolean>(false);
const fetchRecords = useCallback(() => {
setLoading(true);
api
.invoke("GET", `/api/v1/users`)
.then((res: UsersList) => {
const users = res.users === null ? [] : res.users;
setLoading(false);
setRecords(users.sort(usersSort));
})
.catch((err: ErrorResponseHandler) => {
setLoading(false);
setErrorSnackMessage(err);
});
}, [setLoading, setRecords, setErrorSnackMessage]);
const closeAddModalAndRefresh = () => { const closeAddModalAndRefresh = () => {
setAddScreenOpen(false); setAddScreenOpen(false);
fetchRecords(); setLoading(true);
}; };
const closeDeleteModalAndRefresh = (refresh: boolean) => { const closeDeleteModalAndRefresh = (refresh: boolean) => {
setDeleteOpen(false); setDeleteOpen(false);
if (refresh) { if (refresh) {
fetchRecords(); setLoading(true);
} }
}; };
@@ -139,8 +127,21 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
}; };
useEffect(() => { useEffect(() => {
fetchRecords(); if (loading) {
}, [fetchRecords]); api
.invoke("GET", `/api/v1/users`)
.then((res: UsersList) => {
const users = res.users === null ? [] : res.users;
setLoading(false);
setRecords(users.sort(usersSort));
})
.catch((err: ErrorResponseHandler) => {
setLoading(false);
setErrorSnackMessage(err);
});
}
}, [loading, setErrorSnackMessage]);
const filteredRecords = records.filter((elementItem) => const filteredRecords = records.filter((elementItem) =>
elementItem.accessKey.includes(filter) elementItem.accessKey.includes(filter)
@@ -206,7 +207,7 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
selectedGroup={null} selectedGroup={null}
closeModalAndRefresh={() => { closeModalAndRefresh={() => {
setPolicyOpen(false); setPolicyOpen(false);
fetchRecords(); setLoading(true);
}} }}
/> />
)} )}
@@ -278,98 +279,103 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
<Grid item xs={12}> <Grid item xs={12}>
<br /> <br />
</Grid> </Grid>
{records.length > 0 && ( {loading && <LinearProgress />}
{!loading && (
<Fragment> <Fragment>
<Grid item xs={12}> {records.length > 0 && (
<TableWrapper <Fragment>
itemActions={tableActions} <Grid item xs={12}>
columns={[{ label: "Access Key", elementKey: "accessKey" }]} <TableWrapper
onSelect={selectionChanged} itemActions={tableActions}
selectedItems={checkedUsers} columns={[{ label: "Access Key", elementKey: "accessKey" }]}
isLoading={loading} onSelect={selectionChanged}
records={filteredRecords} selectedItems={checkedUsers}
entityName="Users" isLoading={loading}
idField="accessKey" records={filteredRecords}
customPaperHeight={classes.twHeight} entityName="Users"
/> idField="accessKey"
</Grid> customPaperHeight={classes.twHeight}
<Grid item xs={12}> />
<HelpBox </Grid>
title={"Users"} <Grid item xs={12}>
iconComponent={<UsersIcon />} <HelpBox
help={ title={"Users"}
<Fragment> iconComponent={<UsersIcon />}
A MinIO user consists of a unique access key (username) and help={
corresponding secret key (password). Clients must <Fragment>
authenticate their identity by specifying both a valid A MinIO user consists of a unique access key (username)
access key (username) and the corresponding secret key and corresponding secret key (password). Clients must
(password) of an existing MinIO user. authenticate their identity by specifying both a valid
<br /> access key (username) and the corresponding secret key
<br /> (password) of an existing MinIO user.
Each user can have one or more assigned policies that <br />
explicitly list the actions and resources to which that user <br />
has access. Users can also inherit policies from the groups Each user can have one or more assigned policies that
in which they have membership. explicitly list the actions and resources to which that
<br /> user has access. Users can also inherit policies from
<br /> the groups in which they have membership.
You can learn more at our{" "} <br />
<a <br />
href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con" You can learn more at our{" "}
target="_blank" <a
rel="noreferrer" href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con"
> target="_blank"
documentation rel="noreferrer"
</a> >
. documentation
</Fragment> </a>
} .
/> </Fragment>
</Grid> }
/>
</Grid>
</Fragment>
)}
{records.length === 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Users"}
iconComponent={<UsersIcon />}
help={
<Fragment>
A MinIO user consists of a unique access key (username)
and corresponding secret key (password). Clients must
authenticate their identity by specifying both a valid
access key (username) and the corresponding secret key
(password) of an existing MinIO user.
<br />
<br />
Each user can have one or more assigned policies that
explicitly list the actions and resources to which that
user has access. Users can also inherit policies from
the groups in which they have membership.
<br />
<br />
To get started,{" "}
<button
onClick={() => {
setAddScreenOpen(true);
setSelectedUser(null);
}}
className={classes.link}
>
Create a User
</button>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Fragment> </Fragment>
)} )}
{records.length == 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Users"}
iconComponent={<UsersIcon />}
help={
<Fragment>
A MinIO user consists of a unique access key (username) and
corresponding secret key (password). Clients must
authenticate their identity by specifying both a valid
access key (username) and the corresponding secret key
(password) of an existing MinIO user.
<br />
<br />
Each user can have one or more assigned policies that
explicitly list the actions and resources to which that user
has access. Users can also inherit policies from the groups
in which they have membership.
<br />
<br />
To get started,{" "}
<a
onClick={() => {
setAddScreenOpen(true);
setSelectedUser(null);
}}
className={classes.link}
>
Create a User
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid> </Grid>
</Fragment> </Fragment>
); );