Redirect back to original URL if user redirected to loging screen (#1187)

* Redirect back to original URL if user redirected to loging screen

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

* Cover SSO cases

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

Co-authored-by: Lenin Alevski <alevsk.8772@gmail.com>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
This commit is contained in:
Daniel Valdivia
2021-11-04 13:31:55 -07:00
committed by GitHub
parent 1bb7012d53
commit a9937afc64
8 changed files with 197 additions and 197 deletions

View File

@@ -8,7 +8,7 @@ RUN yarn install
COPY ./portal-ui .
RUN yarn install && make build-static
RUN make build-static
USER node

View File

@@ -29,6 +29,9 @@ export class API {
.catch((err) => {
// if we get unauthorized, kick out the user
if (err.status === 401) {
if (window.location.pathname !== "/") {
localStorage.setItem("redirect-path", window.location.pathname);
}
clearSession();
// Refresh the whole page to ensure cache is clear
// and we dont end on an infinite loop

View File

@@ -29,7 +29,7 @@ import {
serverIsLoading,
serverNeedsRestart,
setMenuOpen,
setSnackBarMessage
setSnackBarMessage,
} from "../../actions";
import { ISessionResponse } from "./types";
import { snackBarMessage } from "../../types";
@@ -75,44 +75,44 @@ const styles = (theme: Theme) =>
display: "flex",
"& .MuiPaper-root.MuiSnackbarContent-root": {
borderRadius: "0px 0px 5px 5px",
boxShadow: "none"
}
boxShadow: "none",
},
},
toolbar: {
background: theme.palette.background.default,
color: "black",
paddingRight: 24 // keep right padding when drawer closed
paddingRight: 24, // keep right padding when drawer closed
},
toolbarIcon: {
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: "0 8px",
...theme.mixins.toolbar
...theme.mixins.toolbar,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
})
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginRight: 36
marginRight: 36,
},
menuButtonHidden: {
display: "none"
display: "none",
},
title: {
flexGrow: 1
flexGrow: 1,
},
drawerPaper: {
position: "relative",
@@ -120,44 +120,44 @@ const styles = (theme: Theme) =>
width: drawerWidth,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: "hidden",
background:
"transparent linear-gradient(90deg, #073052 0%, #081C42 100%) 0% 0% no-repeat padding-box",
boxShadow: "0px 3px 7px #00000014"
boxShadow: "0px 3px 7px #00000014",
},
drawerPaperClose: {
overflowX: "hidden",
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing(7),
[theme.breakpoints.up("sm")]: {
width: theme.spacing(9)
}
width: theme.spacing(9),
},
},
content: {
flexGrow: 1,
height: "100vh",
overflow: "auto",
position: "relative"
position: "relative",
},
container: {
paddingBottom: theme.spacing(4),
margin: 0,
width: "100%",
maxWidth: "initial"
maxWidth: "initial",
},
paper: {
padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column"
flexDirection: "column",
},
fixedHeight: {
minHeight: 240
minHeight: 240,
},
warningBar: {
background: theme.palette.primary.main,
@@ -165,13 +165,13 @@ const styles = (theme: Theme) =>
heigh: "60px",
widht: "100%",
lineHeight: "60px",
textAlign: "center"
textAlign: "center",
},
progress: {
height: "3px",
backgroundColor: "#eaeaea"
backgroundColor: "#eaeaea",
},
...snackBarCommon
...snackBarCommon,
});
interface IConsoleProps {
@@ -190,17 +190,17 @@ interface IConsoleProps {
}
const Console = ({
classes,
open,
needsRestart,
isServerLoading,
serverNeedsRestart,
serverIsLoading,
session,
loadingProgress,
snackBarMessage,
setSnackBarMessage
}: IConsoleProps) => {
classes,
open,
needsRestart,
isServerLoading,
serverNeedsRestart,
serverIsLoading,
session,
loadingProgress,
snackBarMessage,
setSnackBarMessage,
}: IConsoleProps) => {
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
const restartServer = () => {
@@ -223,185 +223,186 @@ const Console = ({
});
};
const allowedPages = session.pages.reduce(
(result: any, item: any, index: any) => {
result[item] = true;
return result;
},
{}
);
const allowedPages = !session
? []
: session.pages.reduce((result: any, item: any, index: any) => {
result[item] = true;
return result;
}, {});
const routes = [
{
component: Dashboard,
path: "/dashboard"
path: "/dashboard",
},
{
component: Metrics,
path: "/metrics"
path: "/metrics",
},
{
component: Buckets,
path: "/buckets"
path: "/buckets",
},
{
component: Buckets,
path: "/buckets/*"
path: "/buckets/*",
},
{
component: Watch,
path: "/tools/watch"
path: "/tools/watch",
},
{
component: Users,
path: "/users/:userName+"
path: "/users/:userName+",
},
{
component: Users,
path: "/users"
path: "/users",
},
{
component: Groups,
path: "/groups"
path: "/groups",
},
{
component: GroupsDetails,
path: "/groups/:groupName+"
path: "/groups/:groupName+",
},
{
component: Policies,
path: "/policies/*"
path: "/policies/*",
},
{
component: Policies,
path: "/policies"
path: "/policies",
},
{
component: Heal,
path: "/tools/heal"
path: "/tools/heal",
},
{
component: Trace,
path: "/tools/trace"
path: "/tools/trace",
},
{
component: HealthInfo,
path: "/tools/diagnostics"
path: "/tools/diagnostics",
},
{
component: ErrorLogs,
path: "/tools/logs"
path: "/tools/logs",
},
{
component: LogsSearchMain,
path: "/tools/audit-logs"
path: "/tools/audit-logs",
},
{
component: Tools,
path: "/tools"
path: "/tools",
},
{
component: ConfigurationMain,
path: "/settings"
path: "/settings",
},
{
component: ConfigurationMain,
path: "/settings/:option"
path: "/settings/:option",
},
{
component: AddNotificationEndpoint,
path: "/notification-endpoints/add/:service"
path: "/notification-endpoints/add/:service",
},
{
component: NotificationTypeSelector,
path: "/notification-endpoints/add"
path: "/notification-endpoints/add",
},
{
component: NotificationEndpoints,
path: "/notification-endpoints"
path: "/notification-endpoints",
},
{
component: AddTierConfiguration,
path: "/tiers/add/:service"
path: "/tiers/add/:service",
},
{
component: TierTypeSelector,
path: "/tiers/add"
path: "/tiers/add",
},
{
component: ListTiersConfiguration,
path: "/tiers"
path: "/tiers",
},
{
component: Account,
path: "/account",
props: {
changePassword: session.pages.includes("/account/change-password")
}
changePassword: (!session ? [] : session.pages).includes(
"/account/change-password"
),
},
},
{
component: ListTenants,
path: "/tenants"
path: "/tenants",
},
{
component: AddTenant,
path: "/tenants/add"
path: "/tenants/add",
},
{
component: Storage,
path: "/storage"
path: "/storage",
},
{
component: Storage,
path: "/storage/volumes"
path: "/storage/volumes",
},
{
component: Storage,
path: "/storage/drives"
path: "/storage/drives",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName"
path: "/namespaces/:tenantNamespace/tenants/:tenantName",
},
{
component: Hop,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/hop"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/hop",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/summary"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/summary",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/metrics",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pools"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pools",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/volumes"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/volumes",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/license"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/license",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/security"
path: "/namespaces/:tenantNamespace/tenants/:tenantName/security",
},
{
component: License,
path: "/license"
}
path: "/license",
},
];
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);
@@ -432,7 +433,7 @@ const Console = ({
return (
<Fragment>
{session.status === "ok" ? (
{session && session.status === "ok" ? (
<div className={classes.root}>
<CssBaseline />
{!hideMenu && <Menu pages={session.pages} />}
@@ -486,7 +487,7 @@ const Console = ({
snackBarMessage.type === "error"
? classes.errorSnackBar
: ""
}`
}`,
}}
/>
</div>
@@ -520,14 +521,14 @@ const mapState = (state: AppState) => ({
isServerLoading: state.system.serverIsLoading,
session: state.console.session,
loadingProgress: state.system.loadingProgress,
snackBarMessage: state.system.snackBar
snackBarMessage: state.system.snackBar,
});
const connector = connect(mapState, {
setMenuOpen,
serverNeedsRestart,
serverIsLoading,
setSnackBarMessage
setSnackBarMessage,
});
export default withStyles(styles)(connector(Console));

View File

@@ -14,36 +14,34 @@ import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import withStyles from "@mui/styles/withStyles";
type UserPickerModalProps = {
classes?: any,
title?: string
preSelectedUsers?: string[]
selectedGroup?: string,
open: boolean,
onClose: () => void,
onSaveClick: () => void,
groupStatus?: string,
}
classes?: any;
title?: string;
preSelectedUsers?: string[];
selectedGroup?: string;
open: boolean;
onClose: () => void;
onSaveClick: () => void;
groupStatus?: string;
};
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
marginTop: "1rem"
marginTop: "1rem",
},
...modalBasic
...modalBasic,
});
const AddGroupMember = ({
classes,
title = "",
groupStatus = "enabled",
preSelectedUsers = [],
selectedGroup = "",
open,
onClose
}: UserPickerModalProps) => {
classes,
title = "",
groupStatus = "enabled",
preSelectedUsers = [],
selectedGroup = "",
open,
onClose,
}: UserPickerModalProps) => {
const [selectedUsers, setSelectedUsers] = useState(preSelectedUsers);
function addMembersToGroup() {
@@ -51,8 +49,7 @@ const AddGroupMember = ({
.invoke("PUT", `/api/v1/group?name=${encodeURI(selectedGroup)}`, {
group: selectedGroup,
members: selectedUsers,
status: groupStatus
status: groupStatus,
})
.then((res) => {
onClose();
@@ -64,16 +61,8 @@ const AddGroupMember = ({
}
return (
<ModalWrapper
modalOpen={open}
onClose={onClose}
title={title}
>
<PredefinedList
label={`Selected Group`}
content={selectedGroup}
/>
<ModalWrapper modalOpen={open} onClose={onClose} title={title}>
<PredefinedList label={`Selected Group`} content={selectedGroup} />
<br />
<UsersSelectors
@@ -83,7 +72,6 @@ const AddGroupMember = ({
/>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
@@ -106,15 +94,12 @@ const AddGroupMember = ({
Save
</Button>
</Grid>
</ModalWrapper>
);
};
const mapDispatchToProps = {
setModalErrorSnackMessage
setModalErrorSnackMessage,
};
const connector = connect(null, mapDispatchToProps);

View File

@@ -31,7 +31,7 @@ import {
actionsTray,
containerForHeader,
linkStyles,
searchField
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../common/types";
import api from "../../../common/api";
@@ -53,42 +53,42 @@ interface IGroupsProps {
const styles = (theme: Theme) =>
createStyles({
seeMore: {
marginTop: theme.spacing(3)
marginTop: theme.spacing(3),
},
paper: {
// padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column"
flexDirection: "column",
},
addSideBar: {
width: "320px",
padding: "20px"
padding: "20px",
},
tableToolbar: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(0)
paddingRight: theme.spacing(0),
},
wrapCell: {
maxWidth: "200px",
whiteSpace: "normal",
wordWrap: "break-word"
wordWrap: "break-word",
},
twHeight: {
minHeight: 600
minHeight: 600,
},
minTableHeader: {
color: "#393939",
"& tr": {
"& th": {
fontWeight: "bold"
}
}
fontWeight: "bold",
},
},
},
...linkStyles(theme.palette.info.main),
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4))
...containerForHeader(theme.spacing(4)),
});
const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
@@ -158,7 +158,7 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
const tableActions = [
{ type: "view", onClick: viewAction },
{ type: "delete", onClick: deleteAction }
{ type: "delete", onClick: deleteAction },
];
return (
@@ -202,7 +202,7 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
)
),
}}
onChange={(e) => {
setFilter(e.target.value);
@@ -313,7 +313,7 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
};
const mapDispatchToProps = {
setErrorSnackMessage
setErrorSnackMessage,
};
const connector = connect(null, mapDispatchToProps);

View File

@@ -3,8 +3,15 @@ import PageHeader from "../Common/PageHeader/PageHeader";
import { Link, useParams } from "react-router-dom";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import { actionsTray, containerForHeader, searchField } from "../Common/FormComponents/common/styleLibrary";
import { setErrorSnackMessage, setModalErrorSnackMessage } from "../../../actions";
import {
actionsTray,
containerForHeader,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import {
setErrorSnackMessage,
setModalErrorSnackMessage,
} from "../../../actions";
import { connect } from "react-redux";
import withStyles from "@mui/styles/withStyles";
import { Button, Grid, IconButton, Tooltip } from "@mui/material";
@@ -22,17 +29,16 @@ import AddGroupMember from "./AddGroupMember";
import { ErrorResponseHandler } from "../../../common/types";
import DeleteGroup from "./DeleteGroup";
const styles = (theme: Theme) =>
createStyles({
breadcrumLink: {
textDecoration: "none",
color: "black"
color: "black",
},
...actionsTray,
...searchField,
actionsTray: { ...actionsTray.actionsTray },
...containerForHeader(theme.spacing(4))
...containerForHeader(theme.spacing(4)),
});
interface IGroupDetailsProps {
@@ -42,24 +48,22 @@ interface IGroupDetailsProps {
}
type TabItemsProps = {
activeTab: number,
onTabChange: (tab: number) => void
}
activeTab: number;
onTabChange: (tab: number) => void;
};
type DetailsHeaderProps = {
classes: any
}
classes: any;
};
type GroupInfo = {
members?: any[]
name?: string
policy?: string
status?: string
}
members?: any[];
name?: string;
policy?: string;
status?: string;
};
const TabItems = ({ activeTab, onTabChange }: TabItemsProps) => {
return (
<List component="nav" dense={true}>
<ListItem
@@ -80,12 +84,10 @@ const TabItems = ({ activeTab, onTabChange }: TabItemsProps) => {
>
<ListItemText primary="Policies" />
</ListItem>
</List>
);
};
export const formatPolicy = (policy: string = ""): string[] => {
if (policy.length <= 0) return [];
return policy.split(",");
@@ -95,7 +97,6 @@ export const getPoliciesAsString = (policies: string[]): string => {
return policies.join(", ");
};
const GroupDetailsHeader = ({ classes }: DetailsHeaderProps) => {
return (
<PageHeader
@@ -111,9 +112,7 @@ const GroupDetailsHeader = ({ classes }: DetailsHeaderProps) => {
);
};
const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
const [currentTab, setCurrentTab] = useState<number>(0);
const [groupDetails, setGroupDetails] = useState<GroupInfo>({});
@@ -122,15 +121,9 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
const [usersOpen, setUsersOpen] = useState<boolean>(false);
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const {
groupName = ""
} = useParams<Record<string, string>>();
const { groupName = "" } = useParams<Record<string, string>>();
const {
members = [],
policy = "",
status: groupEnabled
} = groupDetails;
const { members = [], policy = "", status: groupEnabled } = groupDetails;
useEffect(() => {
if (groupName) {
@@ -148,18 +141,18 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
.invoke("GET", `/api/v1/group?name=${encodeURI(groupName)}`)
.then((res: any) => {
setGroupDetails(res);
}).catch(() => {
setGroupDetails({});
});
};
})
.catch(() => {
setGroupDetails({});
});
}
function toggleGroupStatus(nextStatus: boolean) {
return api
.invoke("PUT", `/api/v1/group?name=${encodeURI(groupName)}`, {
group: groupName,
members: members,
status: nextStatus ? "enabled" : "disabled"
status: nextStatus ? "enabled" : "disabled",
})
.then((res) => {
fetchGroupInfo();
@@ -170,7 +163,6 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
}
return (
<React.Fragment>
<GroupDetailsHeader classes={classes} />
<Grid container className={classes.container}>
@@ -183,7 +175,9 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
}
title={groupName}
subTitle={
<Fragment>Status: {isGroupEnabled ? "Enabled" : "Disabled"}</Fragment>
<Fragment>
Status: {isGroupEnabled ? "Enabled" : "Disabled"}
</Fragment>
}
actions={
<Fragment>
@@ -214,9 +208,12 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
</Grid>
<Grid item xs={2}>
<TabItems activeTab={currentTab} onTabChange={(num) => {
setCurrentTab(num);
}} />
<TabItems
activeTab={currentTab}
onTabChange={(num) => {
setCurrentTab(num);
}}
/>
</Grid>
<Grid item xs={10}>
<Grid item xs={12}>
@@ -270,8 +267,8 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
type: "view",
onClick: (policy) => {
history.push(`/policies/${policy}`);
}
}
},
},
]}
columns={[{ label: "Policy", elementKey: "" }]}
isLoading={false}
@@ -297,12 +294,10 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
/>
) : null}
{usersOpen ?
{usersOpen ? (
<AddGroupMember
selectedGroup={groupName}
onSaveClick={() => {
}}
onSaveClick={() => {}}
title={memberActionText}
groupStatus={groupEnabled}
classes={classes}
@@ -311,7 +306,9 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
onClose={() => {
setUsersOpen(false);
fetchGroupInfo();
}} /> : null}
}}
/>
) : null}
{deleteOpen && (
<DeleteGroup
@@ -328,14 +325,12 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
{/*Modals*/}
</React.Fragment>
);
};
const mapDispatchToProps = {
setErrorSnackMessage
setErrorSnackMessage,
};
const connector = connect(null, mapDispatchToProps);
export default withStyles(styles)(connector(GroupsDetails));
export default withStyles(styles)(connector(GroupsDetails));

View File

@@ -40,7 +40,15 @@ const LoginCallback: FC<RouteComponentProps> = ({ location }) => {
// store the jwt token
storage.setItem("token", res.sessionId);
// We push to history the new URL.
window.location.href = "/";
let targetPath = "/";
if (
localStorage.getItem("redirect-path") &&
localStorage.getItem("redirect-path") != ""
) {
targetPath = `${localStorage.getItem("redirect-path")}`;
localStorage.setItem("redirect-path", "");
}
window.location.href = targetPath;
}
})
.catch((res: any) => {

View File

@@ -242,7 +242,15 @@ const Login = ({
if (loginStrategy.loginStrategy === loginStrategyType.form) {
localStorage.setItem("userLoggedIn", encodeFileName(accessKey));
}
history.push("/");
let targetPath = "/";
if (
localStorage.getItem("redirect-path") &&
localStorage.getItem("redirect-path") != ""
) {
targetPath = `${localStorage.getItem("redirect-path")}`;
localStorage.setItem("redirect-path", "");
}
history.push(targetPath);
})
.catch((err) => {
setLoginSending(false);