mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 07:15:52 +08:00
API ingestion keys - CRUD (#4524)
* feat: api keys crud - integration v0.1 * feat: add test cases * fix: add review comments * feat: api integration and ui updates * feat: update test cases * feat: update expiriesAt request payload * feat: ui feedback updates * feat: api keys crud - integration v0.1 * feat: add test cases * fix: add review comments * feat: api integration and ui updates * feat: update test cases * feat: update expiriesAt request payload * feat: ui feedback updates * feat: handle light mode styles * feat: hide pagination on single page * feat: do not show last used if not present or 0 * feat: show tooltip on role --------- Co-authored-by: Rajat Dabade <rajat@signoz.io>
This commit is contained in:
parent
c4bbbf372c
commit
e2669eb370
@ -20,6 +20,8 @@ const config: Config.InitialOptions = {
|
|||||||
transform: {
|
transform: {
|
||||||
'^.+\\.(ts|tsx)?$': 'ts-jest',
|
'^.+\\.(ts|tsx)?$': 'ts-jest',
|
||||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||||
|
'^.+\\.(css|scss|sass|less)$': 'jest-preview/transforms/css',
|
||||||
|
'^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': 'jest-preview/transforms/file',
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens)/)',
|
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens)/)',
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import 'jest-styled-components';
|
import 'jest-styled-components';
|
||||||
|
import './src/styles.scss';
|
||||||
|
|
||||||
import { server } from './src/mocks-server/server';
|
import { server } from './src/mocks-server/server';
|
||||||
// Establish API mocking before all tests.
|
// Establish API mocking before all tests.
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
"jest": "jest",
|
"jest": "jest",
|
||||||
"jest:coverage": "jest --coverage",
|
"jest:coverage": "jest --coverage",
|
||||||
"jest:watch": "jest --watch",
|
"jest:watch": "jest --watch",
|
||||||
|
"jest-preview": "jest-preview",
|
||||||
|
"test:debug": "npm-run-all -p test jest-preview",
|
||||||
"postinstall": "is-ci || yarn husky:configure",
|
"postinstall": "is-ci || yarn husky:configure",
|
||||||
"playwright": "npm run i18n:generate-hash && NODE_ENV=testing playwright test --config=./playwright.config.ts",
|
"playwright": "npm run i18n:generate-hash && NODE_ENV=testing playwright test --config=./playwright.config.ts",
|
||||||
"playwright:local:debug": "PWDEBUG=console yarn playwright --headed --browser=chromium",
|
"playwright:local:debug": "PWDEBUG=console yarn playwright --headed --browser=chromium",
|
||||||
@ -77,7 +79,7 @@
|
|||||||
"less": "^4.1.2",
|
"less": "^4.1.2",
|
||||||
"less-loader": "^10.2.0",
|
"less-loader": "^10.2.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lucide-react": "0.288.0",
|
"lucide-react": "0.321.0",
|
||||||
"mini-css-extract-plugin": "2.4.5",
|
"mini-css-extract-plugin": "2.4.5",
|
||||||
"papaparse": "5.4.1",
|
"papaparse": "5.4.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
@ -192,6 +194,7 @@
|
|||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"is-ci": "^3.0.1",
|
"is-ci": "^3.0.1",
|
||||||
"jest-playwright-preset": "^1.7.2",
|
"jest-playwright-preset": "^1.7.2",
|
||||||
|
"jest-preview": "0.3.1",
|
||||||
"jest-styled-components": "^7.0.8",
|
"jest-styled-components": "^7.0.8",
|
||||||
"lint-staged": "^12.5.0",
|
"lint-staged": "^12.5.0",
|
||||||
"msw": "1.3.2",
|
"msw": "1.3.2",
|
||||||
@ -208,7 +211,8 @@
|
|||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
"typescript-plugin-css-modules": "5.0.1",
|
"typescript-plugin-css-modules": "5.0.1",
|
||||||
"webpack-bundle-analyzer": "^4.5.0",
|
"webpack-bundle-analyzer": "^4.5.0",
|
||||||
"webpack-cli": "^4.9.2"
|
"webpack-cli": "^4.9.2",
|
||||||
|
"npm-run-all": "latest"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.(js|jsx|ts|tsx)": [
|
"*.(js|jsx|ts|tsx)": [
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"alert_channels": "Alert Channels",
|
"alert_channels": "Alert Channels",
|
||||||
"organization_settings": "Organization Settings",
|
"organization_settings": "Organization Settings",
|
||||||
"ingestion_settings": "Ingestion Settings",
|
"ingestion_settings": "Ingestion Settings",
|
||||||
|
"api_keys": "API Keys",
|
||||||
"my_settings": "My Settings",
|
"my_settings": "My Settings",
|
||||||
"overview_metrics": "Overview Metrics",
|
"overview_metrics": "Overview Metrics",
|
||||||
"dbcall_metrics": "Database Calls",
|
"dbcall_metrics": "Database Calls",
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"MY_SETTINGS": "SigNoz | My Settings",
|
"MY_SETTINGS": "SigNoz | My Settings",
|
||||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||||
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
||||||
|
"API_KEYS": "SigNoz | API Keys",
|
||||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||||
|
3
frontend/public/locales/en/apiKeys.json
Normal file
3
frontend/public/locales/en/apiKeys.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"delete_confirm_message": "Are you sure you want to delete {{keyName}} key? Deleting a key is irreversible and cannot be undone."
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
"alert_channels": "Alert Channels",
|
"alert_channels": "Alert Channels",
|
||||||
"organization_settings": "Organization Settings",
|
"organization_settings": "Organization Settings",
|
||||||
"ingestion_settings": "Ingestion Settings",
|
"ingestion_settings": "Ingestion Settings",
|
||||||
|
"api_keys": "API Keys",
|
||||||
"my_settings": "My Settings",
|
"my_settings": "My Settings",
|
||||||
"overview_metrics": "Overview Metrics",
|
"overview_metrics": "Overview Metrics",
|
||||||
"dbcall_metrics": "Database Calls",
|
"dbcall_metrics": "Database Calls",
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"MY_SETTINGS": "SigNoz | My Settings",
|
"MY_SETTINGS": "SigNoz | My Settings",
|
||||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||||
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
||||||
|
"API_KEYS": "SigNoz | API Keys",
|
||||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||||
|
@ -98,6 +98,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
userResponse &&
|
userResponse &&
|
||||||
|
route &&
|
||||||
route.find((e) => e === userResponse.payload.role) === undefined
|
route.find((e) => e === userResponse.payload.role) === undefined
|
||||||
) {
|
) {
|
||||||
history.push(ROUTES.UN_AUTHORIZED);
|
history.push(ROUTES.UN_AUTHORIZED);
|
||||||
|
@ -118,6 +118,10 @@ export const IngestionSettings = Loadable(
|
|||||||
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
|
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const APIKeys = Loadable(
|
||||||
|
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
|
||||||
|
);
|
||||||
|
|
||||||
export const MySettings = Loadable(
|
export const MySettings = Loadable(
|
||||||
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
|
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import { RouteProps } from 'react-router-dom';
|
|||||||
import {
|
import {
|
||||||
AllAlertChannels,
|
AllAlertChannels,
|
||||||
AllErrors,
|
AllErrors,
|
||||||
|
APIKeys,
|
||||||
BillingPage,
|
BillingPage,
|
||||||
CreateAlertChannelAlerts,
|
CreateAlertChannelAlerts,
|
||||||
CreateNewAlerts,
|
CreateNewAlerts,
|
||||||
@ -236,6 +237,13 @@ const routes: AppRoutes[] = [
|
|||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
key: 'INGESTION_SETTINGS',
|
key: 'INGESTION_SETTINGS',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.API_KEYS,
|
||||||
|
exact: true,
|
||||||
|
component: APIKeys,
|
||||||
|
isPrivate: true,
|
||||||
|
key: 'API_KEYS',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: ROUTES.MY_SETTINGS,
|
path: ROUTES.MY_SETTINGS,
|
||||||
exact: true,
|
exact: true,
|
||||||
|
26
frontend/src/api/APIKeys/createAPIKey.ts
Normal file
26
frontend/src/api/APIKeys/createAPIKey.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { APIKeyProps, CreateAPIKeyProps } from 'types/api/pat/types';
|
||||||
|
|
||||||
|
const createAPIKey = async (
|
||||||
|
props: CreateAPIKeyProps,
|
||||||
|
): Promise<SuccessResponse<APIKeyProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/pats', {
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createAPIKey;
|
24
frontend/src/api/APIKeys/deleteAPIKey.ts
Normal file
24
frontend/src/api/APIKeys/deleteAPIKey.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { AllAPIKeyProps } from 'types/api/pat/types';
|
||||||
|
|
||||||
|
const deleteAPIKey = async (
|
||||||
|
id: string,
|
||||||
|
): Promise<SuccessResponse<AllAPIKeyProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(`/pats/${id}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default deleteAPIKey;
|
24
frontend/src/api/APIKeys/getAPIKey.ts
Normal file
24
frontend/src/api/APIKeys/getAPIKey.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { PayloadProps, Props } from 'types/api/alerts/get';
|
||||||
|
|
||||||
|
const get = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/pats/${props.id}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default get;
|
6
frontend/src/api/APIKeys/getAllAPIKeys.ts
Normal file
6
frontend/src/api/APIKeys/getAllAPIKeys.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { AllAPIKeyProps } from 'types/api/pat/types';
|
||||||
|
|
||||||
|
export const getAllAPIKeys = (): Promise<AxiosResponse<AllAPIKeyProps>> =>
|
||||||
|
axios.get(`/pats`);
|
26
frontend/src/api/APIKeys/updateAPIKey.ts
Normal file
26
frontend/src/api/APIKeys/updateAPIKey.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { PayloadProps, UpdateAPIKeyProps } from 'types/api/pat/types';
|
||||||
|
|
||||||
|
const updateAPIKey = async (
|
||||||
|
props: UpdateAPIKeyProps,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.put(`/pats/${props.id}`, {
|
||||||
|
...props.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateAPIKey;
|
@ -24,6 +24,7 @@ const ROUTES = {
|
|||||||
MY_SETTINGS: '/my-settings',
|
MY_SETTINGS: '/my-settings',
|
||||||
SETTINGS: '/settings',
|
SETTINGS: '/settings',
|
||||||
ORG_SETTINGS: '/settings/org-settings',
|
ORG_SETTINGS: '/settings/org-settings',
|
||||||
|
API_KEYS: '/settings/api-keys',
|
||||||
INGESTION_SETTINGS: '/settings/ingestion-settings',
|
INGESTION_SETTINGS: '/settings/ingestion-settings',
|
||||||
SOMETHING_WENT_WRONG: '/something-went-wrong',
|
SOMETHING_WENT_WRONG: '/something-went-wrong',
|
||||||
UN_AUTHORIZED: '/un-authorized',
|
UN_AUTHORIZED: '/un-authorized',
|
||||||
|
685
frontend/src/container/APIKeys/APIKeys.styles.scss
Normal file
685
frontend/src/container/APIKeys/APIKeys.styles.scss
Normal file
@ -0,0 +1,685 @@
|
|||||||
|
.api-key-container {
|
||||||
|
margin-top: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.api-key-content {
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
max-width: 736px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
line-height: 28px; /* 155.556% */
|
||||||
|
letter-spacing: -0.09px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-keys-search-add-new {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
padding: 16px 0;
|
||||||
|
|
||||||
|
.add-new-api-key-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row {
|
||||||
|
.ant-table-cell {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
.column-render {
|
||||||
|
margin: 8px 0 !important;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
|
||||||
|
.title-with-action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
.api-key-data {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.api-key-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0px 12px;
|
||||||
|
|
||||||
|
background: var(--bg-ink-200);
|
||||||
|
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-family: 'Space Mono', monospace;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-key-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility-btn {
|
||||||
|
border: 1px solid rgba(113, 144, 249, 0.2);
|
||||||
|
background: rgba(113, 144, 249, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-collapse {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.ant-collapse-header {
|
||||||
|
padding: 0px 8px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #121317;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-collapse-content {
|
||||||
|
border-top: 1px solid var(--bg-slate-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-collapse-item {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-collapse-expand-icon {
|
||||||
|
padding-inline-end: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-details {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
border-top: 1px solid var(--bg-slate-500);
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
.api-key-tag {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background: var(--bg-slate-300);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.tag-text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
leading-trim: both;
|
||||||
|
text-edge: cap;
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: -0.05px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-created-by {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-last-used-at {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
line-height: 18px; /* 128.571% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
font-variant-numeric: lining-nums tabular-nums stacked-fractions
|
||||||
|
slashed-zero;
|
||||||
|
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-expires-in {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
color: var(--bg-amber-400);
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background: var(--bg-amber-400);
|
||||||
|
box-shadow: 0px 0px 6px 0px var(--bg-amber-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
color: var(--bg-cherry-400);
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background: var(--bg-cherry-400);
|
||||||
|
box-shadow: 0px 0px 6px 0px var(--bg-cherry-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pagination-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> a {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-variant-numeric: lining-nums tabular-nums slashed-zero;
|
||||||
|
font-feature-settings: 'dlig' on, 'salt' on, 'case' on, 'cpsp' on;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pagination-item-active {
|
||||||
|
background-color: var(--bg-robin-500);
|
||||||
|
> a {
|
||||||
|
color: var(--bg-ink-500) !important;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-info-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
background-color: lightslategray;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-email {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0px 12px;
|
||||||
|
background: var(--bg-ink-200);
|
||||||
|
|
||||||
|
font-family: 'Space Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.ant-modal-header {
|
||||||
|
background: none;
|
||||||
|
border-bottom: 1px solid var(--bg-slate-500);
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-close-x {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-footer {
|
||||||
|
padding: 16px;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-access-role {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
|
||||||
|
&.ant-radio-button-wrapper-checked {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--bg-slate-400, #1d212d);
|
||||||
|
border-color: var(--bg-slate-400, #1d212d);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--bg-slate-400, #1d212d);
|
||||||
|
border-color: var(--bg-slate-400, #1d212d);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: var(--bg-slate-400, #1d212d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--bg-slate-400, #1d212d);
|
||||||
|
border-color: var(--bg-slate-400, #1d212d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background: var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--bg-slate-400, #1d212d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.role {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-api-key-modal {
|
||||||
|
width: calc(100% - 30px) !important; /* Adjust the 20px as needed */
|
||||||
|
max-width: 384px;
|
||||||
|
.ant-modal-content {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
.ant-modal-header {
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
padding: 0px 16px 28px 16px;
|
||||||
|
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-input {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-color-picker-trigger {
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
.ant-color-picker-color-block {
|
||||||
|
border-radius: 50px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.ant-color-picker-color-block-inner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 16px 16px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-slate-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-cherry-500);
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn:hover {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
background: var(--bg-cherry-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expiration-selector {
|
||||||
|
.ant-select-selector {
|
||||||
|
border: 1px solid var(--bg-slate-400) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.newAPIKeyDetails {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyable-text {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0px 12px;
|
||||||
|
background: var(--bg-ink-200, #23262e);
|
||||||
|
|
||||||
|
.copy-key-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.api-key-container {
|
||||||
|
.api-key-content {
|
||||||
|
.title {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row {
|
||||||
|
.ant-table-cell {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.ant-table-cell {
|
||||||
|
background: var(--bg-vanilla-200) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-render {
|
||||||
|
border: 1px solid var(--bg-vanilla-200);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
|
||||||
|
.ant-collapse {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.ant-collapse-header {
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-collapse-content {
|
||||||
|
border-top: 1px solid var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-with-action {
|
||||||
|
.api-key-title {
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-value {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-key-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-details {
|
||||||
|
border-top: 1px solid var(--bg-vanilla-200);
|
||||||
|
.api-key-tag {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
.tag-text {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-created-by {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-last-used-at {
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-api-key-modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
border: 1px solid var(--bg-vanilla-200);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
|
||||||
|
.ant-modal-header {
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-input {
|
||||||
|
.ant-input {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-footer {
|
||||||
|
.cancel-btn {
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-info-container {
|
||||||
|
.user-email {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bg-vanilla-200);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.ant-modal-header {
|
||||||
|
background: none;
|
||||||
|
border-bottom: 1px solid var(--bg-vanilla-200);
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-key-access-role {
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
&.ant-radio-button-wrapper-checked {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
border-color: var(--bg-vanilla-300);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
border-color: var(--bg-vanilla-300);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
border-color: var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
border: 1px solid var(--bg-vanilla-300);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyable-text {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
}
|
99
frontend/src/container/APIKeys/APIKeys.test.tsx
Normal file
99
frontend/src/container/APIKeys/APIKeys.test.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import {
|
||||||
|
createAPIKeyResponse,
|
||||||
|
getAPIKeysResponse,
|
||||||
|
} from 'mocks-server/__mockdata__/apiKeys';
|
||||||
|
import { server } from 'mocks-server/server';
|
||||||
|
import { rest } from 'msw';
|
||||||
|
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||||
|
|
||||||
|
import APIKeys from './APIKeys';
|
||||||
|
|
||||||
|
const apiKeysURL = 'http://localhost/api/v1/pats';
|
||||||
|
|
||||||
|
describe('APIKeys component', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
server.use(
|
||||||
|
rest.get(apiKeysURL, (req, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json(getAPIKeysResponse)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<APIKeys />);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders APIKeys component without crashing', () => {
|
||||||
|
expect(screen.getByText('API Keys')).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText('Create and manage access keys for the SigNoz API'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('render list of API Keys', async () => {
|
||||||
|
server.use(
|
||||||
|
rest.get(apiKeysURL, (req, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json(getAPIKeysResponse)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('No Expiry Token')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('1-5 of 18 API Keys')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens add new key modal on button click', async () => {
|
||||||
|
fireEvent.click(screen.getByText('New Key'));
|
||||||
|
await waitFor(() => {
|
||||||
|
const createNewKeyBtn = screen.getByRole('button', {
|
||||||
|
name: /Create new key/i,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createNewKeyBtn).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes add new key modal on cancel button click', async () => {
|
||||||
|
fireEvent.click(screen.getByText('New Key'));
|
||||||
|
|
||||||
|
const createNewKeyBtn = screen.getByRole('button', {
|
||||||
|
name: /Create new key/i,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(createNewKeyBtn).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.getByText('Cancel'));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(createNewKeyBtn).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new key on form submission', async () => {
|
||||||
|
server.use(
|
||||||
|
rest.post(apiKeysURL, (req, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json(createAPIKeyResponse)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('New Key'));
|
||||||
|
|
||||||
|
const createNewKeyBtn = screen.getByRole('button', {
|
||||||
|
name: /Create new key/i,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(createNewKeyBtn).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
const inputElement = screen.getByPlaceholderText('Enter Key Name');
|
||||||
|
fireEvent.change(inputElement, { target: { value: 'Top Secret' } });
|
||||||
|
fireEvent.click(screen.getByTestId('create-form-admin-role-btn'));
|
||||||
|
fireEvent.click(createNewKeyBtn);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
844
frontend/src/container/APIKeys/APIKeys.tsx
Normal file
844
frontend/src/container/APIKeys/APIKeys.tsx
Normal file
@ -0,0 +1,844 @@
|
|||||||
|
import './APIKeys.styles.scss';
|
||||||
|
|
||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Col,
|
||||||
|
Collapse,
|
||||||
|
Flex,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
Radio,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Table,
|
||||||
|
TableProps,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||||
|
import { CollapseProps } from 'antd/lib';
|
||||||
|
import createAPIKeyApi from 'api/APIKeys/createAPIKey';
|
||||||
|
import deleteAPIKeyApi from 'api/APIKeys/deleteAPIKey';
|
||||||
|
import updateAPIKeyApi from 'api/APIKeys/updateAPIKey';
|
||||||
|
import axios, { AxiosError } from 'axios';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import { useGetAllAPIKeys } from 'hooks/APIKeys/useGetAllAPIKeys';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import {
|
||||||
|
CalendarClock,
|
||||||
|
Check,
|
||||||
|
ClipboardEdit,
|
||||||
|
Contact2,
|
||||||
|
Copy,
|
||||||
|
Eye,
|
||||||
|
Minus,
|
||||||
|
PenLine,
|
||||||
|
Plus,
|
||||||
|
Search,
|
||||||
|
Trash2,
|
||||||
|
View,
|
||||||
|
X,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { ChangeEvent, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useMutation } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useCopyToClipboard } from 'react-use';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { APIKeyProps } from 'types/api/pat/types';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
import { USER_ROLES } from 'types/roles';
|
||||||
|
|
||||||
|
export const showErrorNotification = (
|
||||||
|
notifications: NotificationInstance,
|
||||||
|
err: Error,
|
||||||
|
): void => {
|
||||||
|
notifications.error({
|
||||||
|
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
type ExpiryOption = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EXPIRATION_WITHIN_SEVEN_DAYS = 7;
|
||||||
|
|
||||||
|
const API_KEY_EXPIRY_OPTIONS: ExpiryOption[] = [
|
||||||
|
{ value: '1', label: '1 day' },
|
||||||
|
{ value: '7', label: '1 week' },
|
||||||
|
{ value: '30', label: '1 month' },
|
||||||
|
{ value: '90', label: '3 months' },
|
||||||
|
{ value: '365', label: '1 year' },
|
||||||
|
{ value: '0', label: 'No Expiry' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function APIKeys(): JSX.Element {
|
||||||
|
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||||
|
const [showNewAPIKeyDetails, setShowNewAPIKeyDetails] = useState(false);
|
||||||
|
const [, handleCopyToClipboard] = useCopyToClipboard();
|
||||||
|
|
||||||
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||||
|
const [activeAPIKey, setActiveAPIKey] = useState<APIKeyProps | null>();
|
||||||
|
|
||||||
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
|
const [dataSource, setDataSource] = useState<APIKeyProps[]>([]);
|
||||||
|
const { t } = useTranslation(['apiKeys']);
|
||||||
|
|
||||||
|
const [editForm] = Form.useForm();
|
||||||
|
const [createForm] = Form.useForm();
|
||||||
|
|
||||||
|
const handleFormReset = (): void => {
|
||||||
|
editForm.resetFields();
|
||||||
|
createForm.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideDeleteViewModal = (): void => {
|
||||||
|
handleFormReset();
|
||||||
|
setActiveAPIKey(null);
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDeleteModal = (apiKey: APIKeyProps): void => {
|
||||||
|
setActiveAPIKey(apiKey);
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideEditViewModal = (): void => {
|
||||||
|
handleFormReset();
|
||||||
|
setActiveAPIKey(null);
|
||||||
|
setIsEditModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideAddViewModal = (): void => {
|
||||||
|
handleFormReset();
|
||||||
|
setShowNewAPIKeyDetails(false);
|
||||||
|
setActiveAPIKey(null);
|
||||||
|
setIsAddModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showEditModal = (apiKey: APIKeyProps): void => {
|
||||||
|
handleFormReset();
|
||||||
|
setActiveAPIKey(apiKey);
|
||||||
|
|
||||||
|
editForm.setFieldsValue({
|
||||||
|
name: apiKey.name,
|
||||||
|
role: apiKey.role || USER_ROLES.VIEWER,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsEditModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showAddModal = (): void => {
|
||||||
|
setActiveAPIKey(null);
|
||||||
|
setIsAddModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = (): void => {
|
||||||
|
setActiveAPIKey(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: APIKeys,
|
||||||
|
isLoading,
|
||||||
|
isRefetching,
|
||||||
|
refetch: refetchAPIKeys,
|
||||||
|
error,
|
||||||
|
isError,
|
||||||
|
} = useGetAllAPIKeys();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActiveAPIKey(APIKeys?.data.data[0]);
|
||||||
|
}, [APIKeys]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDataSource(APIKeys?.data.data || []);
|
||||||
|
}, [APIKeys?.data.data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isError) {
|
||||||
|
showErrorNotification(notifications, error as AxiosError);
|
||||||
|
}
|
||||||
|
}, [error, isError, notifications]);
|
||||||
|
|
||||||
|
const handleSearch = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setSearchValue(e.target.value);
|
||||||
|
const filteredData = APIKeys?.data?.data?.filter(
|
||||||
|
(key: APIKeyProps) =>
|
||||||
|
key &&
|
||||||
|
key.name &&
|
||||||
|
key.name.toLowerCase().includes(e.target.value.toLowerCase()),
|
||||||
|
);
|
||||||
|
setDataSource(filteredData || []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSearch = (): void => {
|
||||||
|
setSearchValue('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mutate: createAPIKey, isLoading: isLoadingCreateAPIKey } = useMutation(
|
||||||
|
createAPIKeyApi,
|
||||||
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
|
setShowNewAPIKeyDetails(true);
|
||||||
|
setActiveAPIKey(data.payload);
|
||||||
|
|
||||||
|
refetchAPIKeys();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showErrorNotification(notifications, error as AxiosError);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutate: updateAPIKey, isLoading: isLoadingUpdateAPIKey } = useMutation(
|
||||||
|
updateAPIKeyApi,
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
refetchAPIKeys();
|
||||||
|
setIsEditModalOpen(false);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showErrorNotification(notifications, error as AxiosError);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutate: deleteAPIKey, isLoading: isDeleteingAPIKey } = useMutation(
|
||||||
|
deleteAPIKeyApi,
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
refetchAPIKeys();
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showErrorNotification(notifications, error as AxiosError);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDeleteHandler = (): void => {
|
||||||
|
clearSearch();
|
||||||
|
|
||||||
|
if (activeAPIKey) {
|
||||||
|
deleteAPIKey(activeAPIKey.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUpdateApiKey = (): void => {
|
||||||
|
editForm
|
||||||
|
.validateFields()
|
||||||
|
.then((values) => {
|
||||||
|
if (activeAPIKey) {
|
||||||
|
updateAPIKey({
|
||||||
|
id: activeAPIKey.id,
|
||||||
|
data: {
|
||||||
|
name: values.name,
|
||||||
|
role: values.role,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((errorInfo) => {
|
||||||
|
console.error('error info', errorInfo);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreateAPIKey = (): void => {
|
||||||
|
createForm
|
||||||
|
.validateFields()
|
||||||
|
.then((values) => {
|
||||||
|
if (user) {
|
||||||
|
createAPIKey({
|
||||||
|
name: values.name,
|
||||||
|
expiresInDays: parseInt(values.expiration, 10),
|
||||||
|
role: values.role,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((errorInfo) => {
|
||||||
|
console.error('error info', errorInfo);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopyKey = (text: string): void => {
|
||||||
|
handleCopyToClipboard(text);
|
||||||
|
notifications.success({
|
||||||
|
message: 'Copied to clipboard',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormattedTime = (epochTime: number): string => {
|
||||||
|
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
};
|
||||||
|
const formattedTime = new Date(epochTime * 1000).toLocaleTimeString(
|
||||||
|
'en-US',
|
||||||
|
timeOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedDate = new Date(epochTime * 1000).toLocaleDateString(
|
||||||
|
'en-US',
|
||||||
|
dateOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return `${formattedDate} ${formattedTime}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopyClose = (): void => {
|
||||||
|
if (activeAPIKey) {
|
||||||
|
handleCopyKey(activeAPIKey?.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideAddViewModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDateDifference = (
|
||||||
|
createdTimestamp: number,
|
||||||
|
expiryTimestamp: number,
|
||||||
|
): number => {
|
||||||
|
const differenceInSeconds = Math.abs(expiryTimestamp - createdTimestamp);
|
||||||
|
|
||||||
|
// Convert seconds to days
|
||||||
|
return differenceInSeconds / (60 * 60 * 24);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: TableProps<APIKeyProps>['columns'] = [
|
||||||
|
{
|
||||||
|
title: 'API Key',
|
||||||
|
key: 'api-key',
|
||||||
|
render: (APIKey: APIKeyProps): JSX.Element => {
|
||||||
|
const formattedDateAndTime =
|
||||||
|
APIKey && APIKey?.lastUsed && APIKey?.lastUsed !== 0
|
||||||
|
? getFormattedTime(APIKey?.lastUsed)
|
||||||
|
: 'Never';
|
||||||
|
|
||||||
|
const createdOn = getFormattedTime(APIKey.createdAt);
|
||||||
|
|
||||||
|
const expiresIn =
|
||||||
|
APIKey.expiresAt === 0
|
||||||
|
? Number.POSITIVE_INFINITY
|
||||||
|
: getDateDifference(APIKey?.createdAt, APIKey?.expiresAt);
|
||||||
|
|
||||||
|
const expiresOn =
|
||||||
|
!APIKey.expiresAt || APIKey.expiresAt === 0
|
||||||
|
? 'No Expiry'
|
||||||
|
: getFormattedTime(APIKey.expiresAt);
|
||||||
|
|
||||||
|
const updatedOn =
|
||||||
|
!APIKey.updatedAt || APIKey.updatedAt === 0
|
||||||
|
? null
|
||||||
|
: getFormattedTime(APIKey?.updatedAt);
|
||||||
|
|
||||||
|
const items: CollapseProps['items'] = [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: (
|
||||||
|
<div className="title-with-action">
|
||||||
|
<div className="api-key-data">
|
||||||
|
<div className="api-key-title">
|
||||||
|
<Typography.Text>{APIKey?.name}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="api-key-value">
|
||||||
|
<Typography.Text>
|
||||||
|
{APIKey?.token.substring(0, 2)}********
|
||||||
|
{APIKey?.token.substring(APIKey.token.length - 2).trim()}
|
||||||
|
</Typography.Text>
|
||||||
|
|
||||||
|
<Copy
|
||||||
|
className="copy-key-btn"
|
||||||
|
size={12}
|
||||||
|
onClick={(e): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
handleCopyKey(APIKey.token);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{APIKey.role === USER_ROLES.ADMIN && (
|
||||||
|
<Tooltip title={USER_ROLES.ADMIN}>
|
||||||
|
<Contact2 size={14} color={Color.BG_ROBIN_400} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{APIKey.role === USER_ROLES.EDITOR && (
|
||||||
|
<Tooltip title={USER_ROLES.EDITOR}>
|
||||||
|
<ClipboardEdit size={14} color={Color.BG_ROBIN_400} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{APIKey.role === USER_ROLES.VIEWER && (
|
||||||
|
<Tooltip title={USER_ROLES.VIEWER}>
|
||||||
|
<View size={14} color={Color.BG_ROBIN_400} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!APIKey.role && (
|
||||||
|
<Tooltip title={USER_ROLES.ADMIN}>
|
||||||
|
<Contact2 size={14} color={Color.BG_ROBIN_400} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="action-btn">
|
||||||
|
<Button
|
||||||
|
className="periscope-btn ghost"
|
||||||
|
icon={<PenLine size={14} />}
|
||||||
|
onClick={(e): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
showEditModal(APIKey);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="periscope-btn ghost"
|
||||||
|
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
|
||||||
|
onClick={(e): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
showDeleteModal(APIKey);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
children: (
|
||||||
|
<div className="api-key-info-container">
|
||||||
|
{APIKey?.createdByUser && (
|
||||||
|
<Row>
|
||||||
|
<Col span={6}> Creator </Col>
|
||||||
|
<Col span={12} className="user-info">
|
||||||
|
<Avatar className="user-avatar" size="small">
|
||||||
|
{APIKey?.createdByUser?.name?.substring(0, 1)}
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
|
<Typography.Text>{APIKey.createdByUser?.name}</Typography.Text>
|
||||||
|
|
||||||
|
<div className="user-email">{APIKey.createdByUser?.email}</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
<Row>
|
||||||
|
<Col span={6}> Created on </Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Typography.Text>{createdOn}</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{updatedOn && (
|
||||||
|
<Row>
|
||||||
|
<Col span={6}> Updated on </Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Typography.Text>{updatedOn}</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col span={6}> Expires on </Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Typography.Text>{expiresOn}</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="column-render">
|
||||||
|
<Collapse items={items} />
|
||||||
|
|
||||||
|
<div className="api-key-details">
|
||||||
|
<div className="api-key-last-used-at">
|
||||||
|
<CalendarClock size={14} />
|
||||||
|
Last used <Minus size={12} />
|
||||||
|
<Typography.Text>{formattedDateAndTime}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
{expiresIn <= EXPIRATION_WITHIN_SEVEN_DAYS && (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'api-key-expires-in',
|
||||||
|
expiresIn <= 3 ? 'danger' : 'warning',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="dot" /> Expires in {expiresIn} Days
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="api-key-container">
|
||||||
|
<div className="api-key-content">
|
||||||
|
<header>
|
||||||
|
<Typography.Title className="title">API Keys</Typography.Title>
|
||||||
|
<Typography.Text className="subtitle">
|
||||||
|
Create and manage access keys for the SigNoz API
|
||||||
|
</Typography.Text>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="api-keys-search-add-new">
|
||||||
|
<Input
|
||||||
|
placeholder="Search for keys..."
|
||||||
|
prefix={<Search size={12} color={Color.BG_VANILLA_400} />}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={handleSearch}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="add-new-api-key-btn"
|
||||||
|
type="primary"
|
||||||
|
onClick={showAddModal}
|
||||||
|
>
|
||||||
|
<Plus size={14} /> New Key
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
loading={isLoading || isRefetching}
|
||||||
|
showHeader={false}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 5,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
showTotal: (total: number, range: number[]): string =>
|
||||||
|
`${range[0]}-${range[1]} of ${total} API Keys`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Delete Key Modal */}
|
||||||
|
<Modal
|
||||||
|
className="delete-api-key-modal"
|
||||||
|
title={<span className="title">Delete key</span>}
|
||||||
|
open={isDeleteModalOpen}
|
||||||
|
closable
|
||||||
|
afterClose={handleModalClose}
|
||||||
|
onCancel={hideDeleteViewModal}
|
||||||
|
destroyOnClose
|
||||||
|
footer={[
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
onClick={hideDeleteViewModal}
|
||||||
|
className="cancel-btn"
|
||||||
|
icon={<X size={16} />}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="submit"
|
||||||
|
icon={<Trash2 size={16} />}
|
||||||
|
loading={isDeleteingAPIKey}
|
||||||
|
onClick={onDeleteHandler}
|
||||||
|
className="delete-btn"
|
||||||
|
>
|
||||||
|
Delete key
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Typography.Text className="delete-text">
|
||||||
|
{t('delete_confirm_message', {
|
||||||
|
keyName: activeAPIKey?.name,
|
||||||
|
})}
|
||||||
|
</Typography.Text>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* Edit Key Modal */}
|
||||||
|
<Modal
|
||||||
|
className="api-key-modal"
|
||||||
|
title="Edit key"
|
||||||
|
open={isEditModalOpen}
|
||||||
|
key="edit-api-key-modal"
|
||||||
|
afterClose={handleModalClose}
|
||||||
|
// closable
|
||||||
|
onCancel={hideEditViewModal}
|
||||||
|
destroyOnClose
|
||||||
|
footer={[
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
onClick={hideEditViewModal}
|
||||||
|
className="periscope-btn cancel-btn"
|
||||||
|
icon={<X size={16} />}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
className="periscope-btn primary"
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
loading={isLoadingUpdateAPIKey}
|
||||||
|
icon={<Check size={14} />}
|
||||||
|
onClick={onUpdateApiKey}
|
||||||
|
>
|
||||||
|
Update key
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="edit-api-key-form"
|
||||||
|
key={activeAPIKey?.id}
|
||||||
|
form={editForm}
|
||||||
|
layout="vertical"
|
||||||
|
autoComplete="off"
|
||||||
|
initialValues={{
|
||||||
|
name: activeAPIKey?.name,
|
||||||
|
role: activeAPIKey?.role,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="Name"
|
||||||
|
rules={[{ required: true }, { type: 'string', min: 6 }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter Key Name" autoFocus />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="role" label="Role">
|
||||||
|
<Flex vertical gap="middle">
|
||||||
|
<Radio.Group
|
||||||
|
buttonStyle="solid"
|
||||||
|
className="api-key-access-role"
|
||||||
|
defaultValue={activeAPIKey?.role}
|
||||||
|
>
|
||||||
|
<Radio.Button value={USER_ROLES.ADMIN} className={cx('tab')}>
|
||||||
|
<div className="role">
|
||||||
|
<Contact2 size={14} /> Admin
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button value={USER_ROLES.EDITOR} className={cx('tab')}>
|
||||||
|
<div className="role">
|
||||||
|
<ClipboardEdit size={14} /> Editor
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button value={USER_ROLES.VIEWER} className={cx('tab')}>
|
||||||
|
<div className="role">
|
||||||
|
<Eye size={14} /> Viewer
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* Create New Key Modal */}
|
||||||
|
<Modal
|
||||||
|
className="api-key-modal"
|
||||||
|
title="Create new key"
|
||||||
|
open={isAddModalOpen}
|
||||||
|
key="create-api-key-modal"
|
||||||
|
closable
|
||||||
|
onCancel={hideAddViewModal}
|
||||||
|
destroyOnClose
|
||||||
|
footer={
|
||||||
|
showNewAPIKeyDetails
|
||||||
|
? [
|
||||||
|
<Button
|
||||||
|
key="copy-key-close"
|
||||||
|
className="periscope-btn primary"
|
||||||
|
data-testid="copy-key-close-btn"
|
||||||
|
type="primary"
|
||||||
|
onClick={handleCopyClose}
|
||||||
|
icon={<Check size={12} />}
|
||||||
|
>
|
||||||
|
Copy key and close
|
||||||
|
</Button>,
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
onClick={hideAddViewModal}
|
||||||
|
className="periscope-btn cancel-btn"
|
||||||
|
icon={<X size={16} />}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
className="periscope-btn primary"
|
||||||
|
test-id="create-new-key"
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
icon={<Check size={14} />}
|
||||||
|
loading={isLoadingCreateAPIKey}
|
||||||
|
onClick={onCreateAPIKey}
|
||||||
|
>
|
||||||
|
Create new key
|
||||||
|
</Button>,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!showNewAPIKeyDetails && (
|
||||||
|
<Form
|
||||||
|
key="createForm"
|
||||||
|
name="create-api-key-form"
|
||||||
|
form={createForm}
|
||||||
|
initialValues={{
|
||||||
|
role: USER_ROLES.ADMIN,
|
||||||
|
expiration: '1',
|
||||||
|
name: '',
|
||||||
|
}}
|
||||||
|
layout="vertical"
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="Name"
|
||||||
|
rules={[{ required: true }, { type: 'string', min: 6 }]}
|
||||||
|
validateTrigger="onFinish"
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter Key Name" autoFocus />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="role" label="Role">
|
||||||
|
<Flex vertical gap="middle">
|
||||||
|
<Radio.Group
|
||||||
|
buttonStyle="solid"
|
||||||
|
className="api-key-access-role"
|
||||||
|
defaultValue={USER_ROLES.ADMIN}
|
||||||
|
>
|
||||||
|
<Radio.Button value={USER_ROLES.ADMIN} className={cx('tab')}>
|
||||||
|
<div className="role" data-testid="create-form-admin-role-btn">
|
||||||
|
<Contact2 size={14} /> Admin
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button value={USER_ROLES.EDITOR} className="tab">
|
||||||
|
<div className="role" data-testid="create-form-editor-role-btn">
|
||||||
|
<ClipboardEdit size={14} /> Editor
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button value={USER_ROLES.VIEWER} className="tab">
|
||||||
|
<div className="role" data-testid="create-form-viewer-role-btn">
|
||||||
|
<Eye size={14} /> Viewer
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="expiration" label="Expiration">
|
||||||
|
<Select
|
||||||
|
className="expiration-selector"
|
||||||
|
placeholder="Expiration"
|
||||||
|
options={API_KEY_EXPIRY_OPTIONS}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showNewAPIKeyDetails && (
|
||||||
|
<div className="api-key-info-container">
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>Key</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
<span className="copyable-text">
|
||||||
|
<Typography.Text>
|
||||||
|
{activeAPIKey?.token.substring(0, 2)}****************
|
||||||
|
{activeAPIKey?.token.substring(activeAPIKey.token.length - 2).trim()}
|
||||||
|
</Typography.Text>
|
||||||
|
|
||||||
|
<Copy
|
||||||
|
className="copy-key-btn"
|
||||||
|
size={12}
|
||||||
|
onClick={(): void => {
|
||||||
|
if (activeAPIKey) {
|
||||||
|
handleCopyKey(activeAPIKey.token);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>Name</Col>
|
||||||
|
<Col span={16}>{activeAPIKey?.name}</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>Role</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
{activeAPIKey?.role === USER_ROLES.ADMIN && (
|
||||||
|
<div className="role">
|
||||||
|
<Contact2 size={14} /> Admin
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeAPIKey?.role === USER_ROLES.EDITOR && (
|
||||||
|
<div className="role">
|
||||||
|
{' '}
|
||||||
|
<ClipboardEdit size={14} /> Editor
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeAPIKey?.role === USER_ROLES.VIEWER && (
|
||||||
|
<div className="role">
|
||||||
|
{' '}
|
||||||
|
<View size={14} /> Viewer
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>Creator</Col>
|
||||||
|
|
||||||
|
<Col span={16} className="user-info">
|
||||||
|
<Avatar className="user-avatar" size="small">
|
||||||
|
{activeAPIKey?.createdByUser?.name?.substring(0, 1)}
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
|
<Typography.Text>{activeAPIKey?.createdByUser?.name}</Typography.Text>
|
||||||
|
|
||||||
|
<div className="user-email">{activeAPIKey?.createdByUser?.email}</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{activeAPIKey?.createdAt && (
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>Created on</Col>
|
||||||
|
<Col span={16}>{getFormattedTime(activeAPIKey?.createdAt)}</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeAPIKey?.expiresAt !== 0 && activeAPIKey?.expiresAt && (
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>Expires on</Col>
|
||||||
|
<Col span={16}>{getFormattedTime(activeAPIKey?.expiresAt)}</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeAPIKey?.expiresAt === 0 && (
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>Expires on</Col>
|
||||||
|
<Col span={16}> No Expiry </Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default APIKeys;
|
@ -121,6 +121,7 @@ export const routesToSkip = [
|
|||||||
ROUTES.ALL_DASHBOARD,
|
ROUTES.ALL_DASHBOARD,
|
||||||
ROUTES.ORG_SETTINGS,
|
ROUTES.ORG_SETTINGS,
|
||||||
ROUTES.INGESTION_SETTINGS,
|
ROUTES.INGESTION_SETTINGS,
|
||||||
|
ROUTES.API_KEYS,
|
||||||
ROUTES.ERROR_DETAIL,
|
ROUTES.ERROR_DETAIL,
|
||||||
ROUTES.LOGS_PIPELINES,
|
ROUTES.LOGS_PIPELINES,
|
||||||
ROUTES.BILLING,
|
ROUTES.BILLING,
|
||||||
|
13
frontend/src/hooks/APIKeys/useGetAllAPIKeys.ts
Normal file
13
frontend/src/hooks/APIKeys/useGetAllAPIKeys.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { getAllAPIKeys } from 'api/APIKeys/getAllAPIKeys';
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import { useQuery, UseQueryResult } from 'react-query';
|
||||||
|
import { AllAPIKeyProps } from 'types/api/pat/types';
|
||||||
|
|
||||||
|
export const useGetAllAPIKeys = (): UseQueryResult<
|
||||||
|
AxiosResponse<AllAPIKeyProps>,
|
||||||
|
AxiosError
|
||||||
|
> =>
|
||||||
|
useQuery<AxiosResponse<AllAPIKeyProps>, AxiosError>({
|
||||||
|
queryKey: ['APIKeys'],
|
||||||
|
queryFn: () => getAllAPIKeys(),
|
||||||
|
});
|
541
frontend/src/mocks-server/__mockdata__/apiKeys.ts
Normal file
541
frontend/src/mocks-server/__mockdata__/apiKeys.ts
Normal file
@ -0,0 +1,541 @@
|
|||||||
|
const createdByEmail = 'mando@signoz.io';
|
||||||
|
|
||||||
|
export const getAPIKeysResponse = {
|
||||||
|
status: 'success',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '26',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: 'T2DuASwpuUx3wlYraFl5r7N9G1ikBhzGuy2ihcIKDMs=',
|
||||||
|
role: 'ADMIN',
|
||||||
|
name: '1 Day Old',
|
||||||
|
createdAt: 1708010258,
|
||||||
|
expiresAt: 1708096658,
|
||||||
|
updatedAt: 1708010258,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '24',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
token: 'EteVs77BA4FFLJD/TsFE9c+CLX4kXVmlx+0GGK7dpXY=',
|
||||||
|
role: 'ADMIN',
|
||||||
|
name: '1 year expiry - updated',
|
||||||
|
createdAt: 1708008146,
|
||||||
|
expiresAt: 1739544146,
|
||||||
|
updatedAt: 1708008239,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: 'mandalorian',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '25',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
token: '1udrUFmRI6gdb8r/hLabS7zRlgfMQlUw/tz9sac82pE=',
|
||||||
|
role: 'ADMIN',
|
||||||
|
name: 'No Expiry Token',
|
||||||
|
createdAt: 1708008178,
|
||||||
|
expiresAt: 0,
|
||||||
|
updatedAt: 1708008190,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: 'mandalorian',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '22',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
token: 'gtqKF7g7avoe+Yu2+WhyDDLQSr6IsVaR5xpby2XhLAY=',
|
||||||
|
role: 'VIEWER',
|
||||||
|
name: 'No Expiry',
|
||||||
|
createdAt: 1708007395,
|
||||||
|
expiresAt: 0,
|
||||||
|
updatedAt: 1708007936,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: 'mandalorian',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '23',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
token: 'GM/TqEID8N4ynlvQHK38ITEvRAcn5XkJZpmd11xT3OQ=',
|
||||||
|
role: 'VIEWER',
|
||||||
|
name: 'No Expiry - 2',
|
||||||
|
createdAt: 1708007685,
|
||||||
|
expiresAt: 0,
|
||||||
|
updatedAt: 1708007786,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: 'mandalorian',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '19',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
token: 'Oj75e6Zr7JmjFcWIo0UK/Nl06RdC2BKOr/QVHoBA0gM=',
|
||||||
|
role: 'ADMIN',
|
||||||
|
name: '7 Days',
|
||||||
|
createdAt: 1708003326,
|
||||||
|
expiresAt: 1708608126,
|
||||||
|
updatedAt: 1708007380,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: 'mandalorian',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '20',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
token: 'T+sNdYe6I74ya/9mEKqB3UTrFm8+jwI0DiirqEx3bsM=',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: '1 month',
|
||||||
|
createdAt: 1708004012,
|
||||||
|
expiresAt: 1710596012,
|
||||||
|
updatedAt: 1708005206,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: 'mandalorian',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '21',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
token: 'JWw26FuymeHq+fsfFcb+2+Ls/MdokmeXxXdZisuaVeI=',
|
||||||
|
role: 'ADMIN',
|
||||||
|
name: '3 Months',
|
||||||
|
createdAt: 1708004755,
|
||||||
|
expiresAt: 1715780755,
|
||||||
|
updatedAt: 1708005197,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: 'mandalorian',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '17',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: '2zDrYNr+IWXUyA14+afVvO6GI9dcHfEsOYxjA9mrprg=',
|
||||||
|
role: 'ADMIN',
|
||||||
|
name: 'New No Expiry',
|
||||||
|
createdAt: 1708000444,
|
||||||
|
expiresAt: 0,
|
||||||
|
updatedAt: 1708000444,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '14',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: 'Q+/+UB2OrDPcS9b0+5A1dDXYmWHz0abbVVidF48QCso=',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: 'Editor Token for user 1',
|
||||||
|
createdAt: 1707997720,
|
||||||
|
expiresAt: 1708170520,
|
||||||
|
updatedAt: 1707997720,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '13',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: '/X3OEaSOLrrJImvzIB3g5WGg+5831X89fZZQT1JaxvQ=',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: 'Editor Token for user 2',
|
||||||
|
createdAt: 1707997603,
|
||||||
|
expiresAt: 1708170403,
|
||||||
|
updatedAt: 1707997603,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '12',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: 'bTs+Q6waIiP4KJ8L5N58EQonuapWMXsfEra/cmMwmbE=',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: 'Editor Token for user 3',
|
||||||
|
createdAt: 1707997539,
|
||||||
|
expiresAt: 1708170339,
|
||||||
|
updatedAt: 1707997539,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '11',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: 'YaEqQHrH8KOnYFllor/8Tq653TgxPU1Z7ZDzY3+ETmI=',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: 'Editor Token for user',
|
||||||
|
createdAt: 1707997537,
|
||||||
|
expiresAt: 1708170337,
|
||||||
|
updatedAt: 1707997537,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '10',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: 'Hg/QpMU9VQyqIuzSh9ND2454IN5uOHzVkv7owEtBcPo=',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: 'test123',
|
||||||
|
createdAt: 1707997288,
|
||||||
|
expiresAt: 1708083688,
|
||||||
|
updatedAt: 1707997288,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '9',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: 'M5gMsccDthPTibquB7kR7ZSEI76y4endOxZPESZ9/po=',
|
||||||
|
role: 'VIEWER',
|
||||||
|
name: 'Viewer Token for user',
|
||||||
|
createdAt: 1707996747,
|
||||||
|
expiresAt: 1708255947,
|
||||||
|
updatedAt: 1707996747,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '8',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: 'H8NVlOD09IcMgQ/rzfVucb+4+jEcqZ4ZRx6n7QztMSc=',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: 'Editor Token for user',
|
||||||
|
createdAt: 1707996736,
|
||||||
|
expiresAt: 1708169536,
|
||||||
|
updatedAt: 1707996736,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
createdAt: 0,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: true,
|
||||||
|
},
|
||||||
|
token: 'z24SswLmNlPVUgb1j6rfc2u4Kb4xSUolwb11cI8kbrs=',
|
||||||
|
role: 'ADMIN',
|
||||||
|
name: 'Admin Token for user',
|
||||||
|
createdAt: 1707996719,
|
||||||
|
expiresAt: 0,
|
||||||
|
updatedAt: 1707996719,
|
||||||
|
lastUsed: 0,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
createdByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
updatedByUser: {
|
||||||
|
id: '001',
|
||||||
|
name: 'Mando',
|
||||||
|
email: createdByEmail,
|
||||||
|
createdAt: 1707974098,
|
||||||
|
profilePictureURL: '',
|
||||||
|
notFound: false,
|
||||||
|
},
|
||||||
|
token: 'SWuNSF08EB6+VN05312QaAsPum2wkqIm+ujiWZKnm2Q=',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: 'Editor Token',
|
||||||
|
createdAt: 1707992270,
|
||||||
|
expiresAt: 1708165070,
|
||||||
|
updatedAt: 1707995424,
|
||||||
|
lastUsed: 1707992517,
|
||||||
|
revoked: false,
|
||||||
|
updatedByUserId: 'mandalorian',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createAPIKeyResponse = {
|
||||||
|
status: 'success',
|
||||||
|
data: {
|
||||||
|
id: '57',
|
||||||
|
userId: 'mandalorian',
|
||||||
|
token: 'pQ5kiHjcbQ2FbKlS14LQjA2RzXEBi/KvBfM7BRSwltI=',
|
||||||
|
name: 'test1233',
|
||||||
|
createdAt: 1707818550,
|
||||||
|
expiresAt: 0,
|
||||||
|
},
|
||||||
|
};
|
@ -1,16 +1,22 @@
|
|||||||
import { RouteTabProps } from 'components/RouteTab/types';
|
import { RouteTabProps } from 'components/RouteTab/types';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import AlertChannels from 'container/AllAlertChannels';
|
import AlertChannels from 'container/AllAlertChannels';
|
||||||
|
import APIKeys from 'container/APIKeys/APIKeys';
|
||||||
import GeneralSettings from 'container/GeneralSettings';
|
import GeneralSettings from 'container/GeneralSettings';
|
||||||
import GeneralSettingsCloud from 'container/GeneralSettingsCloud';
|
import GeneralSettingsCloud from 'container/GeneralSettingsCloud';
|
||||||
import IngestionSettings from 'container/IngestionSettings/IngestionSettings';
|
import IngestionSettings from 'container/IngestionSettings/IngestionSettings';
|
||||||
import OrganizationSettings from 'container/OrganizationSettings';
|
import OrganizationSettings from 'container/OrganizationSettings';
|
||||||
import { TFunction } from 'i18next';
|
import { TFunction } from 'i18next';
|
||||||
|
import { Backpack, BellDot, Building, Cpu, KeySquare } from 'lucide-react';
|
||||||
|
|
||||||
export const organizationSettings = (t: TFunction): RouteTabProps['routes'] => [
|
export const organizationSettings = (t: TFunction): RouteTabProps['routes'] => [
|
||||||
{
|
{
|
||||||
Component: OrganizationSettings,
|
Component: OrganizationSettings,
|
||||||
name: t('routes:organization_settings').toString(),
|
name: (
|
||||||
|
<div className="periscope-tab">
|
||||||
|
<Building size={16} /> {t('routes:organization_settings').toString()}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
route: ROUTES.ORG_SETTINGS,
|
route: ROUTES.ORG_SETTINGS,
|
||||||
key: ROUTES.ORG_SETTINGS,
|
key: ROUTES.ORG_SETTINGS,
|
||||||
},
|
},
|
||||||
@ -19,7 +25,11 @@ export const organizationSettings = (t: TFunction): RouteTabProps['routes'] => [
|
|||||||
export const alertChannels = (t: TFunction): RouteTabProps['routes'] => [
|
export const alertChannels = (t: TFunction): RouteTabProps['routes'] => [
|
||||||
{
|
{
|
||||||
Component: AlertChannels,
|
Component: AlertChannels,
|
||||||
name: t('routes:alert_channels').toString(),
|
name: (
|
||||||
|
<div className="periscope-tab">
|
||||||
|
<BellDot size={16} /> {t('routes:alert_channels').toString()}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
route: ROUTES.ALL_CHANNELS,
|
route: ROUTES.ALL_CHANNELS,
|
||||||
key: ROUTES.ALL_CHANNELS,
|
key: ROUTES.ALL_CHANNELS,
|
||||||
},
|
},
|
||||||
@ -28,7 +38,11 @@ export const alertChannels = (t: TFunction): RouteTabProps['routes'] => [
|
|||||||
export const ingestionSettings = (t: TFunction): RouteTabProps['routes'] => [
|
export const ingestionSettings = (t: TFunction): RouteTabProps['routes'] => [
|
||||||
{
|
{
|
||||||
Component: IngestionSettings,
|
Component: IngestionSettings,
|
||||||
name: t('routes:ingestion_settings').toString(),
|
name: (
|
||||||
|
<div className="periscope-tab">
|
||||||
|
<Cpu size={16} /> {t('routes:ingestion_settings').toString()}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
route: ROUTES.INGESTION_SETTINGS,
|
route: ROUTES.INGESTION_SETTINGS,
|
||||||
key: ROUTES.INGESTION_SETTINGS,
|
key: ROUTES.INGESTION_SETTINGS,
|
||||||
},
|
},
|
||||||
@ -37,7 +51,11 @@ export const ingestionSettings = (t: TFunction): RouteTabProps['routes'] => [
|
|||||||
export const generalSettings = (t: TFunction): RouteTabProps['routes'] => [
|
export const generalSettings = (t: TFunction): RouteTabProps['routes'] => [
|
||||||
{
|
{
|
||||||
Component: GeneralSettings,
|
Component: GeneralSettings,
|
||||||
name: t('routes:general').toString(),
|
name: (
|
||||||
|
<div className="periscope-tab">
|
||||||
|
<Backpack size={16} /> {t('routes:general').toString()}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
route: ROUTES.SETTINGS,
|
route: ROUTES.SETTINGS,
|
||||||
key: ROUTES.SETTINGS,
|
key: ROUTES.SETTINGS,
|
||||||
},
|
},
|
||||||
@ -46,8 +64,25 @@ export const generalSettings = (t: TFunction): RouteTabProps['routes'] => [
|
|||||||
export const generalSettingsCloud = (t: TFunction): RouteTabProps['routes'] => [
|
export const generalSettingsCloud = (t: TFunction): RouteTabProps['routes'] => [
|
||||||
{
|
{
|
||||||
Component: GeneralSettingsCloud,
|
Component: GeneralSettingsCloud,
|
||||||
name: t('routes:general').toString(),
|
name: (
|
||||||
|
<div className="periscope-tab">
|
||||||
|
<Backpack size={16} /> {t('routes:general').toString()}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
route: ROUTES.SETTINGS,
|
route: ROUTES.SETTINGS,
|
||||||
key: ROUTES.SETTINGS,
|
key: ROUTES.SETTINGS,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const apiKeys = (t: TFunction): RouteTabProps['routes'] => [
|
||||||
|
{
|
||||||
|
Component: APIKeys,
|
||||||
|
name: (
|
||||||
|
<div className="periscope-tab">
|
||||||
|
<KeySquare size={16} /> {t('routes:api_keys').toString()}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
route: ROUTES.API_KEYS,
|
||||||
|
key: ROUTES.API_KEYS,
|
||||||
|
},
|
||||||
|
];
|
@ -19,7 +19,8 @@ function SettingsPage(): JSX.Element {
|
|||||||
);
|
);
|
||||||
const { t } = useTranslation(['routes']);
|
const { t } = useTranslation(['routes']);
|
||||||
|
|
||||||
const routes = useMemo(() => getRoutes(isCurrentOrgSettings, t), [
|
const routes = useMemo(() => getRoutes(role, isCurrentOrgSettings, t), [
|
||||||
|
role,
|
||||||
isCurrentOrgSettings,
|
isCurrentOrgSettings,
|
||||||
t,
|
t,
|
||||||
]);
|
]);
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
import { RouteTabProps } from 'components/RouteTab/types';
|
import { RouteTabProps } from 'components/RouteTab/types';
|
||||||
import { TFunction } from 'i18next';
|
import { TFunction } from 'i18next';
|
||||||
import { isCloudUser } from 'utils/app';
|
import { ROLES, USER_ROLES } from 'types/roles';
|
||||||
|
import { isCloudUser, isEECloudUser } from 'utils/app';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alertChannels,
|
alertChannels,
|
||||||
|
apiKeys,
|
||||||
generalSettings,
|
generalSettings,
|
||||||
ingestionSettings,
|
ingestionSettings,
|
||||||
organizationSettings,
|
organizationSettings,
|
||||||
} from './config';
|
} from './config';
|
||||||
|
|
||||||
export const getRoutes = (
|
export const getRoutes = (
|
||||||
|
userRole: ROLES | null,
|
||||||
isCurrentOrgSettings: boolean,
|
isCurrentOrgSettings: boolean,
|
||||||
t: TFunction,
|
t: TFunction,
|
||||||
): RouteTabProps['routes'] => {
|
): RouteTabProps['routes'] => {
|
||||||
const settings = [];
|
const settings = [];
|
||||||
|
|
||||||
|
settings.push(...generalSettings(t));
|
||||||
|
|
||||||
if (isCurrentOrgSettings) {
|
if (isCurrentOrgSettings) {
|
||||||
settings.push(...organizationSettings(t));
|
settings.push(...organizationSettings(t));
|
||||||
}
|
}
|
||||||
@ -26,7 +31,9 @@ export const getRoutes = (
|
|||||||
settings.push(...alertChannels(t));
|
settings.push(...alertChannels(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.push(...generalSettings(t));
|
if ((isCloudUser() || isEECloudUser()) && userRole === USER_ROLES.ADMIN) {
|
||||||
|
settings.push(...apiKeys(t));
|
||||||
|
}
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
};
|
};
|
||||||
|
@ -24,6 +24,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #4566d6;
|
||||||
|
box-shadow: 0 2px 0 rgba(62, 86, 245, 0.09);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.periscope-tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
|
53
frontend/src/types/api/pat/types.ts
Normal file
53
frontend/src/types/api/pat/types.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
export interface User {
|
||||||
|
createdAt?: number;
|
||||||
|
email?: string;
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
notFound?: boolean;
|
||||||
|
profilePictureURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface APIKeyProps {
|
||||||
|
name: string;
|
||||||
|
expiresAt: number;
|
||||||
|
role: string;
|
||||||
|
token: string;
|
||||||
|
id: string;
|
||||||
|
createdAt: number;
|
||||||
|
createdByUser?: User;
|
||||||
|
updatedAt?: number;
|
||||||
|
updatedByUser?: User;
|
||||||
|
lastUsed?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateAPIKeyProps {
|
||||||
|
name: string;
|
||||||
|
expiresInDays: number;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AllAPIKeyProps {
|
||||||
|
status: string;
|
||||||
|
data: APIKeyProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateAPIKeyProp {
|
||||||
|
data: APIKeyProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteAPIKeyPayloadProps {
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateAPIKeyProps {
|
||||||
|
id: string;
|
||||||
|
data: {
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = {
|
||||||
|
status: string;
|
||||||
|
data: string;
|
||||||
|
};
|
@ -88,6 +88,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
|||||||
SOMETHING_WENT_WRONG: ['ADMIN', 'EDITOR', 'VIEWER'],
|
SOMETHING_WENT_WRONG: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
LOGS_SAVE_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
LOGS_SAVE_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
TRACES_SAVE_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
TRACES_SAVE_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
|
API_KEYS: ['ADMIN'],
|
||||||
LOGS_BASE: [],
|
LOGS_BASE: [],
|
||||||
OLD_LOGS_EXPLORER: [],
|
OLD_LOGS_EXPLORER: [],
|
||||||
SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
|
@ -23,6 +23,11 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"baseUrl": "./src",
|
"baseUrl": "./src",
|
||||||
|
"paths": {
|
||||||
|
"@constants/*": [
|
||||||
|
"/container/OnboardingContainer/constants/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user