From 0906886e9ac3813462cc03044131bd8ceb499a24 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Fri, 3 Nov 2023 17:27:09 +0530 Subject: [PATCH] feat: dashboard lock feature (#3880) * feat: dashboard lock feature * feat: update API method and minor ui updates * feat: update API and author logic * feat: update permissions for author role * feat: use strings and remove console logs --- frontend/public/locales/en/dashboard.json | 4 +- frontend/src/api/dashboard/lockDashboard.ts | 11 +++ frontend/src/api/dashboard/unlockDashboard.ts | 11 +++ .../GridCardLayout/GridCardLayout.tsx | 84 ++++++++++++------- .../src/container/GridCardLayout/config.ts | 7 +- .../src/container/GridCardLayout/styles.ts | 44 +++++----- .../TableComponents/DeleteButton.tsx | 45 ++++++++-- .../ListOfDashboard/TableComponents/Name.tsx | 11 ++- .../src/container/ListOfDashboard/index.tsx | 4 +- .../NewDashboard/ComponentsSlider/styles.ts | 4 +- .../DashboardName}/index.tsx | 9 +- .../SettingsDrawer.tsx | 2 +- .../ShareModal.tsx | 0 .../index.tsx | 49 +++++++++-- .../styles.ts | 0 .../util.ts | 0 frontend/src/container/NewDashboard/index.tsx | 2 +- .../src/providers/Dashboard/Dashboard.tsx | 43 +++++++++- frontend/src/providers/Dashboard/types.ts | 2 + frontend/src/types/api/dashboard/getAll.ts | 1 + frontend/src/types/roles.ts | 4 +- frontend/src/utils/permission/index.ts | 14 ++-- 22 files changed, 266 insertions(+), 85 deletions(-) create mode 100644 frontend/src/api/dashboard/lockDashboard.ts create mode 100644 frontend/src/api/dashboard/unlockDashboard.ts rename frontend/src/container/NewDashboard/{DescriptionOfDashboard/NameOfTheDashboard => DashboardDescription/DashboardName}/index.tsx (72%) rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardDescription}/SettingsDrawer.tsx (90%) rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardDescription}/ShareModal.tsx (100%) rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardDescription}/index.tsx (55%) rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardDescription}/styles.ts (100%) rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardDescription}/util.ts (100%) diff --git a/frontend/public/locales/en/dashboard.json b/frontend/public/locales/en/dashboard.json index b69113483d..7c4b894e76 100644 --- a/frontend/public/locales/en/dashboard.json +++ b/frontend/public/locales/en/dashboard.json @@ -20,5 +20,7 @@ "variable_updated_successfully": "Variable updated successfully", "error_while_updating_variable": "Error while updating variable", "dashboard_has_been_updated": "Dashboard has been updated", - "do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?" + "do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?", +"locked_dashboard_delete_tooltip_admin_author": "Dashboard is locked. Please unlock the dashboard to enable delete.", +"locked_dashboard_delete_tooltip_editor": "Dashboard is locked. Please contact admin to delete the dashboard." } diff --git a/frontend/src/api/dashboard/lockDashboard.ts b/frontend/src/api/dashboard/lockDashboard.ts new file mode 100644 index 0000000000..3393de8fa3 --- /dev/null +++ b/frontend/src/api/dashboard/lockDashboard.ts @@ -0,0 +1,11 @@ +import axios from 'api'; +import { AxiosResponse } from 'axios'; + +interface LockDashboardProps { + uuid: string; +} + +const lockDashboard = (props: LockDashboardProps): Promise => + axios.put(`/dashboards/${props.uuid}/lock`); + +export default lockDashboard; diff --git a/frontend/src/api/dashboard/unlockDashboard.ts b/frontend/src/api/dashboard/unlockDashboard.ts new file mode 100644 index 0000000000..fd4ffbe41a --- /dev/null +++ b/frontend/src/api/dashboard/unlockDashboard.ts @@ -0,0 +1,11 @@ +import axios from 'api'; +import { AxiosResponse } from 'axios'; + +interface UnlockDashboardProps { + uuid: string; +} + +const unlockDashboard = (props: UnlockDashboardProps): Promise => + axios.put(`/dashboards/${props.uuid}/unlock`); + +export default unlockDashboard; diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.tsx b/frontend/src/container/GridCardLayout/GridCardLayout.tsx index 57b1f4222d..a293d1395e 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.tsx +++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx @@ -11,8 +11,10 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { Dashboard, Widgets } from 'types/api/dashboard/getAll'; import AppReducer from 'types/reducer/app'; +import { ROLES, USER_ROLES } from 'types/roles'; +import { ComponentTypes } from 'utils/permission'; -import { headerMenuList } from './config'; +import { EditMenuAction, ViewMenuAction } from './config'; import GridCard from './GridCard'; import { Button, @@ -32,10 +34,11 @@ function GraphLayout({ layouts, setLayouts, setSelectedDashboard, + isDashboardLocked, } = useDashboard(); const { t } = useTranslation(['dashboard']); - const { featureResponse, role } = useSelector( + const { featureResponse, role, user } = useSelector( (state) => state.app, ); @@ -45,9 +48,20 @@ function GraphLayout({ const { notifications } = useNotifications(); + let permissions: ComponentTypes[] = ['save_layout', 'add_panel']; + + if (isDashboardLocked) { + permissions = ['edit_locked_dashboard', 'add_panel_locked_dashboard']; + } + + const userRole: ROLES | null = + selectedDashboard?.created_by === user?.email + ? (USER_ROLES.AUTHOR as ROLES) + : role; + const [saveLayoutPermission, addPanelPermission] = useComponentPermission( - ['save_layout', 'add_panel'], - role, + permissions, + userRole, ); const onSaveHandler = (): void => { @@ -83,35 +97,41 @@ function GraphLayout({ }); }; + const widgetActions = !isDashboardLocked + ? [...ViewMenuAction, ...EditMenuAction] + : [...ViewMenuAction]; + return ( <> - - {saveLayoutPermission && ( - - )} - - {addPanelPermission && ( - - )} - + {!isDashboardLocked && ( + + {saveLayoutPermission && ( + + )} + + {addPanelPermission && ( + + )} + + )} e.id === id); return ( - - + + diff --git a/frontend/src/container/GridCardLayout/config.ts b/frontend/src/container/GridCardLayout/config.ts index 3fa9e8e569..4304c0f231 100644 --- a/frontend/src/container/GridCardLayout/config.ts +++ b/frontend/src/container/GridCardLayout/config.ts @@ -1,13 +1,16 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants'; -export const headerMenuList = [ - MenuItemKeys.View, +export const ViewMenuAction = [MenuItemKeys.View]; + +export const EditMenuAction = [ MenuItemKeys.Clone, MenuItemKeys.Delete, MenuItemKeys.Edit, ]; +export const headerMenuList = [...ViewMenuAction]; + export const EMPTY_WIDGET_LAYOUT = { i: PANEL_TYPES.EMPTY_WIDGET, w: 6, diff --git a/frontend/src/container/GridCardLayout/styles.ts b/frontend/src/container/GridCardLayout/styles.ts index a08ffdc361..9416df6674 100644 --- a/frontend/src/container/GridCardLayout/styles.ts +++ b/frontend/src/container/GridCardLayout/styles.ts @@ -34,29 +34,31 @@ interface Props { export const CardContainer = styled.div` overflow: auto; - :hover { - .react-resizable-handle { - position: absolute; - width: 20px; - height: 20px; - bottom: 0; - right: 0; - background-position: bottom right; - padding: 0 3px 3px 0; - background-repeat: no-repeat; - background-origin: content-box; - box-sizing: border-box; - cursor: se-resize; + &.enable-resize { + :hover { + .react-resizable-handle { + position: absolute; + width: 20px; + height: 20px; + bottom: 0; + right: 0; + background-position: bottom right; + padding: 0 3px 3px 0; + background-repeat: no-repeat; + background-origin: content-box; + box-sizing: border-box; + cursor: se-resize; - ${({ isDarkMode }): StyledCSS => { - const uri = `data:image/svg+xml,%3Csvg viewBox='0 0 6 6' style='background-color:%23ffffff00' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve' x='0px' y='0px' width='6px' height='6px'%0A%3E%3Cg opacity='0.302'%3E%3Cpath d='M 6 6 L 0 6 L 0 4.2 L 4 4.2 L 4.2 4.2 L 4.2 0 L 6 0 L 6 6 L 6 6 Z' fill='${ - isDarkMode ? 'white' : 'grey' - }'/%3E%3C/g%3E%3C/svg%3E`; + ${({ isDarkMode }): StyledCSS => { + const uri = `data:image/svg+xml,%3Csvg viewBox='0 0 6 6' style='background-color:%23ffffff00' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve' x='0px' y='0px' width='6px' height='6px'%0A%3E%3Cg opacity='0.302'%3E%3Cpath d='M 6 6 L 0 6 L 0 4.2 L 4 4.2 L 4.2 4.2 L 4.2 0 L 6 0 L 6 6 L 6 6 Z' fill='${ + isDarkMode ? 'white' : 'grey' + }'/%3E%3C/g%3E%3C/svg%3E`; - return css` - background-image: ${(): string => `url("${uri}")`}; - `; - }} + return css` + background-image: ${(): string => `url("${uri}")`}; + `; + }} + } } } `; diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx index c68b0c2617..b6d51445f8 100644 --- a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx @@ -1,18 +1,27 @@ -import { ExclamationCircleOutlined } from '@ant-design/icons'; -import { Modal } from 'antd'; +import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; +import { Modal, Tooltip } from 'antd'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { useDeleteDashboard } from 'hooks/dashboard/useDeleteDashboard'; import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { useQueryClient } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; +import { USER_ROLES } from 'types/roles'; import { Data } from '../index'; import { TableLinkText } from './styles'; -function DeleteButton({ id }: Data): JSX.Element { +function DeleteButton({ id, createdBy, isLocked }: Data): JSX.Element { const [modal, contextHolder] = Modal.useModal(); + const { role, user } = useSelector((state) => state.app); + const isAuthor = user?.email === createdBy; const queryClient = useQueryClient(); + const { t } = useTranslation(['dashboard']); + const deleteDashboardMutation = useDeleteDashboard(id); const openConfirmationDialog = useCallback((): void => { @@ -32,11 +41,33 @@ function DeleteButton({ id }: Data): JSX.Element { }); }, [modal, deleteDashboardMutation, queryClient]); + const getDeleteTooltipContent = (): string => { + if (isLocked) { + if (role === USER_ROLES.ADMIN || isAuthor) { + return t('dashboard:locked_dashboard_delete_tooltip_admin_author'); + } + + return t('dashboard:locked_dashboard_delete_tooltip_editor'); + } + + return ''; + }; + return ( <> - - Delete - + + { + if (!isLocked) { + openConfirmationDialog(); + } + }} + disabled={isLocked} + > + Delete + + {contextHolder} @@ -55,6 +86,7 @@ function Wrapper(props: Data): JSX.Element { tags, createdBy, lastUpdatedBy, + isLocked, } = props; return ( @@ -69,6 +101,7 @@ function Wrapper(props: Data): JSX.Element { tags, createdBy, lastUpdatedBy, + isLocked, }} /> ); diff --git a/frontend/src/container/ListOfDashboard/TableComponents/Name.tsx b/frontend/src/container/ListOfDashboard/TableComponents/Name.tsx index af53580926..6a85e7e136 100644 --- a/frontend/src/container/ListOfDashboard/TableComponents/Name.tsx +++ b/frontend/src/container/ListOfDashboard/TableComponents/Name.tsx @@ -1,3 +1,4 @@ +import { LockFilled } from '@ant-design/icons'; import ROUTES from 'constants/routes'; import history from 'lib/history'; import { generatePath } from 'react-router-dom'; @@ -6,9 +7,9 @@ import { Data } from '..'; import { TableLinkText } from './styles'; function Name(name: Data['name'], data: Data): JSX.Element { - const onClickHandler = (): void => { - const { id: DashboardId } = data; + const { id: DashboardId, isLocked } = data; + const onClickHandler = (): void => { history.push( generatePath(ROUTES.DASHBOARD, { dashboardId: DashboardId, @@ -16,7 +17,11 @@ function Name(name: Data['name'], data: Data): JSX.Element { ); }; - return {name}; + return ( + + {isLocked && } {name} + + ); } export default Name; diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index cf3fa02a80..a310c5be63 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -130,7 +130,7 @@ function ListOfAllDashboard(): JSX.Element { dataIndex: 'description', }, { - title: 'Tags (can be multiple)', + title: 'Tags', dataIndex: 'tags', width: 50, render: (value): JSX.Element => , @@ -159,6 +159,7 @@ function ListOfAllDashboard(): JSX.Element { tags: e.data.tags || [], key: e.uuid, createdBy: e.created_by, + isLocked: !!e.isLocked || false, lastUpdatedBy: e.updated_by, refetchDashboardList, })) || []; @@ -342,6 +343,7 @@ export interface Data { createdAt: string; lastUpdatedTime: string; lastUpdatedBy: string; + isLocked: boolean; id: string; } diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/styles.ts b/frontend/src/container/NewDashboard/ComponentsSlider/styles.ts index d7d3c6a7f2..29abafa3d5 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/styles.ts +++ b/frontend/src/container/NewDashboard/ComponentsSlider/styles.ts @@ -3,11 +3,13 @@ import styled from 'styled-components'; export const Container = styled.div` display: flex; - gap: 0.6rem; + justify-content: right; + gap: 8px; `; export const Card = styled(CardComponent)` min-height: 10vh; + min-width: 120px; overflow-y: auto; cursor: pointer; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard/index.tsx b/frontend/src/container/NewDashboard/DashboardDescription/DashboardName/index.tsx similarity index 72% rename from frontend/src/container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard/index.tsx rename to frontend/src/container/NewDashboard/DashboardDescription/DashboardName/index.tsx index 32f78d7ec2..53069c78aa 100644 --- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardDescription/DashboardName/index.tsx @@ -1,10 +1,7 @@ import Input from 'components/Input'; import { ChangeEvent, Dispatch, SetStateAction, useCallback } from 'react'; -function NameOfTheDashboard({ - setName, - name, -}: NameOfTheDashboardProps): JSX.Element { +function DashboardName({ setName, name }: DashboardNameProps): JSX.Element { const onChangeHandler = useCallback( (e: ChangeEvent) => { setName(e.target.value); @@ -22,9 +19,9 @@ function NameOfTheDashboard({ ); } -interface NameOfTheDashboardProps { +interface DashboardNameProps { name: string; setName: Dispatch>; } -export default NameOfTheDashboard; +export default DashboardName; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx b/frontend/src/container/NewDashboard/DashboardDescription/SettingsDrawer.tsx similarity index 90% rename from frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx rename to frontend/src/container/NewDashboard/DashboardDescription/SettingsDrawer.tsx index fc51efd69a..fb512e999a 100644 --- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx +++ b/frontend/src/container/NewDashboard/DashboardDescription/SettingsDrawer.tsx @@ -18,7 +18,7 @@ function SettingsDrawer(): JSX.Element { return ( <> - (false); const { t } = useTranslation('common'); - const { role } = useSelector((state) => state.app); + const { user, role } = useSelector((state) => state.app); const [editDashboard] = useComponentPermission(['edit_dashboard'], role); + let isAuthor = false; + + if (selectedDashboard && user && user.email) { + isAuthor = selectedDashboard?.created_by === user?.email; + } + const onToggleHandler = (): void => { isIsJSONModalVisible((state) => !state); }; + const handleLockDashboardToggle = (): void => { + handleDashboardLockToggle(!isDashboardLocked); + }; + return ( + {isDashboardLocked && ( + +   + + )} {title} {description} @@ -55,7 +75,7 @@ function DescriptionOfDashboard(): JSX.Element { )} - {editDashboard && } + {!isDashboardLocked && editDashboard && } + {(isAuthor || role === USER_ROLES.ADMIN) && ( + + + + )} @@ -71,4 +106,4 @@ function DescriptionOfDashboard(): JSX.Element { ); } -export default DescriptionOfDashboard; +export default DashboardDescription; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/styles.ts b/frontend/src/container/NewDashboard/DashboardDescription/styles.ts similarity index 100% rename from frontend/src/container/NewDashboard/DescriptionOfDashboard/styles.ts rename to frontend/src/container/NewDashboard/DashboardDescription/styles.ts diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/util.ts b/frontend/src/container/NewDashboard/DashboardDescription/util.ts similarity index 100% rename from frontend/src/container/NewDashboard/DescriptionOfDashboard/util.ts rename to frontend/src/container/NewDashboard/DashboardDescription/util.ts diff --git a/frontend/src/container/NewDashboard/index.tsx b/frontend/src/container/NewDashboard/index.tsx index 5eff095c6f..ca10b6f89b 100644 --- a/frontend/src/container/NewDashboard/index.tsx +++ b/frontend/src/container/NewDashboard/index.tsx @@ -1,4 +1,4 @@ -import Description from './DescriptionOfDashboard'; +import Description from './DashboardDescription'; import GridGraphs from './GridGraphs'; function NewDashboard(): JSX.Element { diff --git a/frontend/src/providers/Dashboard/Dashboard.tsx b/frontend/src/providers/Dashboard/Dashboard.tsx index 1074465d0b..48190fd3c4 100644 --- a/frontend/src/providers/Dashboard/Dashboard.tsx +++ b/frontend/src/providers/Dashboard/Dashboard.tsx @@ -1,9 +1,12 @@ import { Modal } from 'antd'; import get from 'api/dashboard/get'; +import lockDashboardApi from 'api/dashboard/lockDashboard'; +import unlockDashboardApi from 'api/dashboard/unlockDashboard'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import ROUTES from 'constants/routes'; import { getMinMax } from 'container/TopNav/AutoRefresh/config'; import dayjs, { Dayjs } from 'dayjs'; +import useAxiosError from 'hooks/useAxiosError'; import useTabVisibility from 'hooks/useTabFocus'; import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout'; import { @@ -17,7 +20,7 @@ import { } from 'react'; import { Layout } from 'react-grid-layout'; import { useTranslation } from 'react-i18next'; -import { useQuery, UseQueryResult } from 'react-query'; +import { useMutation, useQuery, UseQueryResult } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { useRouteMatch } from 'react-router-dom'; import { Dispatch } from 'redux'; @@ -32,7 +35,9 @@ import { IDashboardContext } from './types'; const DashboardContext = createContext({ isDashboardSliderOpen: false, + isDashboardLocked: false, handleToggleDashboardSlider: () => {}, + handleDashboardLockToggle: () => {}, dashboardResponse: {} as UseQueryResult, selectedDashboard: {} as Dashboard, dashboardId: '', @@ -50,6 +55,9 @@ export function DashboardProvider({ children, }: PropsWithChildren): JSX.Element { const [isDashboardSliderOpen, setIsDashboardSlider] = useState(false); + + const [isDashboardLocked, setIsDashboardLocked] = useState(false); + const isDashboardPage = useRouteMatch({ path: ROUTES.DASHBOARD, exact: true, @@ -99,6 +107,8 @@ export function DashboardProvider({ onSuccess: (data) => { const updatedDate = dayjs(data.updated_at); + setIsDashboardLocked(data?.isLocked || false); + // on first render if (updatedTimeRef.current === null) { setSelectedDashboard(data); @@ -179,10 +189,39 @@ export function DashboardProvider({ setIsDashboardSlider(value); }; + const handleError = useAxiosError(); + + const { mutate: lockDashboard } = useMutation(lockDashboardApi, { + onSuccess: () => { + setIsDashboardSlider(false); + setIsDashboardLocked(true); + }, + onError: handleError, + }); + + const { mutate: unlockDashboard } = useMutation(unlockDashboardApi, { + onSuccess: () => { + setIsDashboardLocked(false); + }, + onError: handleError, + }); + + const handleDashboardLockToggle = async (value: boolean): Promise => { + if (selectedDashboard) { + if (value) { + lockDashboard(selectedDashboard); + } else { + unlockDashboard(selectedDashboard); + } + } + }; + const value: IDashboardContext = useMemo( () => ({ isDashboardSliderOpen, + isDashboardLocked, handleToggleDashboardSlider, + handleDashboardLockToggle, dashboardResponse, selectedDashboard, dashboardId, @@ -191,8 +230,10 @@ export function DashboardProvider({ setSelectedDashboard, updatedTimeRef, }), + // eslint-disable-next-line react-hooks/exhaustive-deps [ isDashboardSliderOpen, + isDashboardLocked, dashboardResponse, selectedDashboard, dashboardId, diff --git a/frontend/src/providers/Dashboard/types.ts b/frontend/src/providers/Dashboard/types.ts index 9fc15c5f77..11e2da2212 100644 --- a/frontend/src/providers/Dashboard/types.ts +++ b/frontend/src/providers/Dashboard/types.ts @@ -5,7 +5,9 @@ import { Dashboard } from 'types/api/dashboard/getAll'; export interface IDashboardContext { isDashboardSliderOpen: boolean; + isDashboardLocked: boolean; handleToggleDashboardSlider: (value: boolean) => void; + handleDashboardLockToggle: (value: boolean) => void; dashboardResponse: UseQueryResult; selectedDashboard: Dashboard | undefined; dashboardId: string; diff --git a/frontend/src/types/api/dashboard/getAll.ts b/frontend/src/types/api/dashboard/getAll.ts index 0c872e02cc..fafdae6b3c 100644 --- a/frontend/src/types/api/dashboard/getAll.ts +++ b/frontend/src/types/api/dashboard/getAll.ts @@ -45,6 +45,7 @@ export interface Dashboard { created_by: string; updated_by: string; data: DashboardData; + isLocked?: boolean; } export interface DashboardData { diff --git a/frontend/src/types/roles.ts b/frontend/src/types/roles.ts index 0ac7deaf24..18b4b52028 100644 --- a/frontend/src/types/roles.ts +++ b/frontend/src/types/roles.ts @@ -1,11 +1,13 @@ export type ADMIN = 'ADMIN'; export type VIEWER = 'VIEWER'; export type EDITOR = 'EDITOR'; +export type AUTHOR = 'AUTHOR'; -export type ROLES = ADMIN | VIEWER | EDITOR; +export type ROLES = ADMIN | VIEWER | EDITOR | AUTHOR; export const USER_ROLES = { ADMIN: 'ADMIN', VIEWER: 'VIEWER', EDITOR: 'EDITOR', + AUTHOR: 'AUTHOR', }; diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index 057eb2adc4..ee1a7a09e9 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -18,7 +18,9 @@ export type ComponentTypes = | 'new_alert_action' | 'edit_widget' | 'add_panel' - | 'page_pipelines'; + | 'page_pipelines' + | 'edit_locked_dashboard' + | 'add_panel_locked_dashboard'; export const componentPermission: Record = { current_org_settings: ['ADMIN'], @@ -30,14 +32,16 @@ export const componentPermission: Record = { add_new_channel: ['ADMIN'], set_retention_period: ['ADMIN'], action: ['ADMIN', 'EDITOR'], - save_layout: ['ADMIN', 'EDITOR'], - edit_dashboard: ['ADMIN', 'EDITOR'], - delete_widget: ['ADMIN', 'EDITOR'], + save_layout: ['ADMIN', 'EDITOR', 'AUTHOR'], + edit_dashboard: ['ADMIN', 'EDITOR', 'AUTHOR'], + delete_widget: ['ADMIN', 'EDITOR', 'AUTHOR'], new_dashboard: ['ADMIN', 'EDITOR'], new_alert_action: ['ADMIN'], edit_widget: ['ADMIN', 'EDITOR'], - add_panel: ['ADMIN', 'EDITOR'], + add_panel: ['ADMIN', 'EDITOR', 'AUTHOR'], page_pipelines: ['ADMIN', 'EDITOR'], + edit_locked_dashboard: ['ADMIN', 'AUTHOR'], + add_panel_locked_dashboard: ['ADMIN', 'AUTHOR'], }; export const routePermission: Record = {