mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
feat: add create api token screen (NEW PR) (#600)
* feat: add create api token screen * fix: update headers * fix: remove old api create Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
3a41de2246
commit
b209368c84
@ -1,224 +0,0 @@
|
|||||||
import { TextField } from '@material-ui/core';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { styles as commonStyles } from '../../../common';
|
|
||||||
import { IApiTokenCreate } from '../../../../hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
|
||||||
import useEnvironments from '../../../../hooks/api/getters/useEnvironments/useEnvironments';
|
|
||||||
import useProjects from '../../../../hooks/api/getters/useProjects/useProjects';
|
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender';
|
|
||||||
import Dialogue from '../../../common/Dialogue';
|
|
||||||
import GeneralSelect from '../../../common/GeneralSelect/GeneralSelect';
|
|
||||||
|
|
||||||
import { useStyles } from './styles';
|
|
||||||
|
|
||||||
const ALL = '*';
|
|
||||||
const TYPE_ADMIN = 'ADMIN';
|
|
||||||
const TYPE_CLIENT = 'CLIENT';
|
|
||||||
|
|
||||||
interface IApiTokenCreateProps {
|
|
||||||
showDialog: boolean;
|
|
||||||
closeDialog: () => void;
|
|
||||||
createToken: (token: IApiTokenCreate) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IDataError {
|
|
||||||
username?: string;
|
|
||||||
general?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const INITIAL_DATA: IApiTokenCreate = {
|
|
||||||
username: '',
|
|
||||||
type: TYPE_CLIENT,
|
|
||||||
project: ALL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ApiTokenCreate = ({
|
|
||||||
showDialog,
|
|
||||||
closeDialog,
|
|
||||||
createToken,
|
|
||||||
}: IApiTokenCreateProps) => {
|
|
||||||
const styles = useStyles();
|
|
||||||
const [data, setData] = useState(INITIAL_DATA);
|
|
||||||
const [error, setError] = useState<IDataError>({});
|
|
||||||
const { projects } = useProjects();
|
|
||||||
const { environments } = useEnvironments();
|
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
environments &&
|
|
||||||
environments.length > 0 &&
|
|
||||||
data.type === TYPE_CLIENT &&
|
|
||||||
!data.environment
|
|
||||||
) {
|
|
||||||
setData({ ...data, environment: environments[0].name });
|
|
||||||
}
|
|
||||||
}, [data, environments]);
|
|
||||||
|
|
||||||
const clear = () => {
|
|
||||||
const environment =
|
|
||||||
environments && environments.length > 0
|
|
||||||
? environments[0].name
|
|
||||||
: undefined;
|
|
||||||
setData({ ...INITIAL_DATA, environment });
|
|
||||||
setError({});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCancel = (e: Event) => {
|
|
||||||
clear();
|
|
||||||
closeDialog();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isValid = () => {
|
|
||||||
if (!data.username) {
|
|
||||||
setError({ username: 'Username is required.' });
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
setError({});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = async () => {
|
|
||||||
if (!isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await createToken(data);
|
|
||||||
clear();
|
|
||||||
closeDialog();
|
|
||||||
} catch (error) {
|
|
||||||
setError({ general: 'Unable to create new API token' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setType = (event: React.ChangeEvent<{ value: string }>) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
if (value === TYPE_ADMIN) {
|
|
||||||
setData({ ...data, type: value, environment: ALL, project: ALL });
|
|
||||||
} else {
|
|
||||||
setData({
|
|
||||||
...data,
|
|
||||||
type: value,
|
|
||||||
environment: environments[0].name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setUsername = (event: React.ChangeEvent<{ value: string }>) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
setData({ ...data, username: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const setProject = (event: React.ChangeEvent<{ value: string }>) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
setData({ ...data, project: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const setEnvironment = (event: React.ChangeEvent<{ value: string }>) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
setData({ ...data, environment: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectableProjects = [{ id: '*', name: 'ALL' }, ...projects].map(
|
|
||||||
i => ({
|
|
||||||
key: i.id,
|
|
||||||
label: i.name,
|
|
||||||
title: i.name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectableEnvs =
|
|
||||||
data.type === TYPE_ADMIN
|
|
||||||
? [{ key: '*', label: 'ALL' }]
|
|
||||||
: environments.map(i => ({
|
|
||||||
key: i.name,
|
|
||||||
label: i.name,
|
|
||||||
title: i.name,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const selectableTypes = [
|
|
||||||
{ key: 'CLIENT', label: 'Client', title: 'Client SDK token' },
|
|
||||||
{ key: 'ADMIN', label: 'Admin', title: 'Admin API token' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const formId = 'create-api-token-form';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialogue
|
|
||||||
onClick={() => submit()}
|
|
||||||
open={showDialog}
|
|
||||||
onClose={onCancel}
|
|
||||||
primaryButtonText="Create"
|
|
||||||
secondaryButtonText="Cancel"
|
|
||||||
title="New API token"
|
|
||||||
formId={formId}
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
id={formId}
|
|
||||||
onSubmit={submit}
|
|
||||||
className={classNames(
|
|
||||||
styles.addApiKeyForm,
|
|
||||||
commonStyles.contentSpacing
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<TextField
|
|
||||||
autoFocus
|
|
||||||
value={data.username}
|
|
||||||
name="username"
|
|
||||||
onChange={setUsername}
|
|
||||||
onBlur={isValid}
|
|
||||||
label="Username"
|
|
||||||
style={{ width: '200px' }}
|
|
||||||
error={error.username !== undefined}
|
|
||||||
helperText={error.username}
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<GeneralSelect
|
|
||||||
disabled={false}
|
|
||||||
options={selectableTypes}
|
|
||||||
value={data.type}
|
|
||||||
onChange={setType}
|
|
||||||
label="Token Type"
|
|
||||||
id="api_key_type"
|
|
||||||
name="type"
|
|
||||||
className={undefined}
|
|
||||||
classes={undefined}
|
|
||||||
/>
|
|
||||||
<GeneralSelect
|
|
||||||
disabled={data.type === TYPE_ADMIN}
|
|
||||||
options={selectableProjects}
|
|
||||||
value={data.project}
|
|
||||||
onChange={setProject}
|
|
||||||
label="Project"
|
|
||||||
id="api_key_project"
|
|
||||||
name="project"
|
|
||||||
className={undefined}
|
|
||||||
classes={undefined}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender condition={uiConfig?.flags.E} show={
|
|
||||||
<>
|
|
||||||
<GeneralSelect
|
|
||||||
disabled={data.type === TYPE_ADMIN}
|
|
||||||
options={selectableEnvs}
|
|
||||||
value={data.environment}
|
|
||||||
required
|
|
||||||
onChange={setEnvironment}
|
|
||||||
label="Environment"
|
|
||||||
id="api_key_environment"
|
|
||||||
name="environment"
|
|
||||||
className={undefined}
|
|
||||||
classes={undefined}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
} />
|
|
||||||
</form>
|
|
||||||
</Dialogue>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ApiTokenCreate;
|
|
@ -1,8 +0,0 @@
|
|||||||
import { makeStyles } from '@material-ui/styles';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles({
|
|
||||||
addApiKeyForm: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
});
|
|
@ -0,0 +1,53 @@
|
|||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
container: {
|
||||||
|
maxWidth: '400px',
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
input: { width: '100%', marginBottom: '1rem' },
|
||||||
|
selectInput: {
|
||||||
|
marginBottom: '1rem',
|
||||||
|
minWidth: '400px',
|
||||||
|
[theme.breakpoints.down(600)]: {
|
||||||
|
minWidth: '379px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
minWidth: '300px',
|
||||||
|
[theme.breakpoints.down(600)]: {
|
||||||
|
minWidth: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
marginRight: '1.5rem',
|
||||||
|
},
|
||||||
|
inputDescription: {
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
},
|
||||||
|
formHeader: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
marginTop: '0',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
},
|
||||||
|
permissionErrorContainer: {
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-8px',
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,142 @@
|
|||||||
|
import { Button } from '@material-ui/core';
|
||||||
|
import { KeyboardArrowDownOutlined } from '@material-ui/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import useEnvironments from '../../../../hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
|
import useProjects from '../../../../hooks/api/getters/useProjects/useProjects';
|
||||||
|
import GeneralSelect from '../../../common/GeneralSelect/GeneralSelect';
|
||||||
|
import Input from '../../../common/Input/Input';
|
||||||
|
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
|
||||||
|
import { ADMIN } from '../../../providers/AccessProvider/permissions';
|
||||||
|
import { useStyles } from './ApiTokenForm.styles';
|
||||||
|
|
||||||
|
interface IApiTokenFormProps {
|
||||||
|
username: string;
|
||||||
|
type: string;
|
||||||
|
project: string;
|
||||||
|
environment: string;
|
||||||
|
setTokenType: (value: string) => void;
|
||||||
|
setUsername: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
setProject: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
setEnvironment: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
handleSubmit: (e: any) => void;
|
||||||
|
handleCancel: () => void;
|
||||||
|
errors: { [key: string]: string };
|
||||||
|
submitButtonText: string;
|
||||||
|
clearErrors: () => void;
|
||||||
|
}
|
||||||
|
const ApiTokenForm = ({
|
||||||
|
username,
|
||||||
|
type,
|
||||||
|
project,
|
||||||
|
environment,
|
||||||
|
setUsername,
|
||||||
|
setTokenType,
|
||||||
|
setProject,
|
||||||
|
setEnvironment,
|
||||||
|
handleSubmit,
|
||||||
|
handleCancel,
|
||||||
|
errors,
|
||||||
|
clearErrors,
|
||||||
|
submitButtonText,
|
||||||
|
}: IApiTokenFormProps) => {
|
||||||
|
const TYPE_ADMIN = 'ADMIN';
|
||||||
|
const styles = useStyles();
|
||||||
|
const { environments } = useEnvironments();
|
||||||
|
const { projects } = useProjects();
|
||||||
|
|
||||||
|
const selectableTypes = [
|
||||||
|
{ key: 'CLIENT', label: 'Client', title: 'Client SDK token' },
|
||||||
|
{ key: 'ADMIN', label: 'Admin', title: 'Admin API token' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const selectableProjects = [{ id: '*', name: 'ALL' }, ...projects].map(
|
||||||
|
i => ({
|
||||||
|
key: i.id,
|
||||||
|
label: i.name,
|
||||||
|
title: i.name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const selectableEnvs =
|
||||||
|
type === TYPE_ADMIN
|
||||||
|
? [{ key: '*', label: 'ALL' }]
|
||||||
|
: environments.map(i => ({
|
||||||
|
key: i.name,
|
||||||
|
label: i.name,
|
||||||
|
title: i.name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} className={styles.form}>
|
||||||
|
<h3 className={styles.formHeader}>Token information</h3>
|
||||||
|
|
||||||
|
<div className={styles.container}>
|
||||||
|
<p className={styles.inputDescription}>
|
||||||
|
Who are you generating the token for?
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
className={styles.input}
|
||||||
|
value={username}
|
||||||
|
name="username"
|
||||||
|
onChange={e => setUsername(e.target.value)}
|
||||||
|
label="Username"
|
||||||
|
error={errors.username !== undefined}
|
||||||
|
errorText={errors.username}
|
||||||
|
onFocus={() => clearErrors()}
|
||||||
|
/>
|
||||||
|
<p className={styles.inputDescription}>
|
||||||
|
What is your token type?
|
||||||
|
</p>
|
||||||
|
<GeneralSelect
|
||||||
|
options={selectableTypes}
|
||||||
|
value={type}
|
||||||
|
onChange={e => setTokenType(e.target.value as string)}
|
||||||
|
label="Token Type"
|
||||||
|
id="api_key_type"
|
||||||
|
name="type"
|
||||||
|
IconComponent={KeyboardArrowDownOutlined}
|
||||||
|
className={styles.selectInput}
|
||||||
|
/>
|
||||||
|
<p className={styles.inputDescription}>
|
||||||
|
Which project do you want to give access to?
|
||||||
|
</p>
|
||||||
|
<GeneralSelect
|
||||||
|
disabled={type === TYPE_ADMIN}
|
||||||
|
value={project}
|
||||||
|
options={selectableProjects}
|
||||||
|
onChange={e => setProject(e.target.value as string)}
|
||||||
|
label="Project"
|
||||||
|
IconComponent={KeyboardArrowDownOutlined}
|
||||||
|
className={styles.selectInput}
|
||||||
|
/>
|
||||||
|
<p className={styles.inputDescription}>
|
||||||
|
Which environment should the token have access to?
|
||||||
|
</p>
|
||||||
|
<GeneralSelect
|
||||||
|
disabled={type === TYPE_ADMIN}
|
||||||
|
options={selectableEnvs}
|
||||||
|
value={environment}
|
||||||
|
onChange={e => setEnvironment(e.target.value as string)}
|
||||||
|
label="Environment"
|
||||||
|
id="api_key_environment"
|
||||||
|
name="environment"
|
||||||
|
IconComponent={KeyboardArrowDownOutlined}
|
||||||
|
className={styles.selectInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.buttonContainer}>
|
||||||
|
<Button onClick={handleCancel} className={styles.cancelButton}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<PermissionButton
|
||||||
|
onClick={handleSubmit}
|
||||||
|
permission={ADMIN}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{submitButtonText} token
|
||||||
|
</PermissionButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiTokenForm;
|
@ -1,5 +1,5 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -14,9 +14,7 @@ import useToast from '../../../../hooks/useToast';
|
|||||||
import useLoading from '../../../../hooks/useLoading';
|
import useLoading from '../../../../hooks/useLoading';
|
||||||
import useApiTokens from '../../../../hooks/api/getters/useApiTokens/useApiTokens';
|
import useApiTokens from '../../../../hooks/api/getters/useApiTokens/useApiTokens';
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useApiTokensApi, {
|
import useApiTokensApi from '../../../../hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
||||||
IApiTokenCreate,
|
|
||||||
} from '../../../../hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
|
||||||
import ApiError from '../../../common/ApiError/ApiError';
|
import ApiError from '../../../common/ApiError/ApiError';
|
||||||
import PageContent from '../../../common/PageContent';
|
import PageContent from '../../../common/PageContent';
|
||||||
import HeaderTitle from '../../../common/HeaderTitle';
|
import HeaderTitle from '../../../common/HeaderTitle';
|
||||||
@ -29,7 +27,6 @@ import { useStyles } from './ApiTokenList.styles';
|
|||||||
import { formatDateWithLocale } from '../../../common/util';
|
import { formatDateWithLocale } from '../../../common/util';
|
||||||
import Secret from './secret';
|
import Secret from './secret';
|
||||||
import { Delete, FileCopy } from '@material-ui/icons';
|
import { Delete, FileCopy } from '@material-ui/icons';
|
||||||
import ApiTokenCreate from '../ApiTokenCreate/ApiTokenCreate';
|
|
||||||
import Dialogue from '../../../common/Dialogue';
|
import Dialogue from '../../../common/Dialogue';
|
||||||
import { CREATE_API_TOKEN_BUTTON } from '../../../../testIds';
|
import { CREATE_API_TOKEN_BUTTON } from '../../../../testIds';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
@ -54,20 +51,11 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const [showDelete, setShowDelete] = useState(false);
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
const [delToken, setDeleteToken] = useState<IApiToken>();
|
const [delToken, setDeleteToken] = useState<IApiToken>();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData } = useToast();
|
||||||
const { tokens, loading, refetch, error } = useApiTokens();
|
const { tokens, loading, refetch, error } = useApiTokens();
|
||||||
const { deleteToken, createToken } = useApiTokensApi();
|
const { deleteToken } = useApiTokensApi();
|
||||||
const ref = useLoading(loading);
|
const ref = useLoading(loading);
|
||||||
|
const history = useHistory();
|
||||||
const [showDialog, setDialog] = useState(false);
|
|
||||||
|
|
||||||
const openDialog = () => {
|
|
||||||
setDialog(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeDialog = () => {
|
|
||||||
setDialog(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderError = () => {
|
const renderError = () => {
|
||||||
return (
|
return (
|
||||||
@ -79,26 +67,11 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreateToken = async (token: IApiTokenCreate) => {
|
|
||||||
try {
|
|
||||||
await createToken(token);
|
|
||||||
refetch();
|
|
||||||
setToastData({
|
|
||||||
type: 'success',
|
|
||||||
title: 'Created token',
|
|
||||||
text: 'Successfully created API token',
|
|
||||||
confetti: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
setToastApiError(e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const copyToken = (value: string) => {
|
const copyToken = (value: string) => {
|
||||||
if (copy(value)) {
|
if (copy(value)) {
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Token copied',
|
title: 'Token copied',
|
||||||
confetti: true,
|
|
||||||
text: `Token is copied to clipboard`,
|
text: `Token is copied to clipboard`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -271,7 +244,11 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={openDialog}
|
onClick={() =>
|
||||||
|
history.push(
|
||||||
|
'/admin/api/create-token'
|
||||||
|
)
|
||||||
|
}
|
||||||
data-test={CREATE_API_TOKEN_BUTTON}
|
data-test={CREATE_API_TOKEN_BUTTON}
|
||||||
>
|
>
|
||||||
Create API token
|
Create API token
|
||||||
@ -312,11 +289,6 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ApiTokenCreate
|
|
||||||
showDialog={showDialog}
|
|
||||||
createToken={onCreateToken}
|
|
||||||
closeDialog={closeDialog}
|
|
||||||
/>
|
|
||||||
<Dialogue
|
<Dialogue
|
||||||
open={showDelete}
|
open={showDelete}
|
||||||
onClick={onDeleteToken}
|
onClick={onDeleteToken}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import ApiTokenForm from '../ApiTokenForm/ApiTokenForm';
|
||||||
|
import useApiTokenForm from '../hooks/useApiTokenForm';
|
||||||
|
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import useToast from '../../../../hooks/useToast';
|
||||||
|
import useApiTokensApi from '../../../../hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
||||||
|
|
||||||
|
const CreateApiToken = () => {
|
||||||
|
/* @ts-ignore */
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
|
const history = useHistory();
|
||||||
|
const {
|
||||||
|
getApiTokenPayload,
|
||||||
|
username,
|
||||||
|
type,
|
||||||
|
project,
|
||||||
|
environment,
|
||||||
|
setUsername,
|
||||||
|
setTokenType,
|
||||||
|
setProject,
|
||||||
|
setEnvironment,
|
||||||
|
isValid,
|
||||||
|
errors,
|
||||||
|
clearErrors,
|
||||||
|
} = useApiTokenForm();
|
||||||
|
|
||||||
|
const { createToken, loading } = useApiTokensApi();
|
||||||
|
|
||||||
|
const handleSubmit = async (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await createToken(getApiTokenPayload());
|
||||||
|
history.push('/admin/api');
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Created token',
|
||||||
|
text: 'Successfully created API token',
|
||||||
|
confetti: true,
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
setToastApiError(e.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatApiCode = () => {
|
||||||
|
return `curl --location --request POST '${
|
||||||
|
uiConfig.unleashUrl
|
||||||
|
}/api/admin/api-tokens' \\
|
||||||
|
--header 'Authorization: INSERT_API_KEY' \\
|
||||||
|
--header 'Content-Type: application/json' \\
|
||||||
|
--data-raw '${JSON.stringify(getApiTokenPayload(), undefined, 2)}'`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
history.goBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormTemplate
|
||||||
|
loading={loading}
|
||||||
|
title="Create Api Token"
|
||||||
|
description="In order to connect to Unleash clients will need an API token to grant access. A client SDK will need to token with 'client privileges', which allows them to fetch feature toggle configuration and post usage metrics back."
|
||||||
|
documentationLink="https://docs.getunleash.io/user_guide/api-token"
|
||||||
|
formatApiCode={formatApiCode}
|
||||||
|
>
|
||||||
|
<ApiTokenForm
|
||||||
|
username={username}
|
||||||
|
type={type}
|
||||||
|
project={project}
|
||||||
|
environment={environment}
|
||||||
|
setEnvironment={setEnvironment}
|
||||||
|
setTokenType={setTokenType}
|
||||||
|
setUsername={setUsername}
|
||||||
|
setProject={setProject}
|
||||||
|
errors={errors}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
submitButtonText="Create"
|
||||||
|
clearErrors={clearErrors}
|
||||||
|
/>
|
||||||
|
</FormTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateApiToken;
|
@ -0,0 +1,86 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const useApiToken = (
|
||||||
|
initialUserName = '',
|
||||||
|
initialtype = 'CLIENT',
|
||||||
|
initialProject = '*',
|
||||||
|
initialEnvironment = 'default'
|
||||||
|
) => {
|
||||||
|
const [username, setUsername] = useState(initialUserName);
|
||||||
|
const [type, setType] = useState(initialtype);
|
||||||
|
const [project, setProject] = useState(initialtype);
|
||||||
|
const [environment, setEnvironment] = useState(initialEnvironment);
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUsername(initialUserName);
|
||||||
|
}, [initialUserName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setType(initialtype);
|
||||||
|
if (type === 'ADMIN') {
|
||||||
|
setProject('*');
|
||||||
|
setEnvironment('*')
|
||||||
|
}
|
||||||
|
//eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [initialtype]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setProject(initialProject);
|
||||||
|
}, [initialProject]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setEnvironment(initialEnvironment);
|
||||||
|
}, [initialEnvironment]);
|
||||||
|
|
||||||
|
const setTokenType = (value: string) => {
|
||||||
|
if (value === 'ADMIN') {
|
||||||
|
setType(value)
|
||||||
|
setProject('*');
|
||||||
|
setEnvironment('*');
|
||||||
|
} else {
|
||||||
|
setType(value)
|
||||||
|
setEnvironment(initialEnvironment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getApiTokenPayload = () => {
|
||||||
|
return {
|
||||||
|
username: username,
|
||||||
|
type: type,
|
||||||
|
project: project,
|
||||||
|
environment: environment,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValid = () => {
|
||||||
|
if (!username) {
|
||||||
|
setErrors({ username: 'Username is required.' });
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
setErrors({});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearErrors = () => {
|
||||||
|
setErrors({});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
username,
|
||||||
|
type,
|
||||||
|
project,
|
||||||
|
environment,
|
||||||
|
setUsername,
|
||||||
|
setTokenType,
|
||||||
|
setProject,
|
||||||
|
setEnvironment,
|
||||||
|
getApiTokenPayload,
|
||||||
|
isValid,
|
||||||
|
clearErrors,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useApiToken;
|
@ -14,6 +14,7 @@ const FeatureProjectSelect = ({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
filter,
|
filter,
|
||||||
|
...rest
|
||||||
}: IFeatureProjectSelect) => {
|
}: IFeatureProjectSelect) => {
|
||||||
const { projects } = useProjects();
|
const { projects } = useProjects();
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ const FeatureProjectSelect = ({
|
|||||||
options={options}
|
options={options}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -330,6 +330,15 @@ Array [
|
|||||||
"title": "Archived Toggles",
|
"title": "Archived Toggles",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"layout": "main",
|
||||||
|
"menu": Object {},
|
||||||
|
"parent": "/admin",
|
||||||
|
"path": "/admin/api/create-token",
|
||||||
|
"title": "API access",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"flag": "RE",
|
"flag": "RE",
|
||||||
|
@ -43,6 +43,7 @@ import FeatureCreate from '../feature/FeatureCreate/FeatureCreate';
|
|||||||
import ProjectRoles from '../admin/project-roles/ProjectRoles/ProjectRoles';
|
import ProjectRoles from '../admin/project-roles/ProjectRoles/ProjectRoles';
|
||||||
import CreateProjectRole from '../admin/project-roles/CreateProjectRole/CreateProjectRole';
|
import CreateProjectRole from '../admin/project-roles/CreateProjectRole/CreateProjectRole';
|
||||||
import EditProjectRole from '../admin/project-roles/EditProjectRole/EditProjectRole';
|
import EditProjectRole from '../admin/project-roles/EditProjectRole/EditProjectRole';
|
||||||
|
import CreateApiToken from '../admin/api-token/CreateApiToken/CreateApiToken';
|
||||||
|
|
||||||
export const routes = [
|
export const routes = [
|
||||||
// Project
|
// Project
|
||||||
@ -378,6 +379,15 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Admin
|
// Admin
|
||||||
|
{
|
||||||
|
path: '/admin/api/create-token',
|
||||||
|
parent: '/admin',
|
||||||
|
title: 'API access',
|
||||||
|
component: CreateApiToken,
|
||||||
|
type: 'protected',
|
||||||
|
layout: 'main',
|
||||||
|
menu: {},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/create-project-role',
|
path: '/admin/create-project-role',
|
||||||
title: 'Create',
|
title: 'Create',
|
||||||
|
@ -8,7 +8,7 @@ export interface IApiTokenCreate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useApiTokensApi = () => {
|
const useApiTokensApi = () => {
|
||||||
const { makeRequest, createRequest, errors } = useAPI({
|
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||||
propagateErrors: true,
|
propagateErrors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ const useApiTokensApi = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { deleteToken, createToken, errors };
|
return { deleteToken, createToken, errors, loading };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useApiTokensApi;
|
export default useApiTokensApi;
|
||||||
|
Loading…
Reference in New Issue
Block a user