mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 06:49:00 +08:00
parent
6ccdc5296e
commit
59f32884d2
@ -1,8 +1,10 @@
|
||||
{
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"already_logged_in": "Already Logged In",
|
||||
"success": "Success",
|
||||
"cancel": "Cancel",
|
||||
"share": "Share",
|
||||
"save": "Save",
|
||||
"edit": "Edit"
|
||||
"edit": "Edit",
|
||||
"logged_in": "Logged In"
|
||||
}
|
||||
|
13
frontend/public/locales/en-GB/organizationsettings.json
Normal file
13
frontend/public/locales/en-GB/organizationsettings.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"display_name": "Display Name",
|
||||
"signoz": "SigNoz",
|
||||
"email_address": "Email address",
|
||||
"name_optional": "Name (optional)",
|
||||
"role": "Role",
|
||||
"email_placeholder": "john@signoz.io",
|
||||
"name_placeholder": "John",
|
||||
"add_another_team_member": "Add another team member",
|
||||
"invite_team_members": "Invite team members",
|
||||
"invite_members": "Invite Members",
|
||||
"pending_invites": "Pending Invites"
|
||||
}
|
6
frontend/public/locales/en-GB/routes.json
Normal file
6
frontend/public/locales/en-GB/routes.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"my_settings": "My Settings"
|
||||
}
|
5
frontend/public/locales/en-GB/settings.json
Normal file
5
frontend/public/locales/en-GB/settings.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"current_password": "Current Password",
|
||||
"new_password": "New Password",
|
||||
"change_password": "Change Password"
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
{
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"already_logged_in": "Already Logged In",
|
||||
"success": "Success",
|
||||
"cancel": "Cancel",
|
||||
"share": "Share",
|
||||
"save": "Save",
|
||||
"edit": "Edit"
|
||||
"edit": "Edit",
|
||||
"logged_in": "Logged In"
|
||||
}
|
||||
|
13
frontend/public/locales/en/organizationsettings.json
Normal file
13
frontend/public/locales/en/organizationsettings.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"display_name": "Display Name",
|
||||
"signoz": "SigNoz",
|
||||
"email_address": "Email address",
|
||||
"name_optional": "Name (optional)",
|
||||
"role": "Role",
|
||||
"email_placeholder": "john@signoz.io",
|
||||
"name_placeholder": "John",
|
||||
"add_another_team_member": "Add another team member",
|
||||
"invite_team_members": "Invite team members",
|
||||
"invite_members": "Invite Members",
|
||||
"pending_invites": "Pending Invites"
|
||||
}
|
6
frontend/public/locales/en/routes.json
Normal file
6
frontend/public/locales/en/routes.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"my_settings": "My Settings"
|
||||
}
|
6
frontend/public/locales/en/settings.json
Normal file
6
frontend/public/locales/en/settings.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"current_password": "Current Password",
|
||||
"new_password": "New Password",
|
||||
"change_password": "Change Password",
|
||||
"input_password": "input password"
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
<svg width="2130" height="600" viewBox="0 0 2130 600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M765.131 338.922L805.631 334.984C808.068 348.578 812.99 358.562 820.396 364.938C827.896 371.312 837.974 374.5 850.631 374.5C864.037 374.5 874.115 371.688 880.865 366.062C887.709 360.344 891.131 353.688 891.131 346.094C891.131 341.219 889.677 337.094 886.771 333.719C883.959 330.25 878.99 327.25 871.865 324.719C866.99 323.031 855.881 320.031 838.537 315.719C816.224 310.188 800.568 303.391 791.568 295.328C778.912 283.984 772.584 270.156 772.584 253.844C772.584 243.344 775.537 233.547 781.443 224.453C787.443 215.266 796.021 208.281 807.177 203.5C818.427 198.719 831.974 196.328 847.818 196.328C873.693 196.328 893.146 202 906.177 213.344C919.302 224.688 926.193 239.828 926.849 258.766L885.224 260.594C883.443 250 879.599 242.406 873.693 237.812C867.881 233.125 859.115 230.781 847.396 230.781C835.302 230.781 825.834 233.266 818.99 238.234C814.584 241.422 812.381 245.688 812.381 251.031C812.381 255.906 814.443 260.078 818.568 263.547C823.818 267.953 836.568 272.547 856.818 277.328C877.068 282.109 892.021 287.078 901.677 292.234C911.427 297.297 919.021 304.281 924.459 313.188C929.99 322 932.756 332.922 932.756 345.953C932.756 357.766 929.474 368.828 922.912 379.141C916.349 389.453 907.068 397.141 895.068 402.203C883.068 407.172 868.115 409.656 850.209 409.656C824.146 409.656 804.131 403.656 790.162 391.656C776.193 379.562 767.849 361.984 765.131 338.922ZM967.49 236.406V199.844H1007.01V236.406H967.49ZM967.49 406V256.656H1007.01V406H967.49ZM1043.99 415.844L1089.13 421.328C1089.88 426.578 1091.61 430.188 1094.33 432.156C1098.08 434.969 1103.99 436.375 1112.05 436.375C1122.36 436.375 1130.1 434.828 1135.26 431.734C1138.72 429.672 1141.35 426.344 1143.13 421.75C1144.35 418.469 1144.96 412.422 1144.96 403.609V381.812C1133.15 397.938 1118.24 406 1100.24 406C1080.18 406 1064.29 397.516 1052.57 380.547C1043.38 367.141 1038.79 350.453 1038.79 330.484C1038.79 305.453 1044.79 286.328 1056.79 273.109C1068.88 259.891 1083.88 253.281 1101.79 253.281C1120.26 253.281 1135.49 261.391 1147.49 277.609V256.656H1184.47V390.672C1184.47 408.297 1183.02 421.469 1180.11 430.188C1177.21 438.906 1173.13 445.75 1167.88 450.719C1162.63 455.688 1155.6 459.578 1146.79 462.391C1138.07 465.203 1127.01 466.609 1113.6 466.609C1088.29 466.609 1070.33 462.25 1059.74 453.531C1049.15 444.906 1043.85 433.938 1043.85 420.625C1043.85 419.312 1043.9 417.719 1043.99 415.844ZM1079.29 328.234C1079.29 344.078 1082.33 355.703 1088.43 363.109C1094.61 370.422 1102.21 374.078 1111.21 374.078C1120.86 374.078 1129.02 370.328 1135.68 362.828C1142.33 355.234 1145.66 344.031 1145.66 329.219C1145.66 313.75 1142.47 302.266 1136.1 294.766C1129.72 287.266 1121.66 283.516 1111.91 283.516C1102.44 283.516 1094.61 287.219 1088.43 294.625C1082.33 301.938 1079.29 313.141 1079.29 328.234ZM1224.41 406V199.844H1264.91L1349.29 337.516V199.844H1387.96V406H1346.19L1263.08 271.562V406H1224.41ZM1422.69 329.219C1422.69 316.094 1425.93 303.391 1432.4 291.109C1438.86 278.828 1448.01 269.453 1459.82 262.984C1471.72 256.516 1484.99 253.281 1499.61 253.281C1522.21 253.281 1540.72 260.641 1555.16 275.359C1569.6 289.984 1576.82 308.5 1576.82 330.906C1576.82 353.5 1569.51 372.25 1554.88 387.156C1540.35 401.969 1522.02 409.375 1499.9 409.375C1486.21 409.375 1473.13 406.281 1460.66 400.094C1448.29 393.906 1438.86 384.859 1432.4 372.953C1425.93 360.953 1422.69 346.375 1422.69 329.219ZM1463.19 331.328C1463.19 346.141 1466.71 357.484 1473.74 365.359C1480.77 373.234 1489.44 377.172 1499.76 377.172C1510.07 377.172 1518.69 373.234 1525.63 365.359C1532.66 357.484 1536.18 346.047 1536.18 331.047C1536.18 316.422 1532.66 305.172 1525.63 297.297C1518.69 289.422 1510.07 285.484 1499.76 285.484C1489.44 285.484 1480.77 289.422 1473.74 297.297C1466.71 305.172 1463.19 316.516 1463.19 331.328ZM1592.01 406V375.203L1647.97 310.938C1657.16 300.438 1663.96 292.984 1668.36 288.578C1663.77 288.859 1657.72 289.047 1650.22 289.141L1597.49 289.422V256.656H1720.96V284.641L1663.86 350.453L1643.76 372.25C1654.72 371.594 1661.52 371.266 1664.15 371.266H1725.32V406H1592.01Z" fill="white"/>
|
||||
<path opacity="0.9" d="M296.795 599.499C131.909 599.499 0 468.361 0 304.437C0 142.153 131.909 9.37476 296.795 9.37476H483.116C544.124 9.37476 591.941 58.5518 591.941 117.564V304.437C591.941 468.361 460.032 599.499 296.795 599.499Z" fill="#F25733"/>
|
||||
<path d="M294.467 176.702C171.309 176.702 101.936 280.076 99.0428 284.476C91.91 295.315 91.91 309.334 99.0481 320.181C101.936 324.574 171.309 427.947 294.467 427.947C417.624 427.947 486.997 324.574 489.89 320.173C497.023 309.334 497.024 295.315 489.885 284.468C486.997 280.076 417.624 176.702 294.467 176.702ZM116.09 308.659C113.557 304.811 113.557 299.839 116.09 295.99C118.416 292.45 167.808 218.911 256.115 201.271C216.099 216.928 187.625 256.307 187.625 302.325C187.625 348.342 216.099 387.721 256.115 403.378C167.808 385.737 118.416 312.198 116.09 308.659ZM245.232 302.324C245.232 308.059 240.646 312.706 234.989 312.706C229.331 312.706 224.746 308.059 224.746 302.324C224.746 263.357 256.022 231.655 294.466 231.655C300.123 231.655 304.709 236.303 304.709 242.037C304.709 247.772 300.123 252.419 294.466 252.419C267.317 252.419 245.232 274.806 245.232 302.324ZM294.467 327.565C280.736 327.565 269.565 316.243 269.565 302.325C269.565 288.407 280.736 277.084 294.467 277.084C308.199 277.084 319.369 288.406 319.369 302.325C319.369 316.243 308.199 327.565 294.467 327.565ZM472.843 308.659C470.516 312.198 421.125 385.737 332.818 403.378C372.836 387.72 401.309 348.342 401.309 302.325C401.309 256.307 372.836 216.929 332.818 201.272C421.125 218.913 470.516 292.451 472.843 295.99C475.376 299.839 475.376 304.811 472.843 308.659Z" fill="#F9F2F9"/>
|
||||
</svg>
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.9" d="M20.0557 39.9666C8.91365 39.9666 0 31.2241 0 20.2958C0 9.47687 8.91365 0.625 20.0557 0.625H32.6462C36.7688 0.625 40 3.90347 40 7.83763V20.2958C40 31.2241 31.0863 39.9666 20.0557 39.9666Z" fill="#DD431F"/>
|
||||
<path d="M19.8991 11.7803C11.5768 11.7803 6.889 18.6718 6.69348 18.9652C6.21149 19.6878 6.21149 20.6224 6.69384 21.3455C6.889 21.6384 11.5768 28.5299 19.8991 28.5299C28.2214 28.5299 32.9092 21.6384 33.1047 21.345C33.5868 20.6224 33.5868 19.6878 33.1044 18.9647C32.9092 18.6718 28.2214 11.7803 19.8991 11.7803ZM7.84542 20.5774C7.67429 20.3208 7.67429 19.9894 7.84542 19.7328C8.00265 19.4968 11.3403 14.5942 17.3076 13.4182C14.6035 14.462 12.6793 17.0872 12.6793 20.1551C12.6793 23.2229 14.6035 25.8482 17.3076 26.892C11.3403 25.7159 8.00265 20.8133 7.84542 20.5774ZM16.5721 20.1551C16.5721 20.5374 16.2622 20.8472 15.8799 20.8472C15.4977 20.8472 15.1878 20.5374 15.1878 20.1551C15.1878 17.5573 17.3012 15.4438 19.8991 15.4438C20.2813 15.4438 20.5912 15.7536 20.5912 16.1359C20.5912 16.5183 20.2814 16.8281 19.8991 16.8281C18.0645 16.8281 16.5721 18.3205 16.5721 20.1551ZM19.8991 21.8378C18.9713 21.8378 18.2164 21.083 18.2164 20.1551C18.2164 19.2272 18.9713 18.4723 19.8991 18.4723C20.827 18.4723 21.5819 19.2272 21.5819 20.1551C21.5819 21.083 20.827 21.8378 19.8991 21.8378ZM31.9528 20.5774C31.7956 20.8133 28.458 25.7159 22.4907 26.892C25.1949 25.8481 27.1189 23.2229 27.1189 20.1551C27.1189 17.0873 25.1949 14.462 22.4907 13.4182C28.458 14.5943 31.7956 19.4968 31.9528 19.7328C32.124 19.9894 32.124 20.3208 31.9528 20.5774Z" fill="#F9F2F9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 1.6 KiB |
171
frontend/src/AppRoutes/Private.tsx
Normal file
171
frontend/src/AppRoutes/Private.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import { notification } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import loginApi from 'api/user/login';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { matchPath, Redirect } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { getInitialUserTokenRefreshToken } from 'store/utils';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { routePermission } from 'utils/permission';
|
||||
|
||||
import routes from './routes';
|
||||
import afterLogin from './utils';
|
||||
|
||||
function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
const mapRoutes = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
routes.map((e) => {
|
||||
const currentPath = matchPath(history.location.pathname, {
|
||||
path: e.path,
|
||||
});
|
||||
return [currentPath === null ? null : 'current', e];
|
||||
}),
|
||||
),
|
||||
[],
|
||||
);
|
||||
const { isUserFetching, isUserFetchingError } = useSelector<
|
||||
AppState,
|
||||
AppReducer
|
||||
>((state) => state.app);
|
||||
|
||||
const { t } = useTranslation(['common']);
|
||||
|
||||
const currentRoute = mapRoutes.get('current');
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const isLoggedIn = getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
useEffect(() => {
|
||||
(async (): Promise<void> => {
|
||||
try {
|
||||
console.log('asdasd');
|
||||
|
||||
if (currentRoute) {
|
||||
const { isPrivate, key } = currentRoute;
|
||||
|
||||
if (isPrivate) {
|
||||
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
||||
|
||||
if (isLoggedIn) {
|
||||
if (localStorageUserAuthToken && localStorageUserAuthToken.refreshJwt) {
|
||||
// localstorage token is present
|
||||
const { refreshJwt } = localStorageUserAuthToken;
|
||||
|
||||
// renew web access token
|
||||
const response = await loginApi({
|
||||
refreshToken: refreshJwt,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
const route = routePermission[key];
|
||||
|
||||
// get all resource and put it over redux
|
||||
const userResponse = await afterLogin(
|
||||
response.payload.userId,
|
||||
response.payload.accessJwt,
|
||||
response.payload.refreshJwt,
|
||||
);
|
||||
|
||||
if (
|
||||
userResponse &&
|
||||
route.find((e) => e === userResponse.payload.role) === undefined
|
||||
) {
|
||||
history.push(ROUTES.UN_AUTHORIZED);
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// user is not logged in
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
} else {
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
} else {
|
||||
// no need to fetch the user and make user fetching false
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (history.location.pathname === ROUTES.HOME_PAGE) {
|
||||
// routing to application page over root page
|
||||
if (isLoggedIn) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
} else {
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
if (!isLoggedIn) {
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// something went wrong
|
||||
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
||||
}
|
||||
})();
|
||||
// need to run over mount only
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dispatch, currentRoute, isLoggedIn]);
|
||||
|
||||
if (isUserFetchingError) {
|
||||
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
|
||||
}
|
||||
|
||||
if (isUserFetching) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
// NOTE: disabling this rule as there is no need to have div
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
interface PrivateRouteProps {
|
||||
children: React.ReactChild;
|
||||
}
|
||||
|
||||
export default PrivateRoute;
|
@ -1,42 +1,36 @@
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AppLayout from 'container/AppLayout';
|
||||
import history from 'lib/history';
|
||||
import React, { Suspense } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Redirect, Route, Router, Switch } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
|
||||
import PrivateRoute from './Private';
|
||||
import routes from './routes';
|
||||
|
||||
function App(): JSX.Element {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route key={`${path}`} exact={exact} path={path} component={component} />
|
||||
))}
|
||||
<Route
|
||||
path="/"
|
||||
exact
|
||||
render={(): JSX.Element =>
|
||||
isLoggedIn ? (
|
||||
<Redirect to={ROUTES.APPLICATION} />
|
||||
) : (
|
||||
<Redirect to={ROUTES.SIGN_UP} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
<PrivateRoute>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => {
|
||||
return (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</PrivateRoute>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ export const EditAlertChannelsAlerts = Loadable(
|
||||
);
|
||||
|
||||
export const AllAlertChannels = Loadable(
|
||||
() => import(/* webpackChunkName: "All Channels" */ 'pages/AllAlertChannels'),
|
||||
() => import(/* webpackChunkName: "All Channels" */ 'pages/Settings'),
|
||||
);
|
||||
|
||||
export const AllErrors = Loadable(
|
||||
@ -97,3 +97,30 @@ export const ErrorDetails = Loadable(
|
||||
export const StatusPage = Loadable(
|
||||
() => import(/* webpackChunkName: "All Status" */ 'pages/Status'),
|
||||
);
|
||||
|
||||
export const OrganizationSettings = Loadable(
|
||||
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
|
||||
);
|
||||
|
||||
export const MySettings = Loadable(
|
||||
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
|
||||
);
|
||||
|
||||
export const Login = Loadable(
|
||||
() => import(/* webpackChunkName: "Login" */ 'pages/Login'),
|
||||
);
|
||||
|
||||
export const UnAuthorized = Loadable(
|
||||
() => import(/* webpackChunkName: "UnAuthorized" */ 'pages/UnAuthorized'),
|
||||
);
|
||||
|
||||
export const PasswordReset = Loadable(
|
||||
() => import(/* webpackChunkName: "ResetPassword" */ 'pages/ResetPassword'),
|
||||
);
|
||||
|
||||
export const SomethingWentWrong = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "SomethingWentWrong" */ 'pages/SomethingWentWrong'
|
||||
),
|
||||
);
|
||||
|
@ -13,15 +13,21 @@ import {
|
||||
ErrorDetails,
|
||||
InstrumentationPage,
|
||||
ListAllALertsPage,
|
||||
Login,
|
||||
MySettings,
|
||||
NewDashboardPage,
|
||||
OrganizationSettings,
|
||||
PasswordReset,
|
||||
ServiceMapPage,
|
||||
ServiceMetricsPage,
|
||||
ServicesTablePage,
|
||||
SettingsPage,
|
||||
SignupPage,
|
||||
SomethingWentWrong,
|
||||
StatusPage,
|
||||
TraceDetail,
|
||||
TraceFilter,
|
||||
UnAuthorized,
|
||||
UsageExplorerPage,
|
||||
} from './pageComponents';
|
||||
|
||||
@ -30,114 +36,199 @@ const routes: AppRoutes[] = [
|
||||
component: SignupPage,
|
||||
path: ROUTES.SIGN_UP,
|
||||
exact: true,
|
||||
isPrivate: false,
|
||||
key: 'SIGN_UP',
|
||||
},
|
||||
{
|
||||
component: ServicesTablePage,
|
||||
path: ROUTES.APPLICATION,
|
||||
exact: true,
|
||||
isPrivate: true,
|
||||
key: 'APPLICATION',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SERVICE_METRICS,
|
||||
exact: true,
|
||||
component: ServiceMetricsPage,
|
||||
isPrivate: true,
|
||||
key: 'SERVICE_METRICS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SERVICE_MAP,
|
||||
component: ServiceMapPage,
|
||||
isPrivate: true,
|
||||
exact: true,
|
||||
key: 'SERVICE_MAP',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
component: TraceDetail,
|
||||
isPrivate: true,
|
||||
key: 'TRACE_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SETTINGS,
|
||||
exact: true,
|
||||
component: SettingsPage,
|
||||
isPrivate: true,
|
||||
key: 'SETTINGS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.USAGE_EXPLORER,
|
||||
exact: true,
|
||||
component: UsageExplorerPage,
|
||||
isPrivate: true,
|
||||
key: 'USAGE_EXPLORER',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INSTRUMENTATION,
|
||||
exact: true,
|
||||
component: InstrumentationPage,
|
||||
isPrivate: true,
|
||||
key: 'INSTRUMENTATION',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_DASHBOARD,
|
||||
exact: true,
|
||||
component: DashboardPage,
|
||||
isPrivate: true,
|
||||
key: 'ALL_DASHBOARD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.DASHBOARD,
|
||||
exact: true,
|
||||
component: NewDashboardPage,
|
||||
isPrivate: true,
|
||||
key: 'DASHBOARD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.DASHBOARD_WIDGET,
|
||||
exact: true,
|
||||
component: DashboardWidget,
|
||||
isPrivate: true,
|
||||
key: 'DASHBOARD_WIDGET',
|
||||
},
|
||||
{
|
||||
path: ROUTES.EDIT_ALERTS,
|
||||
exact: true,
|
||||
component: EditRulesPage,
|
||||
isPrivate: true,
|
||||
key: 'EDIT_ALERTS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.LIST_ALL_ALERT,
|
||||
exact: true,
|
||||
component: ListAllALertsPage,
|
||||
isPrivate: true,
|
||||
key: 'LIST_ALL_ALERT',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALERTS_NEW,
|
||||
exact: true,
|
||||
component: CreateNewAlerts,
|
||||
isPrivate: true,
|
||||
key: 'ALERTS_NEW',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE,
|
||||
exact: true,
|
||||
component: TraceFilter,
|
||||
isPrivate: true,
|
||||
key: 'TRACE',
|
||||
},
|
||||
{
|
||||
path: ROUTES.CHANNELS_NEW,
|
||||
exact: true,
|
||||
component: CreateAlertChannelAlerts,
|
||||
isPrivate: true,
|
||||
key: 'CHANNELS_NEW',
|
||||
},
|
||||
{
|
||||
path: ROUTES.CHANNELS_EDIT,
|
||||
exact: true,
|
||||
component: EditAlertChannelsAlerts,
|
||||
isPrivate: true,
|
||||
key: 'CHANNELS_EDIT',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_CHANNELS,
|
||||
exact: true,
|
||||
component: AllAlertChannels,
|
||||
isPrivate: true,
|
||||
key: 'ALL_CHANNELS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_ERROR,
|
||||
exact: true,
|
||||
isPrivate: true,
|
||||
component: AllErrors,
|
||||
key: 'ALL_ERROR',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ERROR_DETAIL,
|
||||
exact: true,
|
||||
component: ErrorDetails,
|
||||
isPrivate: true,
|
||||
key: 'ERROR_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.VERSION,
|
||||
exact: true,
|
||||
component: StatusPage,
|
||||
isPrivate: true,
|
||||
key: 'VERSION',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ORG_SETTINGS,
|
||||
exact: true,
|
||||
component: OrganizationSettings,
|
||||
isPrivate: true,
|
||||
key: 'ORG_SETTINGS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.MY_SETTINGS,
|
||||
exact: true,
|
||||
component: MySettings,
|
||||
isPrivate: true,
|
||||
key: 'MY_SETTINGS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGIN,
|
||||
exact: true,
|
||||
component: Login,
|
||||
isPrivate: false,
|
||||
key: 'LOGIN',
|
||||
},
|
||||
{
|
||||
path: ROUTES.UN_AUTHORIZED,
|
||||
exact: true,
|
||||
component: UnAuthorized,
|
||||
key: 'UN_AUTHORIZED',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.PASSWORD_RESET,
|
||||
exact: true,
|
||||
component: PasswordReset,
|
||||
key: 'PASSWORD_RESET',
|
||||
isPrivate: false,
|
||||
},
|
||||
{
|
||||
path: ROUTES.SOMETHING_WENT_WRONG,
|
||||
exact: true,
|
||||
component: SomethingWentWrong,
|
||||
key: 'SOMETHING_WENT_WRONG',
|
||||
isPrivate: false,
|
||||
},
|
||||
];
|
||||
|
||||
interface AppRoutes {
|
||||
export interface AppRoutes {
|
||||
component: RouteProps['component'];
|
||||
path: RouteProps['path'];
|
||||
exact: RouteProps['exact'];
|
||||
isPrivate?: boolean;
|
||||
isPrivate: boolean;
|
||||
key: keyof typeof ROUTES;
|
||||
}
|
||||
|
||||
export default routes;
|
||||
|
97
frontend/src/AppRoutes/utils.ts
Normal file
97
frontend/src/AppRoutes/utils.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { notification } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import getUserApi from 'api/user/getUser';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { t } from 'i18next';
|
||||
import history from 'lib/history';
|
||||
import store from 'store';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
LOGGED_IN,
|
||||
UPDATE_USER,
|
||||
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
|
||||
UPDATE_USER_IS_FETCH,
|
||||
} from 'types/actions/app';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getUser';
|
||||
|
||||
const afterLogin = async (
|
||||
userId: string,
|
||||
authToken: string,
|
||||
refreshToken: string,
|
||||
): Promise<SuccessResponse<PayloadProps> | undefined> => {
|
||||
setLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN, authToken);
|
||||
setLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN, refreshToken);
|
||||
|
||||
store.dispatch<AppActions>({
|
||||
type: UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
|
||||
payload: {
|
||||
accessJwt: authToken,
|
||||
refreshJwt: refreshToken,
|
||||
},
|
||||
});
|
||||
|
||||
const [getUserResponse] = await Promise.all([
|
||||
getUserApi({
|
||||
userId,
|
||||
token: authToken,
|
||||
}),
|
||||
]);
|
||||
|
||||
if (getUserResponse.statusCode === 200) {
|
||||
store.dispatch<AppActions>({
|
||||
type: LOGGED_IN,
|
||||
payload: {
|
||||
isLoggedIn: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { payload } = getUserResponse;
|
||||
|
||||
store.dispatch<AppActions>({
|
||||
type: UPDATE_USER,
|
||||
payload: {
|
||||
ROLE: payload.role,
|
||||
email: payload.email,
|
||||
name: payload.name,
|
||||
orgName: payload.organization,
|
||||
profilePictureURL: payload.profilePictureURL,
|
||||
userId: payload.id,
|
||||
orgId: payload.orgId,
|
||||
},
|
||||
});
|
||||
|
||||
const isLoggedInLocalStorage = getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN);
|
||||
|
||||
if (isLoggedInLocalStorage === null) {
|
||||
setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true');
|
||||
}
|
||||
|
||||
store.dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
|
||||
return getUserResponse;
|
||||
}
|
||||
|
||||
store.dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
|
||||
notification.error({
|
||||
message: getUserResponse.error || t('something_went_wrong'),
|
||||
});
|
||||
|
||||
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export default afterLogin;
|
@ -12,7 +12,7 @@ i18n
|
||||
.use(initReactI18next)
|
||||
// init i18next
|
||||
.init({
|
||||
debug: true,
|
||||
debug: false,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,14 +1,80 @@
|
||||
import axios from 'axios';
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import loginApi from 'api/user/login';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import store from 'store';
|
||||
|
||||
import apiV1, { apiV2 } from './apiV1';
|
||||
import { Logout } from './utils';
|
||||
|
||||
export default axios.create({
|
||||
const interceptorsResponse = (
|
||||
value: AxiosResponse<any>,
|
||||
): Promise<AxiosResponse<any>> => Promise.resolve(value);
|
||||
|
||||
const interceptorsRequestResponse = (
|
||||
value: AxiosRequestConfig,
|
||||
): AxiosRequestConfig => {
|
||||
const token =
|
||||
store.getState().app.user?.accessJwt ||
|
||||
getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) ||
|
||||
'';
|
||||
|
||||
value.headers.Authorization = token ? `Bearer ${token}` : '';
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const interceptorRejected = async (
|
||||
value: AxiosResponse<any>,
|
||||
): Promise<AxiosResponse<any>> => {
|
||||
if (axios.isAxiosError(value) && value.response) {
|
||||
const { response } = value;
|
||||
console.log(response);
|
||||
// reject the refresh token error
|
||||
if (response.status === 401 && response.config.url !== '/login') {
|
||||
const response = await loginApi({
|
||||
refreshToken: store.getState().app.user?.accessJwt,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
await afterLogin(
|
||||
response.payload.userId,
|
||||
response.payload.accessJwt,
|
||||
response.payload.refreshJwt,
|
||||
);
|
||||
} else {
|
||||
Logout();
|
||||
}
|
||||
}
|
||||
|
||||
// when refresh token is expired
|
||||
if (response.status === 401 && response.config.url === '/login') {
|
||||
Logout();
|
||||
}
|
||||
}
|
||||
return Promise.reject(value);
|
||||
};
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: `${ENVIRONMENT.baseURL}${apiV1}`,
|
||||
});
|
||||
|
||||
instance.interceptors.response.use(interceptorsResponse, interceptorRejected);
|
||||
instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
|
||||
export const AxiosAlertManagerInstance = axios.create({
|
||||
baseURL: `${ENVIRONMENT.baseURL}${apiV2}`,
|
||||
});
|
||||
|
||||
AxiosAlertManagerInstance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
interceptorRejected,
|
||||
);
|
||||
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestResponse);
|
||||
|
||||
export { apiV1 };
|
||||
export default instance;
|
||||
|
26
frontend/src/api/user/changeMyPassword.ts
Normal file
26
frontend/src/api/user/changeMyPassword.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, Props } from 'types/api/user/changeMyPassword';
|
||||
|
||||
const changeMyPassword = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/changePassword/${props.userId}`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default changeMyPassword;
|
24
frontend/src/api/user/deleteInvite.ts
Normal file
24
frontend/src/api/user/deleteInvite.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/user/deleteInvite';
|
||||
|
||||
const deleteInvite = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.delete(`/invite/${props.email}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteInvite;
|
24
frontend/src/api/user/deleteUser.ts
Normal file
24
frontend/src/api/user/deleteUser.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/user/deleteUser';
|
||||
|
||||
const deleteUser = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.delete(`/user/${props.userId}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteUser;
|
28
frontend/src/api/user/editOrg.ts
Normal file
28
frontend/src/api/user/editOrg.ts
Normal file
@ -0,0 +1,28 @@
|
||||
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/user/editOrg';
|
||||
|
||||
const editOrg = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/org/${props.orgId}`, {
|
||||
name: props.name,
|
||||
isAnonymous: props.isAnonymous,
|
||||
hasOptedUpdates: props.hasOptedUpdates,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default editOrg;
|
26
frontend/src/api/user/editUser.ts
Normal file
26
frontend/src/api/user/editUser.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, Props } from 'types/api/user/editUser';
|
||||
|
||||
const editUser = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/user/${props.userId}`, {
|
||||
Name: props.name,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default editUser;
|
24
frontend/src/api/user/getInviteDetails.ts
Normal file
24
frontend/src/api/user/getInviteDetails.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/user/getInviteDetails';
|
||||
|
||||
const getInviteDetails = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/invite/${props.inviteId}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getInviteDetails;
|
24
frontend/src/api/user/getOrgUser.ts
Normal file
24
frontend/src/api/user/getOrgUser.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/user/getOrgMembers';
|
||||
|
||||
const getOrgUser = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/orgUsers/${props.orgId}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getOrgUser;
|
28
frontend/src/api/user/getOrganization.ts
Normal file
28
frontend/src/api/user/getOrganization.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getOrganization';
|
||||
|
||||
const getOrganization = async (
|
||||
token?: string,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/org`, {
|
||||
headers: {
|
||||
Authorization: `bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getOrganization;
|
24
frontend/src/api/user/getPendingInvites.ts
Normal file
24
frontend/src/api/user/getPendingInvites.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 } from 'types/api/user/getPendingInvites';
|
||||
|
||||
const getPendingInvites = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/invite`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getPendingInvites;
|
24
frontend/src/api/user/getResetPasswordToken.ts
Normal file
24
frontend/src/api/user/getResetPasswordToken.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/user/getResetPasswordToken';
|
||||
|
||||
const getResetPasswordToken = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/getResetPasswordToken/${props.userId}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getResetPasswordToken;
|
28
frontend/src/api/user/getRoles.ts
Normal file
28
frontend/src/api/user/getRoles.ts
Normal file
@ -0,0 +1,28 @@
|
||||
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/user/getUserRole';
|
||||
|
||||
const getRoles = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/rbac/role/${props.userId}`, {
|
||||
headers: {
|
||||
Authorization: `bearer ${props.token}`,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getRoles;
|
28
frontend/src/api/user/getUser.ts
Normal file
28
frontend/src/api/user/getUser.ts
Normal file
@ -0,0 +1,28 @@
|
||||
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/user/getUser';
|
||||
|
||||
const getUser = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/user/${props.userId}`, {
|
||||
headers: {
|
||||
Authorization: `bearer ${props.token}`,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getUser;
|
26
frontend/src/api/user/login.ts
Normal file
26
frontend/src/api/user/login.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, Props } from 'types/api/user/login';
|
||||
|
||||
const login = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/login`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.statusText,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default login;
|
26
frontend/src/api/user/resetPassword.ts
Normal file
26
frontend/src/api/user/resetPassword.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, Props } from 'types/api/user/resetPassword';
|
||||
|
||||
const resetPassword = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/resetPassword`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.statusText,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default resetPassword;
|
@ -2,13 +2,13 @@ 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/user/setUserPreference';
|
||||
import { PayloadProps, Props } from 'types/api/user/setInvite';
|
||||
|
||||
const setPreference = async (
|
||||
const sendInvite = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/userPreferences`, {
|
||||
const response = await axios.post(`/invite`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
@ -23,4 +23,4 @@ const setPreference = async (
|
||||
}
|
||||
};
|
||||
|
||||
export default setPreference;
|
||||
export default sendInvite;
|
@ -6,9 +6,9 @@ import { Props } from 'types/api/user/signup';
|
||||
|
||||
const signup = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<undefined> | ErrorResponse> => {
|
||||
): Promise<SuccessResponse<string> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/user`, {
|
||||
const response = await axios.post(`/register`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
|
26
frontend/src/api/user/updateRole.ts
Normal file
26
frontend/src/api/user/updateRole.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, Props } from 'types/api/user/updateRole';
|
||||
|
||||
const updateRole = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/rbac/role/${props.userId}`, {
|
||||
group_name: props.group_name,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default updateRole;
|
29
frontend/src/api/utils.ts
Normal file
29
frontend/src/api/utils.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import deleteLocalStorageKey from 'api/browser/localstorage/remove';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import store from 'store';
|
||||
import { LOGGED_IN, UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
||||
|
||||
export const Logout = (): void => {
|
||||
deleteLocalStorageKey(LOCALSTORAGE.REFRESH_AUTH_TOKEN);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN);
|
||||
|
||||
store.dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
|
||||
store.dispatch({
|
||||
type: LOGGED_IN,
|
||||
payload: {
|
||||
isLoggedIn: false,
|
||||
},
|
||||
});
|
||||
|
||||
// navigate to login
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
470
frontend/src/assets/SomethingWentWrong.tsx
Normal file
470
frontend/src/assets/SomethingWentWrong.tsx
Normal file
@ -0,0 +1,470 @@
|
||||
import React from 'react';
|
||||
|
||||
function SomethingWentWrong(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
width="400"
|
||||
height="323"
|
||||
viewBox="0 0 400 323"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_2231_35430)">
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M205.764 322.654C305.597 322.654 386.528 250.425 386.528 161.327C386.528 72.2285 305.597 0 205.764 0C105.931 0 25 72.2285 25 161.327C25 250.425 105.931 322.654 205.764 322.654Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M66.6512 254.181C103.462 254.181 133.302 249.556 133.302 243.85C133.302 238.145 103.462 233.519 66.6512 233.519C29.8407 233.519 0 238.145 0 243.85C0 249.556 29.8407 254.181 66.6512 254.181Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M98.6859 303.398C113.003 303.398 124.61 301.001 124.61 298.043C124.61 295.085 113.003 292.688 98.6859 292.688C84.3684 292.688 72.7617 295.085 72.7617 298.043C72.7617 301.001 84.3684 303.398 98.6859 303.398Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M275.827 282.795C290.144 282.795 301.751 280.397 301.751 277.439C301.751 274.482 290.144 272.084 275.827 272.084C261.509 272.084 249.902 274.482 249.902 277.439C249.902 280.397 261.509 282.795 275.827 282.795Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M187.86 278.3C211.143 278.3 230.017 275.102 230.017 271.158C230.017 267.214 211.143 264.016 187.86 264.016C164.577 264.016 145.703 267.214 145.703 271.158C145.703 275.102 164.577 278.3 187.86 278.3Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M312.074 249.578C360.636 249.578 400.003 243.412 400.003 235.806C400.003 228.2 360.636 222.034 312.074 222.034C263.512 222.034 224.145 228.2 224.145 235.806C224.145 243.412 263.512 249.578 312.074 249.578Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
d="M18.2261 211.769C16.251 215.611 15.3285 219.908 15.5524 224.222C15.7764 228.537 17.1387 232.715 19.501 236.333C20.2761 237.495 20.9891 238.332 21.5587 238.553C24.2906 239.626 29.7622 242.265 29.7622 242.265L42.9374 237.088C42.9374 237.088 45.0765 225.773 47.0993 216.058C48.4788 209.432 49.8118 203.553 50.3582 202.523C51.7222 199.985 50.3582 172.351 50.3582 172.351C50.3582 172.351 37.183 171.468 33.5598 181.624L31.9749 184.725L18.2261 211.769Z"
|
||||
fill="#A8A8A8"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M35.7173 214.822C43.727 224.002 47.1061 216.062 47.1061 216.062C48.4856 209.436 49.8186 203.557 50.365 202.527C51.729 199.988 50.365 172.355 50.365 172.355C50.365 172.355 37.1898 171.472 33.5666 181.628L31.9817 184.728C30.9664 192.854 29.9628 208.215 35.7173 214.822Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M37.4766 240.409L83.9774 235.038L92.2739 180.446L86.0002 166.268C83.3496 160.229 79.6793 154.692 75.15 149.899C71.9918 146.571 68.1748 143.37 64.1603 141.762C63.6117 141.541 63.0516 141.349 62.4824 141.188C61.8281 140.989 61.1361 140.948 60.4627 141.067C59.7894 141.186 59.1536 141.463 58.6073 141.874C52.8722 145.939 50.8223 163.222 50.1597 171.542C49.887 174.96 49.4252 178.361 48.7763 181.729L37.4766 240.409Z"
|
||||
fill="#A8A8A8"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M68.4325 179.857L58.7448 203.689C63.8211 199.977 71.3426 179.761 71.3426 179.761L78.0814 202.809L72.7105 178.517C73.4968 173.241 73.7254 167.898 73.3925 162.575C72.9663 156.475 67.2428 146.687 64.135 141.758C63.5864 141.537 63.0263 141.345 62.4571 141.184C61.8028 140.985 61.1108 140.944 60.4374 141.063C59.7641 141.182 59.1284 141.459 58.582 141.87C61.0233 146.772 67.1266 159.029 68.9866 162.671C71.3465 167.26 68.4325 179.857 68.4325 179.857Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M76.3438 242.265H101.574C102.612 242.266 103.639 242.055 104.593 241.646C105.547 241.237 106.408 240.638 107.123 239.886C107.838 239.134 108.393 238.244 108.753 237.271C109.114 236.297 109.272 235.261 109.22 234.225C108.662 223.405 107.247 204.115 103.884 191.87C102.066 185.263 99.9545 181.795 98.017 180.086C97.1084 179.209 95.9354 178.657 94.6806 178.517C93.7287 178.434 92.7761 178.666 91.968 179.175C87.2831 183.667 81.8115 207.301 81.8115 207.301L76.3438 242.265Z"
|
||||
fill="#A8A8A8"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M99.6355 221.305C100.674 224.184 101.085 227.505 99.6355 230.741L94.0128 233.519C94.0128 233.519 78.9001 227.114 80.7369 213.985C80.7369 213.985 81.2329 211.273 86.4371 211.482C89.3639 211.623 92.1823 212.633 94.5329 214.383C96.8835 216.132 98.6599 218.542 99.6355 221.305Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M93.6302 186.794C93.4558 182.19 95.9552 180.594 98.0206 180.086C97.112 179.209 95.939 178.657 94.6842 178.517C93.2465 179.72 92.2276 181.348 91.774 183.167L86.5039 208.855C86.5039 208.855 93.9247 194.509 93.6302 186.794Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M69.706 207.107L82.1993 224.196L84.3654 238.301L76.3363 242.265L64.7615 244.823C63.6467 245.071 62.5005 245.145 61.363 245.044L29.7773 242.265L34.0748 232.303L48.9163 211.466L69.706 207.107Z"
|
||||
fill="#A8A8A8"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M64.8693 201.24C64.8693 201.24 73.3944 201.825 70.1936 216.473C66.9928 231.121 34.0586 232.295 34.0586 232.295C34.0586 232.295 41.057 219.895 51.8219 211.009C53.0309 210.013 54.1973 208.975 55.3095 207.866C57.7856 205.39 62.521 201.039 64.8693 201.24Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M72.5391 212.284C72.5391 212.284 84.0906 222.797 83.0017 238.975L72.5391 212.284Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M91.9697 179.175C91.9697 179.175 79.8484 195.296 78.4883 220.1L91.9697 179.175Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M50.3668 172.344C50.3668 172.344 56.2337 198.985 41.5859 219.065L50.3668 172.344Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M157.222 271.011H220.44C221.051 271.007 221.651 270.846 222.181 270.542C222.711 270.239 223.154 269.803 223.466 269.277C223.778 268.752 223.949 268.155 223.962 267.544C223.975 266.933 223.829 266.329 223.54 265.791L191.946 207.091C191.643 206.529 191.194 206.058 190.645 205.731C190.097 205.403 189.47 205.23 188.831 205.23C188.192 205.23 187.565 205.403 187.016 205.731C186.468 206.058 186.018 206.529 185.715 207.091L154.106 265.791C153.816 266.33 153.671 266.936 153.684 267.548C153.698 268.16 153.87 268.758 154.184 269.284C154.498 269.81 154.943 270.245 155.475 270.548C156.008 270.851 156.609 271.01 157.222 271.011ZM192.853 260.284H184.808V252.244H192.853V260.284ZM192.853 246.881H184.808V230.795H192.853V246.881Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M157.222 271.011H220.44C221.051 271.007 221.651 270.846 222.181 270.542C222.711 270.239 223.154 269.803 223.466 269.277C223.778 268.752 223.949 268.155 223.962 267.544C223.975 266.933 223.829 266.329 223.54 265.791L191.946 207.091C191.643 206.529 191.194 206.058 190.645 205.731C190.097 205.403 189.47 205.23 188.831 205.23C188.192 205.23 187.565 205.403 187.016 205.731C186.468 206.058 186.018 206.529 185.715 207.091L154.106 265.791C153.816 266.33 153.671 266.936 153.684 267.548C153.698 268.16 153.87 268.758 154.184 269.284C154.498 269.81 154.943 270.245 155.475 270.548C156.008 270.851 156.609 271.01 157.222 271.011ZM192.853 260.284H184.808V252.244H192.853V260.284ZM192.853 246.881H184.808V230.795H192.853V246.881Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M157.222 272.948H220.44C221.051 272.945 221.651 272.783 222.181 272.48C222.711 272.176 223.154 271.74 223.466 271.215C223.778 270.69 223.949 270.092 223.962 269.481C223.975 268.87 223.829 268.267 223.54 267.728L191.946 209.029C191.643 208.466 191.194 207.996 190.645 207.668C190.097 207.34 189.47 207.167 188.831 207.167C188.192 207.167 187.565 207.34 187.016 207.668C186.468 207.996 186.018 208.466 185.715 209.029L154.106 267.728C153.816 268.268 153.671 268.873 153.684 269.485C153.698 270.097 153.87 270.696 154.184 271.222C154.498 271.747 154.943 272.183 155.475 272.485C156.008 272.788 156.609 272.947 157.222 272.948ZM192.853 262.222H184.808V254.181H192.853V262.222ZM192.853 248.818H184.808V232.733H192.853V248.818Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<path
|
||||
d="M90.6046 91.7306C90.562 92.5404 90.965 93.2806 91.2014 94.0556C91.5925 95.5524 91.5535 97.1292 91.089 98.6049C90.655 100.155 89.9265 101.794 90.4806 103.309C90.6085 103.666 90.8023 103.991 90.9185 104.348C91.399 105.82 90.5078 107.37 89.5312 108.575L93.3637 111.261C93.9233 111.725 94.5889 112.043 95.3012 112.187C96.0181 112.272 96.8512 111.889 96.9985 111.183C97.0489 110.943 96.9985 110.691 97.045 110.447C97.0922 110.131 97.2142 109.832 97.4009 109.573C97.5877 109.314 97.8337 109.103 98.1184 108.959C98.694 108.677 99.3359 108.558 99.9746 108.614C102.327 108.742 104.326 110.288 106.465 111.28C107.876 111.891 109.337 112.376 110.832 112.73C111.681 112.95 112.623 113.152 113.39 112.73C114.157 112.307 114.51 111.327 114.452 110.439C114.333 109.557 114.057 108.703 113.638 107.917C113.215 107.132 112.96 106.267 112.89 105.379C112.89 104.077 113.603 102.891 113.843 101.612C114.181 99.7946 113.495 97.9462 112.658 96.2954C111.821 94.6446 110.805 93.0558 110.398 91.2578C110.318 90.7128 110.176 90.1786 109.976 89.6652C109.782 89.2753 109.52 88.9235 109.201 88.6266C107.763 87.2045 105.714 86.5302 103.706 86.383C101.699 86.2357 99.6723 86.5651 97.6805 86.91C96.9206 87.0184 96.1722 87.1961 95.4446 87.4409C94.4049 87.854 93.4177 88.3886 92.5034 89.0335C91.5967 89.6613 90.6628 90.5797 90.6046 91.7306Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<g opacity="0.1">
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M112.879 104.584C112.879 104.7 112.879 104.817 112.902 104.929C113.023 103.794 113.619 102.728 113.832 101.593C113.915 101.138 113.939 100.674 113.906 100.213C113.906 100.415 113.867 100.601 113.832 100.818C113.588 102.096 112.864 103.282 112.879 104.584Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M91.4611 95.6057C91.5067 94.8216 91.4189 94.0354 91.2014 93.2806C90.9721 92.6926 90.7843 92.0892 90.6396 91.4749C90.6396 91.5562 90.6086 91.6337 90.6047 91.7151C90.5621 92.525 90.9651 93.2651 91.2014 94.0401C91.3512 94.5495 91.4383 95.0752 91.4611 95.6057Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M114.417 110.226C114.39 110.573 114.282 110.908 114.101 111.205C113.921 111.501 113.673 111.752 113.378 111.935C112.603 112.358 111.669 112.156 110.821 111.935C109.325 111.582 107.863 111.096 106.454 110.482C104.315 109.494 102.315 107.948 99.9628 107.82C99.3242 107.763 98.6823 107.882 98.1067 108.165C97.822 108.309 97.576 108.52 97.3892 108.778C97.2025 109.037 97.0804 109.337 97.0333 109.653C97.0062 109.897 97.0333 110.149 96.9868 110.389C96.8357 111.094 96.0064 111.478 95.2895 111.393C94.5772 111.249 93.9115 110.93 93.352 110.467L89.907 108.049C89.7792 108.223 89.6513 108.393 89.5195 108.552L93.352 111.242C93.9115 111.705 94.5772 112.024 95.2895 112.168C96.0064 112.253 96.8395 111.869 96.9868 111.164C97.0372 110.924 96.9868 110.672 97.0333 110.428C97.0804 110.112 97.2025 109.812 97.3892 109.553C97.576 109.295 97.822 109.084 98.1067 108.94C98.6823 108.658 99.3242 108.538 99.9628 108.595C102.315 108.723 104.315 110.269 106.454 111.257C107.863 111.871 109.325 112.357 110.821 112.71C111.669 112.931 112.611 113.133 113.378 112.71C114.146 112.288 114.498 111.307 114.44 110.42C114.436 110.354 114.425 110.292 114.417 110.226Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M90.9183 104.328C90.966 104.484 91.001 104.644 91.0229 104.805C91.0829 104.385 91.0471 103.957 90.9183 103.553C90.802 103.197 90.6082 102.871 90.4804 102.515C90.3871 102.256 90.3272 101.987 90.3021 101.713C90.2449 102.245 90.3057 102.784 90.4804 103.29C90.6082 103.646 90.802 103.976 90.9183 104.328Z"
|
||||
fill="black"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M95.4645 251.368C95.438 251.478 95.3752 251.576 95.2863 251.647C95.192 251.696 95.0862 251.719 94.9801 251.713C93.6006 251.744 92.2211 251.713 90.8454 251.81C89.5434 251.922 88.2375 252.197 86.9394 252.05C86.5241 252.065 86.1145 251.95 85.7674 251.721C85.4202 251.493 85.1527 251.162 85.0018 250.775C84.9199 250.606 84.8475 250.433 84.7848 250.256C84.4272 249.342 84.247 248.369 84.254 247.388C84.254 247.129 84.254 246.877 84.254 246.613C84.237 246.329 84.1994 246.047 84.1416 245.768C84.024 245.449 84.038 245.097 84.1803 244.788C84.7306 243.784 86.0597 243.583 87.0827 243.505C89.4528 243.327 91.8362 243.502 94.1547 244.025C94.3369 244.916 94.5423 245.799 94.7593 246.683C94.9194 247.331 95.0899 247.977 95.2708 248.621C95.3921 249.019 95.4905 249.424 95.5653 249.833C95.6611 250.345 95.6265 250.873 95.4645 251.368Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<path
|
||||
opacity="0.06"
|
||||
d="M95.4648 251.368C95.4383 251.478 95.3755 251.576 95.2866 251.647C95.1923 251.696 95.0866 251.719 94.9805 251.713C93.6009 251.744 92.2214 251.713 90.8458 251.81C89.5437 251.922 88.2378 252.197 86.9397 252.05C86.5244 252.065 86.1148 251.95 85.7677 251.721C85.4205 251.493 85.153 251.162 85.0022 250.775C84.9203 250.606 84.8478 250.433 84.7852 250.256H85.2812C86.9126 250.256 88.5479 250.198 90.1793 250.198C90.6714 250.198 91.1674 250.174 91.6557 250.116C92.1439 250.058 92.5431 249.977 92.9887 249.923C93.6558 249.859 94.3259 249.831 94.996 249.841H95.5462C95.647 250.349 95.619 250.874 95.4648 251.368Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M124.004 244.172C123.573 244.676 123.021 245.064 122.4 245.299C119.641 246.462 116.452 245.939 113.604 244.993C112.967 244.757 112.312 244.569 111.647 244.431C111.04 244.346 110.427 244.304 109.814 244.304C109.376 244.304 108.938 244.269 108.5 244.242C107.392 244.172 106.288 244.063 105.187 243.955C104.42 243.899 103.658 243.783 102.909 243.61C102.695 243.555 102.491 243.472 102.3 243.362C102.138 243.291 101.999 243.174 101.9 243.027C101.801 242.879 101.746 242.706 101.742 242.529C101.773 242.037 102.285 241.754 102.734 241.541C106.722 239.816 111.054 239.068 115.355 238.468C115.793 238.395 116.219 238.264 116.622 238.08C117.107 237.887 117.157 238.204 117.397 238.561C117.511 238.726 117.641 238.879 117.785 239.018C118.252 239.44 118.801 239.762 119.397 239.964C120.443 240.351 121.563 240.456 122.609 240.824C122.926 240.934 123.229 241.079 123.512 241.258C123.763 241.394 123.978 241.586 124.143 241.818C124.308 242.051 124.417 242.318 124.463 242.599C124.508 242.88 124.489 243.168 124.406 243.441C124.323 243.713 124.179 243.963 123.985 244.172H124.004Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<path
|
||||
opacity="0.06"
|
||||
d="M124.004 244.172C123.572 244.676 123.021 245.064 122.4 245.299C119.641 246.462 116.452 245.939 113.603 244.993C112.966 244.757 112.312 244.569 111.647 244.431C111.039 244.346 110.427 244.304 109.814 244.304C109.376 244.304 108.938 244.269 108.5 244.242C108.857 243.904 109.221 243.575 109.593 243.257C109.862 243.005 110.173 242.801 110.511 242.653C110.932 242.515 111.381 242.484 111.817 242.564C114.034 242.87 116.025 244.424 118.261 244.288C119.303 244.168 120.311 243.843 121.226 243.331C121.97 243.028 122.633 242.556 123.163 241.951C123.323 241.743 123.445 241.508 123.524 241.258C123.774 241.394 123.99 241.586 124.154 241.818C124.319 242.051 124.428 242.318 124.474 242.599C124.52 242.88 124.5 243.168 124.418 243.441C124.335 243.713 124.191 243.963 123.996 244.172H124.004Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M123.229 176.827C122.841 177.602 122.345 178.443 121.508 178.683C120.292 179.032 119.121 177.939 118.59 176.792C117.867 175.109 117.73 173.231 118.203 171.46C118.424 170.565 118.761 169.705 118.951 168.806C119.088 168.049 119.171 167.284 119.199 166.516C119.313 165.37 119.286 164.214 119.117 163.074C118.493 159.746 115.63 157.262 114.677 153.991C114.196 152.36 114.219 150.624 114.246 148.923C114.237 148.493 114.282 148.063 114.378 147.644C114.407 147.53 114.446 147.418 114.494 147.311C115.134 145.892 117.029 145.699 118.346 144.873C119.075 144.42 120.729 142.959 121.342 144.292C121.474 144.589 121.573 144.899 121.636 145.218C121.888 146.404 121.787 147.795 121.849 148.915C122.086 153.147 123.14 157.37 122.651 161.579C122.535 162.296 122.478 163.022 122.481 163.749C122.586 165.57 123.725 167.147 124.248 168.891C125.054 171.514 124.407 174.37 123.229 176.827Z"
|
||||
fill="#FBBEBE"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M121.653 145.226C121.365 145.121 121.049 145.121 120.761 145.226L118.138 146.001C117.219 146.246 116.323 146.572 115.46 146.974C115.084 147.158 114.727 147.38 114.395 147.636C114.423 147.522 114.462 147.41 114.511 147.303C115.15 145.885 117.045 145.691 118.363 144.866C119.091 144.412 120.746 142.951 121.358 144.284C121.491 144.586 121.59 144.902 121.653 145.226Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M121.824 144.931C121.666 144.835 121.488 144.778 121.303 144.763C121.119 144.748 120.934 144.777 120.762 144.846L118.139 145.621C117.22 145.866 116.324 146.192 115.461 146.594C115.004 146.816 114.575 147.095 114.186 147.423C112.962 145.296 112.636 142.773 112.412 140.332C112.288 138.91 112.195 137.483 112.14 136.069C112.08 135.051 112.1 134.03 112.202 133.016C112.35 131.853 112.687 130.718 112.896 129.563C113.021 128.887 113.101 128.203 113.136 127.517C113.237 126.017 113.168 124.51 112.931 123.026C112.706 121.448 112.345 119.892 111.854 118.376C111.606 117.682 111.307 117.012 111.036 116.329L110.943 116.089C110.453 114.839 110.053 113.556 109.746 112.249C109.622 111.714 109.614 111.152 109.203 110.788C109.589 110.742 109.978 110.726 110.366 110.742C110.443 110.742 110.521 110.742 110.598 110.742C112.19 110.842 113.724 111.378 115.031 112.292C116.715 113.507 117.995 115.2 118.705 117.151C119.522 119.333 119.658 121.704 119.763 124.033C119.972 128.611 120.101 133.191 120.15 137.774C120.15 138.654 120.189 139.592 120.693 140.316C120.834 140.495 120.964 140.682 121.08 140.878C121.262 141.266 121.235 141.696 121.382 142.087C121.491 142.322 121.614 142.55 121.751 142.769C121.924 143.101 122.021 143.468 122.033 143.842C122.046 144.216 121.975 144.588 121.824 144.931Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<path
|
||||
d="M96.0336 99.4458C95.6461 99.318 95.2586 99.1823 94.8711 99.0583L95.6771 99.1204C95.8115 99.2105 95.9316 99.3202 96.0336 99.4458Z"
|
||||
fill="#FBBEBE"
|
||||
/>
|
||||
<path
|
||||
d="M111.899 113.431C111.822 113.694 111.744 113.958 111.659 114.206C111.462 114.843 111.223 115.465 110.942 116.07C110.503 117.063 109.881 117.965 109.109 118.728C108.854 118.997 108.542 119.203 108.195 119.333C107.901 119.408 107.598 119.437 107.296 119.418C106.262 119.418 105.229 119.402 104.196 119.371C103.391 119.38 102.588 119.301 101.801 119.135C100.934 118.899 100.111 118.527 99.3596 118.035C96.465 116.244 94.0857 113.516 93.0665 110.284C92.9387 109.874 92.8495 109.382 93.1324 109.06C93.4108 108.833 93.7614 108.713 94.1205 108.723H94.1671C95.0002 108.614 95.8028 108.339 96.527 107.913C96.9961 107.673 97.3742 107.286 97.6042 106.812C97.7612 106.25 97.7491 105.655 97.5694 105.1L96.7091 101.194C96.6195 100.568 96.3846 99.972 96.0232 99.4536C96.2751 99.5427 96.527 99.6241 96.7982 99.6939C99.193 100.341 101.813 99.9496 104.161 100.798C105.06 101.128 105.959 101.721 106.23 102.643C106.684 104.193 105.168 105.801 105.602 107.351C105.893 108.405 106.966 109.013 107.927 109.506L108.575 109.835L110.369 110.746L110.617 110.87L110.655 110.889C111.221 111.176 111.818 111.532 112.016 112.141C112.1 112.573 112.06 113.021 111.899 113.431Z"
|
||||
fill="#FBBEBE"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M116.627 122.53C116.561 123.52 116.32 124.491 115.914 125.397C115.136 126.964 114.719 128.686 114.693 130.435C114.395 135.666 112.407 141.893 115.255 146.288C115.325 146.396 115.395 146.505 115.461 146.617C115.003 146.84 114.575 147.118 114.186 147.446C112.961 145.319 112.636 142.796 112.411 140.355C112.287 138.933 112.194 137.507 112.14 136.092C112.079 135.075 112.1 134.053 112.202 133.039C112.349 131.876 112.686 130.741 112.895 129.586C113.02 128.91 113.1 128.227 113.136 127.54C113.236 126.04 113.167 124.534 112.93 123.049C112.705 121.471 112.344 119.915 111.853 118.399C111.605 117.705 111.306 117.035 111.035 116.353L110.942 116.113C110.503 117.106 109.881 118.007 109.109 118.771C108.854 119.039 108.542 119.246 108.195 119.375C107.901 119.45 107.598 119.479 107.296 119.461C106.262 119.461 105.229 119.445 104.196 119.414C103.391 119.423 102.588 119.344 101.801 119.178C100.934 118.941 100.111 118.57 99.3596 118.077C96.465 116.287 94.0857 113.559 93.0665 110.327C92.9387 109.916 92.8495 109.424 93.1324 109.103C93.4108 108.875 93.7614 108.756 94.1205 108.765H94.1671L97.6546 113.078C98.0546 113.535 98.3933 114.042 98.6621 114.586C98.7142 114.768 98.8227 114.93 98.9721 115.047C99.0934 115.102 99.2267 115.126 99.3596 115.117C102.634 115.117 106.997 115.117 110.272 115.117C110.283 114.536 110.115 113.967 109.791 113.485C109.691 113.509 109.555 113.516 109.764 113.454C109.575 112.196 109.168 110.981 108.563 109.862C108.527 109.785 108.487 109.71 108.443 109.637C108.183 109.254 107.989 108.83 107.869 108.382C108.283 108.25 108.727 108.25 109.14 108.382C109.617 108.587 109.865 109.103 110.074 109.579C110.252 109.967 110.431 110.381 110.59 110.788C110.59 110.831 110.628 110.877 110.644 110.92C110.982 111.722 111.199 112.569 111.287 113.435C111.31 113.753 111.388 114.156 111.651 114.26C111.693 114.279 111.738 114.29 111.783 114.291C112.056 114.191 112.352 114.175 112.635 114.244C112.917 114.313 113.172 114.464 113.368 114.679C113.749 115.115 114.016 115.64 114.143 116.206C114.269 116.771 114.453 117.321 114.693 117.849C115.193 118.802 114.43 119.228 115.333 119.813C115.755 120.046 116.118 120.372 116.394 120.767C116.65 121.317 116.731 121.932 116.627 122.53Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M94.1196 244.04C94.3017 244.931 94.5071 245.815 94.7241 246.698L94.7008 246.753C92.9299 245.978 91.1164 245.168 89.1828 244.997C87.3886 244.842 85.4394 245.327 84.2188 246.629C84.2018 246.345 84.1643 246.062 84.1064 245.784C83.9889 245.465 84.0028 245.112 84.1452 244.803C84.6954 243.8 86.0246 243.598 87.0476 243.521C89.4176 243.342 91.801 243.517 94.1196 244.04Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M109.384 114.194C109.566 114.194 109.5 114.194 109.411 114.225C109.322 114.256 109.175 114.256 109.384 114.194Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M117.804 239.018C116.076 240.119 114.452 241.376 112.953 242.773C112.517 243.239 111.998 243.619 111.422 243.893C110.827 244.084 110.199 244.149 109.578 244.083C107.14 243.984 104.71 243.743 102.3 243.362C102.138 243.291 101.999 243.174 101.9 243.027C101.801 242.879 101.746 242.706 101.742 242.529C101.773 242.037 102.285 241.754 102.734 241.541C106.722 239.816 111.054 239.068 115.355 238.468C115.793 238.395 116.219 238.264 116.622 238.08C117.107 237.887 117.157 238.204 117.397 238.561C117.517 238.727 117.653 238.88 117.804 239.018Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M89.3762 109.699C89.9458 110.207 90.5968 110.618 91.1742 111.11C92.7242 112.424 93.7008 114.26 94.6618 116.051C94.0919 116.257 93.4755 116.298 92.8831 116.171C92.5784 117.89 92.6299 119.653 93.0342 121.352C92.2589 121.884 91.6566 122.631 91.3021 123.502C91.0266 124.386 90.8371 125.295 90.7363 126.215C90.1783 130.248 89.6177 134.28 89.0546 138.313C89.0432 138.708 88.9076 139.089 88.667 139.402C88.5595 139.489 88.4608 139.586 88.3725 139.692C88.254 139.953 88.2071 140.241 88.2369 140.525C88.2137 140.808 88.0277 141.146 87.7448 141.107C87.6073 141.074 87.4855 140.994 87.3999 140.882C87.1169 140.582 86.8578 140.261 86.6249 139.921C84.3386 144.308 83.2381 149.035 82.0291 153.832C81.1649 157.25 80.3008 160.707 80.2581 164.233C80.2698 165.901 80.3746 167.568 80.572 169.224L80.913 172.627C81.1882 175.362 81.4633 178.102 81.7617 180.838C81.9554 182.574 82.1492 184.325 82.7731 185.961C83.7031 188.445 82.6723 191.246 83.0676 193.874C83.3776 195.931 83.9937 198.059 83.3737 200.043C83.1257 200.837 82.6917 201.562 82.3662 202.329C81.5679 204.235 81.4866 206.355 81.4168 208.421C81.2913 210.231 81.3588 212.05 81.6183 213.846C81.9516 215.702 82.6994 217.519 82.5522 219.395C82.3468 222.065 80.3783 224.347 80.138 227.013C79.9481 229.141 80.882 231.194 81.8857 233.081C82.6258 234.473 81.595 236.271 81.2037 237.801C80.4287 240.801 81.719 243.939 83.3001 246.602C83.3404 246.693 83.4118 246.767 83.5016 246.811C83.6837 246.877 83.8465 246.698 83.9666 246.547C85.1562 245.021 87.2643 244.443 89.1979 244.61C91.1316 244.776 92.9412 245.575 94.716 246.365L97.231 240.766C97.6572 239.816 98.0912 238.789 97.8781 237.77C97.727 237.042 97.2581 236.426 96.9248 235.759C96.1498 234.186 96.1111 232.353 96.1033 230.59L96.0568 220.631C98.0176 218.166 99.215 215.237 99.9706 212.175C101.13 216.481 101.21 221.007 100.203 225.351C100.017 226.008 99.8872 226.679 99.8156 227.358C99.6645 230.121 101.831 232.458 102.408 235.162C102.464 235.369 102.48 235.585 102.455 235.798C102.39 236.06 102.281 236.309 102.133 236.534C101.094 238.417 101.207 240.7 101.358 242.847C104.075 243.32 106.821 243.614 109.577 243.726C110.198 243.788 110.826 243.724 111.421 243.536C111.998 243.261 112.517 242.88 112.952 242.413C114.712 240.758 116.643 239.296 118.714 238.053C117.558 236.134 115.939 234.536 114.006 233.403C114.63 232.485 116.052 232.558 116.881 231.822C117.298 231.41 117.57 230.875 117.656 230.295C117.966 228.776 117.583 227.195 117.199 225.703C116.455 222.766 115.707 219.817 114.487 217.043C113.935 215.902 113.448 214.731 113.03 213.536C112.014 210.195 112.723 206.561 111.956 203.147C111.708 202.054 111.309 200.926 111.588 199.837C112.014 198.159 113.913 197.163 114.332 195.482C115.831 189.495 116.099 183.291 116.951 177.18C117.142 176.012 117.244 174.832 117.257 173.65C117.257 172.413 117.036 171.189 116.947 169.957C116.599 165.024 118.176 160.102 117.773 155.173C117.515 152.136 116.514 149.208 114.859 146.648C112.01 142.25 113.998 136.027 114.297 130.795C114.319 129.046 114.735 127.324 115.514 125.758C115.923 124.853 116.166 123.881 116.23 122.89C116.35 122.279 116.274 121.646 116.013 121.08C115.741 120.684 115.378 120.357 114.956 120.127C114.053 119.538 114.816 119.112 114.316 118.159C114.076 117.632 113.891 117.081 113.766 116.516C113.639 115.951 113.373 115.427 112.991 114.993C112.795 114.778 112.54 114.627 112.257 114.558C111.975 114.489 111.679 114.505 111.406 114.605C111.038 114.605 110.937 114.117 110.906 113.749C110.801 112.4 110.243 111.133 109.697 109.893C109.488 109.416 109.24 108.901 108.763 108.696C108.348 108.566 107.903 108.566 107.488 108.696C107.608 109.145 107.803 109.571 108.066 109.955C108.737 111.133 109.184 112.426 109.383 113.768C108.996 113.884 109.771 113.768 109.383 113.768C109.73 114.252 109.909 114.836 109.891 115.431C106.62 115.431 102.257 115.431 98.9825 115.431C98.8495 115.441 98.716 115.417 98.595 115.361C98.4456 115.243 98.3361 115.082 98.2811 114.9C98.0146 114.355 97.6757 113.85 97.2736 113.396L93.4373 108.665C93.3521 108.541 93.2393 108.439 93.1079 108.366C92.9495 108.322 92.7819 108.322 92.6235 108.366C91.3796 108.548 90.4612 109.068 89.3762 109.699Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M103.142 173.808C103.803 173.799 104.455 173.968 105.029 174.297C105.314 174.464 105.55 174.703 105.714 174.99C105.879 175.277 105.966 175.601 105.967 175.932C105.924 176.664 105.362 177.246 104.955 177.87C104.703 178.25 104.503 178.661 104.358 179.094C104.314 179.192 104.303 179.303 104.327 179.408C104.374 179.555 104.533 179.629 104.668 179.695C105.161 179.918 105.583 180.274 105.884 180.723C106.186 181.172 106.357 181.696 106.377 182.237C106.342 183.748 104.866 184.973 104.947 186.499C104.984 186.817 105.039 187.133 105.11 187.445C105.287 188.746 105.107 190.071 104.587 191.277C104.118 192.494 103.479 193.637 102.975 194.842C101.615 198.117 101.32 201.744 101.425 205.305C101.506 207.723 100.65 210.025 99.875 212.327C100.313 207.69 100.429 203.029 100.224 198.376C100.17 197.113 100.011 195.92 100.069 194.652C100.162 192.641 101.286 190.812 101.572 188.84C101.735 187.712 101.627 186.565 101.654 185.426C101.708 183.101 102.34 180.807 102.382 178.474C102.436 176.827 102.285 175.13 103.142 173.808Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M86.9733 110.529C86.2029 110.913 85.482 111.389 84.8265 111.947C84.1415 112.627 83.5687 113.412 83.1292 114.272C82.5941 115.247 82.1141 116.25 81.6916 117.279C81.1452 118.612 80.6918 120.088 81.0677 121.48C81.4087 122.739 82.3736 123.719 83.0401 124.839C83.7066 125.959 84.098 127.273 84.6095 128.501C85.2915 130.14 86.2254 131.667 86.8997 133.31C87.5739 134.953 87.9808 136.798 87.5545 138.507C87.4305 139.01 86.6129 139.402 86.6129 139.917C86.6129 140.77 87.8452 141.696 88.6628 141.944C89.6045 142.234 90.6313 141.8 91.4025 141.169C92.8169 140.049 93.6772 138.371 94.3127 136.681C95.3357 133.969 95.8821 131.09 96.3742 128.23C96.7307 126.13 97.064 124.025 97.2538 121.91C97.4282 120.003 97.4825 118.035 96.9787 116.206C96.5086 114.527 95.5943 113.006 94.332 111.803C93.3245 110.835 91.6737 109.533 90.209 109.498C89.0503 109.494 87.9653 109.982 86.9733 110.529Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<path
|
||||
d="M97.9897 157.242C97.9476 157.507 97.8486 157.76 97.6994 157.983C97.5502 158.205 97.3544 158.393 97.1256 158.533C96.6683 158.811 96.17 159.015 95.6492 159.137C94.6106 159.44 93.5101 159.719 92.46 159.451C91.6347 159.183 90.8726 158.749 90.2202 158.176L78.4865 149.194C77.5439 148.526 76.6772 147.757 75.9018 146.9C75.4567 146.347 75.0477 145.766 74.6773 145.16C74.1869 144.467 73.8029 143.704 73.538 142.897C73.4434 142.542 73.3965 142.176 73.3985 141.808C73.3954 141.46 73.4807 141.118 73.6465 140.812C73.9294 140.339 74.5339 140.243 75.0299 140.111C76.6947 139.68 78.4227 139.549 80.1334 139.723C80.2837 139.716 80.4322 139.758 80.5566 139.842C80.681 139.927 80.7746 140.05 80.8232 140.192C81.0828 140.669 81.3075 141.165 81.54 141.661C81.8969 142.507 82.3619 143.302 82.9234 144.028C83.4645 144.617 84.0403 145.172 84.6478 145.691C85.8879 146.853 86.9031 148.248 88.2245 149.318C89.7552 150.554 91.6152 151.294 93.359 152.205C95.1028 153.116 96.8233 154.297 97.6991 156.061C97.9022 156.421 98.0028 156.829 97.9897 157.242Z"
|
||||
fill="#FBBEBE"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M111.572 94.5749C111.573 97.0214 110.638 99.3757 108.961 101.156C107.283 102.937 104.988 104.009 102.546 104.154C100.104 104.299 97.6985 103.506 95.8218 101.936C93.9452 100.367 92.739 98.1395 92.45 95.7101C92.161 93.2807 92.811 90.8326 94.267 88.8665C95.723 86.9004 97.8751 85.5648 100.283 85.1328C102.691 84.7007 105.173 85.2049 107.222 86.5422C109.271 87.8796 110.731 89.949 111.305 92.3273C111.483 93.0633 111.572 93.8177 111.572 94.5749Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M111.572 94.1874C111.573 96.634 110.638 98.9883 108.961 100.769C107.283 102.55 104.988 103.622 102.546 103.767C100.104 103.912 97.6985 103.119 95.8218 101.549C93.9452 99.9793 92.739 97.752 92.45 95.3226C92.161 92.8932 92.811 90.4452 94.267 88.4791C95.723 86.513 97.8751 85.1773 100.283 84.7453C102.691 84.3133 105.173 84.8175 107.222 86.1548C109.271 87.4921 110.731 89.5615 111.305 91.9399C111.483 92.6758 111.572 93.4303 111.572 94.1874Z"
|
||||
fill="#FBBEBE"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M81.54 141.645C81.0983 141.928 80.4434 141.835 79.897 141.711C77.7425 141.231 75.3632 140.886 73.3985 141.808C73.3954 141.46 73.4807 141.118 73.6465 140.812C73.9294 140.339 74.5339 140.243 75.0299 140.111C76.6947 139.68 78.4227 139.549 80.1334 139.723C80.2837 139.716 80.4322 139.758 80.5566 139.842C80.681 139.927 80.7746 140.05 80.8232 140.192C81.0828 140.653 81.3075 141.149 81.54 141.645Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M77.4907 128.598C77.0141 130.148 76.6227 131.741 75.6888 133.054C75.3801 133.4 75.1477 133.807 75.0068 134.248C74.9962 134.574 74.9428 134.897 74.8479 135.209C74.7162 135.472 74.4371 135.647 74.3248 135.918C74.1581 136.306 74.3751 136.805 74.1543 137.17C74.0509 137.316 73.9269 137.446 73.7861 137.557C73.2591 138.045 73.1274 138.824 73.1157 139.545C73.1041 140.266 73.1739 141.002 72.9375 141.68C74.9835 140.46 77.5876 140.824 79.9126 141.343C80.5598 141.487 81.3619 141.595 81.7804 141.076C82.1989 140.556 81.9587 139.874 82.0168 139.251C82.0943 138.437 82.6523 137.763 82.9972 137.022C83.3847 136.189 83.5164 135.259 83.8148 134.391C84.431 132.566 85.7523 131.078 86.601 129.354C86.9151 128.761 87.1025 128.109 87.1513 127.439C87.122 126.572 86.8954 125.722 86.4886 124.955C85.6822 123.17 84.7227 121.457 83.6211 119.837C83.1289 119.112 82.2803 117.771 81.5169 118.93C80.8969 119.864 80.5094 121.053 80.0521 122.076C79.0667 124.196 78.2114 126.374 77.4907 128.598Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<path
|
||||
d="M112.453 113.555C111.289 113.635 110.123 113.454 109.038 113.026C107.953 112.598 106.978 111.932 106.183 111.079C106.049 110.957 105.952 110.8 105.904 110.625C105.882 110.399 105.945 110.172 106.082 109.99C106.547 109.273 107.349 108.8 107.721 108.029C108.093 107.258 107.965 106.371 108 105.526C108.062 104.011 108.659 102.511 108.709 101.019C108.727 100.637 108.699 100.255 108.624 99.8799C108.446 99.3231 108.337 98.7466 108.299 98.1632C108.349 97.0278 109.372 96.1443 109.543 95.0244C109.763 93.5984 108.582 92.3584 107.392 91.5369C106.485 90.9077 105.5 90.3985 104.462 90.0217C103.825 89.7934 103.165 89.6336 102.494 89.5451C101.242 89.376 99.9686 89.5184 98.7854 89.9597C98.2957 90.1445 97.832 90.392 97.4059 90.696C97.1521 90.8565 96.9407 91.0758 96.7897 91.3354C96.6655 91.6276 96.6099 91.9444 96.627 92.2615C96.6037 93.8464 96.782 95.532 96.0263 96.9116C95.6388 97.6517 94.9801 98.2523 94.6584 99.0351C94.5268 99.3608 94.4445 99.7043 94.4143 100.054C94.3755 100.417 94.3561 100.782 94.3562 101.147C94.3329 103.189 94.3562 105.344 95.3947 107.115C95.7612 107.65 96.0938 108.208 96.3906 108.785C96.5345 109.077 96.6012 109.401 96.5842 109.725C96.5673 110.05 96.4673 110.366 96.2937 110.641C96.0839 110.892 95.8186 111.091 95.5187 111.222C94.7493 111.591 93.912 111.798 93.0592 111.828C92.2064 111.859 91.3564 111.713 90.5625 111.4C91.4909 110.521 92.1286 109.378 92.3904 108.126C92.6522 106.874 92.5255 105.572 92.0273 104.394C91.7473 103.919 91.5655 103.391 91.4925 102.844C91.5314 102.351 91.6629 101.87 91.88 101.426C92.4112 99.9699 92.4935 98.3879 92.1164 96.8844C91.88 95.9622 91.4693 95.0709 91.3956 94.1215C91.3341 93.4049 91.3654 92.6834 91.4886 91.9747C91.701 90.7069 92.2596 89.5224 93.1028 88.5521C93.9459 87.5818 95.0408 86.8633 96.2666 86.476C96.4642 86.4101 96.6541 86.3481 96.8711 86.2939C97.8112 86.0852 98.7718 85.9825 99.7348 85.9878C101.885 85.918 104.067 85.856 106.14 86.4179C107.357 86.7366 108.49 87.3186 109.457 88.1229C110.144 88.7003 110.692 89.4247 111.062 90.2426C111.263 90.7137 111.415 91.2046 111.515 91.7074C111.604 92.122 111.67 92.5405 111.736 92.959C112.205 95.9389 112.674 98.977 112.174 101.953C111.976 103.116 111.631 104.278 111.476 105.464C111.1 108.196 111.778 110.912 112.453 113.555Z"
|
||||
fill="#464353"
|
||||
/>
|
||||
<path
|
||||
d="M98.6856 117.384C99.0837 117.384 99.4064 117.061 99.4064 116.663C99.4064 116.265 99.0837 115.942 98.6856 115.942C98.2875 115.942 97.9648 116.265 97.9648 116.663C97.9648 117.061 98.2875 117.384 98.6856 117.384Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M110.85 117.043C111.248 117.043 111.57 116.72 111.57 116.322C111.57 115.924 111.248 115.601 110.85 115.601C110.452 115.601 110.129 115.924 110.129 116.322C110.129 116.72 110.452 117.043 110.85 117.043Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M98.6867 80.2371L97.1367 77.3115C97.235 77.1945 97.3425 77.0856 97.4584 76.986C97.6822 76.8481 97.9443 76.7856 98.2062 76.8077L98.6867 80.2371Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M102.962 76.6721L101.207 79.2684L100.207 76.4202C100.634 76.2369 101.095 76.1496 101.559 76.1645C102.052 76.2544 102.526 76.4259 102.962 76.6721Z"
|
||||
fill="black"
|
||||
/>
|
||||
<g opacity="0.1">
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M96.2937 109.874C96.0839 110.125 95.8186 110.324 95.5187 110.455C94.1445 111.112 92.577 111.243 91.1128 110.823C90.9425 111.03 90.7587 111.226 90.5625 111.408C91.3564 111.721 92.2064 111.867 93.0592 111.836C93.912 111.806 94.7493 111.599 95.5187 111.23C95.8186 111.099 96.0839 110.9 96.2937 110.649C96.4271 110.455 96.5181 110.236 96.5608 110.005C96.6035 109.774 96.597 109.536 96.5417 109.308C96.503 109.513 96.4182 109.706 96.2937 109.874Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M92.0903 96.8961C92.2169 97.4038 92.2909 97.9232 92.3112 98.4461C92.3559 97.6644 92.2814 96.8804 92.0903 96.1211C91.8539 95.1988 91.4432 94.3075 91.3696 93.3582C91.3696 93.238 91.3696 93.1179 91.3502 92.9939C91.3316 93.3737 91.3381 93.7542 91.3696 94.1332C91.4432 95.0826 91.8539 95.9738 92.0903 96.8961Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M112.148 101.965C112.313 100.94 112.383 99.9022 112.358 98.8646C112.344 99.6437 112.274 100.421 112.148 101.19C111.951 102.352 111.606 103.515 111.451 104.7C111.371 105.337 111.341 105.978 111.362 106.619C111.372 106.236 111.402 105.855 111.451 105.475C111.606 104.29 111.951 103.139 112.148 101.965Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M92.0115 103.627C91.8085 103.246 91.645 102.844 91.5232 102.43C91.4903 102.569 91.4747 102.712 91.4767 102.856C91.5497 103.403 91.7315 103.93 92.0115 104.406C92.296 105.056 92.4601 105.751 92.4959 106.46C92.566 105.49 92.3998 104.518 92.0115 103.627Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M108.622 99.8644C108.656 100.041 108.681 100.22 108.696 100.399C108.696 100.341 108.696 100.287 108.696 100.229C108.714 99.8469 108.685 99.4643 108.611 99.0894C108.525 98.6825 108.386 98.2795 108.32 97.8688C108.301 97.9607 108.289 98.054 108.285 98.1478C108.327 98.7317 108.44 99.3082 108.622 99.8644Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M106.184 110.288C106.124 110.226 106.068 110.16 106.017 110.091C105.922 110.246 105.882 110.429 105.905 110.61C105.953 110.784 106.05 110.942 106.184 111.063C106.978 111.917 107.954 112.582 109.039 113.011C110.123 113.439 111.29 113.62 112.454 113.54C112.388 113.284 112.322 113.032 112.26 112.764C111.128 112.817 109.999 112.622 108.949 112.195C107.9 111.767 106.957 111.117 106.184 110.288Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M109.536 94.2882C109.534 94.274 109.534 94.2597 109.536 94.2456C109.757 92.8195 108.575 91.5795 107.385 90.758C106.478 90.1289 105.493 89.6196 104.456 89.2429C103.818 89.0146 103.158 88.8548 102.487 88.7662C101.236 88.5972 99.9618 88.7396 98.7786 89.1809C98.2889 89.3656 97.8252 89.6131 97.3991 89.9171C97.1453 90.0776 96.934 90.2969 96.783 90.5565C96.6588 90.8488 96.6031 91.1656 96.6202 91.4826C96.6202 91.75 96.6202 92.0174 96.6202 92.2887V92.2577C96.6031 91.9406 96.6588 91.6238 96.783 91.3315C96.934 91.072 97.1453 90.8526 97.3991 90.6921C97.8252 90.3881 98.2889 90.1407 98.7786 89.9559C99.9618 89.5146 101.236 89.3722 102.487 89.5412C103.158 89.6298 103.818 89.7896 104.456 90.0179C105.493 90.3946 106.478 90.9039 107.385 91.533C108.373 92.1995 109.346 93.1605 109.536 94.2882Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M96.0256 96.1444C95.6381 96.8845 94.9794 97.4851 94.6577 98.2679C94.5261 98.5936 94.4438 98.9371 94.4136 99.2871C94.3748 99.65 94.3554 100.015 94.3555 100.38C94.3555 100.709 94.3555 101.042 94.3555 101.376C94.3555 101.302 94.3555 101.228 94.3555 101.155C94.3554 100.79 94.3748 100.425 94.4136 100.062C94.4438 99.7121 94.5261 99.3686 94.6577 99.0429C94.9794 98.2679 95.6226 97.6595 96.0256 96.9194C96.6495 95.7569 96.6379 94.42 96.6263 93.0908C96.6777 94.1424 96.4715 95.1906 96.0256 96.1444Z"
|
||||
fill="black"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M111.491 91.7345C111.438 91.8104 111.375 91.8793 111.305 91.9399C111.168 92.0524 111.016 92.1439 110.852 92.2111C109.724 92.6994 109.771 91.1067 109.182 90.5604C108.804 90.2818 108.34 90.1446 107.872 90.1729C106.738 90.116 105.602 90.0656 104.466 90.0217C102.571 89.952 100.68 89.9171 98.7887 89.9597C97.882 89.9752 96.9778 90.0127 96.0762 90.0721C95.0299 90.1419 93.9061 90.2775 93.0149 90.7735C92.7098 90.9413 92.4337 91.1572 92.1972 91.4129C91.9841 91.6454 91.7593 91.9399 91.4648 91.9941C91.6772 90.7263 92.2358 89.5418 93.079 88.5715C93.9221 87.6011 95.017 86.8827 96.2428 86.4954C98.2272 85.0173 100.706 84.3634 103.161 84.6703C105.617 84.9772 107.858 86.2212 109.418 88.1423C110.105 88.7197 110.653 89.4441 111.022 90.262C111.23 90.735 111.387 91.2286 111.491 91.7345Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M110.851 91.4361C109.724 91.9243 109.77 90.3317 109.181 89.7853C108.804 89.5067 108.34 89.3695 107.871 89.3978C103.945 89.1395 100.006 89.1058 96.0757 89.297C94.6768 89.39 93.1423 89.5993 92.2007 90.6378C91.9604 90.9013 91.7085 91.2423 91.3481 91.2307C91.228 90.4556 91.1428 89.6496 91.0846 88.8553C91.0495 88.5577 91.0652 88.2563 91.1311 87.964C91.228 87.6307 91.4489 87.3478 91.5613 87.0223C91.6719 86.6083 91.7112 86.1785 91.6775 85.7513C91.6945 84.1704 92.1194 82.6206 92.9109 81.252C93.7025 79.8834 94.834 78.7423 96.1959 77.9392C96.5377 77.772 96.8546 77.5581 97.1375 77.3037C97.2358 77.1867 97.3433 77.0778 97.4591 76.9782C97.6829 76.8402 97.9451 76.7778 98.207 76.7999C98.393 76.7999 98.5945 76.7999 98.7573 76.7999C99.2584 76.7447 99.7483 76.6141 100.21 76.4124C100.637 76.2291 101.099 76.1418 101.563 76.1566C102.055 76.2465 102.53 76.4181 102.966 76.6643C103.175 76.7653 103.395 76.8445 103.62 76.9007C103.973 76.982 104.337 76.9782 104.69 77.0479C105.261 77.1933 105.804 77.4344 106.294 77.7609C106.88 78.0767 107.422 78.4675 107.906 78.9234C108.433 79.5255 108.855 80.212 109.154 80.954C109.667 81.9791 110.058 83.0613 110.317 84.178C110.63 85.7823 110.475 87.5184 111.22 88.9754C111.673 89.8899 112.088 90.9052 110.851 91.4361Z"
|
||||
fill="#FED253"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M91.207 90.2193C91.207 90.2193 99.2401 82.9109 111.202 88.9715C111.202 88.9715 97.6978 85.8986 91.207 90.2193Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M3.67856 127.563C2.58579 125.773 0.942758 124.134 1.19851 122.103C4.20557 122.448 7.05761 121.716 9.81279 120.441C12.568 119.166 15.1255 117.511 17.7063 115.888C18.7216 115.248 19.733 114.621 20.7405 114.039C22.337 113.125 23.9219 112.102 25.5456 111.207C27.8706 109.928 30.2421 108.882 32.8539 108.68C33.327 108.643 33.8018 108.632 34.2761 108.645C37.4924 108.707 40.825 109.571 43.6538 108.13L43.8281 108.037C45.4324 107.157 46.5988 105.661 47.9745 104.449C48.9635 103.593 50.0403 102.845 51.1869 102.217C53.2756 101.054 55.5386 100.124 57.6738 99.0273C58.2203 98.7458 58.7392 98.4137 59.2238 98.0353L59.588 97.7447L59.6694 97.6749C59.7585 97.6013 59.8477 97.5277 59.9329 97.4502L60.0995 97.3029L60.2701 97.1479L60.4018 97.0239L60.6149 96.8224L60.6382 96.7953L60.9753 96.462L61.1109 96.3264L61.3473 96.0823L61.4868 95.9428C61.7232 95.6987 61.9596 95.4507 62.1998 95.2104C62.4401 94.9701 62.7656 94.6446 63.0562 94.3773L63.3352 94.1293L63.4166 94.0595L63.6414 93.8735L63.746 93.7844L63.9669 93.6139L64.0754 93.5286C64.1839 93.4472 64.2962 93.3697 64.4086 93.2922C64.512 93.2206 64.6209 93.1571 64.7341 93.1024L64.0095 91.347L66.0943 90.4867L94.8473 160.257L92.7586 161.118L76.3128 121.204L76.1966 121.321C72.3869 125.08 67.7203 127.857 62.599 129.412H62.568C59.7043 130.276 56.4996 130.888 54.0893 132.492C53.2347 133.048 52.492 133.759 51.8999 134.589C51.3341 135.395 50.9389 136.313 50.3499 137.088C50.2375 137.232 50.1135 137.371 49.9895 137.503C49.2848 138.201 48.505 138.82 47.6645 139.347L47.2963 139.599C44.7997 141.356 42.1986 142.959 39.5074 144.401C37.9545 145.209 36.3353 145.883 34.6675 146.416C33.4546 146.803 32.2029 147.086 30.99 147.47C30.1742 147.717 29.3818 148.036 28.6224 148.423C26.5686 149.496 24.9914 151.178 23.4995 152.972C22.1936 154.545 20.9536 156.212 19.5198 157.645C18.6208 158.537 17.2529 159.382 16.1679 158.858L15.947 158.731C15.9819 158.672 16.009 158.614 16.04 158.552C16.8964 156.758 15.8424 154.542 14.4241 153.104C14.2769 152.953 14.1257 152.806 13.9707 152.666C13.8157 152.527 13.5367 152.279 13.3159 152.085C12.103 151.042 10.8126 150.074 9.97167 148.729C9.32986 147.599 8.89422 146.365 8.68514 145.083C8.29764 143.257 8.03801 141.378 7.04599 139.824C6.39498 138.797 5.42621 137.921 4.72095 136.937C4.27514 136.355 3.98208 135.67 3.86843 134.945C3.70956 133.686 4.29469 132.454 4.51557 131.198C4.58128 130.856 4.60858 130.508 4.59695 130.16C4.516 129.23 4.19996 128.337 3.67856 127.563Z"
|
||||
fill="url(#paint0_linear_2231_35430)"
|
||||
/>
|
||||
<path
|
||||
d="M92.0253 160.519L94.0781 159.673L65.8043 91.075L63.7514 91.9212L92.0253 160.519Z"
|
||||
fill="#D6D8E1"
|
||||
/>
|
||||
<path
|
||||
d="M7.73795 139.58C8.71059 141.111 8.97022 142.955 9.34611 144.749C9.55458 146.011 9.98351 147.225 10.6133 148.338C11.4425 149.659 12.7097 150.612 13.8993 151.635C14.1163 151.825 14.3333 152.023 14.5426 152.209C14.6937 152.344 14.8448 152.492 14.9882 152.635C16.3832 154.049 17.4179 156.231 16.577 157.994C16.5512 158.054 16.5214 158.113 16.4879 158.169C16.5559 158.215 16.6271 158.256 16.701 158.293C17.7705 158.812 19.1152 157.983 19.9987 157.103C21.4092 155.693 22.6298 154.057 23.9125 152.511C25.3773 150.744 26.9312 149.089 28.9501 148.035C29.6954 147.654 30.4735 147.34 31.2751 147.098C32.4648 146.71 33.697 146.435 34.8905 146.063C36.5299 145.539 38.1218 144.876 39.6491 144.083C42.2995 142.664 44.8617 141.087 47.3218 139.359L47.686 139.111C48.5121 138.593 49.2788 137.987 49.9723 137.301C50.0963 137.17 50.2164 137.034 50.3288 136.891C50.9256 136.116 51.317 135.228 51.8788 134.434C52.4616 133.618 53.1924 132.919 54.0334 132.372C56.4049 130.795 59.5515 130.191 62.3687 129.342H62.3997C67.4339 127.811 72.0214 125.081 75.7686 121.387C76.0792 121.127 76.3114 120.786 76.439 120.402C76.5592 119.856 76.2646 119.313 75.9818 118.833C75.6524 118.267 75.3307 117.701 75.013 117.128C73.5839 114.527 72.3082 111.845 71.1922 109.095C70.9481 108.49 70.7117 107.89 70.483 107.281C69.8966 105.731 69.3619 104.159 68.8788 102.565C68.7199 102.038 68.5649 101.507 68.4176 100.977C68.3905 100.876 68.3595 100.767 68.3324 100.651C67.6891 98.0664 66.8289 92.0174 64.1783 93.827C63.39 94.3907 62.6569 95.0277 61.9889 95.7297C61.4735 96.2528 60.9698 96.7915 60.4389 97.3107C60.0065 97.736 59.5497 98.1358 59.071 98.5081C58.5851 98.8802 58.0663 99.207 57.5209 99.4846C55.4168 100.562 53.1925 101.465 51.1426 102.62C50.0137 103.236 48.9538 103.972 47.9805 104.813C46.6281 106.006 45.4811 107.479 43.904 108.343L43.7335 108.436C40.9512 109.85 37.6767 109.002 34.5108 108.94C34.0455 108.928 33.5799 108.94 33.1158 108.975C30.5466 109.168 28.1983 110.199 25.9275 111.459C24.331 112.342 22.7732 113.342 21.2038 114.245C20.2118 114.814 19.2198 115.431 18.22 116.062C15.6857 117.659 13.1165 119.333 10.4699 120.542C7.75733 121.774 4.95565 122.51 1.99898 122.173C1.7471 124.169 3.35913 125.781 4.4364 127.54C4.9538 128.296 5.26745 129.173 5.34704 130.086C5.36128 130.427 5.33657 130.769 5.27341 131.105C5.0564 132.341 4.49839 133.55 4.6379 134.794C4.74786 135.504 5.03435 136.174 5.47104 136.743C6.1453 137.708 7.09469 138.572 7.73795 139.58Z"
|
||||
fill="#F86D70"
|
||||
/>
|
||||
<path
|
||||
d="M86.5224 147.652C86.5224 147.652 89.917 150.612 93.358 152.189C96.7991 153.767 94.5206 159.424 94.5206 159.424C94.5206 159.424 90.8741 160.261 88.3204 156.712C85.7668 153.162 86.5224 147.652 86.5224 147.652Z"
|
||||
fill="#FBBEBE"
|
||||
/>
|
||||
<path
|
||||
d="M269.44 198.047C269.44 198.047 260.45 209.878 252.99 209.974C245.531 210.071 241.152 214.958 244.694 218.011C248.235 221.065 286.831 216.268 286.831 216.268C286.831 216.268 287.195 201.728 283.851 199.605C280.507 197.481 269.44 198.047 269.44 198.047Z"
|
||||
fill="#444176"
|
||||
/>
|
||||
<path
|
||||
d="M366.103 212.249C366.103 212.249 367.479 221.619 363.228 228.009C358.977 234.399 370.908 236.864 370.908 236.864C370.908 236.864 379.577 239.278 380.209 230.714C380.84 222.15 380.635 215.601 379.821 214.903C379.007 214.206 378.329 209.389 378.329 209.389L366.103 212.249Z"
|
||||
fill="#444176"
|
||||
/>
|
||||
<path
|
||||
d="M230.012 229.946C230.012 229.946 229.698 199.64 259.687 187.321L266.651 174.072L269.991 165.934C271.201 162.987 273.103 160.375 275.538 158.32C277.973 156.265 280.867 154.828 283.976 154.131L289.378 152.918L291.048 145.881C291.938 142.119 293.91 138.701 296.72 136.047C299.53 133.393 303.056 131.62 306.862 130.946C310.147 130.336 313.48 130.025 316.821 130.016C318.299 130.039 319.747 130.443 321.023 131.19C322.298 131.937 323.359 133.001 324.102 134.279C324.102 134.279 352.514 133.965 353.774 149.434C353.774 149.434 356.618 163.636 360.722 166.163C360.722 166.163 367.666 164.586 374.61 174.688L382.186 200.573C382.186 200.573 402.073 219.197 396.074 236.224C396.074 236.224 387.867 247.904 346.198 240.01C346.198 240.01 301.689 253.573 277.997 234.972C277.993 234.996 238.537 242.571 230.012 229.946Z"
|
||||
fill="#A8A8A8"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M249.898 225.211C249.898 225.211 256.215 216.372 255.897 210.056C255.579 203.739 257.385 188.355 257.385 188.355"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M272.945 201.535C272.945 201.535 279.258 189.855 278.626 183.543C278.626 183.543 285.256 171.53 290.306 172.491"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M307.668 168.387C307.668 168.387 325.028 162.075 325.978 157.026"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M313.98 182.911C313.98 182.911 332.29 174.704 332.29 172.491H341.439C341.439 172.491 354.7 189.855 353.122 197.431"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M335.448 195.536C335.448 195.536 317.77 194.273 311.453 199.326C305.137 204.379 293.775 206.901 293.775 206.901C293.775 206.901 287.145 221.107 284.619 223.63C282.092 226.153 277.988 234.996 277.988 234.996"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M335.449 212.268C335.449 212.268 337.34 218.895 339.867 222.685C342.393 226.474 346.179 240.045 346.179 240.045"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M365.121 231.524C365.121 231.524 369.128 242.672 372.65 242.781"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M338.875 183.67C338.875 183.67 349.283 186.484 353.635 193.3C357.987 200.116 371.278 206.297 371.278 206.297"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M343.34 150.089C343.34 150.089 343.34 164.609 347.444 164.609C351.547 164.609 360.704 166.186 360.704 166.186"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M323.226 132.845C323.226 132.845 322.176 157.928 316.809 164.423"
|
||||
stroke="black"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<g opacity="0.05">
|
||||
<path
|
||||
opacity="0.05"
|
||||
d="M381.539 200.271L373.963 174.386C367.019 164.283 360.075 165.861 360.075 165.861C355.971 163.334 353.131 149.128 353.131 149.128C351.867 133.659 323.455 133.977 323.455 133.977C322.711 132.696 321.647 131.631 320.369 130.884C319.09 130.137 317.639 129.734 316.159 129.714C314.818 129.714 313.446 129.757 312.074 129.869C312.849 129.946 313.592 130.056 314.302 130.198L316.511 133.988C316.511 133.988 344.919 133.67 346.183 149.14C346.183 149.14 349.027 163.346 353.131 165.872C353.131 165.872 360.075 164.291 367.019 174.397L374.595 200.283C374.595 200.283 394.481 218.906 388.483 235.933C388.483 235.933 384.433 241.696 367.588 242.393C390.3 243.226 395.427 235.933 395.427 235.933C401.426 218.895 381.539 200.271 381.539 200.271Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.05"
|
||||
d="M270.419 234.678C263.737 235.88 256.98 236.62 250.195 236.891C258.104 237.193 266.331 236.337 271.632 235.608C271.221 235.294 270.818 234.992 270.419 234.678Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.05"
|
||||
d="M338.604 239.731C328.886 242.457 318.882 244.038 308.797 244.443C323.348 244.97 337.131 241.936 342.812 240.479C341.44 240.25 340.049 239.991 338.604 239.731Z"
|
||||
fill="black"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M86.3555 288.968C86.3555 288.968 95.0395 288.7 97.6552 286.836C100.271 284.972 111.009 282.748 111.66 285.736C112.311 288.724 124.707 300.597 114.903 300.678C105.099 300.759 92.1293 299.151 89.5175 297.559C86.9057 295.966 86.3555 288.968 86.3555 288.968Z"
|
||||
fill="#A8A8A8"
|
||||
/>
|
||||
<path
|
||||
opacity="0.2"
|
||||
d="M115.081 299.636C105.277 299.717 92.3037 298.109 89.6919 296.536C87.704 295.323 86.9096 290.975 86.6461 288.972H86.3555C86.3555 288.972 86.9057 295.974 89.5175 297.563C92.1293 299.151 105.103 300.76 114.903 300.682C117.736 300.659 118.712 299.651 118.658 298.159C118.267 299.058 117.186 299.62 115.081 299.636Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M298.068 267.19C298.068 267.19 287.842 265.144 285.117 262.416C282.393 259.688 270.121 254.921 268.757 258.328C267.393 261.734 250.354 273.324 261.941 275.378C273.527 277.432 289.206 278.09 292.616 276.742C296.026 275.393 298.068 267.19 298.068 267.19Z"
|
||||
fill="#A8A8A8"
|
||||
/>
|
||||
<path
|
||||
opacity="0.2"
|
||||
d="M261.938 274.091C273.528 276.133 289.203 276.804 292.613 275.451C295.206 274.413 297.011 269.426 297.724 267.104L298.065 267.178C298.065 267.178 296.019 275.355 292.613 276.719C289.207 278.083 273.528 277.401 261.938 275.355C258.594 274.766 257.637 273.378 257.997 271.627C258.284 272.781 259.45 273.661 261.938 274.091Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M200.148 44.5052H197.714C198.167 44.5052 198.6 44.3255 198.92 44.0058C199.24 43.686 199.419 43.2523 199.419 42.8001C199.419 42.3479 199.24 41.9142 198.92 41.5945C198.6 41.2747 198.167 41.0951 197.714 41.0951H177.998C177.546 41.0951 177.112 41.2747 176.792 41.5945C176.473 41.9142 176.293 42.3479 176.293 42.8001C176.293 43.2523 176.473 43.686 176.792 44.0058C177.112 44.3255 177.546 44.5052 177.998 44.5052H180.432C179.994 44.526 179.581 44.7146 179.279 45.0317C178.976 45.3488 178.808 45.7701 178.808 46.2082C178.808 46.6464 178.976 47.0677 179.279 47.3848C179.581 47.7019 179.994 47.8905 180.432 47.9113H177.025C176.573 47.9113 176.139 48.091 175.82 48.4107C175.5 48.7305 175.32 49.1642 175.32 49.6164C175.32 50.0686 175.5 50.5023 175.82 50.822C176.139 51.1418 176.573 51.3214 177.025 51.3214H196.742C197.194 51.3214 197.628 51.1418 197.947 50.822C198.267 50.5023 198.447 50.0686 198.447 49.6164C198.447 49.1642 198.267 48.7305 197.947 48.4107C197.628 48.091 197.194 47.9113 196.742 47.9113H200.148C200.378 47.9223 200.609 47.8864 200.825 47.8058C201.041 47.7252 201.239 47.6015 201.406 47.4423C201.573 47.283 201.706 47.0916 201.796 46.8795C201.887 46.6673 201.934 46.439 201.934 46.2082C201.934 45.9775 201.887 45.7491 201.796 45.537C201.706 45.3249 201.573 45.1334 201.406 44.9742C201.239 44.815 201.041 44.6913 200.825 44.6107C200.609 44.5301 200.378 44.4942 200.148 44.5052Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M276.726 104.495H274.292C274.73 104.474 275.143 104.286 275.445 103.969C275.747 103.651 275.916 103.23 275.916 102.792C275.916 102.354 275.747 101.932 275.445 101.615C275.143 101.298 274.73 101.11 274.292 101.089H254.557C254.326 101.078 254.096 101.114 253.88 101.194C253.663 101.275 253.466 101.399 253.299 101.558C253.132 101.717 252.999 101.909 252.908 102.121C252.817 102.333 252.77 102.561 252.77 102.792C252.77 103.023 252.817 103.251 252.908 103.463C252.999 103.675 253.132 103.867 253.299 104.026C253.466 104.185 253.663 104.309 253.88 104.39C254.096 104.47 254.326 104.506 254.557 104.495H256.99C256.552 104.516 256.14 104.704 255.837 105.022C255.535 105.339 255.366 105.76 255.366 106.198C255.366 106.636 255.535 107.058 255.837 107.375C256.14 107.692 256.552 107.88 256.99 107.901H253.584C253.132 107.901 252.698 108.081 252.378 108.401C252.059 108.72 251.879 109.154 251.879 109.606C251.879 110.058 252.059 110.492 252.378 110.812C252.698 111.132 253.132 111.311 253.584 111.311H273.296C273.749 111.311 274.182 111.132 274.502 110.812C274.822 110.492 275.001 110.058 275.001 109.606C275.001 109.154 274.822 108.72 274.502 108.401C274.182 108.081 273.749 107.901 273.296 107.901H276.726C276.956 107.912 277.187 107.876 277.403 107.796C277.619 107.715 277.817 107.591 277.984 107.432C278.151 107.273 278.284 107.081 278.374 106.869C278.465 106.657 278.512 106.429 278.512 106.198C278.512 105.967 278.465 105.739 278.374 105.527C278.284 105.315 278.151 105.123 277.984 104.964C277.817 104.805 277.619 104.681 277.403 104.601C277.187 104.52 276.956 104.484 276.726 104.495Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M163.253 93.3969H160.82C161.05 93.4079 161.281 93.372 161.497 93.2914C161.713 93.2107 161.911 93.0871 162.078 92.9278C162.245 92.7686 162.378 92.5771 162.468 92.365C162.559 92.1529 162.606 91.9245 162.606 91.6938C162.606 91.463 162.559 91.2347 162.468 91.0226C162.378 90.8105 162.245 90.619 162.078 90.4598C161.911 90.3005 161.713 90.1769 161.497 90.0962C161.281 90.0156 161.05 89.9797 160.82 89.9907H141.1C140.869 89.9797 140.639 90.0156 140.423 90.0962C140.206 90.1769 140.009 90.3005 139.842 90.4598C139.675 90.619 139.542 90.8105 139.451 91.0226C139.36 91.2347 139.313 91.463 139.313 91.6938C139.313 91.9245 139.36 92.1529 139.451 92.365C139.542 92.5771 139.675 92.7686 139.842 92.9278C140.009 93.0871 140.206 93.2107 140.423 93.2914C140.639 93.372 140.869 93.4079 141.1 93.3969H143.533C143.303 93.3859 143.072 93.4218 142.856 93.5024C142.64 93.583 142.442 93.7067 142.275 93.866C142.108 94.0252 141.975 94.2166 141.884 94.4288C141.794 94.6409 141.747 94.8692 141.747 95.1C141.747 95.3307 141.794 95.5591 141.884 95.7712C141.975 95.9833 142.108 96.1748 142.275 96.334C142.442 96.4932 142.64 96.6169 142.856 96.6975C143.072 96.7782 143.303 96.8141 143.533 96.8031H140.123C139.671 96.8031 139.237 96.9827 138.917 97.3025C138.598 97.6222 138.418 98.0559 138.418 98.5081C138.418 98.9603 138.598 99.394 138.917 99.7137C139.237 100.033 139.671 100.213 140.123 100.213H159.839C160.292 100.213 160.725 100.033 161.045 99.7137C161.365 99.394 161.544 98.9603 161.544 98.5081C161.544 98.0559 161.365 97.6222 161.045 97.3025C160.725 96.9827 160.292 96.8031 159.839 96.8031H163.246C163.476 96.8141 163.706 96.7782 163.923 96.6975C164.139 96.6169 164.336 96.4932 164.503 96.334C164.67 96.1748 164.803 95.9833 164.894 95.7712C164.985 95.5591 165.032 95.3307 165.032 95.1C165.032 94.8692 164.985 94.6409 164.894 94.4288C164.803 94.2166 164.67 94.0252 164.503 93.866C164.336 93.7067 164.139 93.583 163.923 93.5024C163.706 93.4218 163.476 93.3859 163.246 93.3969H163.253Z"
|
||||
fill="#177DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_2231_35430"
|
||||
x1="-281042"
|
||||
y1="25396.1"
|
||||
x2="-303694"
|
||||
y2="25396.1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#808080" stopOpacity="0.25" />
|
||||
<stop offset="0.54" stopColor="#808080" stopOpacity="0.12" />
|
||||
<stop offset="1" stopColor="#808080" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_2231_35430">
|
||||
<rect width="400" height="322.65" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default SomethingWentWrong;
|
28
frontend/src/assets/UnAuthorized.tsx
Normal file
28
frontend/src/assets/UnAuthorized.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
function UnAuthorized(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
width="137"
|
||||
height="137"
|
||||
viewBox="0 0 137 137"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M68.2934 136.587C106.011 136.587 136.587 106.011 136.587 68.2934C136.587 30.576 106.011 0 68.2934 0C30.576 0 0 30.576 0 68.2934C0 106.011 30.576 136.587 68.2934 136.587Z"
|
||||
fill="#002B76"
|
||||
/>
|
||||
<path
|
||||
d="M87.2145 63.9368V50.8696C87.2163 48.3836 86.7283 45.9217 85.7782 43.6245C84.8281 41.3273 83.4347 39.2398 81.6776 37.4813C79.9204 35.7229 77.8339 34.3279 75.5374 33.3762C73.2408 32.4244 70.7793 31.9346 68.2933 31.9346C65.8074 31.9346 63.3458 32.4244 61.0493 33.3762C58.7528 34.3279 56.6663 35.7229 54.9091 37.4813C53.1519 39.2398 51.7585 41.3273 50.8085 43.6245C49.8584 45.9217 49.3703 48.3836 49.3721 50.8696V63.9368C46.8429 64.0507 44.455 65.1352 42.705 66.9648C40.9551 68.7945 39.9778 71.2282 39.9766 73.76V104.639H96.6101V73.76C96.6089 71.2282 95.6316 68.7945 93.8817 66.9648C92.1317 65.1352 89.7437 64.0507 87.2145 63.9368ZM68.2933 38.6103C71.5435 38.6141 74.6593 39.9069 76.9575 42.2052C79.2556 44.5035 80.5482 47.6194 80.5518 50.8696V63.914H56.0349V50.8696C56.0385 47.6194 57.3311 44.5035 59.6292 42.2052C61.9273 39.9069 65.0432 38.6141 68.2933 38.6103Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M73.3844 79.1861C73.3847 78.3083 73.158 77.4455 72.7264 76.6812C72.2948 75.9169 71.6729 75.2772 70.9211 74.8242C70.1693 74.3712 69.3132 74.1202 68.4358 74.0957C67.5584 74.0712 66.6896 74.274 65.9137 74.6844C65.1378 75.0947 64.4812 75.6988 64.0077 76.4378C63.5341 77.1768 63.2596 78.0257 63.211 78.9021C63.1623 79.7784 63.341 80.6525 63.7298 81.4394C64.1186 82.2263 64.7043 82.8993 65.43 83.3931V94.4581H71.1566V83.3931C71.8431 82.9269 72.405 82.2998 72.7933 81.5664C73.1816 80.8331 73.3845 80.0159 73.3844 79.1861Z"
|
||||
fill="#002B76"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default UnAuthorized;
|
@ -1,10 +1,19 @@
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import NotFoundImage from 'assets/NotFound';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { LOGGED_IN } from 'types/actions/app';
|
||||
|
||||
import { Button, Container, Text, TextContainer } from './styles';
|
||||
|
||||
function NotFound(): JSX.Element {
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const isLoggedIn = getLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<NotFoundImage />
|
||||
@ -14,7 +23,20 @@ function NotFound(): JSX.Element {
|
||||
<Text>Page Not Found</Text>
|
||||
</TextContainer>
|
||||
|
||||
<Button to={ROUTES.APPLICATION} tabIndex={0}>
|
||||
<Button
|
||||
onClick={(): void => {
|
||||
if (isLoggedIn) {
|
||||
dispatch({
|
||||
type: LOGGED_IN,
|
||||
payload: {
|
||||
isLoggedIn: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
to={ROUTES.APPLICATION}
|
||||
tabIndex={0}
|
||||
>
|
||||
Return To Metrics Page
|
||||
</Button>
|
||||
</Container>
|
||||
|
40
frontend/src/components/WelcomeLeftContainer/index.tsx
Normal file
40
frontend/src/components/WelcomeLeftContainer/index.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { Card, Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Container, LeftContainer, Logo } from './styles';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function WelcomeLeftContainer({
|
||||
version,
|
||||
children,
|
||||
}: WelcomeLeftContainerProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<LeftContainer direction="vertical">
|
||||
<Space align="center">
|
||||
<Logo src="signoz-signup.svg" alt="logo" />
|
||||
<Title style={{ fontSize: '46px', margin: 0 }}>SigNoz</Title>
|
||||
</Space>
|
||||
<Typography>{t('monitor_signup')}</Typography>
|
||||
<Card
|
||||
style={{ width: 'max-content' }}
|
||||
bodyStyle={{ padding: '1px 8px', width: '100%' }}
|
||||
>
|
||||
SigNoz {version}
|
||||
</Card>
|
||||
</LeftContainer>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface WelcomeLeftContainerProps {
|
||||
version: string;
|
||||
children: React.ReactChild;
|
||||
}
|
||||
|
||||
export default WelcomeLeftContainer;
|
22
frontend/src/components/WelcomeLeftContainer/styles.ts
Normal file
22
frontend/src/components/WelcomeLeftContainer/styles.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Space } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const LeftContainer = styled(Space)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const Logo = styled.img`
|
||||
width: 60px;
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
@ -7,3 +7,4 @@ export const AUTH0_REDIRECT_PATH = '/redirect';
|
||||
export const DEFAULT_AUTH0_APP_REDIRECTION_PATH = ROUTES.APPLICATION;
|
||||
|
||||
export const IS_SIDEBAR_COLLAPSED = 'isSideBarCollapsed';
|
||||
export const INVITE_MEMBERS_HASH = '#invite-team-members';
|
||||
|
@ -1 +0,0 @@
|
||||
export const IS_LOGGED_IN = 'isLoggedIn';
|
@ -1,3 +1,6 @@
|
||||
export enum LOCALSTORAGE {
|
||||
METRICS_TIME_IN_DURATION = 'metricsTimeDurations',
|
||||
IS_LOGGED_IN = 'IS_LOGGED_IN',
|
||||
AUTH_TOKEN = 'AUTH_TOKEN',
|
||||
REFRESH_AUTH_TOKEN = 'REFRESH_AUTH_TOKEN',
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
const ROUTES = {
|
||||
SIGN_UP: '/signup',
|
||||
LOGIN: '/login',
|
||||
SERVICE_METRICS: '/application/:servicename',
|
||||
SERVICE_MAP: '/service-map',
|
||||
TRACE: '/trace',
|
||||
@ -20,6 +21,13 @@ const ROUTES = {
|
||||
ALL_ERROR: '/errors',
|
||||
ERROR_DETAIL: '/errors/:serviceName/:errorType',
|
||||
VERSION: '/status',
|
||||
MY_SETTINGS: '/my-settings',
|
||||
ORG_SETTINGS: '/settings/org-settings',
|
||||
SOMETHING_WENT_WRONG: '/something-went-wrong',
|
||||
UN_AUTHORIZED: '/un-authorized',
|
||||
NOT_FOUND: '/not-found',
|
||||
HOME_PAGE: '/',
|
||||
PASSWORD_RESET: '/password-reset',
|
||||
};
|
||||
|
||||
export default ROUTES;
|
||||
|
@ -2,16 +2,22 @@
|
||||
import { Button, notification, Table } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Channels, PayloadProps } from 'types/api/channels/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import Delete from './Delete';
|
||||
|
||||
function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
||||
const [notifications, Element] = notification.useNotification();
|
||||
const [channels, setChannels] = useState<Channels[]>(allChannels);
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [action] = useComponentPermission(['action'], role);
|
||||
|
||||
const onClickEditHandler = useCallback((id: string) => {
|
||||
history.replace(
|
||||
@ -32,7 +38,10 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
if (action) {
|
||||
columns.push({
|
||||
title: 'Action',
|
||||
dataIndex: 'id',
|
||||
key: 'action',
|
||||
@ -45,8 +54,8 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
||||
<Delete id={id} setChannels={setChannels} notifications={notifications} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -4,16 +4,25 @@ import getAll from 'api/channels/getAll';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import AlertChannlesComponent from './AlertChannels';
|
||||
import AlertChannelsComponent from './AlertChannels';
|
||||
import { Button, ButtonContainer } from './styles';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
function AlertChannels(): JSX.Element {
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [addNewChannelPermission] = useComponentPermission(
|
||||
['add_new_channel'],
|
||||
role,
|
||||
);
|
||||
const onToggleHandler = useCallback(() => {
|
||||
history.push(ROUTES.CHANNELS_NEW);
|
||||
}, []);
|
||||
@ -41,13 +50,15 @@ function AlertChannels(): JSX.Element {
|
||||
url="https://signoz.io/docs/userguide/alerts-management/#setting-notification-channel"
|
||||
/>
|
||||
|
||||
<Button onClick={onToggleHandler} icon={<PlusOutlined />}>
|
||||
New Alert Channel
|
||||
</Button>
|
||||
{addNewChannelPermission && (
|
||||
<Button onClick={onToggleHandler} icon={<PlusOutlined />}>
|
||||
New Alert Channel
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</ButtonContainer>
|
||||
|
||||
<AlertChannlesComponent allChannels={payload} />
|
||||
<AlertChannelsComponent allChannels={payload} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { notification } from 'antd';
|
||||
import getUserLatestVersion from 'api/user/getLatestVersion';
|
||||
import getUserVersion from 'api/user/getVersion';
|
||||
import ROUTES from 'constants/routes';
|
||||
import TopNav from 'container/Header';
|
||||
import Header from 'container/Header';
|
||||
import SideNav from 'container/SideNav';
|
||||
import history from 'lib/history';
|
||||
import React, { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import TopNav from 'container/TopNav';
|
||||
import React, { ReactNode, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@ -21,15 +20,13 @@ import {
|
||||
} from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { Content, Layout } from './styles';
|
||||
import { ChildrenContainer, Layout } from './styles';
|
||||
|
||||
function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isSignUpPage, setIsSignUpPage] = useState(ROUTES.SIGN_UP === pathname);
|
||||
|
||||
const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([
|
||||
{
|
||||
queryFn: getUserVersion,
|
||||
@ -57,23 +54,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn) {
|
||||
setIsSignUpPage(true);
|
||||
history.push(ROUTES.SIGN_UP);
|
||||
} else if (isSignUpPage) {
|
||||
setIsSignUpPage(false);
|
||||
}
|
||||
}, [isLoggedIn, isSignUpPage]);
|
||||
|
||||
const latestCurrentCounter = useRef(0);
|
||||
const latestVersionCounter = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn && pathname === ROUTES.SIGN_UP) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
|
||||
if (
|
||||
getUserLatestVersionResponse.isFetched &&
|
||||
getUserLatestVersionResponse.isError &&
|
||||
@ -153,14 +137,19 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
getUserLatestVersionResponse.isSuccess,
|
||||
]);
|
||||
|
||||
const isToDisplayLayout = isLoggedIn;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
{!isSignUpPage && <SideNav />}
|
||||
{isToDisplayLayout && <Header />}
|
||||
<Layout>
|
||||
<Content>
|
||||
{!isSignUpPage && <TopNav />}
|
||||
{children}
|
||||
</Content>
|
||||
{isToDisplayLayout && <SideNav />}
|
||||
<Layout.Content>
|
||||
<ChildrenContainer>
|
||||
{isToDisplayLayout && <TopNav />}
|
||||
{children}
|
||||
</ChildrenContainer>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
|
@ -3,16 +3,12 @@ import styled from 'styled-components';
|
||||
|
||||
export const Layout = styled(LayoutComponent)`
|
||||
&&& {
|
||||
min-height: 100vh;
|
||||
min-height: 91vh;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Content = styled(LayoutComponent.Content)`
|
||||
&&& {
|
||||
margin: 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
export const ChildrenContainer = styled.div`
|
||||
margin: 0 1rem;
|
||||
`;
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { Button, Col, Modal, notification, Row, Typography } from 'antd';
|
||||
import setRetentionApi from 'api/settings/setRetention';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import find from 'lodash-es/find';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import {
|
||||
IDiskType,
|
||||
PayloadProps as GetDisksPayload,
|
||||
} from 'types/api/disks/getDisks';
|
||||
import { PayloadProps as GetRetentionPayload } from 'types/api/settings/getRetention';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import Retention from './Retention';
|
||||
import { ButtonContainer, ErrorText, ErrorTextContainer } from './styles';
|
||||
@ -26,6 +30,12 @@ function GeneralSettings({
|
||||
const [availableDisks] = useState<IDiskType[]>(getAvailableDiskPayload);
|
||||
|
||||
const [currentTTLValues, setCurrentTTLValues] = useState(ttlValuesPayload);
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [setRetentionPermission] = useComponentPermission(
|
||||
['set_retention_period'],
|
||||
role,
|
||||
);
|
||||
|
||||
const [
|
||||
metricsTotalRetentionPeriod,
|
||||
@ -66,8 +76,14 @@ function GeneralSettings({
|
||||
};
|
||||
|
||||
const onClickSaveHandler = useCallback(() => {
|
||||
if (!setRetentionPermission) {
|
||||
notification.error({
|
||||
message: `Sorry you don't have permission to make these changes`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
onModalToggleHandler();
|
||||
}, []);
|
||||
}, [setRetentionPermission]);
|
||||
|
||||
const s3Enabled = useMemo(
|
||||
() => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'),
|
||||
|
79
frontend/src/container/Header/CurrentOrganization/index.tsx
Normal file
79
frontend/src/container/Header/CurrentOrganization/index.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import { PlusSquareOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Typography } from 'antd';
|
||||
import { INVITE_MEMBERS_HASH } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import {
|
||||
InviteMembersContainer,
|
||||
OrganizationContainer,
|
||||
OrganizationWrapper,
|
||||
} from '../styles';
|
||||
|
||||
function CurrentOrganization({
|
||||
onToggle,
|
||||
}: CurrentOrganizationProps): JSX.Element {
|
||||
const { org, role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [currentOrgSettings, inviteMembers] = useComponentPermission(
|
||||
['current_org_settings', 'invite_members'],
|
||||
role,
|
||||
);
|
||||
|
||||
// just to make sure role and org are present in the reducer
|
||||
if (!org || !role) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const orgName = org[0].name;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>CURRENT ORGANIZATION</Typography>
|
||||
|
||||
<OrganizationContainer>
|
||||
<OrganizationWrapper>
|
||||
<Avatar shape="square" size="large">
|
||||
{orgName}
|
||||
</Avatar>
|
||||
<Typography>{orgName}</Typography>
|
||||
</OrganizationWrapper>
|
||||
|
||||
{currentOrgSettings && (
|
||||
<Typography.Link
|
||||
onClick={(): void => {
|
||||
onToggle();
|
||||
history.push(ROUTES.ORG_SETTINGS);
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</Typography.Link>
|
||||
)}
|
||||
</OrganizationContainer>
|
||||
|
||||
{inviteMembers && (
|
||||
<InviteMembersContainer>
|
||||
<PlusSquareOutlined />
|
||||
<Typography.Link
|
||||
onClick={(): void => {
|
||||
onToggle();
|
||||
history.push(`${ROUTES.ORG_SETTINGS}${INVITE_MEMBERS_HASH}`);
|
||||
}}
|
||||
>
|
||||
Invite Members
|
||||
</Typography.Link>
|
||||
</InviteMembersContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface CurrentOrganizationProps {
|
||||
onToggle: VoidFunction;
|
||||
}
|
||||
|
||||
export default CurrentOrganization;
|
45
frontend/src/container/Header/SignedInAs/index.tsx
Normal file
45
frontend/src/container/Header/SignedInAs/index.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { Avatar, Typography } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { AvatarContainer, ManageAccountLink, Wrapper } from '../styles';
|
||||
|
||||
function SignedInAS(): JSX.Element {
|
||||
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
if (!user) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const { name, email } = user;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography>SIGNED IN AS</Typography>
|
||||
<Wrapper>
|
||||
<AvatarContainer>
|
||||
<Avatar shape="circle" size="large">
|
||||
{name[0]}
|
||||
</Avatar>
|
||||
<div>
|
||||
<Typography>{name}</Typography>
|
||||
<Typography>{email}</Typography>
|
||||
</div>
|
||||
</AvatarContainer>
|
||||
<ManageAccountLink
|
||||
onClick={(): void => {
|
||||
history.push(ROUTES.MY_SETTINGS);
|
||||
}}
|
||||
>
|
||||
Manage Account
|
||||
</ManageAccountLink>
|
||||
</Wrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignedInAS;
|
@ -1,49 +1,174 @@
|
||||
import { Col } from 'antd';
|
||||
import {
|
||||
CaretDownFilled,
|
||||
CaretUpFilled,
|
||||
LogoutOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
Avatar,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Layout,
|
||||
Menu,
|
||||
Space,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import remove from 'api/browser/localstorage/remove';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
import setTheme, { AppMode } from 'lib/theme/setTheme';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { ToggleDarkMode } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { LOGGED_IN, UPDATE_USER_ORG_ROLE } from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import ShowBreadcrumbs from './Breadcrumbs';
|
||||
import DateTimeSelector from './DateTimeSelection';
|
||||
import { Container } from './styles';
|
||||
import CurrentOrganization from './CurrentOrganization';
|
||||
import SignedInAS from './SignedInAs';
|
||||
import {
|
||||
Container,
|
||||
LogoutContainer,
|
||||
MenuContainer,
|
||||
ToggleButton,
|
||||
} from './styles';
|
||||
|
||||
const routesToSkip = [
|
||||
ROUTES.SETTINGS,
|
||||
ROUTES.LIST_ALL_ALERT,
|
||||
ROUTES.TRACE_DETAIL,
|
||||
ROUTES.ALL_CHANNELS,
|
||||
];
|
||||
function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
const { isDarkMode, user } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
);
|
||||
const [isUserDropDownOpen, setIsUserDropDownOpen] = useState<boolean>();
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
function TopNav(): JSX.Element | null {
|
||||
if (history.location.pathname === ROUTES.SIGN_UP) {
|
||||
return null;
|
||||
}
|
||||
const onToggleThemeHandler = useCallback(() => {
|
||||
const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode';
|
||||
setTheme(preMode);
|
||||
|
||||
const checkRouteExists = (currentPath: string): boolean => {
|
||||
for (let i = 0; i < routesToSkip.length; i += 1) {
|
||||
if (
|
||||
matchPath(currentPath, { path: routesToSkip[i], exact: true, strict: true })
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
const id: AppMode = preMode;
|
||||
const { head } = document;
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = !isDarkMode ? '/css/antd.dark.min.css' : '/css/antd.min.css';
|
||||
link.media = 'all';
|
||||
link.id = id;
|
||||
head.appendChild(link);
|
||||
|
||||
link.onload = (): void => {
|
||||
toggleDarkMode();
|
||||
const prevNode = document.getElementById('appMode');
|
||||
prevNode?.remove();
|
||||
};
|
||||
}, [toggleDarkMode, isDarkMode]);
|
||||
|
||||
const onArrowClickHandler: VoidFunction = () => {
|
||||
setIsUserDropDownOpen((state) => !state);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Col span={16}>
|
||||
<ShowBreadcrumbs />
|
||||
</Col>
|
||||
const onClickLogoutHandler = (): void => {
|
||||
remove(LOCALSTORAGE.AUTH_TOKEN);
|
||||
remove(LOCALSTORAGE.IS_LOGGED_IN);
|
||||
remove(LOCALSTORAGE.REFRESH_AUTH_TOKEN);
|
||||
dispatch({
|
||||
type: LOGGED_IN,
|
||||
payload: {
|
||||
isLoggedIn: false,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_USER_ORG_ROLE,
|
||||
payload: {
|
||||
org: null,
|
||||
role: null,
|
||||
},
|
||||
});
|
||||
|
||||
{!checkRouteExists(history.location.pathname) && (
|
||||
<Col span={8}>
|
||||
<DateTimeSelector />
|
||||
</Col>
|
||||
)}
|
||||
</Container>
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<MenuContainer>
|
||||
<Menu.ItemGroup>
|
||||
<SignedInAS />
|
||||
<Divider />
|
||||
<CurrentOrganization onToggle={onArrowClickHandler} />
|
||||
<Divider />
|
||||
<LogoutContainer>
|
||||
<LogoutOutlined />
|
||||
<div
|
||||
tabIndex={0}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter' || e.key === 'Space') {
|
||||
onClickLogoutHandler();
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
onClick={onClickLogoutHandler}
|
||||
>
|
||||
<Typography>Logout</Typography>
|
||||
</div>
|
||||
</LogoutContainer>
|
||||
</Menu.ItemGroup>
|
||||
</MenuContainer>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout.Header
|
||||
style={{
|
||||
paddingLeft: '1.125rem',
|
||||
paddingRight: '1.125rem',
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<NavLink
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
||||
to={ROUTES.APPLICATION}
|
||||
>
|
||||
<img src="/signoz.svg" alt="SigNoz" />
|
||||
<Typography.Title style={{ margin: 0 }} level={4}>
|
||||
SigNoz
|
||||
</Typography.Title>
|
||||
</NavLink>
|
||||
<Space align="center">
|
||||
<ToggleButton
|
||||
checked={isDarkMode}
|
||||
onChange={onToggleThemeHandler}
|
||||
defaultChecked={isDarkMode}
|
||||
checkedChildren="🌜"
|
||||
unCheckedChildren="🌞"
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
onVisibleChange={onArrowClickHandler}
|
||||
trigger={['click']}
|
||||
overlay={menu}
|
||||
visible={isUserDropDownOpen}
|
||||
>
|
||||
<Space>
|
||||
<Avatar shape="circle">{user?.name[0]}</Avatar>
|
||||
{!isUserDropDownOpen ? <CaretDownFilled /> : <CaretUpFilled />}
|
||||
</Space>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Container>
|
||||
</Layout.Header>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopNav;
|
||||
interface DispatchProps {
|
||||
toggleDarkMode: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch),
|
||||
});
|
||||
|
||||
type Props = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(HeaderContainer);
|
||||
|
@ -1,9 +1,67 @@
|
||||
import { Row } from 'antd';
|
||||
import { Menu, Switch, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled(Row)`
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
export const AvatarContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
`;
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
export const ManageAccountLink = styled(Typography.Link)`
|
||||
width: 6rem;
|
||||
text-align: end;
|
||||
`;
|
||||
|
||||
export const OrganizationWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
export const OrganizationContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const InviteMembersContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin-top: 1.25rem;
|
||||
`;
|
||||
|
||||
export const LogoutContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const MenuContainer = styled(Menu)`
|
||||
padding: 1rem;
|
||||
`;
|
||||
|
||||
export interface DarkModeProps {
|
||||
checked?: boolean;
|
||||
defaultChecked?: boolean;
|
||||
}
|
||||
|
||||
export const ToggleButton = styled(Switch)<DarkModeProps>`
|
||||
&&& {
|
||||
margin-top: 2rem;
|
||||
min-height: 8vh;
|
||||
background: ${({ checked }): string => (checked === false ? 'grey' : '')};
|
||||
}
|
||||
.ant-switch-inner {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
`;
|
||||
|
7
frontend/src/container/IsRouteAccessible/index.tsx
Normal file
7
frontend/src/container/IsRouteAccessible/index.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
function IsRouteAccessible(): JSX.Element {
|
||||
return <div>asd</div>;
|
||||
}
|
||||
|
||||
export default IsRouteAccessible;
|
@ -4,14 +4,18 @@ import { notification, Tag, Typography } from 'antd';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import useInterval from 'hooks/useInterval';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import DeleteAlert from './DeleteAlert';
|
||||
import { Button, ButtonContainer } from './styles';
|
||||
@ -20,6 +24,11 @@ import Status from './TableComponents/Status';
|
||||
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
const [data, setData] = useState<Alerts[]>(allAlertRules || []);
|
||||
const { t } = useTranslation('common');
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [addNewAlert, action] = useComponentPermission(
|
||||
['add_new_alert', 'action'],
|
||||
role,
|
||||
);
|
||||
|
||||
useInterval(() => {
|
||||
(async (): Promise<void> => {
|
||||
@ -112,7 +121,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
if (action) {
|
||||
columns.push({
|
||||
title: 'Action',
|
||||
dataIndex: 'id',
|
||||
key: 'action',
|
||||
@ -124,12 +136,11 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
<Button onClick={(): void => onEditHandler(id.toString())} type="link">
|
||||
Edit
|
||||
</Button>
|
||||
{/* <Button type="link">Pause</Button> */}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -143,9 +154,11 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button onClick={onClickNewAlertHandler} icon={<PlusOutlined />}>
|
||||
New Alert
|
||||
</Button>
|
||||
{addNewAlert && (
|
||||
<Button onClick={onClickNewAlertHandler} icon={<PlusOutlined />}>
|
||||
New Alert
|
||||
</Button>
|
||||
)}
|
||||
</ButtonContainer>
|
||||
|
||||
<Table rowKey="id" columns={columns} dataSource={data} />
|
||||
|
@ -13,6 +13,7 @@ import { AxiosError } from 'axios';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import ROUTES from 'constants/routes';
|
||||
import SearchFilter from 'container/ListOfDashboard/SearchFilter';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -20,6 +21,7 @@ import { useSelector } from 'react-redux';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import DashboardReducer from 'types/reducer/dashboards';
|
||||
|
||||
import ImportJSON from './ImportJSON';
|
||||
@ -34,6 +36,12 @@ function ListOfAllDashboard(): JSX.Element {
|
||||
const { dashboards, loading } = useSelector<AppState, DashboardReducer>(
|
||||
(state) => state.dashboards,
|
||||
);
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [action, createNewDashboard] = useComponentPermission(
|
||||
['action', 'create_new_dashboards'],
|
||||
role,
|
||||
);
|
||||
|
||||
const { t } = useTranslation('dashboard');
|
||||
const [
|
||||
@ -89,13 +97,16 @@ function ListOfAllDashboard(): JSX.Element {
|
||||
},
|
||||
render: DateComponent,
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
if (action) {
|
||||
columns.push({
|
||||
title: 'Action',
|
||||
dataIndex: '',
|
||||
key: 'x',
|
||||
render: DeleteButton,
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
const data: Data[] = (filteredDashboards || dashboards).map((e) => ({
|
||||
createdBy: e.created_at,
|
||||
@ -165,19 +176,21 @@ function ListOfAllDashboard(): JSX.Element {
|
||||
const menu = useMemo(
|
||||
() => (
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={onNewDashboardHandler}
|
||||
disabled={loading}
|
||||
key={t('create_dashboard').toString()}
|
||||
>
|
||||
{t('create_dashboard')}
|
||||
</Menu.Item>
|
||||
{createNewDashboard && (
|
||||
<Menu.Item
|
||||
onClick={onNewDashboardHandler}
|
||||
disabled={loading}
|
||||
key={t('create_dashboard').toString()}
|
||||
>
|
||||
{t('create_dashboard')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item onClick={onModalHandler} key={t('import_json').toString()}>
|
||||
{t('import_json')}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
),
|
||||
[loading, onNewDashboardHandler, t],
|
||||
[createNewDashboard, loading, onNewDashboardHandler, t],
|
||||
);
|
||||
|
||||
const GetHeader = useMemo(
|
||||
|
123
frontend/src/container/Login/index.tsx
Normal file
123
frontend/src/container/Login/index.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import { Button, Input, notification, Space, Typography } from 'antd';
|
||||
import loginApi from 'api/user/login';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function Login(): JSX.Element {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [password, setPassword] = useState<string>('');
|
||||
|
||||
const onChangeHandler = (
|
||||
setFunc: React.Dispatch<React.SetStateAction<string>>,
|
||||
value: string,
|
||||
): void => {
|
||||
setFunc(value);
|
||||
};
|
||||
|
||||
const onSubmitHandler: React.FormEventHandler<HTMLFormElement> = async (
|
||||
event,
|
||||
) => {
|
||||
try {
|
||||
event.preventDefault();
|
||||
event.persist();
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await loginApi({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
await afterLogin(
|
||||
response.payload.userId,
|
||||
response.payload.accessJwt,
|
||||
response.payload.refreshJwt,
|
||||
);
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
notification.error({
|
||||
message: 'Something went wrong',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormWrapper>
|
||||
<FormContainer onSubmit={onSubmitHandler}>
|
||||
<Title level={4}>Login to SigNoz</Title>
|
||||
<ParentContainer>
|
||||
<Label htmlFor="signupEmail">Email</Label>
|
||||
<Input
|
||||
placeholder="mike@netflix.com"
|
||||
type="email"
|
||||
autoFocus
|
||||
required
|
||||
id="loginEmail"
|
||||
onChange={(event): void => onChangeHandler(setEmail, event.target.value)}
|
||||
value={email}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</ParentContainer>
|
||||
<ParentContainer>
|
||||
<Label htmlFor="Password">Password</Label>
|
||||
<Input.Password
|
||||
required
|
||||
id="currentPassword"
|
||||
onChange={(event): void =>
|
||||
onChangeHandler(setPassword, event.target.value)
|
||||
}
|
||||
disabled={isLoading}
|
||||
value={password}
|
||||
/>
|
||||
</ParentContainer>
|
||||
<Space
|
||||
style={{ marginTop: '1.3125rem' }}
|
||||
align="start"
|
||||
direction="vertical"
|
||||
size={20}
|
||||
>
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
data-attr="signup"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
<Typography.Link
|
||||
onClick={(): void => {
|
||||
history.push(ROUTES.SIGN_UP);
|
||||
}}
|
||||
style={{ fontWeight: 700 }}
|
||||
>
|
||||
Create an account
|
||||
</Typography.Link>
|
||||
|
||||
<Space direction="vertical" size={7}>
|
||||
<Typography style={{ color: '#ACACAC' }}>Forgot Password?</Typography>
|
||||
|
||||
<Typography style={{ color: '#ACACAC' }}>
|
||||
Ask your admin to reset password and send a new invite link
|
||||
</Typography>
|
||||
</Space>
|
||||
</Space>
|
||||
</FormContainer>
|
||||
</FormWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
28
frontend/src/container/Login/styles.ts
Normal file
28
frontend/src/container/Login/styles.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Card } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FormWrapper = styled(Card)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 432px;
|
||||
flex: 1;
|
||||
align-items: flex-start;
|
||||
`;
|
||||
|
||||
export const Label = styled.label`
|
||||
margin-bottom: 11px;
|
||||
margin-top: 19px;
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
line-height: 24px;
|
||||
`;
|
||||
|
||||
export const FormContainer = styled.form`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
`;
|
||||
|
||||
export const ParentContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
147
frontend/src/container/MySettings/Password/index.tsx
Normal file
147
frontend/src/container/MySettings/Password/index.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import { Button, notification, Space, Typography } from 'antd';
|
||||
import changeMyPassword from 'api/user/changeMyPassword';
|
||||
import { isPasswordNotValidMessage, isPasswordValid } from 'pages/SignUp/utils';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { Password } from '../styles';
|
||||
|
||||
function PasswordContainer(): JSX.Element {
|
||||
const [currentPassword, setCurrentPassword] = useState<string>('');
|
||||
const [updatePassword, setUpdatePassword] = useState<string>('');
|
||||
const { t } = useTranslation(['routes', 'settings', 'common']);
|
||||
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isPasswordPolicyError, setIsPasswordPolicyError] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
|
||||
const defaultPlaceHolder = t('input_password', {
|
||||
ns: 'settings',
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const onChangePasswordClickHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (!isPasswordValid(currentPassword)) {
|
||||
setIsPasswordPolicyError(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { statusCode, error } = await changeMyPassword({
|
||||
newPassword: updatePassword,
|
||||
oldPassword: currentPassword,
|
||||
userId: user.userId,
|
||||
});
|
||||
|
||||
if (statusCode === 200) {
|
||||
notification.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message:
|
||||
error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="large">
|
||||
<Typography.Title level={3}>
|
||||
{t('change_password', {
|
||||
ns: 'settings',
|
||||
})}
|
||||
</Typography.Title>
|
||||
<Space direction="vertical">
|
||||
<Typography>
|
||||
{t('current_password', {
|
||||
ns: 'settings',
|
||||
})}
|
||||
</Typography>
|
||||
<Password
|
||||
disabled={isLoading}
|
||||
placeholder={defaultPlaceHolder}
|
||||
onChange={(event): void => {
|
||||
setCurrentPassword(event.target.value);
|
||||
}}
|
||||
value={currentPassword}
|
||||
/>
|
||||
</Space>
|
||||
<Space direction="vertical">
|
||||
<Typography>
|
||||
{t('new_password', {
|
||||
ns: 'settings',
|
||||
})}
|
||||
</Typography>
|
||||
<Password
|
||||
disabled={isLoading}
|
||||
placeholder={defaultPlaceHolder}
|
||||
onChange={(event): void => {
|
||||
const updatedValue = event.target.value;
|
||||
setUpdatePassword(updatedValue);
|
||||
if (!isPasswordValid(updatedValue)) {
|
||||
setIsPasswordPolicyError(true);
|
||||
} else {
|
||||
setIsPasswordPolicyError(false);
|
||||
}
|
||||
}}
|
||||
value={updatePassword}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
{isPasswordPolicyError && (
|
||||
<Typography.Paragraph
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
{isPasswordNotValidMessage}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</Space>
|
||||
<Button
|
||||
disabled={
|
||||
isLoading ||
|
||||
currentPassword.length === 0 ||
|
||||
updatePassword.length === 0 ||
|
||||
isPasswordPolicyError
|
||||
}
|
||||
loading={isLoading}
|
||||
onClick={onChangePasswordClickHandler}
|
||||
type="primary"
|
||||
>
|
||||
{t('change_password', {
|
||||
ns: 'settings',
|
||||
})}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export default PasswordContainer;
|
95
frontend/src/container/MySettings/UpdateName/index.tsx
Normal file
95
frontend/src/container/MySettings/UpdateName/index.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { Button, notification, Space, Typography } from 'antd';
|
||||
import editUser from 'api/user/editUser';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_USER } from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { NameInput } from '../styles';
|
||||
|
||||
function UpdateName(): JSX.Element {
|
||||
const { user, role, org } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const [changedName, setChangedName] = useState<string>(user?.name || '');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
if (!user || !org) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const onClickUpdateHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { statusCode } = await editUser({
|
||||
name: changedName,
|
||||
userId: user.userId,
|
||||
});
|
||||
|
||||
if (statusCode === 200) {
|
||||
notification.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_USER,
|
||||
payload: {
|
||||
...user,
|
||||
name: changedName,
|
||||
ROLE: role || 'ADMIN',
|
||||
orgId: org[0].id,
|
||||
orgName: org[0].name,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space direction="vertical" size="middle">
|
||||
<Typography>Name</Typography>
|
||||
<NameInput
|
||||
placeholder="Mike Tyson"
|
||||
onChange={(event): void => {
|
||||
setChangedName(event.target.value);
|
||||
}}
|
||||
value={changedName}
|
||||
disabled={loading}
|
||||
/>
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
onClick={onClickUpdateHandler}
|
||||
type="primary"
|
||||
>
|
||||
Update Name
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UpdateName;
|
19
frontend/src/container/MySettings/index.tsx
Normal file
19
frontend/src/container/MySettings/index.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Password from './Password';
|
||||
import UpdateName from './UpdateName';
|
||||
|
||||
function MySettings(): JSX.Element {
|
||||
const { t } = useTranslation(['routes']);
|
||||
return (
|
||||
<Space direction="vertical" size="large">
|
||||
<Typography.Title level={2}>{t('my_settings')}</Typography.Title>
|
||||
<UpdateName />
|
||||
<Password />
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export default MySettings;
|
14
frontend/src/container/MySettings/styles.ts
Normal file
14
frontend/src/container/MySettings/styles.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Input } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Password = styled(Input.Password)`
|
||||
&&& {
|
||||
width: 20rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const NameInput = styled(Input)`
|
||||
&&& {
|
||||
width: 20rem;
|
||||
}
|
||||
`;
|
@ -0,0 +1,33 @@
|
||||
import { gold } from '@ant-design/colors';
|
||||
import { ExclamationCircleTwoTone } from '@ant-design/icons';
|
||||
import { Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
function DeleteMembersDetails({
|
||||
name,
|
||||
}: DeleteMembersDetailsProps): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<Space direction="horizontal" size="middle" align="start">
|
||||
<ExclamationCircleTwoTone
|
||||
twoToneColor={[gold[6], '#1f1f1f']}
|
||||
style={{
|
||||
fontSize: '1.4rem',
|
||||
}}
|
||||
/>
|
||||
<Space direction="vertical">
|
||||
<Typography>Are you sure you want to delete {name}</Typography>
|
||||
<Typography>
|
||||
This will remove all access from dashboards and other features in SigNoz
|
||||
</Typography>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface DeleteMembersDetailsProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default DeleteMembersDetails;
|
@ -0,0 +1,99 @@
|
||||
import { Button, Input, notification, Space, Typography } from 'antd';
|
||||
import editOrg from 'api/user/editOrg';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_ORG_NAME } from 'types/actions/app';
|
||||
import AppReducer, { User } from 'types/reducer/app';
|
||||
|
||||
function DisplayName({
|
||||
index,
|
||||
id: orgId,
|
||||
isAnonymous,
|
||||
}: DisplayNameProps): JSX.Element {
|
||||
const { t } = useTranslation(['organizationsettings', 'common']);
|
||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { name } = (org || [])[index];
|
||||
const [orgName, setOrgName] = useState<string>(name);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const onClickHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const { statusCode, error } = await editOrg({
|
||||
isAnonymous,
|
||||
name: orgName,
|
||||
orgId,
|
||||
});
|
||||
if (statusCode === 200) {
|
||||
notification.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_ORG_NAME,
|
||||
payload: {
|
||||
index,
|
||||
name: orgName,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message:
|
||||
error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!org) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical">
|
||||
<Typography.Title level={3}>{t('display_name')}</Typography.Title>
|
||||
<Space direction="vertical" size="middle">
|
||||
<Input
|
||||
value={orgName}
|
||||
onChange={(e): void => setOrgName(e.target.value)}
|
||||
size="large"
|
||||
placeholder={t('signoz')}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Button
|
||||
onClick={onClickHandler}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
type="primary"
|
||||
>
|
||||
Change Org Name
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
interface DisplayNameProps {
|
||||
index: number;
|
||||
id: User['userId'];
|
||||
isAnonymous: boolean;
|
||||
}
|
||||
|
||||
export default DisplayName;
|
@ -0,0 +1,169 @@
|
||||
import { CopyOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, notification, Select, Space, Tooltip } from 'antd';
|
||||
import getResetPasswordToken from 'api/user/getResetPasswordToken';
|
||||
import ROUTES from 'constants/routes';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import { InputGroup, SelectDrawer, Title } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
function EditMembersDetails({
|
||||
emailAddress,
|
||||
name,
|
||||
role,
|
||||
setEmailAddress,
|
||||
setName,
|
||||
setRole,
|
||||
}: EditMembersDetailsProps): JSX.Element {
|
||||
const [passwordLink, setPasswordLink] = useState<string>('');
|
||||
|
||||
const { t } = useTranslation(['common']);
|
||||
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [state, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const getPasswordLink = (token: string): string => {
|
||||
return `${window.location.origin}${ROUTES.PASSWORD_RESET}?token=${token}`;
|
||||
};
|
||||
|
||||
const onChangeHandler = useCallback(
|
||||
(setFunc: React.Dispatch<React.SetStateAction<string>>, value: string) => {
|
||||
setFunc(value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
notification.error({
|
||||
message: t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
|
||||
if (state.value) {
|
||||
notification.success({
|
||||
message: t('success'),
|
||||
});
|
||||
}
|
||||
}, [state.error, state.value, t]);
|
||||
|
||||
const onPasswordChangeHandler = useCallback((event) => {
|
||||
setPasswordLink(event.target.value);
|
||||
}, []);
|
||||
|
||||
const onGeneratePasswordHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await getResetPasswordToken({
|
||||
userId: user?.userId || '',
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setPasswordLink(getPasswordLink(response.payload.token));
|
||||
} else {
|
||||
notification.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="large">
|
||||
<Space direction="horizontal">
|
||||
<Title>Email address</Title>
|
||||
<Input
|
||||
placeholder="john@signoz.io"
|
||||
readOnly
|
||||
onChange={(event): void =>
|
||||
onChangeHandler(setEmailAddress, event.target.value)
|
||||
}
|
||||
disabled={isLoading}
|
||||
value={emailAddress}
|
||||
/>
|
||||
</Space>
|
||||
<Space direction="horizontal">
|
||||
<Title>Name (optional)</Title>
|
||||
<Input
|
||||
placeholder="John"
|
||||
onChange={(event): void => onChangeHandler(setName, event.target.value)}
|
||||
value={name}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Space>
|
||||
<Space direction="horizontal">
|
||||
<Title>Role</Title>
|
||||
<SelectDrawer
|
||||
value={role}
|
||||
onSelect={(value: unknown): void => {
|
||||
if (typeof value === 'string') {
|
||||
setRole(value as ROLES);
|
||||
}
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Option value="ADMIN">ADMIN</Option>
|
||||
<Option value="VIEWER">VIEWER</Option>
|
||||
<Option value="EDITOR">EDITOR</Option>
|
||||
</SelectDrawer>
|
||||
</Space>
|
||||
|
||||
<Button
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
onClick={onGeneratePasswordHandler}
|
||||
type="primary"
|
||||
>
|
||||
Generate Reset Password link
|
||||
</Button>
|
||||
{passwordLink && (
|
||||
<InputGroup>
|
||||
<Input
|
||||
style={{ width: '100%' }}
|
||||
defaultValue="git@github.com:ant-design/ant-design.git"
|
||||
onChange={onPasswordChangeHandler}
|
||||
value={passwordLink}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Tooltip title="COPY LINK">
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
onClick={(): void => copyToClipboard(passwordLink)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</InputGroup>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
interface EditMembersDetailsProps {
|
||||
emailAddress: string;
|
||||
name: string;
|
||||
role: ROLES;
|
||||
setEmailAddress: React.Dispatch<React.SetStateAction<string>>;
|
||||
setName: React.Dispatch<React.SetStateAction<string>>;
|
||||
setRole: React.Dispatch<React.SetStateAction<ROLES>>;
|
||||
}
|
||||
|
||||
export default EditMembersDetails;
|
@ -0,0 +1,16 @@
|
||||
import { Select, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SelectDrawer = styled(Select)`
|
||||
width: 120px;
|
||||
`;
|
||||
|
||||
export const Title = styled(Typography)`
|
||||
width: 7rem;
|
||||
`;
|
||||
|
||||
export const InputGroup = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`;
|
@ -0,0 +1,96 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Input, Select, Space, Typography } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { InviteTeamMembersProps } from '../PendingInvitesContainer/index';
|
||||
import { SelectDrawer, TitleWrapper } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
function InviteTeamMembers({ allMembers, setAllMembers }: Props): JSX.Element {
|
||||
const { t } = useTranslation('organizationsettings');
|
||||
|
||||
const onAddHandler = (): void => {
|
||||
setAllMembers((state) => [
|
||||
...state,
|
||||
{
|
||||
email: '',
|
||||
name: '',
|
||||
role: 'VIEWER',
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const onChangeHandler = useCallback(
|
||||
(value: string, index: number, type: string): void => {
|
||||
setAllMembers((prev) => {
|
||||
return [
|
||||
...prev.slice(0, index),
|
||||
{
|
||||
...prev[index],
|
||||
[type]: value,
|
||||
},
|
||||
...prev.slice(index, prev.length - 1),
|
||||
];
|
||||
});
|
||||
},
|
||||
[setAllMembers],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TitleWrapper>
|
||||
<Typography>{t('email_address')}</Typography>
|
||||
<Typography>{t('name_optional')}</Typography>
|
||||
<Typography>{t('role')}</Typography>
|
||||
</TitleWrapper>
|
||||
<Form>
|
||||
<Space direction="vertical" align="center" size="middle">
|
||||
{allMembers.map((e, index) => (
|
||||
<Space key={Number(index)} direction="horizontal">
|
||||
<Input
|
||||
placeholder={t('email_placeholder')}
|
||||
value={e.email}
|
||||
onChange={(event): void => {
|
||||
onChangeHandler(event.target.value, index, 'email');
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('name_placeholder')}
|
||||
value={e.name}
|
||||
onChange={(event): void => {
|
||||
onChangeHandler(event.target.value, index, 'name');
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<SelectDrawer
|
||||
value={e.role}
|
||||
onSelect={(value: unknown): void => {
|
||||
if (typeof value === 'string') {
|
||||
onChangeHandler(value, index, 'role');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Option value="ADMIN">ADMIN</Option>
|
||||
<Option value="VIEWER">VIEWER</Option>
|
||||
<Option value="EDITOR">EDITOR</Option>
|
||||
</SelectDrawer>
|
||||
</Space>
|
||||
))}
|
||||
<Button onClick={onAddHandler} icon={<PlusOutlined />} type="default">
|
||||
{t('add_another_team_member')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
allMembers: InviteTeamMembersProps[];
|
||||
setAllMembers: React.Dispatch<React.SetStateAction<InviteTeamMembersProps[]>>;
|
||||
}
|
||||
|
||||
export default InviteTeamMembers;
|
@ -0,0 +1,15 @@
|
||||
import { Select } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SelectDrawer = styled(Select)`
|
||||
width: 120px;
|
||||
`;
|
||||
|
||||
export const TitleWrapper = styled.div`
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
> article {
|
||||
min-width: 11rem;
|
||||
}
|
||||
`;
|
325
frontend/src/container/OrganizationSettings/Members/index.tsx
Normal file
325
frontend/src/container/OrganizationSettings/Members/index.tsx
Normal file
@ -0,0 +1,325 @@
|
||||
import { Button, Modal, notification, Space, Typography } from 'antd';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import deleteUser from 'api/user/deleteUser';
|
||||
import editUserApi from 'api/user/editUser';
|
||||
import getOrgUser from 'api/user/getOrgUser';
|
||||
import updateRole from 'api/user/updateRole';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import DeleteMembersDetails from '../DeleteMembersDetails';
|
||||
import EditMembersDetails from '../EditMembersDetails';
|
||||
|
||||
function UserFunction({
|
||||
setDataSource,
|
||||
accessLevel,
|
||||
name,
|
||||
email,
|
||||
id,
|
||||
}: UserFunctionProps): JSX.Element {
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
|
||||
|
||||
const onModalToggleHandler = (
|
||||
func: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
value: boolean,
|
||||
): void => {
|
||||
func(value);
|
||||
};
|
||||
|
||||
const [emailAddress, setEmailAddress] = useState(email);
|
||||
const [updatedName, setUpdatedName] = useState(name);
|
||||
const [role, setRole] = useState<ROLES>(accessLevel);
|
||||
const { t } = useTranslation(['common']);
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState<boolean>(false);
|
||||
const [isUpdateLoading, setIsUpdateLoading] = useState<boolean>(false);
|
||||
|
||||
const onUpdateDetailsHandler = (): void => {
|
||||
setDataSource((data) => {
|
||||
const index = data.findIndex((e) => e.id === id);
|
||||
if (index !== -1) {
|
||||
const current = data[index];
|
||||
|
||||
const updatedData: DataType[] = [
|
||||
...data.slice(0, index),
|
||||
{
|
||||
...current,
|
||||
name: updatedName,
|
||||
accessLevel: role,
|
||||
email: emailAddress,
|
||||
},
|
||||
...data.slice(index + 1, data.length),
|
||||
];
|
||||
|
||||
return updatedData;
|
||||
}
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (): void => {
|
||||
setDataSource((source) => {
|
||||
const index = source.findIndex((e) => e.id === id);
|
||||
|
||||
if (index !== -1) {
|
||||
const updatedData: DataType[] = [
|
||||
...source.slice(0, index),
|
||||
...source.slice(index + 1, source.length),
|
||||
];
|
||||
|
||||
return updatedData;
|
||||
}
|
||||
return source;
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsDeleteLoading(true);
|
||||
const response = await deleteUser({
|
||||
userId: id,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
onDelete();
|
||||
notification.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
setIsDeleteModalVisible(false);
|
||||
} else {
|
||||
notification.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsDeleteLoading(false);
|
||||
} catch (error) {
|
||||
setIsDeleteLoading(false);
|
||||
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onInviteMemberHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsUpdateLoading(true);
|
||||
const [editUserResponse, updateRoleResponse] = await Promise.all([
|
||||
editUserApi({
|
||||
userId: id,
|
||||
name: updatedName,
|
||||
}),
|
||||
updateRole({
|
||||
group_name: role,
|
||||
userId: id,
|
||||
}),
|
||||
]);
|
||||
|
||||
if (
|
||||
editUserResponse.statusCode === 200 &&
|
||||
updateRoleResponse.statusCode === 200
|
||||
) {
|
||||
onUpdateDetailsHandler();
|
||||
notification.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message:
|
||||
editUserResponse.error ||
|
||||
updateRoleResponse.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsUpdateLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
setIsUpdateLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space direction="horizontal">
|
||||
<Typography.Link
|
||||
onClick={(): void => onModalToggleHandler(setIsModalVisible, true)}
|
||||
>
|
||||
Edit
|
||||
</Typography.Link>
|
||||
<Typography.Link
|
||||
onClick={(): void => onModalToggleHandler(setIsDeleteModalVisible, true)}
|
||||
>
|
||||
Delete
|
||||
</Typography.Link>
|
||||
</Space>
|
||||
<Modal
|
||||
title="Edit member details"
|
||||
visible={isModalVisible}
|
||||
onOk={(): void => onModalToggleHandler(setIsModalVisible, false)}
|
||||
onCancel={(): void => onModalToggleHandler(setIsModalVisible, false)}
|
||||
centered
|
||||
footer={[
|
||||
<Button
|
||||
key="back"
|
||||
onClick={(): void => onModalToggleHandler(setIsModalVisible, false)}
|
||||
type="default"
|
||||
>
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button
|
||||
key="Invite_team_members"
|
||||
onClick={onInviteMemberHandler}
|
||||
type="primary"
|
||||
disabled={isUpdateLoading}
|
||||
loading={isUpdateLoading}
|
||||
>
|
||||
Update Details
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<EditMembersDetails
|
||||
{...{
|
||||
emailAddress,
|
||||
name: updatedName,
|
||||
role,
|
||||
setEmailAddress,
|
||||
setName: setUpdatedName,
|
||||
setRole,
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
title="Edit member details"
|
||||
visible={isDeleteModalVisible}
|
||||
onOk={onDeleteHandler}
|
||||
onCancel={(): void => onModalToggleHandler(setIsDeleteModalVisible, false)}
|
||||
centered
|
||||
confirmLoading={isDeleteLoading}
|
||||
>
|
||||
<DeleteMembersDetails name={name} />
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Members(): JSX.Element {
|
||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { status, data } = useQuery({
|
||||
queryFn: () =>
|
||||
getOrgUser({
|
||||
orgId: (org || [])[0].id,
|
||||
}),
|
||||
queryKey: 'getOrgUser',
|
||||
});
|
||||
|
||||
const [dataSource, setDataSource] = useState<DataType[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'success' && data?.payload && Array.isArray(data.payload)) {
|
||||
const updatedData: DataType[] = data?.payload?.map((e) => ({
|
||||
accessLevel: e.role,
|
||||
email: e.email,
|
||||
id: String(e.id),
|
||||
joinedOn: String(e.createdAt),
|
||||
name: e.name,
|
||||
}));
|
||||
setDataSource(updatedData);
|
||||
}
|
||||
}, [data?.payload, status]);
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Emails',
|
||||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'Access Level',
|
||||
dataIndex: 'accessLevel',
|
||||
key: 'accessLevel',
|
||||
},
|
||||
{
|
||||
title: 'Joined On',
|
||||
dataIndex: 'joinedOn',
|
||||
key: 'joinedOn',
|
||||
render: (_, record): JSX.Element => {
|
||||
const { joinedOn } = record;
|
||||
return (
|
||||
<Typography>
|
||||
{dayjs.unix(Number(joinedOn)).format('MMMM DD,YYYY')}
|
||||
</Typography>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
dataIndex: 'action',
|
||||
render: (_, record): JSX.Element => (
|
||||
<UserFunction
|
||||
{...{
|
||||
accessLevel: record.accessLevel,
|
||||
email: record.email,
|
||||
joinedOn: record.joinedOn,
|
||||
name: record.name,
|
||||
id: record.id,
|
||||
setDataSource,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="middle">
|
||||
<Typography.Title level={3}>Members</Typography.Title>
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
loading={status === 'loading'}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
interface DataType {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
accessLevel: ROLES;
|
||||
joinedOn: string;
|
||||
}
|
||||
|
||||
interface UserFunctionProps extends DataType {
|
||||
setDataSource: React.Dispatch<React.SetStateAction<DataType[]>>;
|
||||
}
|
||||
|
||||
export default Members;
|
@ -0,0 +1,286 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, notification, Space, Typography } from 'antd';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import deleteInvite from 'api/user/deleteInvite';
|
||||
import getPendingInvites from 'api/user/getPendingInvites';
|
||||
import sendInvite from 'api/user/sendInvite';
|
||||
import { INVITE_MEMBERS_HASH } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { PayloadProps } from 'types/api/user/getPendingInvites';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import InviteTeamMembers from '../InviteTeamMembers';
|
||||
import { TitleWrapper } from './styles';
|
||||
|
||||
function PendingInvitesContainer(): JSX.Element {
|
||||
const [
|
||||
isInviteTeamMemberModalOpen,
|
||||
setIsInviteTeamMemberModalOpen,
|
||||
] = useState<boolean>(false);
|
||||
const [isInvitingMembers, setIsInvitingMembers] = useState<boolean>(false);
|
||||
const { t } = useTranslation(['organizationsettings', 'common']);
|
||||
const [state, setText] = useCopyToClipboard();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
notification.error({
|
||||
message: state.error.message,
|
||||
});
|
||||
}
|
||||
|
||||
if (state.value) {
|
||||
notification.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}, [state.error, state.value, t]);
|
||||
|
||||
const getPendingInvitesResponse = useQuery({
|
||||
queryFn: () => getPendingInvites(),
|
||||
queryKey: 'getPendingInvites',
|
||||
});
|
||||
|
||||
const toggleModal = (value: boolean): void => {
|
||||
setIsInviteTeamMemberModalOpen(value);
|
||||
};
|
||||
|
||||
const [allMembers, setAllMembers] = useState<InviteTeamMembersProps[]>([
|
||||
{
|
||||
email: '',
|
||||
name: '',
|
||||
role: 'VIEWER',
|
||||
},
|
||||
]);
|
||||
|
||||
const [dataSource, setDataSource] = useState<DataProps[]>([]);
|
||||
|
||||
const { hash } = useLocation();
|
||||
|
||||
const getParsedInviteData = useCallback((payload: PayloadProps = []) => {
|
||||
return payload?.map((data) => ({
|
||||
key: data.createdAt,
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
accessLevel: data.role,
|
||||
inviteLink: `${window.location.origin}${ROUTES.SIGN_UP}?token=${data.token}`,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (hash === INVITE_MEMBERS_HASH) {
|
||||
toggleModal(true);
|
||||
}
|
||||
}, [hash]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
getPendingInvitesResponse.status === 'success' &&
|
||||
getPendingInvitesResponse?.data?.payload
|
||||
) {
|
||||
const data = getParsedInviteData(
|
||||
getPendingInvitesResponse?.data?.payload || [],
|
||||
);
|
||||
setDataSource(data);
|
||||
}
|
||||
}, [
|
||||
getParsedInviteData,
|
||||
getPendingInvitesResponse?.data?.payload,
|
||||
getPendingInvitesResponse.status,
|
||||
]);
|
||||
|
||||
const onRevokeHandler = async (email: string): Promise<void> => {
|
||||
try {
|
||||
const response = await deleteInvite({
|
||||
email,
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
// remove from the client data
|
||||
const index = dataSource.findIndex((e) => e.email === email);
|
||||
|
||||
if (index !== -1) {
|
||||
setDataSource([
|
||||
...dataSource.slice(0, index),
|
||||
...dataSource.slice(index + 1, dataSource.length),
|
||||
]);
|
||||
}
|
||||
notification.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const columns: ColumnsType<DataProps> = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Emails',
|
||||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'Access Level',
|
||||
dataIndex: 'accessLevel',
|
||||
key: 'accessLevel',
|
||||
},
|
||||
{
|
||||
title: 'Invite Link',
|
||||
dataIndex: 'inviteLink',
|
||||
key: 'Invite Link',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
dataIndex: 'action',
|
||||
key: 'Action',
|
||||
render: (_, record): JSX.Element => (
|
||||
<Space direction="horizontal">
|
||||
<Typography.Link
|
||||
onClick={(): Promise<void> => onRevokeHandler(record.email)}
|
||||
>
|
||||
Revoke
|
||||
</Typography.Link>
|
||||
<Typography.Link
|
||||
onClick={(): void => {
|
||||
setText(record.inviteLink);
|
||||
}}
|
||||
>
|
||||
Copy Invite Link
|
||||
</Typography.Link>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const onInviteClickHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsInvitingMembers(true);
|
||||
allMembers.forEach(
|
||||
async (members): Promise<void> => {
|
||||
const { error, statusCode } = await sendInvite({
|
||||
email: members.email,
|
||||
name: members.name,
|
||||
role: members.role,
|
||||
});
|
||||
|
||||
if (statusCode !== 200) {
|
||||
notification.error({
|
||||
message:
|
||||
error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
const { data, status } = await getPendingInvitesResponse.refetch();
|
||||
if (status === 'success' && data.payload) {
|
||||
setDataSource(getParsedInviteData(data?.payload || []));
|
||||
}
|
||||
setIsInvitingMembers(false);
|
||||
toggleModal(false);
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
title={t('invite_team_members')}
|
||||
visible={isInviteTeamMemberModalOpen}
|
||||
onCancel={(): void => toggleModal(false)}
|
||||
centered
|
||||
footer={[
|
||||
<Button key="back" onClick={(): void => toggleModal(false)} type="default">
|
||||
{t('cancel', {
|
||||
ns: 'common',
|
||||
})}
|
||||
</Button>,
|
||||
<Button
|
||||
key={t('invite_team_members').toString()}
|
||||
onClick={onInviteClickHandler}
|
||||
type="primary"
|
||||
disabled={isInvitingMembers}
|
||||
loading={isInvitingMembers}
|
||||
>
|
||||
{t('invite_team_members')}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<InviteTeamMembers allMembers={allMembers} setAllMembers={setAllMembers} />
|
||||
</Modal>
|
||||
|
||||
<Space direction="vertical" size="middle">
|
||||
<TitleWrapper>
|
||||
<Typography.Title level={3}>{t('pending_invites')}</Typography.Title>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
onClick={(): void => {
|
||||
toggleModal(true);
|
||||
}}
|
||||
>
|
||||
{t('invite_members')}
|
||||
</Button>
|
||||
</TitleWrapper>
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
loading={getPendingInvitesResponse.status === 'loading'}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface InviteTeamMembersProps {
|
||||
email: string;
|
||||
name: string;
|
||||
role: ROLES;
|
||||
}
|
||||
|
||||
interface DataProps {
|
||||
key: number;
|
||||
name: string;
|
||||
email: string;
|
||||
accessLevel: ROLES;
|
||||
inviteLink: string;
|
||||
}
|
||||
export default PendingInvitesContainer;
|
@ -0,0 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TitleWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
38
frontend/src/container/OrganizationSettings/index.tsx
Normal file
38
frontend/src/container/OrganizationSettings/index.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { Divider, Space } from 'antd';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import DisplayName from './DisplayName';
|
||||
import Members from './Members';
|
||||
import PendingInvitesContainer from './PendingInvitesContainer';
|
||||
|
||||
function OrganizationSettings(): JSX.Element {
|
||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
if (!org) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space direction="vertical">
|
||||
{org.map((e, index) => (
|
||||
<DisplayName
|
||||
isAnonymous={e.isAnonymous}
|
||||
key={e.id}
|
||||
id={e.id}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
<Divider />
|
||||
<PendingInvitesContainer />
|
||||
<Divider />
|
||||
<Members />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrganizationSettings;
|
156
frontend/src/container/ResetPassword/index.tsx
Normal file
156
frontend/src/container/ResetPassword/index.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import { Button, Input, notification, Typography } from 'antd';
|
||||
import resetPasswordApi from 'api/user/resetPassword';
|
||||
import { Logout } from 'api/utils';
|
||||
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { Label } from 'pages/SignUp/styles';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-use';
|
||||
|
||||
import { ButtonContainer, FormWrapper } from './styles';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
||||
const [password, setPassword] = useState<string>('');
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>('');
|
||||
const [confirmPasswordError, setConfirmPasswordError] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation(['common']);
|
||||
const { search } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const token = params.get('token');
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
Logout();
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const setState = (
|
||||
value: string,
|
||||
setFunction: React.Dispatch<React.SetStateAction<string>>,
|
||||
): void => {
|
||||
setFunction(value);
|
||||
};
|
||||
|
||||
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (
|
||||
event,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
event.preventDefault();
|
||||
event.persist();
|
||||
|
||||
const response = await resetPasswordApi({
|
||||
password,
|
||||
token: token || '',
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notification.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
notification.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<WelcomeLeftContainer version={version}>
|
||||
<FormWrapper>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Title level={4}>Reset Your Password</Title>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="Password">Password</Label>
|
||||
<Input.Password
|
||||
value={password}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setPassword);
|
||||
}}
|
||||
required
|
||||
id="currentPassword"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="ConfirmPassword">Confirm Password</Label>
|
||||
<Input.Password
|
||||
value={confirmPassword}
|
||||
onChange={(e): void => {
|
||||
const updateValue = e.target.value;
|
||||
setState(updateValue, setConfirmPassword);
|
||||
if (password !== updateValue) {
|
||||
setConfirmPasswordError(true);
|
||||
} else {
|
||||
setConfirmPasswordError(false);
|
||||
}
|
||||
}}
|
||||
required
|
||||
id="UpdatePassword"
|
||||
/>
|
||||
|
||||
{confirmPasswordError && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
Passwords don’t match. Please try again
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
data-attr="signup"
|
||||
loading={loading}
|
||||
disabled={
|
||||
loading ||
|
||||
!password ||
|
||||
!confirmPassword ||
|
||||
confirmPasswordError ||
|
||||
token === null
|
||||
}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
</WelcomeLeftContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface ResetPasswordProps {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export default ResetPassword;
|
16
frontend/src/container/ResetPassword/styles.ts
Normal file
16
frontend/src/container/ResetPassword/styles.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Card } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FormWrapper = styled(Card)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 432px;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
margin-top: 1.8125rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
@ -4,68 +4,37 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import setTheme, { AppMode } from 'lib/theme/setTheme';
|
||||
import React, { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { ToggleDarkMode } from 'store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SideBarCollapse } from 'store/actions/app';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import menus from './menuItems';
|
||||
import Slack from './Slack';
|
||||
import {
|
||||
Logo,
|
||||
RedDot,
|
||||
Sider,
|
||||
SlackButton,
|
||||
SlackMenuItemContainer,
|
||||
ThemeSwitcherWrapper,
|
||||
ToggleButton,
|
||||
VersionContainer,
|
||||
} from './styles';
|
||||
|
||||
function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
function SideNav(): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
const [collapsed, setCollapsed] = useState<boolean>(
|
||||
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
||||
);
|
||||
const {
|
||||
isDarkMode,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
isCurrentVersionError,
|
||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { currentVersion, latestVersion, isCurrentVersionError } = useSelector<
|
||||
AppState,
|
||||
AppReducer
|
||||
>((state) => state.app);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation('');
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode';
|
||||
setTheme(preMode);
|
||||
|
||||
const id: AppMode = preMode;
|
||||
const { head } = document;
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = !isDarkMode ? '/css/antd.dark.min.css' : '/css/antd.min.css';
|
||||
link.media = 'all';
|
||||
link.id = id;
|
||||
head.appendChild(link);
|
||||
|
||||
link.onload = (): void => {
|
||||
toggleDarkMode();
|
||||
const prevNode = document.getElementById('appMode');
|
||||
prevNode?.remove();
|
||||
};
|
||||
}, [toggleDarkMode, isDarkMode]);
|
||||
|
||||
const onCollapse = useCallback(() => {
|
||||
setCollapsed((collapsed) => !collapsed);
|
||||
}, []);
|
||||
@ -121,17 +90,6 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
|
||||
return (
|
||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={200}>
|
||||
<ThemeSwitcherWrapper>
|
||||
<ToggleButton
|
||||
checked={isDarkMode}
|
||||
onChange={toggleTheme}
|
||||
defaultChecked={isDarkMode}
|
||||
/>
|
||||
</ThemeSwitcherWrapper>
|
||||
<NavLink to={ROUTES.APPLICATION}>
|
||||
<Logo index={0} src="/signoz.svg" alt="SigNoz" collapsed={collapsed} />
|
||||
</NavLink>
|
||||
|
||||
<Menu
|
||||
theme="dark"
|
||||
defaultSelectedKeys={[ROUTES.APPLICATION]}
|
||||
@ -167,16 +125,4 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
toggleDarkMode: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch),
|
||||
});
|
||||
|
||||
type Props = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(SideNav);
|
||||
export default SideNav;
|
||||
|
@ -25,7 +25,7 @@ const menus: SidebarMenu[] = [
|
||||
{
|
||||
Icon: DashboardFilled,
|
||||
to: ROUTES.ALL_DASHBOARD,
|
||||
name: 'Dashboard',
|
||||
name: 'Dashboards',
|
||||
},
|
||||
{
|
||||
Icon: AlertOutlined,
|
||||
|
@ -1,22 +1,9 @@
|
||||
import { Layout, Switch, Typography } from 'antd';
|
||||
import { Layout, Typography } from 'antd';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const { Sider: SiderComponent } = Layout;
|
||||
|
||||
export const ThemeSwitcherWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
export const Logo = styled.img<LogoProps>`
|
||||
width: 100px;
|
||||
margin: 9% 5% 5% 10%;
|
||||
display: ${({ collapsed }): string => (!collapsed ? 'block' : 'none')};
|
||||
`;
|
||||
|
||||
interface LogoProps {
|
||||
collapsed: boolean;
|
||||
index: number;
|
||||
@ -32,17 +19,6 @@ export const Sider = styled(SiderComponent)`
|
||||
}
|
||||
`;
|
||||
|
||||
interface DarkModeProps {
|
||||
checked?: boolean;
|
||||
defaultChecked?: boolean;
|
||||
}
|
||||
|
||||
export const ToggleButton = styled(Switch)<DarkModeProps>`
|
||||
&&& {
|
||||
background: ${({ checked }): string => (checked === false ? 'grey' : '')};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SlackButton = styled(Typography)`
|
||||
&&& {
|
||||
margin-left: 1rem;
|
||||
|
@ -13,6 +13,8 @@ const breadcrumbNameMap = {
|
||||
[ROUTES.DASHBOARD]: 'Dashboard',
|
||||
[ROUTES.ALL_ERROR]: 'Errors',
|
||||
[ROUTES.VERSION]: 'Status',
|
||||
[ROUTES.ORG_SETTINGS]: 'Organization Settings',
|
||||
[ROUTES.MY_SETTINGS]: 'My Settings',
|
||||
};
|
||||
|
||||
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element {
|
49
frontend/src/container/TopNav/index.tsx
Normal file
49
frontend/src/container/TopNav/index.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { Col } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
|
||||
import ShowBreadcrumbs from './Breadcrumbs';
|
||||
import DateTimeSelector from './DateTimeSelection';
|
||||
import { Container } from './styles';
|
||||
|
||||
const routesToSkip = [
|
||||
ROUTES.SETTINGS,
|
||||
ROUTES.LIST_ALL_ALERT,
|
||||
ROUTES.TRACE_DETAIL,
|
||||
ROUTES.ALL_CHANNELS,
|
||||
];
|
||||
|
||||
function TopNav(): JSX.Element | null {
|
||||
if (history.location.pathname === ROUTES.SIGN_UP) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const checkRouteExists = (currentPath: string): boolean => {
|
||||
for (let i = 0; i < routesToSkip.length; i += 1) {
|
||||
if (
|
||||
matchPath(currentPath, { path: routesToSkip[i], exact: true, strict: true })
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Col span={16}>
|
||||
<ShowBreadcrumbs />
|
||||
</Col>
|
||||
|
||||
{!checkRouteExists(history.location.pathname) && (
|
||||
<Col span={8}>
|
||||
<DateTimeSelector />
|
||||
</Col>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopNav;
|
9
frontend/src/container/TopNav/styles.ts
Normal file
9
frontend/src/container/TopNav/styles.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Row } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled(Row)`
|
||||
&&& {
|
||||
margin-top: 2rem;
|
||||
min-height: 8vh;
|
||||
}
|
||||
`;
|
@ -110,7 +110,6 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
||||
selectedSpanId={activeSelectedId}
|
||||
onSpanHover={setActiveHoverId}
|
||||
onSpanSelect={setActiveSelectedId}
|
||||
intervalUnit={intervalUnit}
|
||||
/>
|
||||
</Col>
|
||||
</StyledRow>
|
||||
|
22
frontend/src/hooks/useComponentPermission.ts
Normal file
22
frontend/src/hooks/useComponentPermission.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { ROLES } from 'types/roles';
|
||||
import { componentPermission, ComponentTypes } from 'utils/permission';
|
||||
|
||||
const useComponentPermission = (
|
||||
component: ComponentTypes[],
|
||||
role: ROLES | null,
|
||||
): boolean[] => {
|
||||
const getComponentPermission = useCallback(
|
||||
(component: ComponentTypes): boolean => {
|
||||
return !!componentPermission[component].find((roles) => role === roles);
|
||||
},
|
||||
[role],
|
||||
);
|
||||
|
||||
return useMemo(() => component.map((e) => getComponentPermission(e)), [
|
||||
component,
|
||||
getComponentPermission,
|
||||
]);
|
||||
};
|
||||
|
||||
export default useComponentPermission;
|
27
frontend/src/hooks/useIfNotLoggedInNavigate.ts
Normal file
27
frontend/src/hooks/useIfNotLoggedInNavigate.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { notification } from 'antd';
|
||||
import history from 'lib/history';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
const useLoggedInNavigate = (navigateTo: string): void => {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn && navigateTo !== pathname) {
|
||||
notification.success({
|
||||
message: t('logged_in', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
history.push(navigateTo);
|
||||
}
|
||||
}, [isLoggedIn, navigateTo, pathname, t]);
|
||||
};
|
||||
|
||||
export default useLoggedInNavigate;
|
@ -1,4 +1,4 @@
|
||||
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import getMinAgo from './getStartAndEndTime/getMinAgo';
|
||||
|
@ -1,36 +0,0 @@
|
||||
import RouteTab from 'components/RouteTab';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AlertChannels from 'container/AllAlertChannels';
|
||||
import GeneralSettings from 'container/GeneralSettings';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function AllAlertChannels(): JSX.Element {
|
||||
const pathName = history.location.pathname;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RouteTab
|
||||
{...{
|
||||
routes: [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: t('routes.general'),
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
Component: AlertChannels,
|
||||
name: t('routes.alert_channels'),
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
],
|
||||
activeKey:
|
||||
pathName === ROUTES.SETTINGS
|
||||
? t('routes.general')
|
||||
: t('routes.alert_channels'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default AllAlertChannels;
|
51
frontend/src/pages/Login/index.tsx
Normal file
51
frontend/src/pages/Login/index.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { Typography } from 'antd';
|
||||
import getUserVersion from 'api/user/getVersion';
|
||||
import Spinner from 'components/Spinner';
|
||||
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
import LoginContainer from 'container/Login';
|
||||
import useLoggedInNavigate from 'hooks/useIfNotLoggedInNavigate';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
function Login(): JSX.Element {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useLoggedInNavigate(ROUTES.APPLICATION);
|
||||
|
||||
const versionResult = useQuery({
|
||||
queryFn: getUserVersion,
|
||||
queryKey: 'getUserVersion',
|
||||
enabled: !isLoggedIn,
|
||||
});
|
||||
|
||||
if (versionResult.status === 'error') {
|
||||
return (
|
||||
<Typography>
|
||||
{versionResult.data?.error || t('something_went_wrong')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
versionResult.status === 'loading' ||
|
||||
!(versionResult.data && versionResult.data.payload)
|
||||
) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
const { version } = versionResult.data.payload;
|
||||
|
||||
return (
|
||||
<WelcomeLeftContainer version={version}>
|
||||
<LoginContainer />
|
||||
</WelcomeLeftContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
7
frontend/src/pages/MySettings/index.tsx
Normal file
7
frontend/src/pages/MySettings/index.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import MySettingsContainer from 'container/MySettings';
|
||||
import React from 'react';
|
||||
|
||||
function MySettings(): JSX.Element {
|
||||
return <MySettingsContainer />;
|
||||
}
|
||||
export default MySettings;
|
52
frontend/src/pages/ResetPassword/index.tsx
Normal file
52
frontend/src/pages/ResetPassword/index.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { Typography } from 'antd';
|
||||
import getUserVersion from 'api/user/getVersion';
|
||||
import Spinner from 'components/Spinner';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ResetPasswordContainer from 'container/ResetPassword';
|
||||
import useLoggedInNavigate from 'hooks/useIfNotLoggedInNavigate';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
function ResetPassword(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
useLoggedInNavigate(ROUTES.APPLICATION);
|
||||
|
||||
const [versionResponse] = useQueries([
|
||||
{
|
||||
queryFn: getUserVersion,
|
||||
queryKey: 'getUserVersion',
|
||||
enabled: !isLoggedIn,
|
||||
},
|
||||
]);
|
||||
|
||||
if (
|
||||
versionResponse.status === 'error' ||
|
||||
(versionResponse.status === 'success' &&
|
||||
versionResponse.data?.statusCode !== 200)
|
||||
) {
|
||||
return (
|
||||
<Typography>
|
||||
{versionResponse.data?.error || t('something_went_wrong')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
versionResponse.status === 'loading' ||
|
||||
!(versionResponse.data && versionResponse.data.payload)
|
||||
) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
const { version } = versionResponse.data.payload;
|
||||
|
||||
return <ResetPasswordContainer version={version} />;
|
||||
}
|
||||
|
||||
export default ResetPassword;
|
@ -2,28 +2,60 @@ import RouteTab from 'components/RouteTab';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AlertChannels from 'container/AllAlertChannels';
|
||||
import GeneralSettings from 'container/GeneralSettings';
|
||||
import OrganizationSettings from 'container/OrganizationSettings';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
function SettingsPage(): JSX.Element {
|
||||
const pathName = history.location.pathname;
|
||||
const { t } = useTranslation(['routes']);
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [currentOrgSettings] = useComponentPermission(
|
||||
['current_org_settings'],
|
||||
role,
|
||||
);
|
||||
|
||||
const getActiveKey = (pathname: string): string => {
|
||||
if (pathname === ROUTES.SETTINGS) {
|
||||
return t('general');
|
||||
}
|
||||
if (pathname === ROUTES.ORG_SETTINGS && currentOrgSettings) {
|
||||
return t('organization_settings');
|
||||
}
|
||||
return t('alert_channels');
|
||||
};
|
||||
|
||||
const common = [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: t('general'),
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
Component: AlertChannels,
|
||||
name: t('alert_channels'),
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
];
|
||||
|
||||
if (currentOrgSettings) {
|
||||
common.push({
|
||||
Component: OrganizationSettings,
|
||||
name: t('organization_settings'),
|
||||
route: ROUTES.ORG_SETTINGS,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteTab
|
||||
{...{
|
||||
routes: [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: 'General',
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
Component: AlertChannels,
|
||||
name: 'Alert Channels',
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
],
|
||||
activeKey: pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General',
|
||||
routes: common,
|
||||
activeKey: getActiveKey(pathName),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -1,55 +1,65 @@
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
notification,
|
||||
Space,
|
||||
Switch,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import setPreference from 'api/user/setPreference';
|
||||
import signup from 'api/user/signup';
|
||||
import { IS_LOGGED_IN } from 'constants/auth';
|
||||
import { Button, Input, notification, Space, Switch, Typography } from 'antd';
|
||||
import editOrg from 'api/user/editOrg';
|
||||
import getInviteDetails from 'api/user/getInviteDetails';
|
||||
import loginApi from 'api/user/login';
|
||||
import signUpApi from 'api/user/signup';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { PayloadProps } from 'types/api/user/getUserPreference';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getUser';
|
||||
|
||||
import {
|
||||
ButtonContainer,
|
||||
Container,
|
||||
FormWrapper,
|
||||
Label,
|
||||
LeftContainer,
|
||||
Logo,
|
||||
MarginTop,
|
||||
} from './styles';
|
||||
import { ButtonContainer, FormWrapper, Label, MarginTop } from './styles';
|
||||
import { isPasswordNotValidMessage, isPasswordValid } from './utils';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [firstName, setFirstName] = useState<string>('');
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [organizationName, setOrganisationName] = useState<string>('');
|
||||
const [hasOptedUpdates, setHasOptedUpdates] = useState<boolean>(
|
||||
userpref.hasOptedUpdates,
|
||||
const [organizationName, setOrganizationName] = useState<string>('');
|
||||
const [hasOptedUpdates, setHasOptedUpdates] = useState<boolean>(true);
|
||||
const [isAnonymous, setIsAnonymous] = useState<boolean>(false);
|
||||
const [password, setPassword] = useState<string>('');
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>('');
|
||||
const [confirmPasswordError, setConfirmPasswordError] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [isAnonymous, setisAnonymous] = useState<boolean>(userpref.isAnonymous);
|
||||
const [isPasswordPolicyError, setIsPasswordPolicyError] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const { search } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const token = params.get('token');
|
||||
const [isDetailsDisable, setIsDetailsDisable] = useState<boolean>(false);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const getInviteDetailsResponse = useQuery({
|
||||
queryFn: () =>
|
||||
getInviteDetails({
|
||||
inviteId: token || '',
|
||||
}),
|
||||
queryKey: 'getInviteDetails',
|
||||
enabled: token !== null,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setisAnonymous(userpref.isAnonymous);
|
||||
setHasOptedUpdates(userpref.hasOptedUpdates);
|
||||
}, [userpref]);
|
||||
if (
|
||||
getInviteDetailsResponse.status === 'success' &&
|
||||
getInviteDetailsResponse.data.payload
|
||||
) {
|
||||
const responseDetails = getInviteDetailsResponse.data.payload;
|
||||
setFirstName(responseDetails.name);
|
||||
setEmail(responseDetails.email);
|
||||
setOrganizationName(responseDetails.organization);
|
||||
setIsDetailsDisable(true);
|
||||
}
|
||||
}, [getInviteDetailsResponse?.data?.payload, getInviteDetailsResponse.status]);
|
||||
|
||||
const setState = (
|
||||
value: string,
|
||||
@ -59,6 +69,70 @@ function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
};
|
||||
|
||||
const defaultError = 'Something went wrong';
|
||||
const isPreferenceVisible = token === null;
|
||||
|
||||
const commonHandler = async (
|
||||
callback: (e: SuccessResponse<PayloadProps>) => Promise<void> | VoidFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const response = await signUpApi({
|
||||
email,
|
||||
name: firstName,
|
||||
orgName: organizationName,
|
||||
password,
|
||||
token: params.get('token') || undefined,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
const loginResponse = await loginApi({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (loginResponse.statusCode === 200) {
|
||||
const { payload } = loginResponse;
|
||||
const userResponse = await afterLogin(
|
||||
payload.userId,
|
||||
payload.accessJwt,
|
||||
payload.refreshJwt,
|
||||
);
|
||||
if (userResponse) {
|
||||
callback(userResponse);
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: loginResponse.error || defaultError,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: defaultError,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: defaultError,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onAdminAfterLogin = async (
|
||||
userResponse: SuccessResponse<PayloadProps>,
|
||||
): Promise<void> => {
|
||||
const editResponse = await editOrg({
|
||||
isAnonymous,
|
||||
name: organizationName,
|
||||
hasOptedUpdates,
|
||||
orgId: userResponse.payload.orgId,
|
||||
});
|
||||
if (editResponse.statusCode === 200) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
notification.error({
|
||||
message: editResponse.error || defaultError,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
(async (): Promise<void> => {
|
||||
@ -66,39 +140,23 @@ function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
const userPrefernceResponse = await setPreference({
|
||||
isAnonymous,
|
||||
hasOptedUpdates,
|
||||
});
|
||||
|
||||
if (userPrefernceResponse.statusCode === 200) {
|
||||
const response = await signup({
|
||||
email,
|
||||
name: firstName,
|
||||
organizationName,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setLocalStorageKey(IS_LOGGED_IN, 'yes');
|
||||
dispatch({
|
||||
type: 'LOGGED_IN',
|
||||
});
|
||||
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
setLoading(false);
|
||||
|
||||
notification.error({
|
||||
message: defaultError,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!isPasswordValid(password)) {
|
||||
setIsPasswordPolicyError(true);
|
||||
setLoading(false);
|
||||
|
||||
notification.error({
|
||||
message: defaultError,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPreferenceVisible) {
|
||||
await commonHandler(onAdminAfterLogin);
|
||||
} else {
|
||||
await commonHandler(
|
||||
async (): Promise<void> => {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: defaultError,
|
||||
@ -108,8 +166,6 @@ function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
})();
|
||||
};
|
||||
|
||||
console.log(userpref);
|
||||
|
||||
const onSwitchHandler = (
|
||||
value: boolean,
|
||||
setFunction: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
@ -118,21 +174,7 @@ function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<LeftContainer direction="vertical">
|
||||
<Space align="center">
|
||||
<Logo src="signoz-signup.svg" alt="logo" />
|
||||
<Title style={{ fontSize: '46px', margin: 0 }}>SigNoz</Title>
|
||||
</Space>
|
||||
<Typography>{t('monitor_signup')}</Typography>
|
||||
<Card
|
||||
style={{ width: 'max-content' }}
|
||||
bodyStyle={{ padding: '1px 8px', width: '100%' }}
|
||||
>
|
||||
SigNoz {version}
|
||||
</Card>
|
||||
</LeftContainer>
|
||||
|
||||
<WelcomeLeftContainer version={version}>
|
||||
<FormWrapper>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Title level={4}>Create your account</Title>
|
||||
@ -148,6 +190,7 @@ function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
}}
|
||||
required
|
||||
id="signupEmail"
|
||||
disabled={isDetailsDisable}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -161,6 +204,7 @@ function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
}}
|
||||
required
|
||||
id="signupFirstName"
|
||||
disabled={isDetailsDisable}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -169,34 +213,106 @@ function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
placeholder="Netflix"
|
||||
value={organizationName}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setOrganisationName);
|
||||
setState(e.target.value, setOrganizationName);
|
||||
}}
|
||||
required
|
||||
id="organizationName"
|
||||
disabled={isDetailsDisable}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="Password">Password</Label>
|
||||
<Input.Password
|
||||
value={password}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setPassword);
|
||||
}}
|
||||
required
|
||||
id="currentPassword"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="ConfirmPassword">Confirm Password</Label>
|
||||
<Input.Password
|
||||
value={confirmPassword}
|
||||
onChange={(e): void => {
|
||||
const updateValue = e.target.value;
|
||||
setState(updateValue, setConfirmPassword);
|
||||
if (password !== updateValue) {
|
||||
setConfirmPasswordError(true);
|
||||
} else {
|
||||
setConfirmPasswordError(false);
|
||||
}
|
||||
if (!isPasswordValid(updateValue)) {
|
||||
setIsPasswordPolicyError(true);
|
||||
} else {
|
||||
setIsPasswordPolicyError(false);
|
||||
}
|
||||
}}
|
||||
required
|
||||
id="UpdatePassword"
|
||||
/>
|
||||
|
||||
<MarginTop marginTop="2.4375rem">
|
||||
<Space>
|
||||
<Switch
|
||||
onChange={(value): void => onSwitchHandler(value, setHasOptedUpdates)}
|
||||
checked={hasOptedUpdates}
|
||||
/>
|
||||
<Typography>Keep me updated on new SigNoz features</Typography>
|
||||
</Space>
|
||||
</MarginTop>
|
||||
{confirmPasswordError && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
Passwords don’t match. Please try again
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
{isPasswordPolicyError && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
{isPasswordNotValidMessage}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<MarginTop marginTop="0.5rem">
|
||||
<Space>
|
||||
<Switch
|
||||
onChange={(value): void => onSwitchHandler(value, setisAnonymous)}
|
||||
checked={isAnonymous}
|
||||
/>
|
||||
<Typography>
|
||||
Anonymise my usage date. We collect data to measure product usage
|
||||
</Typography>
|
||||
</Space>
|
||||
</MarginTop>
|
||||
{isPreferenceVisible && (
|
||||
<>
|
||||
<MarginTop marginTop="2.4375rem">
|
||||
<Space>
|
||||
<Switch
|
||||
onChange={(value): void => onSwitchHandler(value, setHasOptedUpdates)}
|
||||
checked={hasOptedUpdates}
|
||||
/>
|
||||
<Typography>Keep me updated on new SigNoz features</Typography>
|
||||
</Space>
|
||||
</MarginTop>
|
||||
|
||||
<MarginTop marginTop="0.5rem">
|
||||
<Space>
|
||||
<Switch
|
||||
onChange={(value): void => onSwitchHandler(value, setIsAnonymous)}
|
||||
checked={isAnonymous}
|
||||
/>
|
||||
<Typography>
|
||||
Anonymise my usage date. We collect data to measure product usage
|
||||
</Typography>
|
||||
</Space>
|
||||
</MarginTop>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
This will create an admin account. If you are not an admin, please ask
|
||||
your admin for an invite link
|
||||
</Typography.Paragraph>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
@ -204,20 +320,27 @@ function Signup({ version, userpref }: SignupProps): JSX.Element {
|
||||
htmlType="submit"
|
||||
data-attr="signup"
|
||||
loading={loading}
|
||||
disabled={loading || !email || !organizationName || !firstName}
|
||||
disabled={
|
||||
loading ||
|
||||
!email ||
|
||||
!organizationName ||
|
||||
!firstName ||
|
||||
!password ||
|
||||
!confirmPassword ||
|
||||
confirmPasswordError
|
||||
}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
</Container>
|
||||
</WelcomeLeftContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface SignupProps {
|
||||
interface SignUpProps {
|
||||
version: string;
|
||||
userpref: PayloadProps;
|
||||
}
|
||||
|
||||
export default Signup;
|
||||
export default SignUp;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Typography } from 'antd';
|
||||
import getUserPreference from 'api/user/getPreference';
|
||||
import getUserVersion from 'api/user/getVersion';
|
||||
import Spinner from 'components/Spinner';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useLoggedInNavigate from 'hooks/useIfNotLoggedInNavigate';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueries } from 'react-query';
|
||||
@ -15,46 +16,38 @@ function SignUp(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [versionResponse, userPrefResponse] = useQueries([
|
||||
useLoggedInNavigate(ROUTES.APPLICATION);
|
||||
|
||||
const [versionResponse] = useQueries([
|
||||
{
|
||||
queryFn: getUserVersion,
|
||||
queryKey: 'getUserVersion',
|
||||
enabled: !isLoggedIn,
|
||||
},
|
||||
{
|
||||
queryFn: getUserPreference,
|
||||
queryKey: 'getUserPreference',
|
||||
enabled: !isLoggedIn,
|
||||
},
|
||||
]);
|
||||
|
||||
if (
|
||||
versionResponse.status === 'error' ||
|
||||
userPrefResponse.status === 'error'
|
||||
(versionResponse.status === 'success' &&
|
||||
versionResponse.data?.statusCode !== 200)
|
||||
) {
|
||||
return (
|
||||
<Typography>
|
||||
{versionResponse.data?.error ||
|
||||
userPrefResponse.data?.error ||
|
||||
t('something_went_wrong')}
|
||||
{versionResponse.data?.error || t('something_went_wrong')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
versionResponse.status === 'loading' ||
|
||||
userPrefResponse.status === 'loading' ||
|
||||
!(versionResponse.data && versionResponse.data.payload) ||
|
||||
!(userPrefResponse.data && userPrefResponse.data.payload)
|
||||
!(versionResponse.data && versionResponse.data.payload)
|
||||
) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
const { version } = versionResponse.data.payload;
|
||||
|
||||
const userpref = userPrefResponse.data.payload;
|
||||
|
||||
return <SignUpComponent userpref={userpref} version={version} />;
|
||||
return <SignUpComponent version={version} />;
|
||||
}
|
||||
|
||||
export default SignUp;
|
||||
|
@ -1,19 +1,7 @@
|
||||
import { Card, Space } from 'antd';
|
||||
import { Card } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export const FormWrapper = styled(Card)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -29,10 +17,6 @@ export const Label = styled.label`
|
||||
line-height: 24px;
|
||||
`;
|
||||
|
||||
export const LeftContainer = styled(Space)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
margin-top: 1.8125rem;
|
||||
display: flex;
|
||||
@ -47,7 +31,3 @@ interface Props {
|
||||
export const MarginTop = styled.div<Props>`
|
||||
margin-top: ${({ marginTop = 0 }): number | string => marginTop};
|
||||
`;
|
||||
|
||||
export const Logo = styled.img`
|
||||
width: 60px;
|
||||
`;
|
||||
|
15
frontend/src/pages/SignUp/utils.ts
Normal file
15
frontend/src/pages/SignUp/utils.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @function
|
||||
* @description to check whether password is valid or not
|
||||
* @reference stackoverflow.com/a/69807687
|
||||
* @returns Boolean
|
||||
*/
|
||||
export const isPasswordValid = (value: string): boolean => {
|
||||
// eslint-disable-next-line prefer-regex-literals
|
||||
const pattern = new RegExp(
|
||||
'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$',
|
||||
);
|
||||
return pattern.test(value);
|
||||
};
|
||||
|
||||
export const isPasswordNotValidMessage = `Password must a have minimum of 8 characters with at least one lower case, one upper case and one special character`;
|
43
frontend/src/pages/SomethingWentWrong/index.tsx
Normal file
43
frontend/src/pages/SomethingWentWrong/index.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { Button, Typography } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import SomethingWentWrongAsset from 'assets/SomethingWentWrong';
|
||||
import { Container } from 'components/NotFound/styles';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { LOGGED_IN } from 'types/actions/app';
|
||||
|
||||
function SomethingWentWrong(): JSX.Element {
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const isLoggedIn = getLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SomethingWentWrongAsset />
|
||||
<Typography.Title level={3}>Oops! Something went wrong</Typography.Title>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={(): void => {
|
||||
if (isLoggedIn) {
|
||||
dispatch({
|
||||
type: LOGGED_IN,
|
||||
payload: {
|
||||
isLoggedIn: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}}
|
||||
>
|
||||
Return to Metrics page
|
||||
</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default SomethingWentWrong;
|
23
frontend/src/pages/UnAuthorized/index.tsx
Normal file
23
frontend/src/pages/UnAuthorized/index.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { Space, Typography } from 'antd';
|
||||
import UnAuthorized from 'assets/UnAuthorized';
|
||||
import { Button, Container } from 'components/NotFound/styles';
|
||||
import ROUTES from 'constants/routes';
|
||||
import React from 'react';
|
||||
|
||||
function UnAuthorizePage(): JSX.Element {
|
||||
return (
|
||||
<Container>
|
||||
<Space align="center" direction="vertical">
|
||||
<UnAuthorized />
|
||||
<Typography.Title level={3}>
|
||||
Oops.. you don't have permission to view this page
|
||||
</Typography.Title>
|
||||
<Button to={ROUTES.APPLICATION} tabIndex={0}>
|
||||
Return To Metrics Page
|
||||
</Button>
|
||||
</Space>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default UnAuthorizePage;
|
@ -1,4 +1,4 @@
|
||||
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user