mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-16 04:55:55 +08:00
feat: [SIG-546]: user with viewer roles can only view saved views (#4663)
* feat: [SIG-543]: Users with VIEWER access can create/edit/delete views for logs and traces * feat: [SIG-543]: remove extra code * feat: [SIG-543]: role changes in the save views toolbar * feat: [SIG-543]: role changes in the save views toolbar * feat: remove the save feature / dashboard / alert feature for viewer roles * feat: remove the save feature / dashboard / alert feature for viewer roles * fix: address review comments
This commit is contained in:
parent
49aba4fb1c
commit
6b87118fc6
@ -1,3 +1,6 @@
|
|||||||
|
.hide-update {
|
||||||
|
left: calc(50% - 41px) !important;
|
||||||
|
}
|
||||||
.explorer-update {
|
.explorer-update {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 16px;
|
bottom: 16px;
|
||||||
@ -23,6 +26,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-divider {
|
.ant-divider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
@ -55,6 +62,10 @@
|
|||||||
|
|
||||||
.view-options,
|
.view-options,
|
||||||
.actions {
|
.actions {
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -102,6 +113,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-content {
|
.app-content {
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import cx from 'classnames';
|
||||||
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
|
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
@ -31,10 +32,14 @@ import { useNotifications } from 'hooks/useNotifications';
|
|||||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||||
import { Check, ConciergeBell, Disc3, Plus, X, XCircle } from 'lucide-react';
|
import { Check, ConciergeBell, Disc3, Plus, X, XCircle } from 'lucide-react';
|
||||||
import { CSSProperties, useCallback, useMemo, useRef, useState } from 'react';
|
import { CSSProperties, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
import { USER_ROLES } from 'types/roles';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DATASOURCE_VS_ROUTES,
|
DATASOURCE_VS_ROUTES,
|
||||||
@ -43,6 +48,9 @@ import {
|
|||||||
saveNewViewHandler,
|
saveNewViewHandler,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
|
const allowedRoles = [USER_ROLES.ADMIN, USER_ROLES.AUTHOR, USER_ROLES.EDITOR];
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
function ExplorerOptions({
|
function ExplorerOptions({
|
||||||
disabled,
|
disabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -71,6 +79,8 @@ function ExplorerOptions({
|
|||||||
setIsSaveModalOpen(false);
|
setIsSaveModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const onCreateAlertsHandler = useCallback(() => {
|
const onCreateAlertsHandler = useCallback(() => {
|
||||||
history.push(
|
history.push(
|
||||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||||
@ -247,10 +257,17 @@ function ExplorerOptions({
|
|||||||
[isDarkMode],
|
[isDarkMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isEditDeleteSupported = allowedRoles.includes(role as string);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isQueryUpdated && (
|
{isQueryUpdated && (
|
||||||
<div className="explorer-update">
|
<div
|
||||||
|
className={cx(
|
||||||
|
isEditDeleteSupported ? '' : 'hide-update',
|
||||||
|
'explorer-update',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Tooltip title="Clear this view" placement="top">
|
<Tooltip title="Clear this view" placement="top">
|
||||||
<Button
|
<Button
|
||||||
className="action-icon"
|
className="action-icon"
|
||||||
@ -258,10 +275,13 @@ function ExplorerOptions({
|
|||||||
icon={<X size={14} />}
|
icon={<X size={14} />}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Divider type="vertical" />
|
<Divider
|
||||||
|
type="vertical"
|
||||||
|
className={isEditDeleteSupported ? '' : 'hidden'}
|
||||||
|
/>
|
||||||
<Tooltip title="Update this view" placement="top">
|
<Tooltip title="Update this view" placement="top">
|
||||||
<Button
|
<Button
|
||||||
className="action-icon"
|
className={cx('action-icon', isEditDeleteSupported ? ' ' : 'hidden')}
|
||||||
disabled={isViewUpdating}
|
disabled={isViewUpdating}
|
||||||
onClick={onUpdateQueryHandler}
|
onClick={onUpdateQueryHandler}
|
||||||
icon={<Disc3 size={14} />}
|
icon={<Disc3 size={14} />}
|
||||||
@ -323,15 +343,16 @@ function ExplorerOptions({
|
|||||||
<Button
|
<Button
|
||||||
shape="round"
|
shape="round"
|
||||||
onClick={handleSaveViewModalToggle}
|
onClick={handleSaveViewModalToggle}
|
||||||
|
className={isEditDeleteSupported ? '' : 'hidden'}
|
||||||
disabled={viewsIsLoading || isRefetching}
|
disabled={viewsIsLoading || isRefetching}
|
||||||
>
|
>
|
||||||
<Disc3 size={16} /> Save this view
|
<Disc3 size={16} /> Save this view
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr className={isEditDeleteSupported ? '' : 'hidden'} />
|
||||||
|
|
||||||
<div className="actions">
|
<div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
|
||||||
<Tooltip title="Create Alerts">
|
<Tooltip title="Create Alerts">
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -309,6 +309,7 @@ function DashboardsList(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
|
{createNewDashboard && (
|
||||||
<Col
|
<Col
|
||||||
span={6}
|
span={6}
|
||||||
style={{
|
style={{
|
||||||
@ -341,17 +342,19 @@ function DashboardsList(): JSX.Element {
|
|||||||
</NewDashboardButton>
|
</NewDashboardButton>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Col>
|
</Col>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
isDashboardListLoading,
|
isDashboardListLoading,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
isFilteringDashboards,
|
isFilteringDashboards,
|
||||||
|
searchString,
|
||||||
|
createNewDashboard,
|
||||||
getMenuItems,
|
getMenuItems,
|
||||||
newDashboardState.loading,
|
newDashboardState.loading,
|
||||||
newDashboardState.error,
|
newDashboardState.error,
|
||||||
getText,
|
getText,
|
||||||
searchString,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
max-width: 736px;
|
max-width: 736px;
|
||||||
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: var(--bg-vanilla-100);
|
color: var(--bg-vanilla-100);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
@ -37,7 +36,6 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
background: var(--bg-ink-500);
|
background: var(--bg-ink-500);
|
||||||
|
|
||||||
}
|
}
|
||||||
.column-render {
|
.column-render {
|
||||||
margin: 8px 0 !important;
|
margin: 8px 0 !important;
|
||||||
@ -75,8 +73,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.view-details {
|
.view-details {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@ -127,7 +128,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-pagination-item {
|
.ant-pagination-item {
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -141,7 +141,6 @@
|
|||||||
font-weight: var(--font-weight-normal);
|
font-weight: var(--font-weight-normal);
|
||||||
line-height: 20px; /* 142.857% */
|
line-height: 20px; /* 142.857% */
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pagination-item-active {
|
.ant-pagination-item-active {
|
||||||
@ -165,7 +164,7 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid var(--bg-slate-500);
|
border: 1px solid var(--bg-slate-500);
|
||||||
background: var(--bg-ink-400);
|
background: var(--bg-ink-400);
|
||||||
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.20);
|
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
.ant-modal-header {
|
.ant-modal-header {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
@ -211,7 +210,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-modal-footer {
|
.ant-modal-footer {
|
||||||
@ -255,7 +253,6 @@
|
|||||||
.lightMode {
|
.lightMode {
|
||||||
.save-view-container {
|
.save-view-container {
|
||||||
.save-view-content {
|
.save-view-content {
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: var(--bg-ink-500);
|
color: var(--bg-ink-500);
|
||||||
}
|
}
|
||||||
|
@ -32,14 +32,20 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { ChangeEvent, useEffect, useState } from 'react';
|
import { ChangeEvent, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||||
import { ViewProps } from 'types/api/saveViews/types';
|
import { ViewProps } from 'types/api/saveViews/types';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
import { USER_ROLES } from 'types/roles';
|
||||||
|
|
||||||
import { ROUTES_VS_SOURCEPAGE, SOURCEPAGE_VS_ROUTES } from './constants';
|
import { ROUTES_VS_SOURCEPAGE, SOURCEPAGE_VS_ROUTES } from './constants';
|
||||||
import { deleteViewHandler } from './utils';
|
import { deleteViewHandler } from './utils';
|
||||||
|
|
||||||
|
const allowedRoles = [USER_ROLES.ADMIN, USER_ROLES.AUTHOR, USER_ROLES.EDITOR];
|
||||||
|
|
||||||
function SaveView(): JSX.Element {
|
function SaveView(): JSX.Element {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const sourcepage = ROUTES_VS_SOURCEPAGE[pathname];
|
const sourcepage = ROUTES_VS_SOURCEPAGE[pathname];
|
||||||
@ -61,6 +67,8 @@ function SaveView(): JSX.Element {
|
|||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const handleDeleteModelOpen = (uuid: string, name: string): void => {
|
const handleDeleteModelOpen = (uuid: string, name: string): void => {
|
||||||
setActiveViewKey(uuid);
|
setActiveViewKey(uuid);
|
||||||
setActiveViewName(name);
|
setActiveViewName(name);
|
||||||
@ -217,6 +225,9 @@ function SaveView(): JSX.Element {
|
|||||||
|
|
||||||
// Combine time and date
|
// Combine time and date
|
||||||
const formattedDateAndTime = `${formattedTime} ⎯ ${formattedDate}`;
|
const formattedDateAndTime = `${formattedTime} ⎯ ${formattedDate}`;
|
||||||
|
|
||||||
|
const isEditDeleteSupported = allowedRoles.includes(role as string);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="column-render">
|
<div className="column-render">
|
||||||
<div className="title-with-action">
|
<div className="title-with-action">
|
||||||
@ -234,11 +245,13 @@ function SaveView(): JSX.Element {
|
|||||||
<div className="action-btn">
|
<div className="action-btn">
|
||||||
<PenLine
|
<PenLine
|
||||||
size={14}
|
size={14}
|
||||||
|
className={isEditDeleteSupported ? '' : 'hidden'}
|
||||||
onClick={(): void => handleEditModelOpen(view, bgColor)}
|
onClick={(): void => handleEditModelOpen(view, bgColor)}
|
||||||
/>
|
/>
|
||||||
<Compass size={14} onClick={(): void => handleRedirectQuery(view)} />
|
<Compass size={14} onClick={(): void => handleRedirectQuery(view)} />
|
||||||
<Trash2
|
<Trash2
|
||||||
size={14}
|
size={14}
|
||||||
|
className={isEditDeleteSupported ? '' : 'hidden'}
|
||||||
color={Color.BG_CHERRY_500}
|
color={Color.BG_CHERRY_500}
|
||||||
onClick={(): void => handleDeleteModelOpen(view.uuid, view.name)}
|
onClick={(): void => handleDeleteModelOpen(view.uuid, view.name)}
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user