mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
refactor: restrict API tokens to enabled environments (#809)
* refactor: add missing Tooltip wrapper elements * refactor: rewrite useEnvironments * refactor: disable environments in select box * refactor: make sure initial environment is enabled
This commit is contained in:
parent
cc0b9f7291
commit
2ca88b019a
@ -1,7 +1,7 @@
|
|||||||
import { Button } from '@material-ui/core';
|
import { Button } from '@material-ui/core';
|
||||||
import { KeyboardArrowDownOutlined } from '@material-ui/icons';
|
import { KeyboardArrowDownOutlined } from '@material-ui/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useEnvironments from '../../../../hooks/api/getters/useEnvironments/useEnvironments';
|
import { useEnvironments } from '../../../../hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
import useProjects from '../../../../hooks/api/getters/useProjects/useProjects';
|
import useProjects from '../../../../hooks/api/getters/useProjects/useProjects';
|
||||||
import GeneralSelect from '../../../common/GeneralSelect/GeneralSelect';
|
import GeneralSelect from '../../../common/GeneralSelect/GeneralSelect';
|
||||||
import Input from '../../../common/Input/Input';
|
import Input from '../../../common/Input/Input';
|
||||||
@ -10,11 +10,11 @@ interface IApiTokenFormProps {
|
|||||||
username: string;
|
username: string;
|
||||||
type: string;
|
type: string;
|
||||||
project: string;
|
project: string;
|
||||||
environment: string;
|
environment?: string;
|
||||||
setTokenType: (value: string) => void;
|
setTokenType: (value: string) => void;
|
||||||
setUsername: React.Dispatch<React.SetStateAction<string>>;
|
setUsername: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setProject: React.Dispatch<React.SetStateAction<string>>;
|
setProject: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setEnvironment: React.Dispatch<React.SetStateAction<string>>;
|
setEnvironment: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||||
handleSubmit: (e: any) => void;
|
handleSubmit: (e: any) => void;
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
@ -54,13 +54,15 @@ const ApiTokenForm: React.FC<IApiTokenFormProps> = ({
|
|||||||
title: i.name,
|
title: i.name,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectableEnvs =
|
const selectableEnvs =
|
||||||
type === TYPE_ADMIN
|
type === TYPE_ADMIN
|
||||||
? [{ key: '*', label: 'ALL' }]
|
? [{ key: '*', label: 'ALL' }]
|
||||||
: environments.map(i => ({
|
: environments.map(environment => ({
|
||||||
key: i.name,
|
key: environment.name,
|
||||||
label: i.name,
|
label: environment.name,
|
||||||
title: i.name,
|
title: environment.name,
|
||||||
|
disabled: !environment.enabled,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -5,7 +5,7 @@ import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
|||||||
import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import useApiTokenForm from '../hooks/useApiTokenForm';
|
import { useApiTokenForm } from '../hooks/useApiTokenForm';
|
||||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
import { ConfirmToken } from '../ConfirmToken/ConfirmToken';
|
import { ConfirmToken } from '../ConfirmToken/ConfirmToken';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
@ -1,37 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
|
|
||||||
const useApiToken = (
|
export const useApiTokenForm = () => {
|
||||||
initialUserName = '',
|
const { environments } = useEnvironments();
|
||||||
initialtype = 'CLIENT',
|
const initialEnvironment = environments?.find(e => e.enabled)?.name;
|
||||||
initialProject = '*',
|
|
||||||
initialEnvironment = 'default'
|
const [username, setUsername] = useState('');
|
||||||
) => {
|
const [type, setType] = useState('CLIENT');
|
||||||
const [username, setUsername] = useState(initialUserName);
|
const [project, setProject] = useState('*');
|
||||||
const [type, setType] = useState(initialtype);
|
const [environment, setEnvironment] = useState<string>();
|
||||||
const [project, setProject] = useState(initialtype);
|
|
||||||
const [environment, setEnvironment] = useState(initialEnvironment);
|
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUsername(initialUserName);
|
setEnvironment(type === 'ADMIN' ? '*' : initialEnvironment);
|
||||||
}, [initialUserName]);
|
}, [type, initialEnvironment]);
|
||||||
|
|
||||||
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) => {
|
const setTokenType = (value: string) => {
|
||||||
if (value === 'ADMIN') {
|
if (value === 'ADMIN') {
|
||||||
@ -82,5 +64,3 @@ const useApiToken = (
|
|||||||
errors,
|
errors,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useApiToken;
|
|
||||||
|
@ -7,6 +7,7 @@ export interface ISelectOption {
|
|||||||
key: string;
|
key: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISelectMenuProps {
|
export interface ISelectMenuProps {
|
||||||
@ -52,6 +53,7 @@ const GeneralSelect: React.FC<ISelectMenuProps> = ({
|
|||||||
value={option.key}
|
value={option.key}
|
||||||
title={option.title || ''}
|
title={option.title || ''}
|
||||||
data-test={`${SELECT_ITEM_ID}-${option.label}`}
|
data-test={`${SELECT_ITEM_ID}-${option.label}`}
|
||||||
|
disabled={option.disabled}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -8,7 +8,7 @@ import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
|||||||
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import useEnvironments from 'hooks/api/getters/useEnvironments/useEnvironments';
|
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
||||||
import ConditionallyRender from 'component/common/ConditionallyRender';
|
import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||||
import PageContent from 'component/common/PageContent/PageContent';
|
import PageContent from 'component/common/PageContent/PageContent';
|
||||||
|
@ -3,9 +3,7 @@ import ResponsiveButton from '../../common/ResponsiveButton/ResponsiveButton';
|
|||||||
import { Add } from '@material-ui/icons';
|
import { Add } from '@material-ui/icons';
|
||||||
import PageContent from '../../common/PageContent';
|
import PageContent from '../../common/PageContent';
|
||||||
import { List } from '@material-ui/core';
|
import { List } from '@material-ui/core';
|
||||||
import useEnvironments, {
|
import { useEnvironments } from '../../../hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
ENVIRONMENT_CACHE_KEY,
|
|
||||||
} from '../../../hooks/api/getters/useEnvironments/useEnvironments';
|
|
||||||
import {
|
import {
|
||||||
IEnvironment,
|
IEnvironment,
|
||||||
ISortOrderPayload,
|
ISortOrderPayload,
|
||||||
@ -16,7 +14,6 @@ import EnvironmentDeleteConfirm from './EnvironmentDeleteConfirm/EnvironmentDele
|
|||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
||||||
import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem';
|
import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem';
|
||||||
import { mutate } from 'swr';
|
|
||||||
import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm';
|
import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm';
|
||||||
import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
||||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
@ -32,7 +29,7 @@ const EnvironmentList = () => {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
protected: false,
|
protected: false,
|
||||||
};
|
};
|
||||||
const { environments, refetch } = useEnvironments();
|
const { environments, refetchEnvironments } = useEnvironments();
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const { refetch: refetchProjectRolePermissions } =
|
const { refetch: refetchProjectRolePermissions } =
|
||||||
useProjectRolePermissions();
|
useProjectRolePermissions();
|
||||||
@ -58,8 +55,7 @@ const EnvironmentList = () => {
|
|||||||
const item = newEnvList.splice(dragIndex, 1)[0];
|
const item = newEnvList.splice(dragIndex, 1)[0];
|
||||||
|
|
||||||
newEnvList.splice(hoverIndex, 0, item);
|
newEnvList.splice(hoverIndex, 0, item);
|
||||||
|
refetchEnvironments({ environments: newEnvList }, false);
|
||||||
mutate(ENVIRONMENT_CACHE_KEY, { environments: newEnvList }, false);
|
|
||||||
return newEnvList;
|
return newEnvList;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,7 +71,6 @@ const EnvironmentList = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await sortOrderAPICall(sortOrder);
|
await sortOrderAPICall(sortOrder);
|
||||||
refetch();
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(error));
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
@ -104,7 +99,7 @@ const EnvironmentList = () => {
|
|||||||
setDeldialogue(false);
|
setDeldialogue(false);
|
||||||
setSelectedEnv(defaultEnv);
|
setSelectedEnv(defaultEnv);
|
||||||
setConfirmName('');
|
setConfirmName('');
|
||||||
refetch();
|
refetchEnvironments();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -128,7 +123,7 @@ const EnvironmentList = () => {
|
|||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(error));
|
setToastApiError(formatUnknownError(error));
|
||||||
} finally {
|
} finally {
|
||||||
refetch();
|
refetchEnvironments();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -144,7 +139,7 @@ const EnvironmentList = () => {
|
|||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(error));
|
setToastApiError(formatUnknownError(error));
|
||||||
} finally {
|
} finally {
|
||||||
refetch();
|
refetchEnvironments();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -152,9 +152,11 @@ const EnvironmentListItem = ({
|
|||||||
condition={updatePermission}
|
condition={updatePermission}
|
||||||
show={
|
show={
|
||||||
<Tooltip title="Drag to reorder">
|
<Tooltip title="Drag to reorder">
|
||||||
<IconButton>
|
<div>
|
||||||
<DragIndicator />
|
<IconButton>
|
||||||
</IconButton>
|
<DragIndicator titleAccess="Drag" />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -162,15 +164,16 @@ const EnvironmentListItem = ({
|
|||||||
condition={updatePermission}
|
condition={updatePermission}
|
||||||
show={
|
show={
|
||||||
<Tooltip title={`${tooltipText} environment`}>
|
<Tooltip title={`${tooltipText} environment`}>
|
||||||
<IconButton
|
<div>
|
||||||
aria-label="disable"
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedEnv(env);
|
setSelectedEnv(env);
|
||||||
setToggleDialog(prev => !prev);
|
setToggleDialog(prev => !prev);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OfflineBolt />
|
<OfflineBolt titleAccess="Toggle" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -178,15 +181,16 @@ const EnvironmentListItem = ({
|
|||||||
condition={updatePermission}
|
condition={updatePermission}
|
||||||
show={
|
show={
|
||||||
<Tooltip title="Update environment">
|
<Tooltip title="Update environment">
|
||||||
<IconButton
|
<div>
|
||||||
aria-label="update"
|
<IconButton
|
||||||
disabled={env.protected}
|
disabled={env.protected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
history.push(`/environments/${env.name}`);
|
history.push(`/environments/${env.name}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Edit />
|
<Edit titleAccess="Edit" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -194,16 +198,17 @@ const EnvironmentListItem = ({
|
|||||||
condition={hasAccess(DELETE_ENVIRONMENT)}
|
condition={hasAccess(DELETE_ENVIRONMENT)}
|
||||||
show={
|
show={
|
||||||
<Tooltip title="Delete environment">
|
<Tooltip title="Delete environment">
|
||||||
<IconButton
|
<div>
|
||||||
aria-label="delete"
|
<IconButton
|
||||||
disabled={env.protected}
|
disabled={env.protected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeldialogue(true);
|
setDeldialogue(true);
|
||||||
setSelectedEnv(env);
|
setSelectedEnv(env);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Delete />
|
<Delete titleAccess="Delete" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -10,7 +10,7 @@ import { UPDATE_PROJECT } from '../../providers/AccessProvider/permissions';
|
|||||||
import ApiError from '../../common/ApiError/ApiError';
|
import ApiError from '../../common/ApiError/ApiError';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments';
|
import { useEnvironments } from '../../../hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
import useProject from '../../../hooks/api/getters/useProject/useProject';
|
import useProject from '../../../hooks/api/getters/useProject/useProject';
|
||||||
import { FormControlLabel, FormGroup } from '@material-ui/core';
|
import { FormControlLabel, FormGroup } from '@material-ui/core';
|
||||||
import useProjectApi from '../../../hooks/api/actions/useProjectApi/useProjectApi';
|
import useProjectApi from '../../../hooks/api/actions/useProjectApi/useProjectApi';
|
||||||
@ -32,12 +32,8 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
|
|||||||
const [envs, setEnvs] = useState<IProjectEnvironment[]>([]);
|
const [envs, setEnvs] = useState<IProjectEnvironment[]>([]);
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const {
|
const { environments, loading, error, refetchEnvironments } =
|
||||||
environments,
|
useEnvironments();
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
refetch: refetchEnvs,
|
|
||||||
} = useEnvironments();
|
|
||||||
const { project, refetch: refetchProject } = useProject(projectId);
|
const { project, refetch: refetchProject } = useProject(projectId);
|
||||||
const { removeEnvironmentFromProject, addEnvironmentToProject } =
|
const { removeEnvironmentFromProject, addEnvironmentToProject } =
|
||||||
useProjectApi();
|
useProjectApi();
|
||||||
@ -59,7 +55,7 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
|
|||||||
}, [environments, project?.environments]);
|
}, [environments, project?.environments]);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
refetchEnvs();
|
refetchEnvironments();
|
||||||
refetchProject();
|
refetchProject();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,42 +1,35 @@
|
|||||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
import useSWR, { mutate } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { IEnvironmentResponse } from '../../../../interfaces/environments';
|
import { IEnvironmentResponse } from 'interfaces/environments';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from 'utils/format-path';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
export const ENVIRONMENT_CACHE_KEY = `api/admin/environments`;
|
const PATH = formatApiPath(`api/admin/environments`);
|
||||||
|
|
||||||
const useEnvironments = (options: SWRConfiguration = {}) => {
|
export const useEnvironments = () => {
|
||||||
const fetcher = () => {
|
const { data, error } = useSWR<IEnvironmentResponse>(PATH, fetcher);
|
||||||
const path = formatApiPath(`api/admin/environments`);
|
|
||||||
return fetch(path, {
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.then(handleErrorResponses('Environments'))
|
|
||||||
.then(res => res.json());
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data, error } = useSWR<IEnvironmentResponse>(
|
const refetchEnvironments = useCallback(
|
||||||
ENVIRONMENT_CACHE_KEY,
|
(data?: IEnvironmentResponse, revalidate?: boolean) => {
|
||||||
fetcher,
|
mutate(PATH, data, revalidate).catch(console.warn);
|
||||||
options
|
},
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = useState(!error && !data);
|
|
||||||
|
|
||||||
const refetch = () => {
|
const environments = useMemo(() => {
|
||||||
mutate(ENVIRONMENT_CACHE_KEY);
|
return data?.environments || [];
|
||||||
};
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(!error && !data);
|
|
||||||
}, [data, error]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
environments: data?.environments || [],
|
environments,
|
||||||
|
refetchEnvironments,
|
||||||
|
loading: !error && !data,
|
||||||
error,
|
error,
|
||||||
loading,
|
|
||||||
refetch,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useEnvironments;
|
const fetcher = (): Promise<IEnvironmentResponse> => {
|
||||||
|
return fetch(PATH)
|
||||||
|
.then(handleErrorResponses('Environments'))
|
||||||
|
.then(res => res.json());
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user