mirror of
https://github.com/jlengrand/signoz.git
synced 2026-03-10 08:41:20 +00:00
Feature: SSO Login and Feature gating in UI (#1605)
* feat: added usefeatureflags hook and relevant code * chore: resolved lint issues * chore: applied translations * feat: added signup for sso
This commit is contained in:
13
frontend/public/locales/en-GB/licenses.json
Normal file
13
frontend/public/locales/en-GB/licenses.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"column_license_key": "License Key",
|
||||
"column_valid_from": "Valid From",
|
||||
"column_valid_until": "Valid Until",
|
||||
"column_license_status": "Status",
|
||||
"button_apply": "Apply",
|
||||
"placeholder_license_key": "Enter a License Key",
|
||||
"tab_current_license": "Current License",
|
||||
"tab_license_history": "History",
|
||||
"loading_licenses": "Loading licenses...",
|
||||
"enter_license_key": "Please enter a license key",
|
||||
"license_applied": "License applied successfully, please refresh the page to see changes."
|
||||
}
|
||||
22
frontend/public/locales/en-GB/login.json
Normal file
22
frontend/public/locales/en-GB/login.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"label_email": "Email",
|
||||
"placeholder_email": "name@yourcompany.com",
|
||||
"label_password": "Password",
|
||||
"button_initiate_login": "Next",
|
||||
"button_login": "Login",
|
||||
"login_page_title": "Login with SigNoz",
|
||||
"login_with_sso": "Login with SSO",
|
||||
"login_with_pwd": "Login with password",
|
||||
"forgot_password": "Forgot password?",
|
||||
"create_an_account": "Create an account",
|
||||
"prompt_if_admin": "If you are admin,",
|
||||
"prompt_create_account": "If you are setting up SigNoz for the first time,",
|
||||
"prompt_no_account": "Don't have an account? Contact your admin to send you an invite link.",
|
||||
"prompt_forgot_password": "Ask your admin to reset your password and send you a new invite link",
|
||||
"prompt_on_sso_error": "Are you trying to resolve SSO configuration issue?",
|
||||
"unexpected_error": "Sorry, something went wrong",
|
||||
"failed_to_login": "sorry, failed to login",
|
||||
"invalid_email": "Please enter a valid email address",
|
||||
"invalid_account": "This account does not exist. To create a new account, contact your admin to get an invite link",
|
||||
"invalid_config": "Invalid configuration detected, please contact your administrator"
|
||||
}
|
||||
18
frontend/public/locales/en-GB/signup.json
Normal file
18
frontend/public/locales/en-GB/signup.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"label_email": "Email",
|
||||
"placeholder_email": "name@yourcompany.com",
|
||||
"label_password": "Password",
|
||||
"label_confirm_password": "Confirm Password",
|
||||
"label_firstname": "First Name",
|
||||
"placeholder_firstname": "Your Name",
|
||||
"label_orgname": "Organization Name",
|
||||
"placeholder_orgname": "Your Company",
|
||||
"prompt_keepme_posted": "Keep me updated on new SigNoz features",
|
||||
"prompt_anonymise": "Anonymise my usage date. We collect data to measure product usage",
|
||||
"failed_confirm_password": "Passwords don’t match. Please try again",
|
||||
"unexpected_error": "Something went wrong",
|
||||
"failed_to_initiate_login": "Signup completed but failed to initiate login",
|
||||
"token_required": "Invite token is required for signup, please request one from your admin",
|
||||
"button_get_started": "Get Started",
|
||||
"prompt_admin_warning": "This will create an admin account. If you are not an admin, please ask your admin for an invite link"
|
||||
}
|
||||
13
frontend/public/locales/en/licenses.json
Normal file
13
frontend/public/locales/en/licenses.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"column_license_key": "License Key",
|
||||
"column_valid_from": "Valid From",
|
||||
"column_valid_until": "Valid Until",
|
||||
"column_license_status": "Status",
|
||||
"button_apply": "Apply",
|
||||
"placeholder_license_key": "Enter a License Key",
|
||||
"tab_current_license": "Current License",
|
||||
"tab_license_history": "History",
|
||||
"loading_licenses": "Loading licenses...",
|
||||
"enter_license_key": "Please enter a license key",
|
||||
"license_applied": "License applied successfully, please refresh the page to see changes."
|
||||
}
|
||||
22
frontend/public/locales/en/login.json
Normal file
22
frontend/public/locales/en/login.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"label_email": "Email",
|
||||
"placeholder_email": "name@yourcompany.com",
|
||||
"label_password": "Password",
|
||||
"button_initiate_login": "Next",
|
||||
"button_login": "Login",
|
||||
"login_page_title": "Login with SigNoz",
|
||||
"login_with_sso": "Login with SSO",
|
||||
"login_with_pwd": "Login with password",
|
||||
"forgot_password": "Forgot password?",
|
||||
"create_an_account": "Create an account",
|
||||
"prompt_if_admin": "If you are admin,",
|
||||
"prompt_create_account": "If you are setting up SigNoz for the first time,",
|
||||
"prompt_no_account": "Don't have an account? Contact your admin to send you an invite link.",
|
||||
"prompt_forgot_password": "Ask your admin to reset your password and send you a new invite link",
|
||||
"prompt_on_sso_error": "Are you trying to resolve SSO configuration issue?",
|
||||
"unexpected_error": "Sorry, something went wrong",
|
||||
"failed_to_login": "sorry, failed to login",
|
||||
"invalid_email": "Please enter a valid email address",
|
||||
"invalid_account": "This account does not exist. To create a new account, contact your admin to get an invite link",
|
||||
"invalid_config": "Invalid configuration detected, please contact your administrator"
|
||||
}
|
||||
18
frontend/public/locales/en/signup.json
Normal file
18
frontend/public/locales/en/signup.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"label_email": "Email",
|
||||
"placeholder_email": "name@yourcompany.com",
|
||||
"label_password": "Password",
|
||||
"label_confirm_password": "Confirm Password",
|
||||
"label_firstname": "First Name",
|
||||
"placeholder_firstname": "Your Name",
|
||||
"label_orgname": "Organization Name",
|
||||
"placeholder_orgname": "Your Company",
|
||||
"prompt_keepme_posted": "Keep me updated on new SigNoz features",
|
||||
"prompt_anonymise": "Anonymise my usage date. We collect data to measure product usage",
|
||||
"failed_confirm_password": "Passwords don’t match. Please try again",
|
||||
"unexpected_error": "Something went wrong",
|
||||
"failed_to_initiate_login": "Signup completed but failed to initiate login",
|
||||
"token_required": "Invite token is required for signup, please request one from your admin",
|
||||
"button_get_started": "Get Started",
|
||||
"prompt_admin_warning": "This will create an admin account. If you are not an admin, please ask your admin for an invite link"
|
||||
}
|
||||
@@ -119,3 +119,7 @@ export const SomethingWentWrong = Loadable(
|
||||
/* webpackChunkName: "SomethingWentWrong" */ 'pages/SomethingWentWrong'
|
||||
),
|
||||
);
|
||||
|
||||
export const LicensePage = Loadable(
|
||||
() => import(/* webpackChunkName: "All Channels" */ 'pages/License'),
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
EditRulesPage,
|
||||
ErrorDetails,
|
||||
GettingStarted,
|
||||
LicensePage,
|
||||
ListAllALertsPage,
|
||||
Login,
|
||||
Logs,
|
||||
@@ -166,6 +167,13 @@ const routes: AppRoutes[] = [
|
||||
component: AllErrors,
|
||||
key: 'ALL_ERROR',
|
||||
},
|
||||
{
|
||||
path: ROUTES.LIST_LICENSES,
|
||||
exact: true,
|
||||
component: LicensePage,
|
||||
isPrivate: true,
|
||||
key: 'LIST_LICENSES',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ERROR_DETAIL,
|
||||
exact: true,
|
||||
|
||||
23
frontend/src/api/features/getFeatures.ts
Normal file
23
frontend/src/api/features/getFeatures.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/features/getFeatures';
|
||||
|
||||
const getFeaturesFlags = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/featureFlags`);
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getFeaturesFlags;
|
||||
@@ -79,6 +79,7 @@ const interceptorRejected = async (
|
||||
|
||||
// when refresh token is expired
|
||||
if (response.status === 401 && response.config.url === '/login') {
|
||||
console.log('logging out ');
|
||||
Logout();
|
||||
}
|
||||
}
|
||||
|
||||
26
frontend/src/api/licenses/apply.ts
Normal file
26
frontend/src/api/licenses/apply.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/licenses/apply';
|
||||
|
||||
const apply = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/licenses', {
|
||||
key: props.key,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default apply;
|
||||
24
frontend/src/api/licenses/getAll.ts
Normal file
24
frontend/src/api/licenses/getAll.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/licenses/getAll';
|
||||
|
||||
const getAll = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get('/licenses');
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAll;
|
||||
@@ -8,7 +8,9 @@ const getInviteDetails = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/invite/${props.inviteId}`);
|
||||
const response = await axios.get(
|
||||
`/invite/${props.inviteId}?ref=${window.location.href}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
28
frontend/src/api/user/loginPrecheck.ts
Normal file
28
frontend/src/api/user/loginPrecheck.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/loginPrecheck';
|
||||
|
||||
const loginPrecheck = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/loginPrecheck?email=${props.email}&ref=${encodeURIComponent(
|
||||
window.location.href,
|
||||
)}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.statusText,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default loginPrecheck;
|
||||
@@ -2,21 +2,24 @@ import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import * as loginPrecheck from 'types/api/user/loginPrecheck';
|
||||
import { Props } from 'types/api/user/signup';
|
||||
|
||||
const signup = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<string> | ErrorResponse> => {
|
||||
): Promise<
|
||||
SuccessResponse<null | loginPrecheck.PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.post(`/register`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
console.log(' response.data.data', response.data.data);
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
payload: response.data?.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
6
frontend/src/constants/features.ts
Normal file
6
frontend/src/constants/features.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// keep this consistent with backend constants.go
|
||||
export enum FeatureKeys {
|
||||
SSO = 'SSO',
|
||||
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
|
||||
BASIC_PLAN = 'BASIC_PLAN',
|
||||
}
|
||||
@@ -29,6 +29,7 @@ const ROUTES = {
|
||||
LOGS: '/logs',
|
||||
HOME_PAGE: '/',
|
||||
PASSWORD_RESET: '/password-reset',
|
||||
LIST_LICENSES: '/licenses',
|
||||
};
|
||||
|
||||
export default ROUTES;
|
||||
|
||||
@@ -62,6 +62,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
if (getUserVersionResponse.status === 'idle' && isLoggedIn) {
|
||||
getUserVersionResponse.refetch();
|
||||
}
|
||||
if (getFeaturesResponse.status === 'idle') {
|
||||
getFeaturesResponse.refetch();
|
||||
}
|
||||
}, [
|
||||
getFeaturesResponse,
|
||||
getUserLatestVersionResponse,
|
||||
@@ -112,6 +115,19 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
message: t('oops_something_went_wrong_version'),
|
||||
});
|
||||
}
|
||||
if (
|
||||
getFeaturesResponse.isFetched &&
|
||||
getFeaturesResponse.isSuccess &&
|
||||
getFeaturesResponse.data &&
|
||||
getFeaturesResponse.data.payload
|
||||
) {
|
||||
dispatch({
|
||||
type: UPDATE_FEATURE_FLAGS,
|
||||
payload: {
|
||||
...getFeaturesResponse.data.payload,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
getUserVersionResponse.isFetched &&
|
||||
|
||||
43
frontend/src/container/Header/ManageLicense/index.tsx
Normal file
43
frontend/src/container/Header/ManageLicense/index.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Typography } from 'antd';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useFeatureFlags from 'hooks/useFeatureFlag';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
FreePlanIcon,
|
||||
ManageLicenseContainer,
|
||||
ManageLicenseWrapper,
|
||||
} from './styles';
|
||||
|
||||
function ManageLicense({ onToggle }: ManageLicenseProps): JSX.Element {
|
||||
const isEnterprise = useFeatureFlags(FeatureKeys.ENTERPRISE_PLAN);
|
||||
return (
|
||||
<>
|
||||
<Typography>SIGNOZ STATUS</Typography>
|
||||
|
||||
<ManageLicenseContainer>
|
||||
<ManageLicenseWrapper>
|
||||
<FreePlanIcon />
|
||||
<Typography>{!isEnterprise ? 'Free Plan' : 'Enterprise Plan'} </Typography>
|
||||
</ManageLicenseWrapper>
|
||||
|
||||
<Typography.Link
|
||||
onClick={(): void => {
|
||||
onToggle();
|
||||
history.push(ROUTES.LIST_LICENSES);
|
||||
}}
|
||||
>
|
||||
Manage Licenses
|
||||
</Typography.Link>
|
||||
</ManageLicenseContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface ManageLicenseProps {
|
||||
onToggle: VoidFunction;
|
||||
}
|
||||
|
||||
export default ManageLicense;
|
||||
19
frontend/src/container/Header/ManageLicense/styles.ts
Normal file
19
frontend/src/container/Header/ManageLicense/styles.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { MinusSquareOutlined } from '@ant-design/icons';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ManageLicenseContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
export const ManageLicenseWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const FreePlanIcon = styled(MinusSquareOutlined)`
|
||||
background-color: hsla(0, 0%, 100%, 0.3);
|
||||
`;
|
||||
@@ -26,6 +26,7 @@ import AppActions from 'types/actions';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import CurrentOrganization from './CurrentOrganization';
|
||||
import ManageLicense from './ManageLicense';
|
||||
import SignedInAS from './SignedInAs';
|
||||
import { Container, LogoutContainer, ToggleButton } from './styles';
|
||||
|
||||
@@ -71,6 +72,8 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
<Divider />
|
||||
<CurrentOrganization onToggle={onArrowClickHandler} />
|
||||
<Divider />
|
||||
<ManageLicense onToggle={onArrowClickHandler} />
|
||||
<Divider />
|
||||
<LogoutContainer>
|
||||
<LogoutOutlined />
|
||||
<div
|
||||
|
||||
77
frontend/src/container/Licenses/ApplyLicenseForm.tsx
Normal file
77
frontend/src/container/Licenses/ApplyLicenseForm.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Button, Input, notification } from 'antd';
|
||||
import FormItem from 'antd/lib/form/FormItem';
|
||||
import apply from 'api/licenses/apply';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ApplyForm, ApplyFormContainer, LicenseInput } from './applyFormStyles';
|
||||
|
||||
function ApplyLicenseForm(): JSX.Element {
|
||||
const { t } = useTranslation(['licenses']);
|
||||
const [key, setKey] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onFinish = async (values: unknown | { key: string }): Promise<void> => {
|
||||
const params = values as { key: string };
|
||||
if (params.key === '' || !params.key) {
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: t('enter_license_key'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await apply({
|
||||
key: params.key,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notification.success({
|
||||
message: 'Success',
|
||||
description: t('license_applied'),
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: response.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<ApplyFormContainer>
|
||||
<ApplyForm layout="inline" onFinish={onFinish}>
|
||||
<LicenseInput labelAlign="left" name="key">
|
||||
<Input
|
||||
onChange={(e): void => {
|
||||
setKey(e.target.value as string);
|
||||
}}
|
||||
placeholder={t('placeholder_license_key')}
|
||||
/>
|
||||
</LicenseInput>
|
||||
<FormItem>
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
{t('button_apply')}
|
||||
</Button>
|
||||
</FormItem>
|
||||
</ApplyForm>
|
||||
{key && <div style={{ paddingLeft: '0.5em', color: '#666' }}> {key}</div>}
|
||||
</ApplyFormContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApplyLicenseForm;
|
||||
42
frontend/src/container/Licenses/ListLicenses.tsx
Normal file
42
frontend/src/container/Licenses/ListLicenses.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { Table } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { License } from 'types/api/licenses/def';
|
||||
import { PayloadProps } from 'types/api/licenses/getAll';
|
||||
|
||||
function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
|
||||
const { t } = useTranslation(['licenses']);
|
||||
|
||||
const columns: ColumnsType<License> = [
|
||||
{
|
||||
title: t('column_license_status'),
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: t('column_license_key'),
|
||||
dataIndex: 'key',
|
||||
key: 'key',
|
||||
},
|
||||
{
|
||||
title: t('column_valid_from'),
|
||||
dataIndex: 'ValidFrom',
|
||||
key: 'valid from',
|
||||
},
|
||||
{
|
||||
title: t('column_valid_until'),
|
||||
dataIndex: 'ValidUntil',
|
||||
key: 'valid until',
|
||||
},
|
||||
];
|
||||
|
||||
return <Table rowKey="id" dataSource={licenses} columns={columns} />;
|
||||
}
|
||||
|
||||
interface ListLicensesProps {
|
||||
licenses: PayloadProps;
|
||||
}
|
||||
|
||||
export default ListLicenses;
|
||||
26
frontend/src/container/Licenses/applyFormStyles.ts
Normal file
26
frontend/src/container/Licenses/applyFormStyles.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Form } from 'antd';
|
||||
import FormItem from 'antd/lib/form/FormItem';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ApplyFormContainer = styled.div`
|
||||
&&& {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ApplyForm = styled(Form)`
|
||||
&&& {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LicenseInput = styled(FormItem)`
|
||||
width: 200px;
|
||||
&:focus {
|
||||
width: 350px;
|
||||
input {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
43
frontend/src/container/Licenses/index.tsx
Normal file
43
frontend/src/container/Licenses/index.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Tabs, Typography } from 'antd';
|
||||
import getAll from 'api/licenses/getAll';
|
||||
import Spinner from 'components/Spinner';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ApplyLicenseForm from './ApplyLicenseForm';
|
||||
import ListLicenses from './ListLicenses';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
function Licenses(): JSX.Element {
|
||||
const { t } = useTranslation(['licenses']);
|
||||
const { loading, payload, error, errorMessage } = useFetch(getAll);
|
||||
|
||||
if (error) {
|
||||
return <Typography>{errorMessage}</Typography>;
|
||||
}
|
||||
|
||||
if (loading || payload === undefined) {
|
||||
return <Spinner tip={t('loading_licenses')} height="90vh" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs destroyInactiveTabPane defaultActiveKey="licenses">
|
||||
<TabPane tabKey="licenses" tab={t('tab_current_license')} key="licenses">
|
||||
<ApplyLicenseForm />
|
||||
<ListLicenses
|
||||
licenses={payload ? payload.filter((l) => l.isCurrent === true) : []}
|
||||
/>
|
||||
</TabPane>
|
||||
|
||||
<TabPane tabKey="history" tab={t('tab_license_history')} key="history">
|
||||
<ListLicenses
|
||||
licenses={payload ? payload.filter((l) => l.isCurrent === false) : []}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default Licenses;
|
||||
@@ -1,19 +1,109 @@
|
||||
import { Button, Input, notification, Space, Typography } from 'antd';
|
||||
import { Button, Input, notification, Space, Tooltip, Typography } from 'antd';
|
||||
import loginApi from 'api/user/login';
|
||||
import loginPrecheckApi from 'api/user/loginPrecheck';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck';
|
||||
|
||||
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function Login(): JSX.Element {
|
||||
interface LoginProps {
|
||||
jwt: string;
|
||||
refreshjwt: string;
|
||||
userId: string;
|
||||
ssoerror: string;
|
||||
withPassword: string;
|
||||
}
|
||||
|
||||
function Login({
|
||||
jwt,
|
||||
refreshjwt,
|
||||
userId,
|
||||
ssoerror = '',
|
||||
withPassword = '0',
|
||||
}: LoginProps): JSX.Element {
|
||||
const { t } = useTranslation(['login']);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [password, setPassword] = useState<string>('');
|
||||
|
||||
const [precheckResult, setPrecheckResult] = useState<PrecheckResultType>({
|
||||
sso: false,
|
||||
ssoUrl: '',
|
||||
canSelfRegister: false,
|
||||
isUser: true,
|
||||
});
|
||||
|
||||
const [precheckInProcess, setPrecheckInProcess] = useState(false);
|
||||
const [precheckComplete, setPrecheckComplete] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (withPassword === 'Y') {
|
||||
setPrecheckComplete(true);
|
||||
}
|
||||
}, [withPassword]);
|
||||
|
||||
useEffect(() => {
|
||||
async function processJwt(): Promise<void> {
|
||||
if (jwt && jwt !== '') {
|
||||
setIsLoading(true);
|
||||
await afterLogin(userId, jwt, refreshjwt);
|
||||
setIsLoading(false);
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
}
|
||||
processJwt();
|
||||
}, [jwt, refreshjwt, userId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ssoerror !== '') {
|
||||
notification.error({
|
||||
message: t('failed_to_login'),
|
||||
});
|
||||
}
|
||||
}, [ssoerror, t]);
|
||||
|
||||
const onNextHandler = async (): Promise<void> => {
|
||||
if (!email) {
|
||||
notification.error({
|
||||
message: t('invalid_email'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setPrecheckInProcess(true);
|
||||
try {
|
||||
const response = await loginPrecheckApi({
|
||||
email,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setPrecheckResult({ ...precheckResult, ...response.payload });
|
||||
|
||||
const { isUser } = response.payload;
|
||||
if (isUser) {
|
||||
setPrecheckComplete(true);
|
||||
} else {
|
||||
notification.error({
|
||||
message: t('invalid_account'),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: t('invalid_config'),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('failed to call precheck Api', e);
|
||||
notification.error({ message: t('unexpected_error') });
|
||||
}
|
||||
setPrecheckInProcess(false);
|
||||
};
|
||||
|
||||
const onChangeHandler = (
|
||||
setFunc: React.Dispatch<React.SetStateAction<string>>,
|
||||
value: string,
|
||||
@@ -42,26 +132,53 @@ function Login(): JSX.Element {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
message: response.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
notification.error({
|
||||
message: 'Something went wrong',
|
||||
message: t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderSAMLAction = (): JSX.Element => {
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
href={precheckResult.ssoUrl}
|
||||
>
|
||||
{t('login_with_sso')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const renderOnSsoError = (): JSX.Element | null => {
|
||||
if (!ssoerror) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
|
||||
{t('prompt_on_sso_error')}{' '}
|
||||
<a href="/login?password=Y">{t('login_with_pwd')}</a>.
|
||||
</Typography.Paragraph>
|
||||
);
|
||||
};
|
||||
|
||||
const { sso, canSelfRegister } = precheckResult;
|
||||
return (
|
||||
<FormWrapper>
|
||||
<FormContainer onSubmit={onSubmitHandler}>
|
||||
<Title level={4}>Login to SigNoz</Title>
|
||||
<Title level={4}>{t('login_page_title')}</Title>
|
||||
<ParentContainer>
|
||||
<Label htmlFor="signupEmail">Email</Label>
|
||||
<Label htmlFor="signupEmail">{t('label_email')}</Label>
|
||||
<Input
|
||||
placeholder="name@yourcompany.com"
|
||||
placeholder={t('placeholder_email')}
|
||||
type="email"
|
||||
autoFocus
|
||||
required
|
||||
@@ -71,46 +188,87 @@ function Login(): JSX.Element {
|
||||
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>
|
||||
{precheckComplete && !sso && (
|
||||
<ParentContainer>
|
||||
<Label htmlFor="Password">{t('label_password')}</Label>
|
||||
<Input.Password
|
||||
required
|
||||
id="currentPassword"
|
||||
onChange={(event): void =>
|
||||
onChangeHandler(setPassword, event.target.value)
|
||||
}
|
||||
disabled={isLoading}
|
||||
value={password}
|
||||
/>
|
||||
<Tooltip title={t('prompt_forgot_password')}>
|
||||
<Typography.Link>{t('forgot_password')}</Typography.Link>
|
||||
</Tooltip>
|
||||
</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>
|
||||
{!precheckComplete && (
|
||||
<Button
|
||||
disabled={precheckInProcess}
|
||||
loading={precheckInProcess}
|
||||
type="primary"
|
||||
onClick={onNextHandler}
|
||||
>
|
||||
{t('button_initiate_login')}
|
||||
</Button>
|
||||
)}
|
||||
{precheckComplete && !sso && (
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
data-attr="signup"
|
||||
>
|
||||
{t('button_login')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
|
||||
If you have forgotten you password, ask your admin to reset password and
|
||||
send you a new invite link
|
||||
</Typography.Paragraph>
|
||||
{precheckComplete && sso && renderSAMLAction()}
|
||||
{!precheckComplete && ssoerror && renderOnSsoError()}
|
||||
|
||||
{!canSelfRegister && (
|
||||
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
|
||||
{t('prompt_no_account')}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
|
||||
{!canSelfRegister && (
|
||||
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
|
||||
{t('prompt_create_account')}{' '}
|
||||
<Typography.Link
|
||||
onClick={(): void => {
|
||||
history.push(ROUTES.SIGN_UP);
|
||||
}}
|
||||
style={{ fontWeight: 700 }}
|
||||
>
|
||||
{t('create_an_account')}
|
||||
</Typography.Link>
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
|
||||
{canSelfRegister && (
|
||||
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
|
||||
{t('prompt_if_admin')}{' '}
|
||||
<Typography.Link
|
||||
onClick={(): void => {
|
||||
history.push(ROUTES.SIGN_UP);
|
||||
}}
|
||||
style={{ fontWeight: 700 }}
|
||||
>
|
||||
{t('create_an_account')}
|
||||
</Typography.Link>
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</Space>
|
||||
</FormContainer>
|
||||
</FormWrapper>
|
||||
|
||||
@@ -4,9 +4,14 @@ import styled from 'styled-components';
|
||||
export const FormWrapper = styled(Card)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-width: 390px;
|
||||
min-height: 430px;
|
||||
max-width: 432px;
|
||||
flex: 1;
|
||||
align-items: flex-start;
|
||||
&&&.ant-card-body {
|
||||
min-width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Label = styled.label`
|
||||
@@ -21,6 +26,7 @@ export const FormContainer = styled.form`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const ParentContainer = styled.div`
|
||||
|
||||
@@ -7,7 +7,6 @@ const useFeatureFlag = (flagKey: string): boolean => {
|
||||
const { featureFlags } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
);
|
||||
|
||||
return _get(featureFlags, flagKey, false);
|
||||
};
|
||||
|
||||
|
||||
8
frontend/src/pages/License/index.tsx
Normal file
8
frontend/src/pages/License/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import Licenses from 'container/Licenses';
|
||||
import React from 'react';
|
||||
|
||||
function LicensePage(): JSX.Element {
|
||||
return <Licenses />;
|
||||
}
|
||||
|
||||
export default LicensePage;
|
||||
@@ -3,6 +3,7 @@ import getUserVersion from 'api/user/getVersion';
|
||||
import Spinner from 'components/Spinner';
|
||||
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
|
||||
import LoginContainer from 'container/Login';
|
||||
import useURLQuery from 'hooks/useUrlQuery';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
@@ -14,6 +15,13 @@ function Login(): JSX.Element {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const urlQueryParams = useURLQuery();
|
||||
const jwt = urlQueryParams.get('jwt') || '';
|
||||
const refreshJwt = urlQueryParams.get('refreshjwt') || '';
|
||||
const userId = urlQueryParams.get('usr') || '';
|
||||
const ssoerror = urlQueryParams.get('ssoerror') || '';
|
||||
const withPassword = urlQueryParams.get('password') || '';
|
||||
|
||||
const versionResult = useQuery({
|
||||
queryFn: getUserVersion,
|
||||
queryKey: 'getUserVersion',
|
||||
@@ -42,7 +50,13 @@ function Login(): JSX.Element {
|
||||
|
||||
return (
|
||||
<WelcomeLeftContainer version={version}>
|
||||
<LoginContainer />
|
||||
<LoginContainer
|
||||
ssoerror={ssoerror}
|
||||
jwt={jwt}
|
||||
refreshjwt={refreshJwt}
|
||||
userId={userId}
|
||||
withPassword={withPassword}
|
||||
/>
|
||||
</WelcomeLeftContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@ 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 { useQuery } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getUser';
|
||||
import * as loginPrecheck from 'types/api/user/loginPrecheck';
|
||||
|
||||
import { ButtonContainer, FormWrapper, Label, MarginTop } from './styles';
|
||||
import { isPasswordNotValidMessage, isPasswordValid } from './utils';
|
||||
@@ -19,8 +21,14 @@ import { isPasswordNotValidMessage, isPasswordValid } from './utils';
|
||||
const { Title } = Typography;
|
||||
|
||||
function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
const { t } = useTranslation(['signup']);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [precheck, setPrecheck] = useState<loginPrecheck.PayloadProps>({
|
||||
sso: false,
|
||||
isUser: false,
|
||||
});
|
||||
|
||||
const [firstName, setFirstName] = useState<string>('');
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [organizationName, setOrganizationName] = useState<string>('');
|
||||
@@ -54,12 +62,27 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
getInviteDetailsResponse.data.payload
|
||||
) {
|
||||
const responseDetails = getInviteDetailsResponse.data.payload;
|
||||
if (responseDetails.precheck) setPrecheck(responseDetails.precheck);
|
||||
setFirstName(responseDetails.name);
|
||||
setEmail(responseDetails.email);
|
||||
setOrganizationName(responseDetails.organization);
|
||||
setIsDetailsDisable(true);
|
||||
}
|
||||
}, [getInviteDetailsResponse?.data?.payload, getInviteDetailsResponse.status]);
|
||||
if (
|
||||
getInviteDetailsResponse.status === 'success' &&
|
||||
getInviteDetailsResponse.data?.error
|
||||
) {
|
||||
const { error } = getInviteDetailsResponse.data;
|
||||
notification.error({
|
||||
message: error,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
getInviteDetailsResponse.data?.payload,
|
||||
getInviteDetailsResponse.data?.error,
|
||||
getInviteDetailsResponse.status,
|
||||
getInviteDetailsResponse,
|
||||
]);
|
||||
|
||||
const setState = (
|
||||
value: string,
|
||||
@@ -68,7 +91,6 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
setFunction(value);
|
||||
};
|
||||
|
||||
const defaultError = 'Something went wrong';
|
||||
const isPreferenceVisible = token === null;
|
||||
|
||||
const commonHandler = async (
|
||||
@@ -101,17 +123,17 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: loginResponse.error || defaultError,
|
||||
message: loginResponse.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || defaultError,
|
||||
message: response.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: defaultError,
|
||||
message: t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -129,10 +151,57 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
notification.error({
|
||||
message: editResponse.error || defaultError,
|
||||
message: editResponse.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleSubmitSSO = async (
|
||||
e: React.FormEvent<HTMLFormElement>,
|
||||
): Promise<void> => {
|
||||
if (!params.get('token')) {
|
||||
notification.error({
|
||||
message: t('token_required'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
e.preventDefault();
|
||||
const response = await signUpApi({
|
||||
email,
|
||||
name: firstName,
|
||||
orgName: organizationName,
|
||||
password,
|
||||
token: params.get('token') || undefined,
|
||||
sourceUrl: encodeURIComponent(window.location.href),
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
if (response.payload?.sso) {
|
||||
if (response.payload?.ssoUrl) {
|
||||
window.location.href = response.payload?.ssoUrl;
|
||||
} else {
|
||||
notification.error({
|
||||
message: t('failed_to_initiate_login'),
|
||||
});
|
||||
// take user to login page as there is nothing to do here
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
(async (): Promise<void> => {
|
||||
@@ -159,7 +228,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: defaultError,
|
||||
message: t('unexpected_error'),
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -195,12 +264,12 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
return (
|
||||
<WelcomeLeftContainer version={version}>
|
||||
<FormWrapper>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<form onSubmit={!precheck.sso ? handleSubmit : handleSubmitSSO}>
|
||||
<Title level={4}>Create your account</Title>
|
||||
<div>
|
||||
<Label htmlFor="signupEmail">Email</Label>
|
||||
<Label htmlFor="signupEmail">{t('label_email')}</Label>
|
||||
<Input
|
||||
placeholder="name@yourcompany.com"
|
||||
placeholder={t('placeholder_email')}
|
||||
type="email"
|
||||
autoFocus
|
||||
value={email}
|
||||
@@ -215,9 +284,9 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
|
||||
{isNameVisible && (
|
||||
<div>
|
||||
<Label htmlFor="signupFirstName">First Name</Label>
|
||||
<Label htmlFor="signupFirstName">{t('label_firstname')}</Label>
|
||||
<Input
|
||||
placeholder="Your Name"
|
||||
placeholder={t('placeholder_firstname')}
|
||||
value={firstName}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setFirstName);
|
||||
@@ -230,9 +299,9 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Label htmlFor="organizationName">Organization Name</Label>
|
||||
<Label htmlFor="organizationName">{t('label_orgname')}</Label>
|
||||
<Input
|
||||
placeholder="Your Company"
|
||||
placeholder={t('placeholder_orgname')}
|
||||
value={organizationName}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setOrganizationName);
|
||||
@@ -242,53 +311,57 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
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);
|
||||
}}
|
||||
required
|
||||
id="confirmPassword"
|
||||
/>
|
||||
{!precheck.sso && (
|
||||
<div>
|
||||
<Label htmlFor="Password">{t('label_password')}</Label>
|
||||
<Input.Password
|
||||
value={password}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setPassword);
|
||||
}}
|
||||
required
|
||||
id="currentPassword"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!precheck.sso && (
|
||||
<div>
|
||||
<Label htmlFor="ConfirmPassword">{t('label_confirm_password')}</Label>
|
||||
<Input.Password
|
||||
value={confirmPassword}
|
||||
onChange={(e): void => {
|
||||
const updateValue = e.target.value;
|
||||
setState(updateValue, setConfirmPassword);
|
||||
}}
|
||||
required
|
||||
id="confirmPassword"
|
||||
/>
|
||||
|
||||
{confirmPasswordError && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
id="password-confirm-error"
|
||||
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>
|
||||
{confirmPasswordError && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
id="password-confirm-error"
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
{t('failed_confirm_password')}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
{isPasswordPolicyError && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
{isPasswordNotValidMessage}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isPreferenceVisible && (
|
||||
<>
|
||||
@@ -298,7 +371,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
onChange={(value): void => onSwitchHandler(value, setHasOptedUpdates)}
|
||||
checked={hasOptedUpdates}
|
||||
/>
|
||||
<Typography>Keep me updated on new SigNoz features</Typography>
|
||||
<Typography>{t('prompt_keepme_posted')} </Typography>
|
||||
</Space>
|
||||
</MarginTop>
|
||||
|
||||
@@ -308,9 +381,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
onChange={(value): void => onSwitchHandler(value, setIsAnonymous)}
|
||||
checked={isAnonymous}
|
||||
/>
|
||||
<Typography>
|
||||
Anonymise my usage date. We collect data to measure product usage
|
||||
</Typography>
|
||||
<Typography>{t('prompt_anonymise')}</Typography>
|
||||
</Space>
|
||||
</MarginTop>
|
||||
</>
|
||||
@@ -339,14 +410,13 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
loading ||
|
||||
!email ||
|
||||
!organizationName ||
|
||||
!password ||
|
||||
!confirmPassword ||
|
||||
(!precheck.sso && (!password || !confirmPassword)) ||
|
||||
!firstName ||
|
||||
confirmPasswordError ||
|
||||
isPasswordPolicyError
|
||||
}
|
||||
>
|
||||
Get Started
|
||||
{t('button_get_started')}
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
|
||||
@@ -48,6 +48,7 @@ const InitialValue: InitialValueTypes = {
|
||||
isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
||||
currentVersion: '',
|
||||
latestVersion: '',
|
||||
featureFlags: {},
|
||||
isCurrentVersionError: false,
|
||||
isLatestVersionError: false,
|
||||
user: getInitialUser(),
|
||||
@@ -55,7 +56,6 @@ const InitialValue: InitialValueTypes = {
|
||||
isUserFetchingError: false,
|
||||
org: null,
|
||||
role: null,
|
||||
featureFlags: null,
|
||||
};
|
||||
|
||||
const appReducer = (
|
||||
@@ -84,6 +84,13 @@ const appReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_FEATURE_FLAGS: {
|
||||
return {
|
||||
...state,
|
||||
featureFlags: { ...action.payload },
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_CURRENT_VERSION: {
|
||||
return {
|
||||
...state,
|
||||
@@ -196,13 +203,6 @@ const appReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_FEATURE_FLAGS: {
|
||||
return {
|
||||
...state,
|
||||
featureFlags: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_ORG: {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -40,6 +40,10 @@ export interface SideBarCollapse {
|
||||
payload: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateFeatureFlags {
|
||||
type: typeof UPDATE_FEATURE_FLAGS;
|
||||
payload: null | FeatureFlagPayload;
|
||||
}
|
||||
export interface UpdateAppVersion {
|
||||
type: typeof UPDATE_CURRENT_VERSION;
|
||||
payload: {
|
||||
@@ -112,11 +116,6 @@ export interface UpdateOrg {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateFeatureFlags {
|
||||
type: typeof UPDATE_FEATURE_FLAGS;
|
||||
payload: FeatureFlagPayload;
|
||||
}
|
||||
|
||||
export type AppAction =
|
||||
| SwitchDarkMode
|
||||
| LoggedInUser
|
||||
|
||||
3
frontend/src/types/api/features/getFeatures.ts
Normal file
3
frontend/src/types/api/features/getFeatures.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface PayloadProps {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
10
frontend/src/types/api/licenses/apply.ts
Normal file
10
frontend/src/types/api/licenses/apply.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { License } from './def';
|
||||
|
||||
export interface Props {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
status: string;
|
||||
data: License;
|
||||
}
|
||||
8
frontend/src/types/api/licenses/def.ts
Normal file
8
frontend/src/types/api/licenses/def.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface License {
|
||||
key: string;
|
||||
ValidFrom: Date;
|
||||
ValidUntil: Date;
|
||||
planKey: string;
|
||||
status: string;
|
||||
isCurrent: boolean;
|
||||
}
|
||||
3
frontend/src/types/api/licenses/getAll.ts
Normal file
3
frontend/src/types/api/licenses/getAll.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { License } from './def';
|
||||
|
||||
export type PayloadProps = License[];
|
||||
@@ -2,6 +2,7 @@ import { User } from 'types/reducer/app';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import { Organization } from './getOrganization';
|
||||
import * as loginPrecheck from './loginPrecheck';
|
||||
|
||||
export interface Props {
|
||||
inviteId: string;
|
||||
@@ -14,4 +15,5 @@ export interface PayloadProps {
|
||||
role: ROLES;
|
||||
token: string;
|
||||
organization: Organization['name'];
|
||||
precheck?: loginPrecheck.PayloadProps;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface PayloadProps {
|
||||
version: string;
|
||||
ee: string;
|
||||
}
|
||||
|
||||
11
frontend/src/types/api/user/loginPrecheck.ts
Normal file
11
frontend/src/types/api/user/loginPrecheck.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface PayloadProps {
|
||||
sso: boolean;
|
||||
ssoUrl?: string;
|
||||
canSelfRegister?: boolean;
|
||||
isUser: boolean;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
email?: string;
|
||||
path?: string;
|
||||
}
|
||||
@@ -4,4 +4,5 @@ export interface Props {
|
||||
email: string;
|
||||
password: string;
|
||||
token?: string;
|
||||
sourceUrl?: string;
|
||||
}
|
||||
|
||||
@@ -69,4 +69,5 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
USAGE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
VERSION: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LIST_LICENSES: ['ADMIN'],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user