Add Info Cards on Empty Screens (#1164)

* Add Info Cards on Empty Screens

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Fix ICon

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Daniel Valdivia
2021-10-29 12:31:36 -07:00
committed by GitHub
parent 7a5cc660d4
commit c9352f1a6a
12 changed files with 534 additions and 240 deletions

View File

@@ -1,9 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# beginning of customizations
#namespace: min-ns
resources:
- ../base
- https://github.com/minio/operator/?ref=v3.0.29

View File

@@ -25,38 +25,47 @@ const styles = (theme: Theme) =>
root: {
border: "1px solid rgb(234, 237, 238)",
borderRadius: 5,
paddingTop: 10,
paddingLeft: 40,
paddingRight: 40,
paddingBottom: 40,
marginTop: 10,
marginBottom: 10,
backgroundColor: "#fbfafa",
},
icon: {
textAlign: "center",
padding: 30,
fontSize: 64,
fontSize: 16,
fontWeight: "bold",
marginBottom: 20,
"& .MuiSvgIcon-root": {
fontSize: 64,
fontSize: 44,
marginRight: 15,
},
},
iconSize: {
fontSize: 64,
},
helpText: { padding: 30, paddingLeft: 0, fontSize: 16 },
helpText: {
fontSize: 16,
},
});
interface IHelpBox {
classes: any;
iconComponent: any;
title: string;
help: any;
}
const HelpBox = ({ classes, iconComponent, help }: IHelpBox) => {
const HelpBox = ({ classes, iconComponent, title, help }: IHelpBox) => {
return (
<div className={classes.root}>
<Grid container>
<Grid xs={2} className={classes.icon}>
<Grid xs={12} className={classes.icon}>
{iconComponent}
{title}
</Grid>
<Grid xs={10} className={classes.helpText}>
<Grid xs={12} className={classes.helpText}>
{help}
</Grid>
</Grid>

View File

@@ -170,6 +170,7 @@ const BucketEventsPanel = ({
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Lambda Notifications"}
iconComponent={<LambdaIcon />}
help={
<Fragment>

View File

@@ -210,6 +210,7 @@ const BucketLifecyclePanel = ({
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Lifecycle Rules"}
iconComponent={<TiersIcon />}
help={
<Fragment>

View File

@@ -246,6 +246,7 @@ const BucketReplicationPanel = ({
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Replication"}
iconComponent={<BucketsIcon />}
help={
<Fragment>

View File

@@ -25,7 +25,12 @@ import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import FileCopyIcon from "@mui/icons-material/FileCopy";
import { Bucket, BucketList, HasPermissionResponse } from "../types";
import { AddIcon, WatchIcon } from "../../../../icons";
import {
AddIcon,
BucketsIcon,
TenantsIcon,
WatchIcon,
} from "../../../../icons";
import { AppState } from "../../../../store";
import { addBucketOpen, addBucketReset } from "../actions";
import { setErrorSnackMessage } from "../../../../actions";
@@ -38,6 +43,7 @@ import PageHeader from "../../Common/PageHeader/PageHeader";
import BucketListItem from "./BucketListItem";
import BulkReplicationModal from "./BulkReplicationModal";
import SearchIcon from "../../../../icons/SearchIcon";
import HelpBox from "../../../../common/HelpBox";
const styles = (theme: Theme) =>
createStyles({
@@ -121,6 +127,10 @@ const styles = (theme: Theme) =>
constrainedContainer: {
maxWidth: 1180,
},
link: {
textDecoration: "underline",
color: theme.palette.info.main,
},
});
interface IListBucketsProps {
@@ -316,7 +326,7 @@ const ListBuckets = ({
className={classes.theaderSearch}
variant={"outlined"}
id="search-resource"
placeholder={"Search Buckets"}
placeholder={"Filter Buckets"}
onChange={(val) => {
setFilterBuckets(val.target.value);
}}
@@ -411,6 +421,39 @@ const ListBuckets = ({
/>
);
})}
{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>
}
/>
</Grid>
</Grid>
)}
</Grid>
</Grid>
</Grid>

View File

@@ -70,7 +70,7 @@ const PageHeader = ({
&nbsp;
</Grid>
</Box>
<Grid item xs={12} sm={12} md={6} className={classes.label}>
<Grid item xs={12} sm={12} md={3} className={classes.label}>
{!sidebarOpen && (
<div className={classes.logo}>
{operatorMode ? <OperatorLogo /> : <ConsoleLogo />}
@@ -81,7 +81,7 @@ const PageHeader = ({
</Typography>
</Grid>
{actions && (
<Grid item xs={12} sm={12} md={6} className={classes.rightMenu}>
<Grid item xs={12} sm={12} md={9} className={classes.rightMenu}>
{actions}
</Grid>
)}

View File

@@ -76,6 +76,10 @@ const styles = (theme: Theme) =>
...settingsCommon.customTitle,
marginTop: 0,
},
link: {
textDecoration: "underline",
color: theme.palette.info.main,
},
});
const ListTiersConfiguration = ({
@@ -189,130 +193,164 @@ const ListTiersConfiguration = ({
)}
<PageHeader label="Tiers" />
<Grid container className={classes.container}>
<Grid item xs={12} className={classes.lambdaContainer}>
<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>
),
}}
variant="standard"
/>
<IconButton
color="primary"
aria-label="Refresh List"
component="span"
onClick={() => {
setIsLoading(true);
}}
size="large"
>
<RefreshIcon />
</IconButton>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={addTier}
>
Add Tier
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={[
{
type: "edit",
onClick: (tierData: ITierElement) => {
setSelectedTier(tierData);
setUpdateCredentialsOpen(true);
},
},
]}
columns={[
{
label: "Tier Name",
elementKey: "type",
renderFunction: renderTierName,
renderFullObject: true,
},
{
label: "Type",
elementKey: "type",
width: 150,
},
{
label: "Endpoint",
elementKey: "type",
renderFunction: renderTierEndpoint,
renderFullObject: true,
},
{
label: "Bucket",
elementKey: "type",
renderFunction: renderTierBucket,
renderFullObject: true,
},
{
label: "Prefix",
elementKey: "type",
renderFunction: renderTierPrefix,
renderFullObject: true,
},
{
label: "Region",
elementKey: "type",
renderFunction: renderTierRegion,
renderFullObject: true,
},
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Tiers"
idField="service_name"
customPaperHeight={classes.customConfigurationPage}
/>
</Grid>
<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>
),
}}
variant="standard"
/>
<IconButton
color="primary"
aria-label="Refresh List"
component="span"
onClick={() => {
setIsLoading(true);
}}
size="large"
>
<RefreshIcon />
</IconButton>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={addTier}
>
Add a Tier
</Button>
</Grid>
<Grid item xs={12}>
<HelpBox
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 />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html?ref=con"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
<br />
</Grid>
{records.length > 0 && (
<Fragment>
<Grid item xs={12}>
<TableWrapper
itemActions={[
{
type: "edit",
onClick: (tierData: ITierElement) => {
setSelectedTier(tierData);
setUpdateCredentialsOpen(true);
},
},
]}
columns={[
{
label: "Tier Name",
elementKey: "type",
renderFunction: renderTierName,
renderFullObject: true,
},
{
label: "Type",
elementKey: "type",
width: 150,
},
{
label: "Endpoint",
elementKey: "type",
renderFunction: renderTierEndpoint,
renderFullObject: true,
},
{
label: "Bucket",
elementKey: "type",
renderFunction: renderTierBucket,
renderFullObject: true,
},
{
label: "Prefix",
elementKey: "type",
renderFunction: renderTierPrefix,
renderFullObject: true,
},
{
label: "Region",
elementKey: "type",
renderFunction: renderTierRegion,
renderFullObject: true,
},
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Tiers"
idField="service_name"
customPaperHeight={classes.customConfigurationPage}
/>
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Learn more about 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 />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html?ref=con"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
</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,{" "}
<a onClick={addTier} className={classes.link}>
Add Tier
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid>
</Fragment>
);

View File

@@ -14,7 +14,7 @@
// 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 React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
@@ -23,7 +23,7 @@ import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import { Button } from "@mui/material";
import { AddIcon } from "../../../icons";
import { AddIcon, GroupsIcon, UsersIcon } from "../../../icons";
import { setErrorSnackMessage } from "../../../actions";
import { GroupsList } from "./types";
import { stringSort } from "../../../utils/sortFunctions";
@@ -40,6 +40,7 @@ import TableWrapper from "../Common/TableWrapper/TableWrapper";
import SetPolicy from "../Policies/SetPolicy";
import PageHeader from "../Common/PageHeader/PageHeader";
import SearchIcon from "../../../icons/SearchIcon";
import HelpBox from "../../../common/HelpBox";
interface IGroupsProps {
classes: any;
@@ -79,6 +80,10 @@ const styles = (theme: Theme) =>
},
},
},
link: {
textDecoration: "underline",
color: theme.palette.info.main,
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
@@ -225,16 +230,81 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "" }]}
isLoading={loading}
records={filteredRecords}
entityName="Groups"
idField=""
/>
</Grid>
{records.length > 0 && (
<Fragment>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "" }]}
isLoading={loading}
records={filteredRecords}
entityName="Groups"
idField=""
/>
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Groups"}
iconComponent={<GroupsIcon />}
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 />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/k8s/tutorials/group-management.html?ref=con"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
</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,{" "}
<a
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
className={classes.link}
>
Create a Group
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid>
</Grid>
</React.Fragment>

View File

@@ -73,6 +73,10 @@ const styles = (theme: Theme) =>
lambdaContainer: {
padding: "15px 0",
},
link: {
textDecoration: "underline",
color: theme.palette.info.main,
},
});
const ListNotificationEndpoints = ({
@@ -180,48 +184,93 @@ const ListNotificationEndpoints = ({
</Button>
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={[]}
columns={[
{
label: "Status",
elementKey: "status",
renderFunction: statusDisplay,
width: 150,
},
{ label: "Service", elementKey: "service_name" },
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Notification Endpoints"
idField="service_name"
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
<HelpBox
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 />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
<br />
</Grid>
{records.length > 0 && (
<Fragment>
<Grid item xs={12}>
<TableWrapper
itemActions={[]}
columns={[
{
label: "Status",
elementKey: "status",
renderFunction: statusDisplay,
width: 150,
},
{ label: "Service", elementKey: "service_name" },
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Notification Endpoints"
idField="service_name"
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Notification Endpoints"}
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 />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
</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,{" "}
<a
onClick={() => {
history.push("/notification-endpoints/add");
}}
className={classes.link}
>
Add a Notification Target
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid>
</Fragment>
);

View File

@@ -32,7 +32,7 @@ import {
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import { setErrorSnackMessage } from "../../../../actions";
import { AddIcon } from "../../../../icons";
import { AddIcon, TenantsIcon } from "../../../../icons";
import { ErrorResponseHandler } from "../../../../common/types";
import api from "../../../../common/api";
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
@@ -41,6 +41,7 @@ import RefreshIcon from "../../../../icons/RefreshIcon";
import SearchIcon from "../../../../icons/SearchIcon";
import PageHeader from "../../Common/PageHeader/PageHeader";
import TenantListItem from "./TenantListItem";
import HelpBox from "../../../../common/HelpBox";
interface ITenantsList {
classes: any;
@@ -103,6 +104,10 @@ const styles = (theme: Theme) =>
paddingTop: 30,
paddingBottom: 30,
},
link: {
textDecoration: "underline",
color: theme.palette.info.main,
},
});
const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
@@ -272,6 +277,40 @@ const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
{filteredRecords.map((t) => {
return <TenantListItem tenant={t} />;
})}
{filteredRecords.length == 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
iconComponent={<TenantsIcon />}
title={"Tenants"}
help={
<Fragment>
Tenant is the logical structure to represent a MinIO
deployment. A tenant can have different size and
configurations from other tenants, even a different
storage class.
<br />
<br />
To get started,&nbsp;
<a
className={classes.link}
onClick={() => {
history.push("/tenants/add");
}}
>
Create a Tenant.
</a>
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid>
</Grid>
</Grid>

View File

@@ -80,6 +80,10 @@ const styles = (theme: Theme) =>
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
link: {
textDecoration: "underline",
color: theme.palette.info.main,
},
});
interface IUsersProps {
@@ -274,50 +278,98 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
onSelect={selectionChanged}
selectedItems={checkedUsers}
isLoading={loading}
records={filteredRecords}
entityName="Users"
idField="accessKey"
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
<HelpBox
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 />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
</Grid>
{records.length > 0 && (
<Fragment>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
onSelect={selectionChanged}
selectedItems={checkedUsers}
isLoading={loading}
records={filteredRecords}
entityName="Users"
idField="accessKey"
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
<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 />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html?ref=con"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
</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,{" "}
<a
onClick={() => {
setAddScreenOpen(true);
setSelectedUser(null);
}}
className={classes.link}
>
Create a User
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Grid>
</Fragment>
);