New Vertical Tab UX refactor (#1210)

Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Prakash Senthil Vel
2021-11-13 01:56:29 +05:30
committed by GitHub
parent 33f13c4853
commit 258a9400d9
31 changed files with 1366 additions and 1262 deletions

View File

@@ -19,18 +19,27 @@ import { Link } from "react-router-dom";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid";
import { BackSettingsIcon } from "../icons";
const styles = (theme: Theme) =>
createStyles({
link: {
display: "flex",
alignItems: "center",
textDecoration: "none",
color: theme.palette.primary.main,
fontSize: 18,
fontWeight: 600,
marginBottom: 10,
marginTop: 10,
maxWidth: "250px",
padding: "2rem 2rem 0rem 2rem",
color: theme.palette.primary.light,
fontSize: ".8rem",
"&:hover": {
textDecoration: "underline",
},
},
icon: {
marginRight: ".3rem",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
});
@@ -43,12 +52,10 @@ interface IBackLink {
const BackLink = ({ to, label, classes }: IBackLink) => {
return (
<Link to={to} className={classes.link}>
<Grid container spacing={1}>
<Grid item>
<BackSettingsIcon />
</Grid>
<Grid item>{label}</Grid>
</Grid>
<div className={classes.icon}>
<BackSettingsIcon />
</div>
<div>{label}</div>
</Link>
);
};

View File

@@ -61,11 +61,11 @@ const HelpBox = ({ classes, iconComponent, title, help }: IHelpBox) => {
return (
<div className={classes.root}>
<Grid container>
<Grid xs={12} className={classes.icon}>
<Grid item xs={12} className={classes.icon}>
{iconComponent}
{title}
</Grid>
<Grid xs={12} className={classes.helpText}>
<Grid item xs={12} className={classes.helpText}>
{help}
</Grid>
</Grid>

View File

@@ -42,6 +42,7 @@ import { ErrorResponseHandler } from "../../../common/types";
import ChangePasswordModal from "./ChangePasswordModal";
import SearchIcon from "../../../icons/SearchIcon";
import HelpBox from "../../../common/HelpBox";
import PageLayout from "../Common/Layout/PageLayout";
const styles = (theme: Theme) =>
createStyles({
@@ -75,9 +76,6 @@ const styles = (theme: Theme) =>
},
},
},
twHeight: {
minHeight: 600,
},
imageIcon: {
height: "100%",
},
@@ -234,7 +232,7 @@ const Account = ({
</React.Fragment>
}
/>
<Grid container className={classes.container}>
<PageLayout>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Service Accounts"
@@ -277,7 +275,6 @@ const Account = ({
idField={""}
columns={[{ label: "Service Account", elementKey: "" }]}
itemActions={tableActions}
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
@@ -308,7 +305,7 @@ const Account = ({
}
/>
</Grid>
</Grid>
</PageLayout>
</React.Fragment>
);
};

View File

@@ -16,9 +16,6 @@
import React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Paper } from "@mui/material";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
@@ -42,8 +39,6 @@ import {
} from "../../../../types";
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
const styles = (theme: Theme) => createStyles({});
const mapState = (state: AppState) => ({
session: state.console.session,
loadingBucket: state.buckets.bucketDetails.loadingBucket,
@@ -206,4 +201,4 @@ const AccessDetails = ({
);
};
export default withStyles(styles)(connector(AccessDetails));
export default connector(AccessDetails);

View File

@@ -99,9 +99,7 @@ const BrowserHandler = ({
)
}
/>
<Grid container className={classes.container}>
{fileMode ? <ObjectDetails /> : <ListObjects />}
</Grid>
<Grid>{fileMode ? <ObjectDetails /> : <ListObjects />}</Grid>
</Fragment>
);
};

View File

@@ -28,6 +28,7 @@ import {
buttonsStyles,
containerForHeader,
hrClass,
pageContentStyles,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import { setErrorSnackMessage } from "../../../../actions";
@@ -44,9 +45,6 @@ import BucketSummaryPanel from "./BucketSummaryPanel";
import BucketEventsPanel from "./BucketEventsPanel";
import BucketReplicationPanel from "./BucketReplicationPanel";
import BucketLifecyclePanel from "./BucketLifecyclePanel";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
import { IconButton, Tooltip } from "@mui/material";
import { BucketsIcon, DeleteIcon, FolderIcon } from "../../../../icons";
@@ -69,6 +67,9 @@ import {
S3_PUT_REPLICATION_CONFIGURATION,
} from "../../../../types";
import { displayComponent } from "../../../../utils/permissions";
import PageLayout from "../../Common/Layout/PageLayout";
import VerticalTabs from "../../Common/VerticalTabs/VerticalTabs";
import BackLink from "../../../../common/BackLink";
const styles = (theme: Theme) =>
createStyles({
@@ -80,6 +81,11 @@ const styles = (theme: Theme) =>
overflow: "auto",
flexDirection: "column",
},
pageContainer: {
border: "1px solid #EAEAEA",
height: "100%",
},
...pageContentStyles,
addSideBar: {
width: "320px",
padding: "20px",
@@ -216,6 +222,15 @@ const BucketDetails = ({
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const bucketName = match.params["bucketName"];
let selTab = match?.params["0"];
selTab = selTab ? selTab : "summary";
const [activeTab, setActiveTab] = useState(selTab);
useEffect(() => {
setActiveTab(selTab);
}, [selTab]);
useEffect(() => {
if (!iniLoad) {
setBucketDetailsLoad(true);
@@ -244,45 +259,25 @@ const BucketDetails = ({
setErrorSnackMessage,
]);
useEffect(() => {
let matchURL = match.params ? match.params["0"] : "browse";
let topLevelRoute = `/buckets/${bucketName}`;
const defaultRoute = "/admin/summary";
if (!matchURL) {
matchURL = "";
const manageBucketRoutes: Record<string, any> = {
events: "/admin/events",
replication: "/admin/replication",
lifecycle: "/admin/lifecycle",
access: "/admin/access",
prefix: "/admin/prefix",
};
const getRoutePath = (routeKey: string) => {
let path = manageBucketRoutes[routeKey];
if (!path) {
path = `${topLevelRoute}${defaultRoute}`;
} else {
path = `${topLevelRoute}${path}`;
}
const splitMatch = matchURL.split("/");
if (selectedTab !== splitMatch[0]) {
setBucketDetailsTab(splitMatch[0]);
}
}, [match, bucketName, setBucketDetailsTab, selectedTab]);
const changeRoute = (newTab: string) => {
let mainRoute = `/buckets/${bucketName}`;
switch (newTab) {
case "events":
mainRoute += "/admin/events";
break;
case "replication":
mainRoute += "/admin/replication";
break;
case "lifecycle":
mainRoute += "/admin/lifecycle";
break;
case "access":
mainRoute += "/admin/access";
break;
case "prefix":
mainRoute += "/admin/prefix";
break;
default:
mainRoute += "/admin/summary";
}
setBucketDetailsTab(newTab);
history.push(mainRoute);
return path;
};
const closeDeleteModalAndRefresh = (refresh: boolean) => {
@@ -331,7 +326,8 @@ const BucketDetails = ({
</Fragment>
}
/>
<Grid container className={classes.container}>
<BackLink to={"/buckets"} label={"Back to Buckets"} />
<PageLayout className={classes.pageContainer}>
<Grid item xs={12}>
<ScreenTitle
icon={
@@ -390,138 +386,133 @@ const BucketDetails = ({
}
/>
</Grid>
<Grid item xs={2}>
<List component="nav" dense={true}>
<ListItem
button
selected={selectedTab === "summary"}
onClick={() => {
changeRoute("summary");
}}
>
<ListItemText primary="Summary" />
</ListItem>
<ListItem
disabled={
!displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_NOTIFICATIONS,
S3_PUT_BUCKET_NOTIFICATIONS,
])
}
button
selected={selectedTab === "events"}
onClick={() => {
changeRoute("events");
}}
>
<ListItemText primary="Events" />
</ListItem>
<ListItem
button
disabled={
<VerticalTabs
selectedTab={activeTab}
isRouteTabs
routes={
<div className={classes.contentSpacer}>
<Router history={history}>
<Switch>
<Route
exact
path="/buckets/:bucketName/admin/summary"
component={BucketSummaryPanel}
/>
<Route
exact
path="/buckets/:bucketName/admin/events"
component={BucketEventsPanel}
/>
{distributedSetup && (
<Route
exact
path="/buckets/:bucketName/admin/replication"
component={BucketReplicationPanel}
/>
)}
{distributedSetup && (
<Route
exact
path="/buckets/:bucketName/admin/lifecycle"
component={BucketLifecyclePanel}
/>
)}
<Route
exact
path="/buckets/:bucketName/admin/access"
component={AccessDetailsPanel}
/>
<Route
exact
path="/buckets/:bucketName/admin/prefix"
component={AccessRulePanel}
/>
<Route
path="/buckets/:bucketName"
component={() => (
<Redirect to={`/buckets/${bucketName}/admin/summary`} />
)}
/>
</Switch>
</Router>
</div>
}
>
{{
tabConfig: {
label: "Summary",
value: "summary",
component: Link,
to: getRoutePath("summary"),
},
}}
{{
tabConfig: {
label: "Events",
value: "events",
component: Link,
disabled: !displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_NOTIFICATIONS,
S3_PUT_BUCKET_NOTIFICATIONS,
]),
to: getRoutePath("events"),
},
}}
{{
tabConfig: {
label: "Replication",
value: "replication",
component: Link,
disabled:
!distributedSetup ||
!displayComponent(bucketInfo?.allowedActions, [
S3_GET_REPLICATION_CONFIGURATION,
S3_PUT_REPLICATION_CONFIGURATION,
])
}
selected={selectedTab === "replication"}
onClick={() => {
changeRoute("replication");
}}
>
<ListItemText primary="Replication" />
</ListItem>
<ListItem
button
disabled={
]),
to: getRoutePath("replication"),
},
}}
{{
tabConfig: {
label: "Lifecycle",
value: "lifecycle",
component: Link,
disabled:
!distributedSetup ||
!displayComponent(bucketInfo?.allowedActions, [
S3_GET_LIFECYCLE_CONFIGURATION,
S3_PUT_LIFECYCLE_CONFIGURATION,
])
}
selected={selectedTab === "lifecycle"}
onClick={() => {
changeRoute("lifecycle");
}}
>
<ListItemText primary="Lifecycle" />
</ListItem>
<ListItem
button
disabled={
!displayComponent(bucketInfo?.allowedActions, [
ADMIN_GET_POLICY,
ADMIN_LIST_USER_POLICIES,
ADMIN_LIST_USERS,
])
}
selected={selectedTab === "access"}
onClick={() => {
changeRoute("access");
}}
>
<ListItemText primary="Access Audit" />
</ListItem>
<ListItem
button
disabled={
!displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_POLICY,
])
}
selected={selectedTab === "prefix"}
onClick={() => {
changeRoute("prefix");
}}
>
<ListItemText primary="Access Rules" />
</ListItem>
</List>
</Grid>
<Grid item xs={10}>
<Router history={history}>
<Switch>
<Route
path="/buckets/:bucketName/admin/summary"
component={BucketSummaryPanel}
/>
<Route
path="/buckets/:bucketName/admin/events"
component={BucketEventsPanel}
/>
{distributedSetup && (
<Route
path="/buckets/:bucketName/admin/replication"
component={BucketReplicationPanel}
/>
)}
{distributedSetup && (
<Route
path="/buckets/:bucketName/admin/lifecycle"
component={BucketLifecyclePanel}
/>
)}
<Route
path="/buckets/:bucketName/admin/access"
component={AccessDetailsPanel}
/>
<Route
path="/buckets/:bucketName/admin/prefix"
component={AccessRulePanel}
/>
<Route
path="/buckets/:bucketName"
component={() => (
<Redirect to={`/buckets/${bucketName}/admin/summary`} />
)}
/>
</Switch>
</Router>
</Grid>
</Grid>
]),
to: getRoutePath("lifecycle"),
},
}}
{{
tabConfig: {
label: "Access Audit",
value: "access",
component: Link,
disabled: !displayComponent(bucketInfo?.allowedActions, [
ADMIN_GET_POLICY,
ADMIN_LIST_USER_POLICIES,
ADMIN_LIST_USERS,
]),
to: getRoutePath("access"),
},
}}
{{
tabConfig: {
label: "Access Rules",
value: "prefix",
component: Link,
disabled: !displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_POLICY,
]),
to: getRoutePath("prefix"),
},
}}
</VerticalTabs>
</PageLayout>
</Fragment>
);
};

View File

@@ -48,9 +48,6 @@ const styles = (theme: Theme) =>
createStyles({
...searchField,
...actionsTray,
actionsTray: {
...actionsTray.actionsTray,
},
twHeight: {
minHeight: 400,
},

View File

@@ -34,7 +34,7 @@ import {
BucketReplication,
BucketVersioning,
} from "../types";
import { encodeFileName, niceBytes } from "../../../../common/utils";
import { niceBytes } from "../../../../common/utils";
import { Bucket, BucketList } from "../../Watch/types";
import {
buttonsStyles,

View File

@@ -102,6 +102,7 @@ import {
} from "../../../../../../types";
import { setBucketDetailsLoad, setBucketInfo } from "../../../actions";
import { AppState } from "../../../../../../store";
import PageLayout from "../../../../Common/Layout/PageLayout";
import BoxIconButton from "../../../../Common/BoxIconButton/BoxIconButton";
const commonIcon = {
@@ -1063,7 +1064,7 @@ const ListObjects = ({
/>
)}
<Grid container>
<PageLayout>
<Grid item xs={12}>
<ScreenTitle
icon={
@@ -1227,7 +1228,7 @@ const ListObjects = ({
}}
/>
</Grid>
</Grid>
</PageLayout>
</React.Fragment>
);
};

View File

@@ -26,16 +26,12 @@ import withStyles from "@mui/styles/withStyles";
import {
CircularProgress,
LinearProgress,
Paper,
Table,
TableBody,
TableCell,
TableRow,
Tooltip,
} from "@mui/material";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import List from "@mui/material/List";
import Grid from "@mui/material/Grid";
import Chip from "@mui/material/Chip";
import TextField from "@mui/material/TextField";
@@ -53,7 +49,6 @@ import {
} from "../../../../Common/FormComponents/common/styleLibrary";
import { FileInfoResponse, IFileInfo } from "./types";
import { download, extensionPreview } from "../utils";
import { TabPanel } from "../../../../../shared/tabs";
import history from "../../../../../../history";
import api from "../../../../../../common/api";
import ShareIcon from "../../../../../../icons/ShareIcon";
@@ -93,6 +88,8 @@ import SearchIcon from "../../../../../../icons/SearchIcon";
import ObjectBrowserIcon from "../../../../../../icons/ObjectBrowserIcon";
import PreviewFileContent from "../Preview/PreviewFileContent";
import RestoreFileVersion from "./RestoreFileVersion";
import PageLayout from "../../../../Common/Layout/PageLayout";
import VerticalTabs from "../../../../Common/VerticalTabs/VerticalTabs";
import BoxIconButton from "../../../../Common/BoxIconButton/BoxIconButton";
import { RecoverIcon } from "../../../../../../icons";
@@ -101,6 +98,10 @@ const styles = (theme: Theme) =>
currentItemContainer: {
marginBottom: 8,
},
pageContainer: {
border: "1px solid #EAEAEA",
height: "100%",
},
objectPathContainer: {
marginBottom: 26,
fontSize: 10,
@@ -271,7 +272,6 @@ const ObjectDetails = ({
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [metadataLoad, setMetadataLoad] = useState<boolean>(true);
const [metadata, setMetadata] = useState<any>({});
const [selectedTab, setSelectedTab] = useState<number>(0);
const [loadingBucket, setLoadingBucket] = useState<boolean>(false);
const [bucketInfo, setBucketInfo] = useState<any>(null);
const [restoreVersionOpen, setRestoreVersionOpen] = useState<boolean>(false);
@@ -615,7 +615,7 @@ const ObjectDetails = ({
/>
)}
<Grid container>
<PageLayout className={classes.pageContainer}>
{!actualInfo && (
<Grid item xs={12}>
<LinearProgress />
@@ -705,176 +705,135 @@ const ObjectDetails = ({
}
/>
</Grid>
<Grid item xs={2}>
<List component="nav" dense={true}>
<ListItem
button
selected={selectedTab === 0}
onClick={() => {
setSelectedTab(0);
}}
>
<ListItemText primary="Details" />
</ListItem>
<ListItem
button
selected={selectedTab === 1}
onClick={() => {
setSelectedTab(1);
}}
disabled={
!(actualInfo.version_id && actualInfo.version_id !== "null")
}
>
<ListItemText primary="Versions" />
</ListItem>
<ListItem
button
selected={selectedTab === 2}
onClick={() => {
setSelectedTab(2);
}}
disabled={extensionPreview(currentItem) === "none"}
>
<ListItemText primary="Preview" />
</ListItem>
</List>
</Grid>
<Grid item xs={10}>
<Grid item xs={12}>
<TabPanel index={0} value={selectedTab}>
<div className={classes.actionsTray}>
<h1 className={classes.sectionTitle}>Details</h1>
</div>
<br />
{(displayObjectLegalHold ||
displayObjectRetention ||
displayObjectTag) && (
<Fragment>
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={10}>
<table width={"100%"}>
<tbody>
{displayObjectLegalHold && (
<tr>
<td className={classes.titleCol}>
Legal Hold:
</td>
<td className={classes.capitalizeFirst}>
{actualInfo.version_id &&
actualInfo.version_id !== "null" ? (
<Fragment>
{actualInfo.legal_hold_status
? actualInfo.legal_hold_status.toLowerCase()
: "Off"}
{displayEditObjectLegalHold && (
<IconButton
color="primary"
aria-label="legal-hold"
size="small"
className={classes.propertiesIcon}
onClick={() => {
setLegalholdOpen(true);
}}
>
<EditIcon />
</IconButton>
)}
</Fragment>
) : (
"Disabled"
)}
</td>
</tr>
)}
{displayObjectRetention && (
<tr>
<td className={classes.titleCol}>
Retention:
</td>
<td className={classes.capitalizeFirst}>
{actualInfo.retention_mode
? actualInfo.retention_mode.toLowerCase()
: "None"}
{displayEditObjectRetention && (
<VerticalTabs>
{{
tabConfig: {
label: "Details",
},
content: (
<React.Fragment>
<div className={classes.actionsTray}>
<h1 className={classes.sectionTitle}>Details</h1>
</div>
<br />
{(displayObjectLegalHold ||
displayObjectRetention ||
displayObjectTag) && (
<Grid item xs={12}>
<table width={"100%"}>
<tbody>
{displayObjectLegalHold && (
<tr>
<td className={classes.titleCol}>
Legal Hold:
</td>
<td className={classes.capitalizeFirst}>
{actualInfo.version_id &&
actualInfo.version_id !== "null" ? (
<Fragment>
{actualInfo.legal_hold_status
? actualInfo.legal_hold_status.toLowerCase()
: "Off"}
{displayEditObjectLegalHold && (
<IconButton
color="primary"
aria-label="retention"
aria-label="legal-hold"
size="small"
className={classes.propertiesIcon}
onClick={() => {
openRetentionModal();
setLegalholdOpen(true);
}}
>
<EditIcon />
</IconButton>
)}
</td>
</tr>
)}
{displayObjectTag && (
<tr>
<td className={classes.titleCol}>Tags:</td>
<td>
{tagKeys &&
tagKeys.map((tagKey, index) => {
const tag = get(
actualInfo,
`tags.${tagKey}`,
""
);
if (tag !== "") {
return displayRemoveObjectTagging ? (
<Chip
key={`chip-${index}`}
className={classes.tag}
size="small"
label={`${tagKey} : ${tag}`}
color="primary"
deleteIcon={<CloseIcon />}
onDelete={() => {
deleteTag(tagKey, tag);
}}
/>
) : (
<Chip
key={`chip-${index}`}
className={classes.tag}
size="small"
label={`${tagKey} : ${tag}`}
color="primary"
/>
);
}
return null;
})}
{displayEditObjectTagging && (
<Chip
className={classes.tag}
icon={<AddIcon />}
clickable
size="small"
label="Add tag"
color="primary"
variant="outlined"
onClick={() => {
setTagModalOpen(true);
}}
/>
)}
</td>
</tr>
)}
</tbody>
</table>
</Grid>
</Grid>
</Paper>
<br />
</Fragment>
)}
<Paper className={classes.paperContainer}>
</Fragment>
) : (
"Disabled"
)}
</td>
</tr>
)}
{displayObjectRetention && (
<tr>
<td className={classes.titleCol}>Retention:</td>
<td className={classes.capitalizeFirst}>
{actualInfo.retention_mode
? actualInfo.retention_mode.toLowerCase()
: "None"}
{displayEditObjectRetention && (
<IconButton
color="primary"
aria-label="retention"
size="small"
className={classes.propertiesIcon}
onClick={() => {
openRetentionModal();
}}
>
<EditIcon />
</IconButton>
)}
</td>
</tr>
)}
{displayObjectTag && (
<tr>
<td className={classes.titleCol}>Tags:</td>
<td>
{tagKeys &&
tagKeys.map((tagKey, index) => {
const tag = get(
actualInfo,
`tags.${tagKey}`,
""
);
if (tag !== "") {
return displayRemoveObjectTagging ? (
<Chip
key={`chip-${index}`}
className={classes.tag}
size="small"
label={`${tagKey} : ${tag}`}
color="primary"
deleteIcon={<CloseIcon />}
onDelete={() => {
deleteTag(tagKey, tag);
}}
/>
) : (
<Chip
key={`chip-${index}`}
className={classes.tag}
size="small"
label={`${tagKey} : ${tag}`}
color="primary"
/>
);
}
return null;
})}
{displayEditObjectTagging && (
<Chip
className={classes.tag}
icon={<AddIcon />}
clickable
size="small"
label="Add tag"
color="primary"
variant="outlined"
onClick={() => {
setTagModalOpen(true);
}}
/>
)}
</td>
</tr>
)}
</tbody>
</table>
</Grid>
)}
<Grid item xs={12}>
<Grid item xs={12}>
<h2>Object Metadata</h2>
@@ -907,9 +866,17 @@ const ObjectDetails = ({
</Table>
</Grid>
</Grid>
</Paper>
</TabPanel>
<TabPanel index={1} value={selectedTab}>
</React.Fragment>
),
}}
{{
tabConfig: {
label: "Versions",
disabled: !(
actualInfo.version_id && actualInfo.version_id !== "null"
),
},
content: (
<Fragment>
<div className={classes.actionsTray}>
<h1 className={classes.sectionTitle}>Versions</h1>
@@ -987,27 +954,35 @@ const ObjectDetails = ({
)}
</Grid>
</Fragment>
</TabPanel>
<TabPanel index={2} value={selectedTab}>
{selectedTab === 2 && actualInfo && (
<PreviewFileContent
bucketName={bucketName}
object={{
name: actualInfo.name,
version_id: actualInfo.version_id || "null",
size: parseInt(actualInfo.size || "0"),
content_type: "",
last_modified: new Date(actualInfo.last_modified),
}}
isFullscreen
/>
)}
</TabPanel>
</Grid>
</Grid>
),
}}
{{
tabConfig: {
label: "Preview",
disabled: extensionPreview(currentItem) === "none",
},
content: (
<React.Fragment>
{actualInfo && (
<PreviewFileContent
bucketName={bucketName}
object={{
name: actualInfo.name,
version_id: actualInfo.version_id || "null",
size: parseInt(actualInfo.size || "0"),
content_type: "",
last_modified: new Date(actualInfo.last_modified),
}}
isFullscreen
/>
)}
</React.Fragment>
),
}}
</VerticalTabs>
</Fragment>
)}
</Grid>
</PageLayout>
</React.Fragment>
);
};

View File

@@ -177,8 +177,9 @@ export const containerForHeader = (bottomSpacing: any) => ({
},
},
sectionTitle: {
padding: "0px",
margin: "0px",
margin: 0,
marginBottom: ".8rem",
fontSize: "1.3rem",
},
topSpacer: {
height: "8px",
@@ -221,6 +222,7 @@ export const actionsTray = {
actionsTray: {
display: "flex" as const,
justifyContent: "space-between" as const,
marginBottom: "1rem",
"& button": {
flexGrow: 0,
marginLeft: 8,
@@ -988,6 +990,12 @@ export const commonDashboardInfocard = {
},
};
export const pageContentStyles = {
contentSpacer: {
padding: "2rem",
},
};
export const linkStyles = (color: string) => ({
link: {
textDecoration: "underline",

View File

@@ -0,0 +1,34 @@
import React from "react";
import { Grid } from "@mui/material";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { pageContentStyles } from "../FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
pageContainer: {
width: "100%",
},
...pageContentStyles,
});
type PageLayoutProps = {
className?: string;
classes?: any;
children: any;
};
const PageLayout = ({ classes, className = "", children }: PageLayoutProps) => {
return (
<div className={classes.contentSpacer}>
<Grid container>
<Grid item xs={12} className={className}>
{children}
</Grid>
</Grid>
</div>
);
};
export default withStyles(styles)(PageLayout);

View File

@@ -25,6 +25,7 @@ const styles = (theme: Theme) =>
root: {
padding: 0,
margin: 0,
fontSize: "1.2rem",
},
});

View File

@@ -32,18 +32,37 @@ interface IScreenTitle {
const styles = (theme: Theme) =>
createStyles({
headerBarIcon: {
float: "left",
paddingTop: 10,
marginRight: 12,
marginRight: ".7rem",
color: theme.palette.primary.main,
"& .MuiSvgIcon-root": {
width: 44,
height: 44,
width: "100%",
height: "100%",
},
},
headerBarSubheader: {
color: "grey",
},
screenTitle: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "1rem",
borderBottom: "1px solid #EAEAEA",
},
titleColumn: {
height: "auto",
justifyContent: "center",
display: "flex",
flexFlow: "column",
alignItems: "flex-start",
"& h1": {
fontSize: "1.4rem",
},
},
leftItems: {
display: "flex",
alignItems: "center",
},
});
const ScreenTitle = ({
@@ -55,16 +74,16 @@ const ScreenTitle = ({
}: IScreenTitle) => {
return (
<Grid container>
<Grid item xs={12} style={{ paddingTop: 8 }}>
<div className={classes.headerBarIcon}>{icon}</div>
<div style={{ float: "left" }}>
<h1 style={{ margin: 0 }}>{title}</h1>
<span className={classes.headerBarSubheader}>{subTitle}</span>
<Grid item xs={12} className={classes.screenTitle}>
<div className={classes.leftItems}>
{icon ? <div className={classes.headerBarIcon}>{icon}</div> : null}
<div className={classes.titleColumn}>
<h1 style={{ margin: 0 }}>{title}</h1>
<span className={classes.headerBarSubheader}>{subTitle}</span>
</div>
</div>
<div style={{ float: "right", paddingTop: 12 }}>{actions}</div>
</Grid>
<Grid item xs={12}>
<hr style={{ border: 0, borderTop: "1px solid #EAEAEA" }} />
<div>{actions}</div>
</Grid>
</Grid>
);

View File

@@ -0,0 +1,163 @@
import React from "react";
import { Box, Tab, TabProps } from "@mui/material";
import { TabPanel, TabContext, TabList } from "@mui/lab";
import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
export type TabItemProps = {
tabConfig: TabProps | any;
content?: JSX.Element | JSX.Element[];
};
type VerticalTabsProps = {
classes: any;
children: TabItemProps[];
selectedTab?: string;
routes?: any;
isRouteTabs?: boolean;
};
const styles = (theme: Theme) =>
createStyles({
tabsContainer: {
display: "flex",
height: "100%",
},
tabsHeaderContainer: {
width: "300px",
background: "#FBFAFA",
borderRight: "1px solid #EAEAEA",
"& .MuiTabs-root": {
"& .MuiTabs-indicator": {
display: "none",
},
"& .MuiTab-root": {
display: "flex",
flexFlow: "row",
alignItems: "center",
justifyContent: "flex-start",
borderBottom: "1px solid #EAEAEA",
"& .MuiSvgIcon-root": {
marginRight: ".3rem",
marginBottom: 0,
height: ".8rem",
width: ".8rem",
},
"&.Mui-selected": {
background: "#E5E5E5",
},
},
"&. MuiTabs-scroller": {
display: "none",
},
},
},
tabContentContainer: {
width: "100%",
"& .MuiTabPanel-root": {
height: "100%",
},
},
tabPanel: {
height: "100%",
},
/*Below md breakpoint make it horizontal and style it for scrolling tabs*/
"@media (max-width: 900px)": {
tabsContainer: {
flexFlow: "column",
flexDirection: "column",
},
tabsHeaderContainer: {
width: "100%",
borderBottom: " 1px solid #EAEAEA",
"& .MuiTabs-root .MuiTabs-scroller .MuiButtonBase-root": {
borderBottom: " 0px",
},
},
},
});
const VerticalTabs = ({
children,
classes,
selectedTab = "0",
routes,
isRouteTabs,
}: VerticalTabsProps) => {
const [value, setValue] = React.useState(selectedTab);
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down("md"));
const handleChange = (event: React.SyntheticEvent, newValue: string) => {
setValue(newValue);
};
const headerList: TabProps[] = [];
const contentList: React.ReactNode[] = [];
if (!children) return null;
children.forEach((child) => {
headerList.push(child.tabConfig);
contentList.push(child.content);
});
return (
<TabContext value={`${value}`}>
<Box className={classes.tabsContainer}>
<Box className={classes.tabsHeaderContainer}>
<TabList
onChange={handleChange}
orientation={isSmallScreen ? "horizontal" : "vertical"}
variant={isSmallScreen ? "scrollable" : "standard"}
scrollButtons="auto"
className={classes.tabList}
>
{headerList.map((item, index) => {
if (item) {
return (
<Tab
className={classes.tabHeader}
key={`v-tab-${index}`}
value={`${index}`}
{...item}
disableRipple
disableTouchRipple
focusRipple={true}
/>
);
}
return null;
})}
</TabList>
</Box>
<Box className={classes.tabContentContainer}>
{!isRouteTabs
? contentList.map((item, index) => {
return (
<TabPanel
classes={{ ...classes.tabPanel }}
key={`v-tab-p-${index}`}
value={`${index}`}
>
{item ? item : null}
</TabPanel>
);
})
: null}
{isRouteTabs ? (
<div className={classes.tabPanel}>{routes}</div>
) : null}
</Box>
</Box>
</TabContext>
);
};
export default withStyles(styles)(VerticalTabs);

View File

@@ -298,9 +298,6 @@ const DirectCSIMain = ({
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
{notAvailable && !loading ? (
<div className={classes.notAvailableNotice}>

View File

@@ -43,6 +43,7 @@ import SearchIcon from "../../../icons/SearchIcon";
import HelpBox from "../../../common/HelpBox";
import history from "../../../history";
import AButton from "../Common/AButton/AButton";
import PageLayout from "../Common/Layout/PageLayout";
interface IGroupsProps {
classes: any;
@@ -55,6 +56,10 @@ const styles = (theme: Theme) =>
seeMore: {
marginTop: theme.spacing(3),
},
pageContainer: {
border: "1px solid #EAEAEA",
width: "100%",
},
paper: {
// padding: theme.spacing(2),
display: "flex",
@@ -187,125 +192,123 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
/>
)}
<PageHeader label={"Groups"} />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Groups"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
}}
variant="standard"
/>
<Button
variant="contained"
color="primary"
endIcon={<AddIcon />}
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
>
Create Group
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{loading && <LinearProgress />}
{!loading && (
<Fragment>
{records.length > 0 && (
<Fragment>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "" }]}
isLoading={loading}
records={filteredRecords}
entityName="Groups"
idField=""
customPaperHeight={classes.twHeight}
/>
</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,{" "}
<AButton
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
>
Create a Group
</AButton>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Fragment>
)}
<PageLayout>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Groups"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
}}
variant="standard"
/>
<Button
variant="contained"
color="primary"
endIcon={<AddIcon />}
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
>
Create Group
</Button>
</Grid>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{loading && <LinearProgress />}
{!loading && (
<Fragment>
{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,{" "}
<AButton
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
>
Create a Group
</AButton>
.
</Fragment>
}
/>
</Grid>
</Grid>
)}
</Fragment>
)}
</PageLayout>
</React.Fragment>
);
};

View File

@@ -17,10 +17,6 @@ import withStyles from "@mui/styles/withStyles";
import { Button, Grid, IconButton, Tooltip } from "@mui/material";
import ScreenTitle from "../Common/ScreenTitle/ScreenTitle";
import { DeleteIcon, IAMPoliciesIcon, UsersIcon } from "../../../icons";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import { TabPanel } from "../../shared/tabs";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import history from "../../../history";
import api from "../../../common/api";
@@ -28,17 +24,33 @@ import SetPolicy from "../Policies/SetPolicy";
import AddGroupMember from "./AddGroupMember";
import { ErrorResponseHandler } from "../../../common/types";
import DeleteGroup from "./DeleteGroup";
import VerticalTabs from "../Common/VerticalTabs/VerticalTabs";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import PageLayout from "../Common/Layout/PageLayout";
import BackLink from "../../../common/BackLink";
import PanelTitle from "../Common/PanelTitle/PanelTitle";
const styles = (theme: Theme) =>
createStyles({
pageContainer: {
border: "1px solid #EAEAEA",
width: "100%",
},
breadcrumLink: {
textDecoration: "none",
color: "black",
},
statusLabel: {
fontSize: ".8rem",
marginRight: ".5rem",
},
statusValue: {
fontWeight: "bold",
fontSize: ".9rem",
marginRight: ".5rem",
},
...actionsTray,
...searchField,
actionsTray: { ...actionsTray.actionsTray },
...containerForHeader(theme.spacing(4)),
});
@@ -48,11 +60,6 @@ interface IGroupDetailsProps {
setErrorSnackMessage: typeof setErrorSnackMessage;
}
type TabItemsProps = {
activeTab: number;
onTabChange: (tab: number) => void;
};
type DetailsHeaderProps = {
classes: any;
};
@@ -64,31 +71,6 @@ type GroupInfo = {
status?: string;
};
const TabItems = ({ activeTab, onTabChange }: TabItemsProps) => {
return (
<List component="nav" dense={true}>
<ListItem
button
selected={activeTab === 0}
onClick={() => {
onTabChange(0);
}}
>
<ListItemText primary="Members" />
</ListItem>
<ListItem
button
selected={activeTab === 1}
onClick={() => {
onTabChange(1);
}}
>
<ListItemText primary="Policies" />
</ListItem>
</List>
);
};
export const formatPolicy = (policy: string = ""): string[] => {
if (policy.length <= 0) return [];
return policy.split(",");
@@ -114,7 +96,6 @@ const GroupDetailsHeader = ({ classes }: DetailsHeaderProps) => {
};
const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
const [currentTab, setCurrentTab] = useState<number>(0);
const [groupDetails, setGroupDetails] = useState<GroupInfo>({});
/*Modals*/
@@ -143,7 +124,8 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
.then((res: any) => {
setGroupDetails(res);
})
.catch(() => {
.catch((err) => {
setModalErrorSnackMessage(err);
setGroupDetails({});
});
}
@@ -163,10 +145,74 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
});
}
const groupsTabContent = (
<React.Fragment>
<div className={classes.actionsTray}>
<PanelTitle>Members</PanelTitle>
<Button
variant="contained"
color="primary"
endIcon={<UsersIcon />}
size="medium"
onClick={() => {
setUsersOpen(true);
}}
>
{memberActionText}
</Button>
</div>
<TableWrapper
columns={[{ label: "Access Key", elementKey: "" }]}
selectedItems={[]}
isLoading={false}
records={members}
entityName="Users"
idField=""
/>
</React.Fragment>
);
const policiesTabContent = (
<React.Fragment>
<div className={classes.actionsTray}>
<PanelTitle>Policies</PanelTitle>
<Button
variant="contained"
color="primary"
endIcon={<IAMPoliciesIcon />}
size="medium"
onClick={() => {
setPolicyOpen(true);
}}
>
Set Policies
</Button>
</div>
<TableWrapper
itemActions={[
{
type: "view",
onClick: (policy) => {
history.push(`/policies/${policy}`);
},
},
]}
columns={[{ label: "Policy", elementKey: "" }]}
isLoading={false}
records={groupPolicies}
entityName="Policies"
idField=""
/>
</React.Fragment>
);
return (
<React.Fragment>
<GroupDetailsHeader classes={classes} />
<Grid container className={classes.container}>
<BackLink to={"/groups"} label={"Return to Groups"} />
<PageLayout className={classes.pageContainer}>
<Grid item xs={12}>
<ScreenTitle
icon={
@@ -175,21 +221,24 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
</Fragment>
}
title={groupName}
subTitle={
<Fragment>
Status: {isGroupEnabled ? "Enabled" : "Disabled"}
</Fragment>
}
subTitle={null}
actions={
<Fragment>
<Button
onClick={() => {
<span className={classes.statusLabel}>Group Status:</span>
<span className={classes.statusValue}>
{isGroupEnabled ? "Enabled" : "Disabled"}
</span>
<FormSwitchWrapper
indicatorLabels={["Enabled", "Disabled"]}
checked={isGroupEnabled}
value={"group_enabled"}
id="group-status"
name="group-status"
onChange={() => {
toggleGroupStatus(!isGroupEnabled);
}}
color={"primary"}
>
{isGroupEnabled ? "Disable" : "Enable"}
</Button>
switchOnly
/>
<Tooltip title="Delete User">
<IconButton
color="primary"
@@ -208,80 +257,19 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
/>
</Grid>
<Grid item xs={2}>
<TabItems
activeTab={currentTab}
onTabChange={(num) => {
setCurrentTab(num);
<Grid item xs={12}>
<VerticalTabs>
{{
tabConfig: { label: "Members" },
content: groupsTabContent,
}}
/>
{{
tabConfig: { label: "Policies" },
content: policiesTabContent,
}}
</VerticalTabs>
</Grid>
<Grid item xs={10}>
<Grid item xs={12}>
<TabPanel index={0} value={currentTab}>
<div className={classes.actionsTray}>
<PanelTitle>Members</PanelTitle>
<Button
variant="contained"
color="primary"
endIcon={<UsersIcon />}
size="medium"
onClick={() => {
setUsersOpen(true);
}}
>
{memberActionText}
</Button>
</div>
<TableWrapper
//itemActions={tableActions}
columns={[{ label: "Access Key", elementKey: "" }]}
// onSelect={selectionChanged}
selectedItems={[]}
isLoading={false}
records={members}
entityName="Users"
idField=""
customPaperHeight={classes.twHeight}
/>
</TabPanel>
<TabPanel index={1} value={currentTab}>
<div className={classes.actionsTray}>
<PanelTitle>Policies</PanelTitle>
<Button
variant="contained"
color="primary"
endIcon={<IAMPoliciesIcon />}
size="medium"
onClick={() => {
setPolicyOpen(true);
}}
>
Set Policies
</Button>
</div>
<TableWrapper
itemActions={[
{
type: "view",
onClick: (policy) => {
history.push(`/policies/${policy}`);
},
},
]}
columns={[{ label: "Policy", elementKey: "" }]}
isLoading={false}
records={groupPolicies}
entityName="Policies"
idField=""
/>
</TabPanel>
</Grid>
</Grid>
</Grid>
</PageLayout>
{/*Modals*/}
{policyOpen ? (
<SetPolicy

View File

@@ -41,6 +41,7 @@ import api from "../../../common/api";
import history from "../../../history";
import SearchIcon from "../../../icons/SearchIcon";
import HelpBox from "../../../common/HelpBox";
import PageLayout from "../Common/Layout/PageLayout";
const styles = (theme: Theme) =>
createStyles({
@@ -52,10 +53,9 @@ const styles = (theme: Theme) =>
overflow: "auto",
flexDirection: "column",
},
addSideBar: {
width: "320px",
padding: "20px",
width: 320,
padding: 20,
},
tableToolbar: {
paddingLeft: theme.spacing(2),
@@ -179,7 +179,7 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => {
/>
)}
<PageHeader label="IAM Policies" />
<Grid container className={classes.container}>
<PageLayout className={classes.pageContainer}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Policies"
@@ -222,7 +222,6 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => {
records={filteredRecords}
entityName="Policies"
idField="name"
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
@@ -258,7 +257,7 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => {
}
/>
</Grid>
</Grid>
</PageLayout>
</React.Fragment>
);
};

View File

@@ -39,14 +39,14 @@ import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMi
import history from "../../../history";
import InputAdornment from "@mui/material/InputAdornment";
import TextField from "@mui/material/TextField";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import List from "@mui/material/List";
import ScreenTitle from "../Common/ScreenTitle/ScreenTitle";
import IAMPoliciesIcon from "../../../icons/IAMPoliciesIcon";
import RefreshIcon from "../../../icons/RefreshIcon";
import SearchIcon from "../../../icons/SearchIcon";
import TrashIcon from "../../../icons/TrashIcon";
import PageLayout from "../Common/Layout/PageLayout";
import VerticalTabs from "../Common/VerticalTabs/VerticalTabs";
import BackLink from "../../../common/BackLink";
import BoxIconButton from "../Common/BoxIconButton/BoxIconButton";
interface IPolicyDetailsProps {
@@ -61,6 +61,10 @@ const styles = (theme: Theme) =>
buttonContainer: {
textAlign: "right",
},
pageContainer: {
border: "1px solid #EAEAEA",
height: "100%",
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
@@ -75,6 +79,7 @@ const styles = (theme: Theme) =>
},
paperContainer: {
padding: "15px 15px 15px 50px",
minHeight: "450px",
},
infoGrid: {
display: "grid",
@@ -190,7 +195,6 @@ const PolicyDetails = ({
setErrorSnackMessage,
setSnackBarMessage,
}: IPolicyDetailsProps) => {
const [selectedTab, setSelectedTab] = useState<number>(0);
const [policy, setPolicy] = useState<Policy | null>(null);
const [policyStatements, setPolicyStatements] = useState<IAMStatement[]>([]);
const [userList, setUserList] = useState<string[]>([]);
@@ -352,7 +356,12 @@ const PolicyDetails = ({
</Fragment>
}
/>
<Grid container className={classes.container}>
<BackLink
to={"/policies"}
label={"Return to Policies"}
classes={classes}
/>
<PageLayout className={classes.pageContainer}>
<Grid item xs={12}>
<ScreenTitle
icon={
@@ -392,245 +401,227 @@ const PolicyDetails = ({
}
/>
</Grid>
<Grid item xs={2}>
<List component="nav" dense={true}>
<ListItem
button
selected={selectedTab === 0}
onClick={() => {
setSelectedTab(0);
}}
>
<ListItemText primary="Summary" />
</ListItem>
<ListItem
button
selected={selectedTab === 1}
onClick={() => {
setSelectedTab(1);
}}
>
<ListItemText primary="Users" />
</ListItem>
<ListItem
button
selected={selectedTab === 2}
onClick={() => {
setSelectedTab(2);
}}
>
<ListItemText primary="Groups" />
</ListItem>
<ListItem
button
selected={selectedTab === 3}
onClick={() => {
setSelectedTab(3);
}}
>
<ListItemText primary="JSON" />
</ListItem>
</List>
</Grid>
<Grid item xs={10}>
{selectedTab === 0 && (
<Fragment>
<h1 className={classes.sectionTitle}>Summary</h1>
<Paper className={classes.paperContainer}>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
saveRecord(e);
}}
>
<Grid container>
<Grid item xs={8}>
<h4>Statements</h4>
</Grid>
<Grid item xs={4} />
<Fragment>
{policyStatements.map((stmt, i) => {
return (
<Grid
item
xs={12}
className={classes.statement}
key={`s-${i}`}
>
<Grid container>
<Grid item xs={2} className={classes.labelCol}>
Effect
</Grid>
<Grid item xs={4}>
<Fragment>{stmt.Effect}</Fragment>
</Grid>
<Grid item xs={2} className={classes.labelCol} />
<Grid item xs={4} />
<Grid item xs={2} className={classes.labelCol}>
Actions
</Grid>
<Grid item xs={4}>
<ul>
{stmt.Action &&
stmt.Action.map((act, actIndex) => (
<li key={`${i}-r-${actIndex}`}>{act}</li>
))}
</ul>
</Grid>
<Grid item xs={2} className={classes.labelCol}>
Resources
</Grid>
<Grid item xs={4}>
<ul>
{stmt.Resource &&
stmt.Resource.map((res, resIndex) => (
<li key={`${i}-r-${resIndex}`}>{res}</li>
))}
</ul>
<VerticalTabs>
{{
tabConfig: { label: "Summary" },
content: (
<Fragment>
<div className={classes.sectionTitle}>Policy Summary</div>
<Paper className={classes.paperContainer}>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
saveRecord(e);
}}
>
<Grid container>
<Grid item xs={8}>
<h4>Statements</h4>
</Grid>
<Grid item xs={4} />
<Fragment>
{policyStatements.map((stmt, i) => {
return (
<Grid
item
xs={12}
className={classes.statement}
key={`s-${i}`}
>
<Grid container>
<Grid item xs={2} className={classes.labelCol}>
Effect
</Grid>
<Grid item xs={4}>
<Fragment>{stmt.Effect}</Fragment>
</Grid>
<Grid
item
xs={2}
className={classes.labelCol}
/>
<Grid item xs={4} />
<Grid item xs={2} className={classes.labelCol}>
Actions
</Grid>
<Grid item xs={4}>
<ul>
{stmt.Action &&
stmt.Action.map((act, actIndex) => (
<li key={`${i}-r-${actIndex}`}>
{act}
</li>
))}
</ul>
</Grid>
<Grid item xs={2} className={classes.labelCol}>
Resources
</Grid>
<Grid item xs={4}>
<ul>
{stmt.Resource &&
stmt.Resource.map((res, resIndex) => (
<li key={`${i}-r-${resIndex}`}>
{res}
</li>
))}
</ul>
</Grid>
</Grid>
</Grid>
</Grid>
);
})}
</Fragment>
</Grid>
</form>
</Paper>
</Fragment>
)}
{selectedTab === 1 && (
<Fragment>
<h1 className={classes.sectionTitle}>Users</h1>
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Users"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setFilterUsers(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
variant="standard"
/>
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<br />
</Grid>
<TableWrapper
itemActions={userTableActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingUsers}
records={filteredUsers}
entityName="Users"
idField="name"
/>
</Grid>
</Fragment>
)}
{selectedTab === 2 && (
<Fragment>
<h1 className={classes.sectionTitle}>Groups</h1>
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Groups"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setFilterGroups(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
variant="standard"
/>
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<br />
</Grid>
<TableWrapper
itemActions={[]}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingGroups}
records={filteredGroups}
entityName="Groups"
idField="name"
/>
</Grid>
</Fragment>
)}
{selectedTab === 3 && (
<Fragment>
<h1 className={classes.sectionTitle}>Raw Policy</h1>
<Paper className={classes.paperContainer}>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
saveRecord(e);
}}
>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
<CodeMirrorWrapper
value={policyDefinition}
onBeforeChange={(editor, data, value) => {
setPolicyDefinition(value);
}}
/>
);
})}
</Fragment>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
{!policy && (
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={() => {
resetForm();
</form>
</Paper>
</Fragment>
),
}}
{{
tabConfig: { label: "Users" },
content: (
<Fragment>
<div className={classes.sectionTitle}>Users</div>
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Users"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setFilterUsers(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
variant="standard"
/>
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<br />
</Grid>
<TableWrapper
itemActions={userTableActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingUsers}
records={filteredUsers}
entityName="Users"
idField="name"
/>
</Grid>
</Fragment>
),
}}
{{
tabConfig: { label: "Groups" },
content: (
<Fragment>
<div className={classes.sectionTitle}>Groups</div>
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Groups"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setFilterGroups(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
variant="standard"
/>
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<br />
</Grid>
<TableWrapper
itemActions={[]}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingGroups}
records={filteredGroups}
entityName="Groups"
idField="name"
/* customPaperHeight={classes.tableHeight}*/
/>
</Grid>
</Fragment>
),
}}
{{
tabConfig: { label: "Raw Policy" },
content: (
<Fragment>
<div className={classes.sectionTitle}>Raw Policy</div>
<Paper className={classes.paperContainer}>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
saveRecord(e);
}}
>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
<CodeMirrorWrapper
value={policyDefinition}
onBeforeChange={(editor, data, value) => {
setPolicyDefinition(value);
}}
>
Clear
</button>
)}
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading || !validSave}
>
Save
</Button>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
/>
</Grid>
)}
</Grid>
</form>
</Paper>
</Fragment>
)}
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
{!policy && (
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={() => {
resetForm();
}}
>
Clear
</button>
)}
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading || !validSave}
>
Save
</Button>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
</form>
</Paper>
</Fragment>
),
}}
</VerticalTabs>
</PageLayout>
</Fragment>
);
};

View File

@@ -18,18 +18,19 @@ import React, { Fragment, useState, useEffect } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Grid, ListItem, ListItemText } from "@mui/material";
import { Route, Router, Switch, Redirect } from "react-router-dom";
import { Route, Router, Switch, Redirect, Link } from "react-router-dom";
import {
actionsTray,
containerForHeader,
pageContentStyles,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import history from "../../../history";
import PageHeader from "../Common/PageHeader/PageHeader";
import StoragePVCs from "./StoragePVCs";
import DirectCSIDrives from "../DirectCSI/DirectCSIDrives";
import List from "@mui/material/List";
import PageLayout from "../Common/Layout/PageLayout";
import VerticalTabs from "../Common/VerticalTabs/VerticalTabs";
interface IStorageProps {
classes: any;
@@ -44,6 +45,11 @@ const styles = (theme: Theme) =>
color: "#000",
marginTop: 4,
},
pageContainer: {
border: "1px solid #EAEAEA",
height: "100%",
},
...pageContentStyles,
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
@@ -52,53 +58,52 @@ const styles = (theme: Theme) =>
const routes = ["/storage/volumes", "/storage/drives"];
const Storage = ({ classes, match }: IStorageProps) => {
const [selectedTab, setSelectedTab] = useState<number>(0);
let selTab = match?.path;
selTab = selTab ? selTab : routes[0];
const [activeTab, setActiveTab] = useState(selTab);
useEffect(() => {
const index = routes.findIndex((route) => route === match.path);
setSelectedTab(index);
}, [match]);
const routeChange = (newValue: number) => {
history.push(routes[newValue]);
};
setActiveTab(selTab);
}, [selTab]);
return (
<Fragment>
<PageHeader label={"Storage"} />
<Grid container className={classes.container}>
<Grid item xs={2}>
<List component="nav" dense={true}>
<ListItem
button
selected={selectedTab === 0}
onClick={() => {
routeChange(0);
}}
>
<ListItemText primary="Volumes" />
</ListItem>
<ListItem
button
selected={selectedTab === 1}
onClick={() => {
routeChange(1);
}}
>
<ListItemText primary="Drives" />
</ListItem>
</List>
</Grid>
<Grid item xs={10}>
<Router history={history}>
<Switch>
<Route path={routes[0]} component={StoragePVCs} />
<Route path={routes[1]} component={DirectCSIDrives} />
<Route render={() => <Redirect to="/storage/volumes" />} />
</Switch>
</Router>
</Grid>
</Grid>
<PageLayout className={classes.pageContainer}>
<VerticalTabs
selectedTab={activeTab}
isRouteTabs
routes={
<div className={classes.contentSpacer}>
<Router history={history}>
<Switch>
<Route exact path={routes[0]} component={StoragePVCs} />
<Route exact path={routes[1]} component={DirectCSIDrives} />
<Route render={() => <Redirect to={routes[0]} />} />
</Switch>
</Router>
</div>
}
>
{{
tabConfig: {
label: "Volumes",
value: routes[0],
component: Link,
to: routes[0],
},
}}
{{
tabConfig: {
label: "Drives",
value: routes[1],
component: Link,
to: routes[1],
},
}}
</VerticalTabs>
</PageLayout>
</Fragment>
);
};

View File

@@ -125,7 +125,6 @@ const PodsSummary = ({
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
/>
)}
<div className={classes.topSpacer} />
<h1 className={classes.sectionTitle}>Pods</h1>
<TableWrapper
columns={[

View File

@@ -105,7 +105,7 @@ const PoolsSummary = ({
tenant={tenant}
/>
)}
<div className={classes.topSpacer} />
<h1 className={classes.sectionTitle}>Pools</h1>
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>

View File

@@ -20,7 +20,7 @@ import { Link, Redirect, Route, Router, Switch } from "react-router-dom";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Box, Tab, Tabs, Tooltip } from "@mui/material";
import { Tooltip } from "@mui/material";
import get from "lodash/get";
import Grid from "@mui/material/Grid";
import { setErrorSnackMessage, setSnackBarMessage } from "../../../../actions";
@@ -33,6 +33,7 @@ import {
import { ITenant } from "../ListTenants/types";
import {
containerForHeader,
pageContentStyles,
tenantDetailsStyles,
} from "../../Common/FormComponents/common/styleLibrary";
import { AppState } from "../../../../store";
@@ -47,9 +48,6 @@ import PodsSummary from "./PodsSummary";
import VolumesSummary from "./VolumesSummary";
import TenantMetrics from "./TenantMetrics";
import TenantSecurity from "./TenantSecurity";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import { CircleIcon, DeleteIcon } from "../../../../icons";
import DeleteTenant from "../ListTenants/DeleteTenant";
import PodDetails from "./pods/PodDetails";
@@ -58,6 +56,9 @@ import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
import EditIcon from "../../../../icons/EditIcon";
import RefreshIcon from "../../../../icons/RefreshIcon";
import TenantsIcon from "../../../../icons/TenantsIcon";
import PageLayout from "../../Common/Layout/PageLayout";
import BackLink from "../../../../common/BackLink";
import VerticalTabs from "../../Common/VerticalTabs/VerticalTabs";
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
interface ITenantDetailsProps {
@@ -80,6 +81,15 @@ interface ITenantDetailsProps {
const styles = (theme: Theme) =>
createStyles({
...tenantDetailsStyles,
pageContainer: {
border: "1px solid #EAEAEA",
width: "100%",
height: "100%",
},
contentSpacer: {
...pageContentStyles.contentSpacer,
minHeight: 400,
},
redState: {
color: theme.palette.error.main,
"& .MuiSvgIcon-root": {
@@ -210,25 +220,19 @@ const TenantDetails = ({
setErrorSnackMessage,
]);
useEffect(() => {
const path = get(match, "path", "/");
const splitSections = path.split("/");
const section = splitSections[splitSections.length - 1];
const path = get(match, "path", "/");
const splitSections = path.split("/");
switch (section) {
case "pools":
case "pods":
case ":podName":
case "volumes":
case "metrics":
case "license":
case "security":
setTenantTab(section);
break;
default:
setTenantTab("summary");
}
}, [match, setTenantTab]);
let highlightedTab = splitSections[splitSections.length - 1] || "summary";
if (highlightedTab === ":podName" || highlightedTab === "pods") {
// It has SUB Route
highlightedTab = "pods";
}
const [activeTab, setActiveTab] = useState(highlightedTab);
useEffect(() => {
setActiveTab(highlightedTab);
}, [highlightedTab]);
const editYaml = () => {
setYamlScreenOpen(true);
@@ -239,11 +243,8 @@ const TenantDetails = ({
setTenantDetailsLoad(true);
};
const changeRoute = (newValue: string) => {
setTenantTab(newValue);
history.push(
`/namespaces/${tenantNamespace}/tenants/${tenantName}/${newValue}`
);
const getRoutePath = (newValue: string) => {
return `/namespaces/${tenantNamespace}/tenants/${tenantName}/${newValue}`;
};
const confirmDeleteTenant = () => {
@@ -269,94 +270,6 @@ const TenantDetails = ({
: classes.greyState;
};
interface ListMenuItem {
label: string;
value: string;
onclick: (val: string) => void;
selected: () => boolean;
}
const menu: ListMenuItem[] = [
{
label: "Summary",
value: "summary",
onclick: (val) => {
changeRoute(val);
},
selected: () => {
return currentTab === "summary";
},
},
{
label: "Metrics",
value: "metrics",
onclick: (val) => {
changeRoute("metrics");
},
selected: () => {
return currentTab === "metrics";
},
},
{
label: "Security",
value: "security",
onclick: (val) => {
changeRoute("security");
},
selected: () => {
return currentTab === "security";
},
},
{
label: "Pools",
value: "pools",
onclick: (val) => {
changeRoute("pools");
},
selected: () => {
return currentTab === "pools";
},
},
{
label: "Pods",
value: "pods",
onclick: (val) => {
changeRoute("pods");
},
selected: () => {
return currentTab === "pods" || currentTab === ":podName";
},
},
{
label: "Volumes",
value: "volumes",
onclick: (val) => {
changeRoute("volumes");
},
selected: () => {
return currentTab === "volumes";
},
},
{
label: "License",
value: "license",
onclick: (val) => {
changeRoute("license");
},
selected: () => {
return currentTab === "license";
},
},
];
let value = menu[0].value;
for (const mli of menu) {
if (mli.selected()) {
value = mli.value;
break;
}
}
return (
<Fragment>
{yamlScreenOpen && (
@@ -383,7 +296,8 @@ const TenantDetails = ({
</Fragment>
}
/>
<Grid container className={classes.container}>
<BackLink to={"/tenants"} label={"Return to Tenants"} />
<PageLayout className={classes.pageContainer}>
<Grid item xs={12}>
<ScreenTitle
icon={
@@ -450,94 +364,118 @@ const TenantDetails = ({
}
/>
</Grid>
<Grid item xs={12} sm={12} md={2}>
<Box display={{ xs: "none", sm: "none", md: "block" }}>
<List component="nav" dense={true}>
{menu.map((mli) => {
return (
<ListItem
button
selected={mli.selected()}
onClick={() => {
mli.onclick(mli.value);
}}
>
<ListItemText primary={mli.label} />
</ListItem>
);
})}
</List>
</Box>
<Box display={{ xs: "block", sm: "block", md: "none" }}>
<Tabs
value={value}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs example"
>
{menu.map((mli) => {
return (
<Tab
label={mli.label}
value={mli.value}
onClick={() => {
mli.onclick(mli.value);
}}
<VerticalTabs
selectedTab={activeTab}
isRouteTabs
routes={
<div className={classes.contentSpacer}>
<Router history={history}>
<Switch>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/summary"
component={TenantSummary}
/>
);
})}
</Tabs>
</Box>
</Grid>
<Grid item xs={12} sm={12} md={10}>
<Router history={history}>
<Switch>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/summary"
component={TenantSummary}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
component={TenantMetrics}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/security"
component={TenantSecurity}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/pools"
component={PoolsSummary}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName"
component={PodDetails}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/pods"
component={PodsSummary}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/volumes"
component={VolumesSummary}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/license"
component={TenantLicense}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName"
component={() => (
<Redirect
to={`/namespaces/${tenantNamespace}/tenants/${tenantName}/summary`}
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
component={TenantMetrics}
/>
)}
/>
</Switch>
</Router>
</Grid>
</Grid>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/security"
component={TenantSecurity}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/pools"
component={PoolsSummary}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName"
component={PodDetails}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/pods"
component={PodsSummary}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/volumes"
component={VolumesSummary}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/license"
component={TenantLicense}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName"
component={() => (
<Redirect
to={`/namespaces/${tenantNamespace}/tenants/${tenantName}/summary`}
/>
)}
/>
</Switch>
</Router>
</div>
}
>
{{
tabConfig: {
label: "Summary",
value: "summary",
component: Link,
to: getRoutePath("summary"),
},
}}
{{
tabConfig: {
label: "Metrics",
value: "metrics",
component: Link,
to: getRoutePath("metrics"),
},
}}
{{
tabConfig: {
label: "Security",
value: "security",
component: Link,
to: getRoutePath("security"),
},
}}
{{
tabConfig: {
label: "Pools",
value: "pools",
component: Link,
to: getRoutePath("pools"),
},
}}
{{
tabConfig: {
label: "Pods",
value: "pods",
component: Link,
to: getRoutePath("pods"),
},
}}
{{
tabConfig: {
label: "Volumes",
value: "volumes",
component: Link,
to: getRoutePath("volumes"),
},
}}
{{
tabConfig: {
label: "License",
value: "license",
component: Link,
to: getRoutePath("license"),
},
}}
</VerticalTabs>
</PageLayout>
</Fragment>
);
};

View File

@@ -99,7 +99,6 @@ const TenantLicense = ({
return (
<Fragment>
<div className={classes.topSpacer} />
<h1 className={classes.sectionTitle}>License</h1>
{loadingTenant ? (
<div className={classes.loaderAlign}>

View File

@@ -321,7 +321,6 @@ const TenantSecurity = ({
cancelLabel="Cancel"
okLabel={"Restart"}
/>
<div className={classes.topSpacer} />
{loadingTenant ? (
<Paper className={classes.paperContainer}>
<div className={classes.loaderAlign}>

View File

@@ -169,7 +169,6 @@ const TenantSummary = ({
namespace={tenantNamespace}
/>
)}
<div className={classes.topSpacer} />
<h1 className={classes.sectionTitle}>Summary</h1>
<Paper className={classes.paperContainer}>
<Grid container>

View File

@@ -52,7 +52,7 @@ const styles = (theme: Theme) =>
color: "black",
},
tableWrapper: {
height: "calc(100vh - 267px)",
height: "450px",
},
...actionsTray,
...searchField,

View File

@@ -65,7 +65,6 @@ const PodDetails = ({ classes, match }: IPodDetailsProps) => {
return (
<Fragment>
<Grid item xs={12}>
<div className={classes.topSpacer} />
<h1 className={classes.sectionTitle}>
<Link
to={`/namespaces/${tenantNamespace}/tenants/${tenantName}/pods`}

View File

@@ -48,6 +48,7 @@ import SearchIcon from "../../../icons/SearchIcon";
import { decodeFileName } from "../../../common/utils";
import HelpBox from "../../../common/HelpBox";
import AButton from "../Common/AButton/AButton";
import PageLayout from "../Common/Layout/PageLayout";
const styles = (theme: Theme) =>
createStyles({
@@ -81,9 +82,6 @@ const styles = (theme: Theme) =>
},
},
},
twHeight: {
minHeight: 600,
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
@@ -229,7 +227,7 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
/>
)}
<PageHeader label={"Users"} />
<Grid container className={classes.container}>
<PageLayout>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Users"
@@ -293,7 +291,6 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
records={filteredRecords}
entityName="Users"
idField="accessKey"
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
@@ -374,7 +371,7 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
)}
</Fragment>
)}
</Grid>
</PageLayout>
</Fragment>
);
};

View File

@@ -39,7 +39,6 @@ import {
} from "../Common/FormComponents/common/styleLibrary";
import { IPolicyItem } from "./types";
import { ErrorResponseHandler } from "../../../common/types";
import { TabPanel } from "../../shared/tabs";
import PageHeader from "../Common/PageHeader/PageHeader";
import api from "../../../common/api";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
@@ -49,16 +48,29 @@ import history from "../../../history";
import UserServiceAccountsPanel from "./UserServiceAccountsPanel";
import ChangeUserPasswordModal from "../Account/ChangeUserPasswordModal";
import DeleteUserString from "./DeleteUserString";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import List from "@mui/material/List";
import LockIcon from "@mui/icons-material/Lock";
import ScreenTitle from "../Common/ScreenTitle/ScreenTitle";
import BoxIconButton from "../Common/BoxIconButton/BoxIconButton";
import PanelTitle from "../Common/PanelTitle/PanelTitle";
import PageLayout from "../Common/Layout/PageLayout";
import VerticalTabs from "../Common/VerticalTabs/VerticalTabs";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import BackLink from "../../../common/BackLink";
const styles = (theme: Theme) =>
createStyles({
pageContainer: {
border: "1px solid #EAEAEA",
},
statusLabel: {
fontSize: ".8rem",
marginRight: ".5rem",
},
statusValue: {
fontWeight: "bold",
fontSize: ".9rem",
marginRight: ".5rem",
},
seeMore: {
marginTop: theme.spacing(3),
},
@@ -126,7 +138,6 @@ const styles = (theme: Theme) =>
},
...actionsTray,
...searchField,
actionsTray: { ...actionsTray.actionsTray },
...containerForHeader(theme.spacing(4)),
});
@@ -141,7 +152,6 @@ interface IGroupItem {
}
const UserDetails = ({ classes, match }: IUserDetailsProps) => {
const [curTab, setCurTab] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(false);
const [addGroupOpen, setAddGroupOpen] = useState<boolean>(false);
const [policyOpen, setPolicyOpen] = useState<boolean>(false);
@@ -281,7 +291,8 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
closeModal={() => setChangeUserPasswordModalOpen(false)}
/>
)}
<Grid container className={classes.container}>
<BackLink label={"Return to Users"} to={"/users"} />
<PageLayout className={classes.pageContainer}>
<Grid item xs={12}>
<ScreenTitle
icon={
@@ -290,20 +301,25 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
</Fragment>
}
title={userName}
subTitle={
<Fragment>Status: {enabled ? "Enabled" : "Disabled"}</Fragment>
}
actions={
<Fragment>
<Button
onClick={() => {
<span className={classes.statusLabel}>User Status:</span>
<span className={classes.statusValue}>
{enabled ? "Enabled" : "Disabled"}
</span>
<FormSwitchWrapper
indicatorLabels={["Enabled", "Disabled"]}
checked={enabled}
value={"group_enabled"}
id="group-status"
name="group-status"
onChange={() => {
setEnabled(!enabled);
saveRecord(!enabled);
}}
color={"primary"}
>
{enabled ? "Disable" : "Enable"}
</Button>
switchOnly
/>
<Tooltip title="Delete User">
<BoxIconButton
color="primary"
@@ -328,104 +344,93 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
}
/>
</Grid>
<Grid item xs={2}>
<List component="nav" dense={true}>
<ListItem
button
selected={curTab === 0}
onClick={() => {
setCurTab(0);
}}
>
<ListItemText primary="Groups" />
</ListItem>
<ListItem
button
selected={curTab === 1}
onClick={() => {
setCurTab(1);
}}
>
<ListItemText primary="Service Accounts" />
</ListItem>
<ListItem
button
selected={curTab === 2}
onClick={() => {
setCurTab(2);
}}
>
<ListItemText primary="Policies" />
</ListItem>
</List>
<Grid item xs={12}>
<VerticalTabs>
{{
tabConfig: {
label: "Groups",
},
content: (
<React.Fragment>
<div className={classes.actionsTray}>
<PanelTitle>Groups</PanelTitle>
<Button
variant="contained"
color="primary"
endIcon={<AddIcon />}
size="medium"
onClick={() => {
setAddGroupOpen(true);
}}
>
Add to Groups
</Button>
</div>
<TableWrapper
// itemActions={userTableActions}
columns={[{ label: "Name", elementKey: "group" }]}
isLoading={loading}
records={currentGroups}
entityName="Groups"
idField="group"
/>
</React.Fragment>
),
}}
{{
tabConfig: {
label: "Service Accounts",
},
content: (
<UserServiceAccountsPanel
user={userName}
classes={classes}
hasPolicy={hasPolicy}
/>
),
}}
{{
tabConfig: {
label: "Policies",
},
content: (
<React.Fragment>
<div className={classes.actionsTray}>
<PanelTitle>Policies</PanelTitle>
<Button
variant="contained"
color="primary"
endIcon={<IAMPoliciesIcon />}
size="medium"
onClick={() => {
setPolicyOpen(true);
}}
>
Assign Policies
</Button>
</div>
<TableWrapper
itemActions={[
{
type: "view",
onClick: (policy: IPolicyItem) => {
history.push(`/policies/${policy.policy}`);
},
},
]}
columns={[{ label: "Name", elementKey: "policy" }]}
isLoading={loading}
records={currentPolicies}
entityName="Policies"
idField="policy"
/>
</React.Fragment>
),
}}
</VerticalTabs>
</Grid>
<Grid item xs={10}>
<Grid item xs={12}>
<TabPanel index={0} value={curTab}>
<div className={classes.actionsTray}>
<PanelTitle>Groups</PanelTitle>
<Button
variant="contained"
color="primary"
endIcon={<AddIcon />}
size="medium"
onClick={() => {
setAddGroupOpen(true);
}}
>
Add to Groups
</Button>
</div>
<TableWrapper
// itemActions={userTableActions}
columns={[{ label: "Name", elementKey: "group" }]}
isLoading={loading}
records={currentGroups}
entityName="Groups"
idField="group"
/>
</TabPanel>
<TabPanel index={1} value={curTab}>
<UserServiceAccountsPanel
user={userName}
classes={classes}
hasPolicy={hasPolicy}
/>
</TabPanel>
<TabPanel index={2} value={curTab}>
<div className={classes.actionsTray}>
<PanelTitle>Policies</PanelTitle>
<Button
variant="contained"
color="primary"
endIcon={<IAMPoliciesIcon />}
size="medium"
onClick={() => {
setPolicyOpen(true);
}}
>
Assign Policies
</Button>
</div>
<TableWrapper
itemActions={[
{
type: "view",
onClick: (policy: IPolicyItem) => {
history.push(`/policies/${policy.policy}`);
},
},
]}
columns={[{ label: "Name", elementKey: "policy" }]}
isLoading={loading}
records={currentPolicies}
entityName="Policies"
idField="policy"
/>
</TabPanel>
</Grid>
</Grid>
</Grid>
</PageLayout>
</React.Fragment>
);
};