From 99e700a34f05eb000f57807963f0fdf889544e6c Mon Sep 17 00:00:00 2001 From: tdari Date: Sun, 21 Apr 2024 14:54:05 +0300 Subject: [PATCH 1/6] feat: update workspace invitation card ui --- .../workspaces/WorkspacePendingListItem.tsx | 94 +++++++++++++------ frontend/src/providers/theme.config.ts | 10 ++ 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/frontend/src/features/workspaces/components/workspaces/WorkspacePendingListItem.tsx b/frontend/src/features/workspaces/components/workspaces/WorkspacePendingListItem.tsx index e44518e1..7b731abe 100644 --- a/frontend/src/features/workspaces/components/workspaces/WorkspacePendingListItem.tsx +++ b/frontend/src/features/workspaces/components/workspaces/WorkspacePendingListItem.tsx @@ -1,12 +1,17 @@ +import CheckIcon from "@mui/icons-material/Check"; +import CloseIcon from "@mui/icons-material/Close"; +import MailOutlineIcon from "@mui/icons-material/MailOutline"; import { Card, - CardActionArea, CardHeader, CardContent, Typography, CardActions, Button, Grid, + Divider, + Tooltip, + Chip, } from "@mui/material"; import { useWorkspaces } from "context/workspaces"; import { type WorkspaceSummary } from "context/workspaces/types"; @@ -33,57 +38,86 @@ export const WorkspacePendingListItem: FC<{ sx={{ display: "flex", flexDirection: "column", - borderColor: "#f90", + borderColor: "warning.main", }} > - + {workspace.workspace_name} + + + + + } + titleTypographyProps={{ variant: "body1" }} + color="primary" + sx={{ py: 1, width: "100%" }} + /> + - - - - - - Permission: - - {workspace.user_permission} - - - - Status: - - Pending - + + + + Permission: + + - - + + + + + + Status: + + + + + diff --git a/frontend/src/providers/theme.config.ts b/frontend/src/providers/theme.config.ts index ed79875b..4d887c73 100644 --- a/frontend/src/providers/theme.config.ts +++ b/frontend/src/providers/theme.config.ts @@ -22,6 +22,16 @@ export const theme = createTheme({ dark: "#008F34", contrastText: "#FFFFFF", }, + error: { + main: "#E71D1D", + light: "#C03335", + dark: "#7B0002", + }, + warning: { + main: "#F90", + light: "#FFAD33", + dark: "#B26B00", + }, }, typography: { fontFamily: "Rethink-Sans", From 345d4b412fe9fb1bdf5acbedf495a9fb1a880175 Mon Sep 17 00:00:00 2001 From: tdari Date: Sun, 21 Apr 2024 14:55:03 +0300 Subject: [PATCH 2/6] ref: update .gitignore to exclude .vscode folder in subfolders --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5cf39cd2..a8d31d56 100644 --- a/.gitignore +++ b/.gitignore @@ -183,7 +183,7 @@ pyvenv.cfg pip-selfcheck.json ### VisualStudioCode ### -.vscode/* +**/.vscode/* ### VisualStudioCode Patch ### # Ignore all local history of files From 96c78adb452bbe9392c66a204a5ac5fa844ceccb Mon Sep 17 00:00:00 2001 From: Nathan Vieira Marcelino Date: Mon, 22 Apr 2024 20:03:02 -0300 Subject: [PATCH 3/6] fix(frontend): overflow on flex components --- frontend/src/components/PrivateLayout/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/PrivateLayout/index.tsx b/frontend/src/components/PrivateLayout/index.tsx index fc8f710d..1b27ff33 100644 --- a/frontend/src/components/PrivateLayout/index.tsx +++ b/frontend/src/components/PrivateLayout/index.tsx @@ -1,6 +1,7 @@ import { Box, Container } from "@mui/material"; import { type FC, type ReactNode } from "react"; +import { DrawerHeader } from "./header/drawerMenu.style"; import { Header } from "./header/header"; interface Props { @@ -9,10 +10,16 @@ interface Props { export const PrivateLayout: FC = ({ children }) => { return ( - +
- + + + {children} From a75279364e447223abc554367c50e3c94b55ea96 Mon Sep 17 00:00:00 2001 From: Nathan Vieira Marcelino Date: Tue, 23 Apr 2024 18:18:33 -0300 Subject: [PATCH 4/6] feat: forbidden page and component --- .../AuthorizationComponent/index.tsx | 54 +++++++++ .../PrivateLayout/header/drawerMenu.tsx | 50 ++++---- .../src/components/RoleComponent/index.tsx | 21 ---- .../Routes/AuthorizationRoute/index.tsx | 8 +- .../components/Routes/ForbiddenPage/index.tsx | 74 ++++++++++++ frontend/src/features/auth/routes/index.tsx | 2 + .../WorkflowRunTableFooter/index.tsx | 11 +- .../components/ButtonsMenu/index.tsx | 113 ++++++++++-------- .../Panel/WorkflowPanel/WorkflowPanel.tsx | 15 ++- .../src/features/workspaces/routes/index.tsx | 2 +- frontend/src/routes/protected.tsx | 5 +- frontend/src/utils/roles.ts | 2 + 12 files changed, 252 insertions(+), 105 deletions(-) create mode 100644 frontend/src/components/AuthorizationComponent/index.tsx delete mode 100644 frontend/src/components/RoleComponent/index.tsx create mode 100644 frontend/src/components/Routes/ForbiddenPage/index.tsx diff --git a/frontend/src/components/AuthorizationComponent/index.tsx b/frontend/src/components/AuthorizationComponent/index.tsx new file mode 100644 index 00000000..4b5ecba6 --- /dev/null +++ b/frontend/src/components/AuthorizationComponent/index.tsx @@ -0,0 +1,54 @@ +import { useWorkspaces } from "@context/workspaces"; +import ReportProblemIcon from "@mui/icons-material/ReportProblem"; +import { Box, Button, type SxProps, type Theme, Tooltip } from "@mui/material"; +import { type Roles } from "@utils/roles"; +import React, { type ReactNode } from "react"; + +interface IProps { + children?: ReactNode; + allowedRoles: Roles[]; + sx?: SxProps; +} + +export const AuthorizationComponent: React.FC = ({ + allowedRoles, + children, + sx, +}) => { + const { workspace } = useWorkspaces(); + + const authorized = allowedRoles.some( + (item) => workspace?.user_permission === item, + ); + + return authorized ? ( + <>{children} + ) : React.isValidElement(children) && children.type === Button ? ( + + + + + + ) : ( + + + + + + ); +}; diff --git a/frontend/src/components/PrivateLayout/header/drawerMenu.tsx b/frontend/src/components/PrivateLayout/header/drawerMenu.tsx index 60db29b3..e8fea241 100644 --- a/frontend/src/components/PrivateLayout/header/drawerMenu.tsx +++ b/frontend/src/components/PrivateLayout/header/drawerMenu.tsx @@ -1,3 +1,4 @@ +import { AuthorizationComponent } from "@components/AuthorizationComponent"; import { AccountTree as AccountTreeIcon, BlurCircular, @@ -53,31 +54,36 @@ export const DrawerMenu: FC = ({ isOpen, handleClose }) => { alt="logo" style={{ width: "190px", marginRight: "8px", marginLeft: "20px" }} /> - { - if (workspace) { - navigate("/workspaces/settings"); - } - }} + - - {workspace?.workspace_name - ? workspace?.workspace_name - : "No workspace selected"} - + onClick={() => { + if (workspace) { + navigate("/workspaces/settings"); + } + }} + > + + {workspace?.workspace_name + ? workspace?.workspace_name + : "No workspace selected"} + + diff --git a/frontend/src/components/RoleComponent/index.tsx b/frontend/src/components/RoleComponent/index.tsx deleted file mode 100644 index 378d3cef..00000000 --- a/frontend/src/components/RoleComponent/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useWorkspaces } from "@context/workspaces"; -import { type Roles } from "@utils/roles"; -import React, { type ReactNode } from "react"; - -interface IProps { - children?: ReactNode; - allowedRoles: Roles[]; -} - -export const RequireRoleComponent: React.FC = ({ - allowedRoles, - children, -}) => { - const { workspace } = useWorkspaces(); - - const authorized = allowedRoles.some( - (item) => workspace?.user_permission === item, - ); - - return authorized ? <>{children} :
Not authorized
; -}; diff --git a/frontend/src/components/Routes/AuthorizationRoute/index.tsx b/frontend/src/components/Routes/AuthorizationRoute/index.tsx index c9106aae..3564d683 100644 --- a/frontend/src/components/Routes/AuthorizationRoute/index.tsx +++ b/frontend/src/components/Routes/AuthorizationRoute/index.tsx @@ -24,7 +24,7 @@ type Props = { * | requireWorkspace | allowedRoles.length | workspace | Result | * |------------------|---------------------|-----------|---------------------------| * | false | 0 | null | Render children | - * | false | 0 | not null | Redirect to unauthorized | + * | false | 0 | not null | Redirect to forbidden | * | false | > 0 | null | Redirect to workspaces | * | false | > 0 | not null | Check permission | * | true | 0 | null | Redirect to workspaces | @@ -68,10 +68,10 @@ export const AuthorizationRoute = ({ return hasPermission ? ( <>{children} ) : ( - + ); } - // Redirect to unauthorized if no other conditions met - return ; + // Redirect to forbidden if no other conditions met + return ; }; diff --git a/frontend/src/components/Routes/ForbiddenPage/index.tsx b/frontend/src/components/Routes/ForbiddenPage/index.tsx new file mode 100644 index 00000000..a00108f3 --- /dev/null +++ b/frontend/src/components/Routes/ForbiddenPage/index.tsx @@ -0,0 +1,74 @@ +import PrivateLayout from "@components/PrivateLayout"; +import { useAuthentication } from "@context/authentication"; +import { + Button, + Card, + CardContent, + CardHeader, + Divider, + Typography, +} from "@mui/material"; +import React from "react"; +import { type NavigateFunction, useNavigate } from "react-router-dom"; + +export const ForbiddenPage: React.FC = () => { + const { isLogged } = useAuthentication(); + const navigate = useNavigate(); + + return ( + <> + {isLogged ? ( + + + + ) : ( + + )} + + ); +}; + +const ForbiddenCard: React.FC<{ navigate: NavigateFunction }> = ({ + navigate, +}) => ( + + theme.palette.error.main, + color: "white", + textAlign: "center", + }} + /> + + + + You are not authorized to access this page. + + + + +); diff --git a/frontend/src/features/auth/routes/index.tsx b/frontend/src/features/auth/routes/index.tsx index 90fb0386..f16afd34 100644 --- a/frontend/src/features/auth/routes/index.tsx +++ b/frontend/src/features/auth/routes/index.tsx @@ -1,3 +1,4 @@ +import { ForbiddenPage } from "@components/Routes/ForbiddenPage"; import { NotFoundRoute } from "@components/Routes/NotFoundRoute"; import { PublicRoute } from "@components/Routes/PublicRoute"; import React from "react"; @@ -14,6 +15,7 @@ export const AuthRoutes: React.FC = () => { } /> Recover password} /> } />, + } /> ( sx={{ height: "100%" }} > - + + + + + + - + + + - + + + - + + + = ({ /> - + + + ); diff --git a/frontend/src/features/workflowEditor/components/Panel/WorkflowPanel/WorkflowPanel.tsx b/frontend/src/features/workflowEditor/components/Panel/WorkflowPanel/WorkflowPanel.tsx index 87da8b55..ea37be02 100644 --- a/frontend/src/features/workflowEditor/components/Panel/WorkflowPanel/WorkflowPanel.tsx +++ b/frontend/src/features/workflowEditor/components/Panel/WorkflowPanel/WorkflowPanel.tsx @@ -1,6 +1,7 @@ import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh"; import { Paper } from "@mui/material"; -import { usesPieces } from "context/workspaces"; +import { type Roles } from "@utils/roles"; +import { useWorkspaces, usesPieces } from "context/workspaces"; import Elk from "elkjs"; import { useWorkflowsEditor } from "features/workflowEditor/context"; import { @@ -74,6 +75,12 @@ export interface WorkflowPanelRef { const WorkflowPanel = forwardRef( ({ onNodeDoubleClick }, ref) => { + const { workspace } = useWorkspaces(); + + const authorized = (["owner", "admin", "write"] as Roles[]).some( + (item) => workspace?.user_permission === item, + ); + const reactFlowWrapper = useRef(null); const [instance, setInstance] = useState(null); const [rawNodes, setNodes, onNodesChange] = useNodesState([]); @@ -160,6 +167,12 @@ const WorkflowPanel = forwardRef( const onDrop = useCallback( async (event: DragEvent) => { event.preventDefault(); + + if (!authorized) { + toast.error("You don't have write permissions"); + return; + } + if (reactFlowWrapper?.current === null || instance === null) { return; } diff --git a/frontend/src/features/workspaces/routes/index.tsx b/frontend/src/features/workspaces/routes/index.tsx index e5d915ed..629f3408 100644 --- a/frontend/src/features/workspaces/routes/index.tsx +++ b/frontend/src/features/workspaces/routes/index.tsx @@ -12,7 +12,7 @@ export const WorkspaceRoute: React.FC = () => { + } diff --git a/frontend/src/routes/protected.tsx b/frontend/src/routes/protected.tsx index 422a8f3d..d265c74d 100644 --- a/frontend/src/routes/protected.tsx +++ b/frontend/src/routes/protected.tsx @@ -1,4 +1,5 @@ import PrivateLayout from "@components/PrivateLayout"; +import { ForbiddenPage } from "@components/Routes/ForbiddenPage"; import { NotFoundRoute } from "@components/Routes/NotFoundRoute"; import { MyWorkflowsRoutes } from "@features/myWorkflows/routes"; import { WorkflowEditorRoute } from "@features/workflowEditor/routes"; @@ -54,8 +55,8 @@ export const protectedRoutes = [ { path: "my-workflows/*", element: }, { path: "workflows-editor/*", element: }, { - path: "unauthorized", - element:
Unauthorized
, + path: "forbidden", + element: , }, { path: "404", diff --git a/frontend/src/utils/roles.ts b/frontend/src/utils/roles.ts index 01a818d4..ad2602b8 100644 --- a/frontend/src/utils/roles.ts +++ b/frontend/src/utils/roles.ts @@ -1,5 +1,7 @@ export enum role { owner = "owner", + admin = "admin", + write = "write", read = "read", } From 682461fe2a05427d8896662a0cc1cafca94d5be2 Mon Sep 17 00:00:00 2001 From: Nathan Vieira Marcelino Date: Wed, 24 Apr 2024 11:00:30 -0300 Subject: [PATCH 5/6] chore: fix lint --- frontend/src/utils/validationResolver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/utils/validationResolver.ts b/frontend/src/utils/validationResolver.ts index 4c7d4cbb..d4b698bf 100644 --- a/frontend/src/utils/validationResolver.ts +++ b/frontend/src/utils/validationResolver.ts @@ -42,8 +42,8 @@ function set(object: FieldValues, path: string, value?: unknown) { isObject(objValue) || Array.isArray(objValue) ? objValue : !isNaN(+tempPath[index + 1]) - ? [] - : {}; + ? [] + : {}; } object[key] = newValue; object = object[key]; From 6a5a967b861923289139719f3290e8a1266a6c84 Mon Sep 17 00:00:00 2001 From: Nathan Vieira Marcelino Date: Wed, 24 Apr 2024 12:34:56 -0300 Subject: [PATCH 6/6] chore: remove authorization block on nav bar --- .../PrivateLayout/header/drawerMenu.tsx | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/PrivateLayout/header/drawerMenu.tsx b/frontend/src/components/PrivateLayout/header/drawerMenu.tsx index e8fea241..60db29b3 100644 --- a/frontend/src/components/PrivateLayout/header/drawerMenu.tsx +++ b/frontend/src/components/PrivateLayout/header/drawerMenu.tsx @@ -1,4 +1,3 @@ -import { AuthorizationComponent } from "@components/AuthorizationComponent"; import { AccountTree as AccountTreeIcon, BlurCircular, @@ -54,36 +53,31 @@ export const DrawerMenu: FC = ({ isOpen, handleClose }) => { alt="logo" style={{ width: "190px", marginRight: "8px", marginLeft: "20px" }} /> - { + if (workspace) { + navigate("/workspaces/settings"); + } + }} > - { - if (workspace) { - navigate("/workspaces/settings"); - } - }} - > - - {workspace?.workspace_name - ? workspace?.workspace_name - : "No workspace selected"} - - + /> + {workspace?.workspace_name + ? workspace?.workspace_name + : "No workspace selected"} +