mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
Fix/login redirect (#442)
* fix: use swr with login * fix: remove metrics poller * fix: do not allow retry on 401 * fix: create swr provider * fix: move accessprovider * fix: remove metrics poller test * fix: hide password auth if disableDefault is set * Update src/component/project/ProjectList/ProjectList.tsx Co-authored-by: Christopher Kolstad <chriswk@getunleash.ai> * fix: console log Co-authored-by: Christopher Kolstad <chriswk@getunleash.ai>
This commit is contained in:
parent
ea2086a7f4
commit
005daa3740
@ -1,64 +0,0 @@
|
|||||||
import configureStore from 'redux-mock-store';
|
|
||||||
import { List } from 'immutable';
|
|
||||||
import thunkMiddleware from 'redux-thunk';
|
|
||||||
import fetchMock from 'fetch-mock';
|
|
||||||
import MetricsPoller from '../metrics-poller';
|
|
||||||
|
|
||||||
const mockStore = configureStore([thunkMiddleware]);
|
|
||||||
|
|
||||||
describe('metrics-poller.js', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
fetchMock.reset();
|
|
||||||
fetchMock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should not start poller before toggles are recieved', () => {
|
|
||||||
const initialState = { features: List.of([{ name: 'test1' }]) };
|
|
||||||
const store = mockStore(initialState);
|
|
||||||
fetchMock.getOnce('api/admin/metrics/feature-toggles', {
|
|
||||||
body: { lastHour: {}, lastMinute: {} },
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const metricsPoller = new MetricsPoller(store);
|
|
||||||
metricsPoller.start();
|
|
||||||
|
|
||||||
expect(metricsPoller.timer).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should not start poller when state does not contain toggles', () => {
|
|
||||||
const initialState = { features: new List([]) };
|
|
||||||
const store = mockStore(initialState);
|
|
||||||
|
|
||||||
const metricsPoller = new MetricsPoller(store);
|
|
||||||
metricsPoller.start();
|
|
||||||
|
|
||||||
store.dispatch({
|
|
||||||
type: 'some',
|
|
||||||
receivedAt: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(metricsPoller.timer).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should start poller when state gets toggles', () => {
|
|
||||||
fetchMock.getOnce('api/admin/metrics/feature-toggles', {
|
|
||||||
body: { lastHour: {}, lastMinute: {} },
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialState = { features: List.of([{ name: 'test1' }]) };
|
|
||||||
const store = mockStore(initialState);
|
|
||||||
|
|
||||||
const metricsPoller = new MetricsPoller(store);
|
|
||||||
metricsPoller.start();
|
|
||||||
|
|
||||||
store.dispatch({
|
|
||||||
type: 'RECEIVE_FEATURE_TOGGLES',
|
|
||||||
featureToggles: [{ name: 'test' }],
|
|
||||||
receivedAt: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(metricsPoller.timer).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
@ -13,8 +13,8 @@ import IAuthStatus from '../interfaces/user';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import NotFound from './common/NotFound/NotFound';
|
import NotFound from './common/NotFound/NotFound';
|
||||||
import Feedback from './common/Feedback';
|
import Feedback from './common/Feedback';
|
||||||
import { SWRConfig } from 'swr';
|
|
||||||
import useToast from '../hooks/useToast';
|
import useToast from '../hooks/useToast';
|
||||||
|
import SWRProvider from './providers/SWRProvider/SWRProvider';
|
||||||
|
|
||||||
interface IAppProps extends RouteComponentProps {
|
interface IAppProps extends RouteComponentProps {
|
||||||
user: IAuthStatus;
|
user: IAuthStatus;
|
||||||
@ -75,55 +75,34 @@ const App = ({ location, user, fetchUiBootstrap, feedback }: IAppProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SWRConfig
|
<SWRProvider
|
||||||
value={{
|
setToastData={setToastData}
|
||||||
onErrorRetry: (
|
isUnauthorized={isUnauthorized}
|
||||||
error,
|
|
||||||
_key,
|
|
||||||
_config,
|
|
||||||
revalidate,
|
|
||||||
{ retryCount }
|
|
||||||
) => {
|
|
||||||
// Never retry on 404.
|
|
||||||
if (error.status === 404) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
setTimeout(() => revalidate({ retryCount }), 5000);
|
|
||||||
},
|
|
||||||
onError: error => {
|
|
||||||
if (!isUnauthorized()) {
|
|
||||||
setToastData({
|
|
||||||
show: true,
|
|
||||||
type: 'error',
|
|
||||||
text: error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
{' '}
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<LayoutPicker location={location}>
|
<LayoutPicker location={location}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<ProtectedRoute
|
<ProtectedRoute
|
||||||
exact
|
exact
|
||||||
path='/'
|
path="/"
|
||||||
unauthorized={isUnauthorized()}
|
unauthorized={isUnauthorized()}
|
||||||
component={Redirect}
|
component={Redirect}
|
||||||
renderProps={{ to: '/features' }}
|
renderProps={{ to: '/features' }}
|
||||||
/>
|
/>
|
||||||
{renderMainLayoutRoutes()}
|
{renderMainLayoutRoutes()}
|
||||||
{renderStandaloneRoutes()}
|
{renderStandaloneRoutes()}
|
||||||
<Route path='/404' component={NotFound} />
|
<Route path="/404" component={NotFound} />
|
||||||
<Redirect to='/404' />
|
<Redirect to="/404" />
|
||||||
</Switch>
|
</Switch>
|
||||||
<Feedback
|
<Feedback
|
||||||
feedbackId='pnps'
|
feedbackId="pnps"
|
||||||
openUrl='http://feedback.unleash.run'
|
openUrl="http://feedback.unleash.run"
|
||||||
/>
|
/>
|
||||||
</LayoutPicker>
|
</LayoutPicker>
|
||||||
{toast}
|
{toast}
|
||||||
</div>
|
</div>
|
||||||
</SWRConfig>
|
</SWRProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
// Set state to any for now, to avoid typing up entire state object while converting to tsx.
|
// Set state to any for now, to avoid typing up entire state object while converting to tsx.
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
ListItemText,
|
ListItemText,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import { CREATE_ADDON } from '../../../AccessProvider/permissions';
|
import { CREATE_ADDON } from '../../../providers/AccessProvider/permissions';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const AvailableAddons = ({ providers, getIcon, hasAccess, history }) => {
|
const AvailableAddons = ({ providers, getIcon, hasAccess, history }) => {
|
||||||
|
@ -13,7 +13,7 @@ import ConditionallyRender from '../../../common/ConditionallyRender/Conditional
|
|||||||
import {
|
import {
|
||||||
DELETE_ADDON,
|
DELETE_ADDON,
|
||||||
UPDATE_ADDON,
|
UPDATE_ADDON,
|
||||||
} from '../../../AccessProvider/permissions';
|
} from '../../../providers/AccessProvider/permissions';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import PageContent from '../../../common/PageContent/PageContent';
|
import PageContent from '../../../common/PageContent/PageContent';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -1,24 +1,37 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Button, IconButton, Table, TableBody, TableCell, TableHead, TableRow, } from '@material-ui/core';
|
import {
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
} from '@material-ui/core';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import useToast from '../../../hooks/useToast';
|
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, { IApiTokenCreate } from '../../../hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
import 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';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
import { CREATE_API_TOKEN, DELETE_API_TOKEN } from '../../AccessProvider/permissions';
|
import {
|
||||||
|
CREATE_API_TOKEN,
|
||||||
|
DELETE_API_TOKEN,
|
||||||
|
} from '../../providers/AccessProvider/permissions';
|
||||||
import { useStyles } from './ApiTokenList.styles';
|
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 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';
|
||||||
|
|
||||||
interface IApiToken {
|
interface IApiToken {
|
||||||
@ -54,7 +67,6 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
const closeDialog = () => {
|
const closeDialog = () => {
|
||||||
setDialog(false);
|
setDialog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const renderError = () => {
|
const renderError = () => {
|
||||||
return (
|
return (
|
||||||
@ -74,7 +86,7 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
show: true,
|
show: true,
|
||||||
text: 'Successfully created API token.',
|
text: 'Successfully created API token.',
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
const copyToken = (value: string) => {
|
const copyToken = (value: string) => {
|
||||||
navigator.clipboard.writeText(value);
|
navigator.clipboard.writeText(value);
|
||||||
setToastData({
|
setToastData({
|
||||||
@ -85,7 +97,7 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteToken = async () => {
|
const onDeleteToken = async () => {
|
||||||
if(delToken) {
|
if (delToken) {
|
||||||
await deleteToken(delToken.secret);
|
await deleteToken(delToken.secret);
|
||||||
}
|
}
|
||||||
setDeleteToken(undefined);
|
setDeleteToken(undefined);
|
||||||
@ -99,12 +111,12 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderProject = (projectId: string) => {
|
const renderProject = (projectId: string) => {
|
||||||
if(!projectId || projectId === '*') {
|
if (!projectId || projectId === '*') {
|
||||||
return projectId;
|
return projectId;
|
||||||
} else {
|
} else {
|
||||||
return (<Link to={`/projects/${projectId}`}>{projectId}</Link>);
|
return <Link to={`/projects/${projectId}`}>{projectId}</Link>;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const renderApiTokens = (tokens: IApiToken[]) => {
|
const renderApiTokens = (tokens: IApiToken[]) => {
|
||||||
return (
|
return (
|
||||||
@ -112,65 +124,106 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className={styles.hideSM}>Created</TableCell>
|
<TableCell className={styles.hideSM}>Created</TableCell>
|
||||||
<TableCell className={styles.hideSM}>Username</TableCell>
|
<TableCell className={styles.hideSM}>
|
||||||
<TableCell className={`${styles.center} ${styles.hideXS}`}>Type</TableCell>
|
Username
|
||||||
<ConditionallyRender condition={uiConfig.flags.E} show={<>
|
</TableCell>
|
||||||
<TableCell className={`${styles.center} ${styles.hideXS}`}>Project</TableCell>
|
<TableCell
|
||||||
<TableCell className={`${styles.center} ${styles.hideXS}`}>Environment</TableCell>
|
className={`${styles.center} ${styles.hideXS}`}
|
||||||
</>} />
|
>
|
||||||
|
Type
|
||||||
|
</TableCell>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={uiConfig.flags.E}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<TableCell
|
||||||
|
className={`${styles.center} ${styles.hideXS}`}
|
||||||
|
>
|
||||||
|
Project
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
className={`${styles.center} ${styles.hideXS}`}
|
||||||
|
>
|
||||||
|
Environment
|
||||||
|
</TableCell>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<TableCell className={styles.hideMD}>Secret</TableCell>
|
<TableCell className={styles.hideMD}>Secret</TableCell>
|
||||||
<TableCell className={styles.token}>Token</TableCell>
|
<TableCell className={styles.token}>Token</TableCell>
|
||||||
<TableCell className={styles.actionsContainer}>Actions</TableCell>
|
<TableCell className={styles.actionsContainer}>
|
||||||
|
Actions
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{tokens.map(item => {
|
{tokens.map(item => {
|
||||||
return (
|
return (
|
||||||
<TableRow key={item.secret}>
|
<TableRow key={item.secret}>
|
||||||
<TableCell align="left" className={styles.hideSM}>
|
<TableCell
|
||||||
|
align="left"
|
||||||
|
className={styles.hideSM}
|
||||||
|
>
|
||||||
{formatDateWithLocale(
|
{formatDateWithLocale(
|
||||||
item.createdAt,
|
item.createdAt,
|
||||||
location.locale
|
location.locale
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="left" className={styles.hideSM}>
|
<TableCell
|
||||||
|
align="left"
|
||||||
|
className={styles.hideSM}
|
||||||
|
>
|
||||||
{item.username}
|
{item.username}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={`${styles.center} ${styles.hideXS}`}>
|
<TableCell
|
||||||
|
className={`${styles.center} ${styles.hideXS}`}
|
||||||
|
>
|
||||||
{item.type}
|
{item.type}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<ConditionallyRender condition={uiConfig.flags.E} show={<>
|
<ConditionallyRender
|
||||||
<TableCell className={`${styles.center} ${styles.hideXS}`}>
|
condition={uiConfig.flags.E}
|
||||||
{renderProject(item.project)}
|
show={
|
||||||
</TableCell>
|
<>
|
||||||
<TableCell className={`${styles.center} ${styles.hideXS}`}>
|
<TableCell
|
||||||
{item.environment}
|
className={`${styles.center} ${styles.hideXS}`}
|
||||||
</TableCell>
|
>
|
||||||
<TableCell className={styles.token}>
|
{renderProject(item.project)}
|
||||||
<b>Type:</b> {item.type}<br/>
|
</TableCell>
|
||||||
<b>Env:</b> {item.environment}<br/>
|
<TableCell
|
||||||
<b>Project:</b> {renderProject(item.project)}
|
className={`${styles.center} ${styles.hideXS}`}
|
||||||
</TableCell>
|
>
|
||||||
</>}
|
{item.environment}
|
||||||
elseShow={<>
|
</TableCell>
|
||||||
<TableCell className={styles.token}>
|
<TableCell className={styles.token}>
|
||||||
<b>Type:</b> {item.type}<br/>
|
<b>Type:</b> {item.type}
|
||||||
<b>Username:</b> {item.username}
|
<br />
|
||||||
</TableCell>
|
<b>Env:</b> {item.environment}
|
||||||
</>}
|
<br />
|
||||||
|
<b>Project:</b>{' '}
|
||||||
|
{renderProject(item.project)}
|
||||||
|
</TableCell>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<>
|
||||||
|
<TableCell className={styles.token}>
|
||||||
|
<b>Type:</b> {item.type}
|
||||||
|
<br />
|
||||||
|
<b>Username:</b> {item.username}
|
||||||
|
</TableCell>
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<TableCell className={styles.hideMD}>
|
<TableCell className={styles.hideMD}>
|
||||||
<Secret value={item.secret} />
|
<Secret value={item.secret} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell className={styles.actionsContainer}>
|
||||||
className={styles.actionsContainer}
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToken(item.secret)
|
copyToken(item.secret);
|
||||||
} }
|
}}
|
||||||
>
|
>
|
||||||
<FileCopy />
|
<FileCopy />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={hasAccess(DELETE_API_TOKEN)}
|
condition={hasAccess(DELETE_API_TOKEN)}
|
||||||
@ -179,29 +232,45 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeleteToken(item);
|
setDeleteToken(item);
|
||||||
setShowDelete(true);
|
setShowDelete(true);
|
||||||
} }
|
}}
|
||||||
>
|
>
|
||||||
<Delete />
|
<Delete />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
} />
|
}
|
||||||
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
<PageContent
|
<PageContent
|
||||||
headerContent={<HeaderTitle
|
headerContent={
|
||||||
title="API Access"
|
<HeaderTitle
|
||||||
actions={<ConditionallyRender
|
title="API Access"
|
||||||
condition={hasAccess(CREATE_API_TOKEN)}
|
actions={
|
||||||
show={<Button variant="contained" color="primary" onClick={openDialog} data-test={CREATE_API_TOKEN_BUTTON}>Create API token</Button>} />} />}
|
<ConditionallyRender
|
||||||
>
|
condition={hasAccess(CREATE_API_TOKEN)}
|
||||||
|
show={
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={openDialog}
|
||||||
|
data-test={CREATE_API_TOKEN_BUTTON}
|
||||||
|
>
|
||||||
|
Create API token
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Alert severity="info" className={styles.infoBoxContainer}>
|
<Alert severity="info" className={styles.infoBoxContainer}>
|
||||||
<p>
|
<p>
|
||||||
Read the{' '}
|
Read the{' '}
|
||||||
@ -218,7 +287,9 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<strong>API URL: </strong>{' '}
|
<strong>API URL: </strong>{' '}
|
||||||
<pre style={{ display: 'inline' }}>{uiConfig.unleashUrl}/api/</pre>
|
<pre style={{ display: 'inline' }}>
|
||||||
|
{uiConfig.unleashUrl}/api/
|
||||||
|
</pre>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<ConditionallyRender condition={error} show={renderError()} />
|
<ConditionallyRender condition={error} show={renderError()} />
|
||||||
@ -230,7 +301,11 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{toast}
|
{toast}
|
||||||
<ApiTokenCreate showDialog={showDialog} createToken={onCreateToken} closeDialog={closeDialog} />
|
<ApiTokenCreate
|
||||||
|
showDialog={showDialog}
|
||||||
|
createToken={onCreateToken}
|
||||||
|
closeDialog={closeDialog}
|
||||||
|
/>
|
||||||
<Dialogue
|
<Dialogue
|
||||||
open={showDelete}
|
open={showDelete}
|
||||||
onClick={onDeleteToken}
|
onClick={onDeleteToken}
|
||||||
@ -241,10 +316,17 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
|
|||||||
title="Confirm deletion"
|
title="Confirm deletion"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Are you sure you want to delete the following API token?<br />
|
Are you sure you want to delete the following API token?
|
||||||
|
<br />
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>username</strong>: <code>{delToken?.username}</code></li>
|
<li>
|
||||||
<li><strong>type</strong>: <code>{delToken?.type}</code></li>
|
<strong>username</strong>:{' '}
|
||||||
|
<code>{delToken?.username}</code>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>type</strong>:{' '}
|
||||||
|
<code>{delToken?.type}</code>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Dialogue>
|
</Dialogue>
|
||||||
|
@ -4,11 +4,11 @@ import { ThemeProvider } from '@material-ui/core';
|
|||||||
import ClientApplications from '../application-edit-component';
|
import ClientApplications from '../application-edit-component';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { ADMIN } from '../../AccessProvider/permissions';
|
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
||||||
import theme from '../../../themes/main-theme';
|
import theme from '../../../themes/main-theme';
|
||||||
|
|
||||||
import { createFakeStore } from '../../../accessStoreFake';
|
import { createFakeStore } from '../../../accessStoreFake';
|
||||||
import AccessProvider from '../../AccessProvider/AccessProvider';
|
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
||||||
|
|
||||||
test('renders correctly if no application', () => {
|
test('renders correctly if no application', () => {
|
||||||
const tree = renderer
|
const tree = renderer
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
formatFullDateTimeWithLocale,
|
formatFullDateTimeWithLocale,
|
||||||
formatDateWithLocale,
|
formatDateWithLocale,
|
||||||
} from '../common/util';
|
} from '../common/util';
|
||||||
import { UPDATE_APPLICATION } from '../AccessProvider/permissions';
|
import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions';
|
||||||
import ApplicationView from './application-view';
|
import ApplicationView from './application-view';
|
||||||
import ApplicationUpdate from './application-update';
|
import ApplicationUpdate from './application-update';
|
||||||
import TabNav from '../common/TabNav/TabNav';
|
import TabNav from '../common/TabNav/TabNav';
|
||||||
|
@ -13,7 +13,10 @@ import {
|
|||||||
import { Report, Extension, Timeline } from '@material-ui/icons';
|
import { Report, Extension, Timeline } from '@material-ui/icons';
|
||||||
|
|
||||||
import { shorten } from '../common';
|
import { shorten } from '../common';
|
||||||
import { CREATE_FEATURE, CREATE_STRATEGY } from '../AccessProvider/permissions';
|
import {
|
||||||
|
CREATE_FEATURE,
|
||||||
|
CREATE_STRATEGY,
|
||||||
|
} from '../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
|
||||||
import { getTogglePath } from '../../utils/route-path-helpers';
|
import { getTogglePath } from '../../utils/route-path-helpers';
|
||||||
function ApplicationView({
|
function ApplicationView({
|
||||||
@ -33,9 +36,7 @@ function ApplicationView({
|
|||||||
<Report />
|
<Report />
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={<Link to={`${createUrl}`}>{name}</Link>}
|
||||||
<Link to={`${createUrl}`}>{name}</Link>
|
|
||||||
}
|
|
||||||
secondary={'Missing, want to create?'}
|
secondary={'Missing, want to create?'}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -5,7 +5,7 @@ import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyR
|
|||||||
import {
|
import {
|
||||||
CREATE_CONTEXT_FIELD,
|
CREATE_CONTEXT_FIELD,
|
||||||
DELETE_CONTEXT_FIELD,
|
DELETE_CONTEXT_FIELD,
|
||||||
} from '../../AccessProvider/permissions';
|
} from '../../providers/AccessProvider/permissions';
|
||||||
import {
|
import {
|
||||||
IconButton,
|
IconButton,
|
||||||
List,
|
List,
|
||||||
|
@ -20,7 +20,7 @@ import AccessContext from '../../../../contexts/AccessContext';
|
|||||||
import {
|
import {
|
||||||
DELETE_ENVIRONMENT,
|
DELETE_ENVIRONMENT,
|
||||||
UPDATE_ENVIRONMENT,
|
UPDATE_ENVIRONMENT,
|
||||||
} from '../../../AccessProvider/permissions';
|
} from '../../../providers/AccessProvider/permissions';
|
||||||
import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
|
import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
|
||||||
import { XYCoord } from 'dnd-core';
|
import { XYCoord } from 'dnd-core';
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ const FeatureCreate = () => {
|
|||||||
const { projectId } = useParams<IFeatureViewParams>();
|
const { projectId } = useParams<IFeatureViewParams>();
|
||||||
const { createFeatureToggle, validateFeatureToggleName } = useFeatureApi();
|
const { createFeatureToggle, validateFeatureToggleName } = useFeatureApi();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [ toggle, setToggle ] = useState<IFeatureToggleDTO>({
|
const [toggle, setToggle] = useState<IFeatureToggleDTO>({
|
||||||
name: loadNameFromUrl(),
|
name: loadNameFromUrl(),
|
||||||
description: '',
|
description: '',
|
||||||
type: 'release',
|
type: 'release',
|
||||||
@ -41,7 +41,6 @@ const FeatureCreate = () => {
|
|||||||
});
|
});
|
||||||
const [errors, setErrors] = useState<Errors>({});
|
const [errors, setErrors] = useState<Errors>({});
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.onbeforeunload = () =>
|
window.onbeforeunload = () =>
|
||||||
'Data will be lost if you leave the page, are you sure?';
|
'Data will be lost if you leave the page, are you sure?';
|
||||||
@ -52,10 +51,7 @@ const FeatureCreate = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onCancel = () => history.push(
|
const onCancel = () => history.push(`/projects/${projectId}`);
|
||||||
`/projects/${projectId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
const validateName = async (featureToggleName: string) => {
|
const validateName = async (featureToggleName: string) => {
|
||||||
const e = { ...errors };
|
const e = { ...errors };
|
||||||
@ -80,25 +76,25 @@ const FeatureCreate = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await createFeatureToggle(projectId, toggle).then(() =>
|
await createFeatureToggle(projectId, toggle).then(() =>
|
||||||
history.push(
|
history.push(getTogglePath(toggle.project, toggle.name, true))
|
||||||
getTogglePath(toggle.project, toggle.name, true)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
// Trigger
|
// Trigger
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.toString().includes('not allowed to be empty')) {
|
if (e.toString().includes('not allowed to be empty')) {
|
||||||
setErrors({ name: 'Name is not allowed to be empty' })
|
setErrors({ name: 'Name is not allowed to be empty' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setValue = (field:string, value:string) => {
|
const setValue = (field: string, value: string) => {
|
||||||
setToggle({...toggle, [field]: value})
|
setToggle({ ...toggle, [field]: value });
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent headerContent="Create feature toggle" bodyClass={styles.bodyContainer}>
|
<PageContent
|
||||||
|
headerContent="Create feature toggle"
|
||||||
|
bodyClass={styles.bodyContainer}
|
||||||
|
>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<input type="hidden" name="project" value={projectId} />
|
<input type="hidden" name="project" value={projectId} />
|
||||||
<div className={styles.formContainer}>
|
<div className={styles.formContainer}>
|
||||||
|
@ -15,7 +15,7 @@ import HeaderTitle from '../../common/HeaderTitle';
|
|||||||
|
|
||||||
import loadingFeatures from './loadingFeatures';
|
import loadingFeatures from './loadingFeatures';
|
||||||
|
|
||||||
import { CREATE_FEATURE } from '../../AccessProvider/permissions';
|
import { CREATE_FEATURE } from '../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import TimeAgo from 'react-timeago';
|
|||||||
import Status from '../../status-component';
|
import Status from '../../status-component';
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
import { UPDATE_FEATURE } from '../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions';
|
||||||
import { styles as commonStyles } from '../../../common';
|
import { styles as commonStyles } from '../../../common';
|
||||||
|
|
||||||
import { useStyles } from './styles';
|
import { useStyles } from './styles';
|
||||||
@ -18,8 +18,6 @@ import { getTogglePath } from '../../../../utils/route-path-helpers';
|
|||||||
import FeatureStatus from '../../FeatureView2/FeatureStatus/FeatureStatus';
|
import FeatureStatus from '../../FeatureView2/FeatureStatus/FeatureStatus';
|
||||||
import FeatureType from '../../FeatureView2/FeatureType/FeatureType';
|
import FeatureType from '../../FeatureView2/FeatureType/FeatureType';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const FeatureToggleListItem = ({
|
const FeatureToggleListItem = ({
|
||||||
feature,
|
feature,
|
||||||
toggleFeature,
|
toggleFeature,
|
||||||
@ -37,7 +35,7 @@ const FeatureToggleListItem = ({
|
|||||||
|
|
||||||
const { name, description, type, stale, createdAt, project, lastSeenAt } =
|
const { name, description, type, stale, createdAt, project, lastSeenAt } =
|
||||||
feature;
|
feature;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
{...rest}
|
{...rest}
|
||||||
@ -46,56 +44,60 @@ const FeatureToggleListItem = ({
|
|||||||
<span className={styles.listItemMetric}>
|
<span className={styles.listItemMetric}>
|
||||||
<FeatureStatus lastSeenAt={lastSeenAt} />
|
<FeatureStatus lastSeenAt={lastSeenAt} />
|
||||||
</span>
|
</span>
|
||||||
<span className={classnames(styles.listItemType, commonStyles.hideLt600)}>
|
<span
|
||||||
|
className={classnames(
|
||||||
|
styles.listItemType,
|
||||||
|
commonStyles.hideLt600
|
||||||
|
)}
|
||||||
|
>
|
||||||
<FeatureType type={type} />
|
<FeatureType type={type} />
|
||||||
</span>
|
</span>
|
||||||
<span className={classnames(styles.listItemLink)}>
|
<span className={classnames(styles.listItemLink)}>
|
||||||
<ConditionallyRender condition={!isArchive} show={
|
<ConditionallyRender
|
||||||
<Link
|
condition={!isArchive}
|
||||||
to={getTogglePath(feature.project, name, flags.E)}
|
show={
|
||||||
className={classnames(
|
<Link
|
||||||
commonStyles.listLink,
|
to={getTogglePath(feature.project, name, flags.E)}
|
||||||
commonStyles.truncate
|
className={classnames(
|
||||||
)}
|
commonStyles.listLink,
|
||||||
>
|
commonStyles.truncate
|
||||||
<Tooltip title={description}>
|
)}
|
||||||
<span className={commonStyles.toggleName}>
|
>
|
||||||
|
<Tooltip title={description}>
|
||||||
|
<span className={commonStyles.toggleName}>
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span className={styles.listItemToggle}>
|
<span className={styles.listItemToggle}></span>
|
||||||
</span>
|
<small>
|
||||||
<small>
|
<TimeAgo date={createdAt} live={false} />
|
||||||
<TimeAgo date={createdAt} live={false} />
|
</small>
|
||||||
</small>
|
<div>
|
||||||
<div>
|
<span className={commonStyles.truncate}>
|
||||||
<span className={commonStyles.truncate}>
|
<small>{description}</small>
|
||||||
<small>{description}</small>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
</Link>
|
}
|
||||||
} elseShow={
|
elseShow={
|
||||||
<>
|
<>
|
||||||
<Tooltip title={description}>
|
<Tooltip title={description}>
|
||||||
<span className={commonStyles.toggleName}>
|
<span className={commonStyles.toggleName}>
|
||||||
{name}
|
{name} z{' '}
|
||||||
z </span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span className={styles.listItemToggle}>
|
<span className={styles.listItemToggle}></span>
|
||||||
</span>
|
<small>
|
||||||
<small>
|
<TimeAgo date={createdAt} live={false} />
|
||||||
<TimeAgo date={createdAt} live={false} />
|
</small>
|
||||||
</small>
|
<div>
|
||||||
<div>
|
<span className={commonStyles.truncate}>
|
||||||
<span className={commonStyles.truncate}>
|
<small>{description}</small>
|
||||||
<small>{description}</small>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</>
|
}
|
||||||
}/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={classnames(
|
className={classnames(
|
||||||
@ -104,8 +106,18 @@ z </span>
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Status stale={stale} showActive={false} />
|
<Status stale={stale} showActive={false} />
|
||||||
<Link to={`/projects/${project}`} style={{textDecoration: 'none'}}>
|
<Link
|
||||||
<Chip color="primary" variant="outlined" className={styles.typeChip} style={{marginLeft: '8px' }} title={`Project: ${project}`} label={project}/>
|
to={`/projects/${project}`}
|
||||||
|
style={{ textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
className={styles.typeChip}
|
||||||
|
style={{ marginLeft: '8px' }}
|
||||||
|
title={`Project: ${project}`}
|
||||||
|
label={project}
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
|
@ -6,8 +6,11 @@ import FeatureToggleList from '../FeatureToggleList';
|
|||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import theme from '../../../../themes/main-theme';
|
import theme from '../../../../themes/main-theme';
|
||||||
import { createFakeStore } from '../../../../accessStoreFake';
|
import { createFakeStore } from '../../../../accessStoreFake';
|
||||||
import { ADMIN, CREATE_FEATURE } from '../../../AccessProvider/permissions';
|
import {
|
||||||
import AccessProvider from '../../../AccessProvider/AccessProvider';
|
ADMIN,
|
||||||
|
CREATE_FEATURE,
|
||||||
|
} from '../../../providers/AccessProvider/permissions';
|
||||||
|
import AccessProvider from '../../../providers/AccessProvider/AccessProvider';
|
||||||
|
|
||||||
jest.mock('../FeatureToggleListItem', () => ({
|
jest.mock('../FeatureToggleListItem', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
CREATE_FEATURE,
|
CREATE_FEATURE,
|
||||||
DELETE_FEATURE,
|
DELETE_FEATURE,
|
||||||
UPDATE_FEATURE,
|
UPDATE_FEATURE,
|
||||||
} from '../../AccessProvider/permissions';
|
} from '../../providers/AccessProvider/permissions';
|
||||||
import StatusComponent from '../status-component';
|
import StatusComponent from '../status-component';
|
||||||
import FeatureTagComponent from '../feature-tag-component';
|
import FeatureTagComponent from '../feature-tag-component';
|
||||||
import StatusUpdateComponent from '../view/status-update-component';
|
import StatusUpdateComponent from '../view/status-update-component';
|
||||||
|
@ -3,7 +3,7 @@ import classnames from 'classnames';
|
|||||||
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
|
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
||||||
import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import StaleDialog from './StaleDialog/StaleDialog';
|
import StaleDialog from './StaleDialog/StaleDialog';
|
||||||
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
|
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
|
||||||
|
@ -31,7 +31,6 @@ const FeatureOverviewEnvironment = ({
|
|||||||
return strategies.map(strategy => {
|
return strategies.map(strategy => {
|
||||||
return (
|
return (
|
||||||
<FeatureOverviewStrategyCard
|
<FeatureOverviewStrategyCard
|
||||||
data-loading
|
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
key={strategy.id}
|
key={strategy.id}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
@ -2,7 +2,7 @@ import { Add } from '@material-ui/icons';
|
|||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
|
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
|
||||||
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
||||||
import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
||||||
import ResponsiveButton from '../../../../common/ResponsiveButton/ResponsiveButton';
|
import ResponsiveButton from '../../../../common/ResponsiveButton/ResponsiveButton';
|
||||||
import FeatureOverviewEnvironment from './FeatureOverviewEnvironment/FeatureOverviewEnvironment';
|
import FeatureOverviewEnvironment from './FeatureOverviewEnvironment/FeatureOverviewEnvironment';
|
||||||
import { useStyles } from './FeatureOverviewStrategies.styles';
|
import { useStyles } from './FeatureOverviewStrategies.styles';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useContext } from 'react';
|
import { useState, useContext } from 'react';
|
||||||
import { Chip } from '@material-ui/core';
|
import { Chip } from '@material-ui/core';
|
||||||
import { Add, Label } from '@material-ui/icons';
|
import { Add, Label } from '@material-ui/icons';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@ -16,7 +16,10 @@ import AddTagDialog from './AddTagDialog/AddTagDialog';
|
|||||||
import Dialogue from '../../../../common/Dialogue';
|
import Dialogue from '../../../../common/Dialogue';
|
||||||
import { ITag } from '../../../../../interfaces/tags';
|
import { ITag } from '../../../../../interfaces/tags';
|
||||||
import useToast from '../../../../../hooks/useToast';
|
import useToast from '../../../../../hooks/useToast';
|
||||||
import { UPDATE_FEATURE, DELETE_TAG } from '../../../../AccessProvider/permissions';
|
import {
|
||||||
|
UPDATE_FEATURE,
|
||||||
|
DELETE_TAG,
|
||||||
|
} from '../../../../providers/AccessProvider/permissions';
|
||||||
import PermissionIconButton from '../../../../common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from '../../../../common/PermissionIconButton/PermissionIconButton';
|
||||||
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
@ -105,10 +108,14 @@ const FeatureOverviewTags = () => {
|
|||||||
data-loading
|
data-loading
|
||||||
label={t.value}
|
label={t.value}
|
||||||
key={`${t.type}:${t.value}`}
|
key={`${t.type}:${t.value}`}
|
||||||
onDelete={canDeleteTag ? () => {
|
onDelete={
|
||||||
setShowDelDialog(true);
|
canDeleteTag
|
||||||
setSelectedTag({ type: t.type, value: t.value });
|
? () => {
|
||||||
}: undefined}
|
setShowDelDialog(true);
|
||||||
|
setSelectedTag({ type: t.type, value: t.value });
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import PermissionButton from '../../../../common/PermissionButton/PermissionButt
|
|||||||
import FeatureTypeSelect from './FeatureTypeSelect/FeatureTypeSelect';
|
import FeatureTypeSelect from './FeatureTypeSelect/FeatureTypeSelect';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
||||||
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
|
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
|
||||||
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
||||||
import useToast from '../../../../../hooks/useToast';
|
import useToast from '../../../../../hooks/useToast';
|
||||||
|
@ -10,7 +10,7 @@ import { projectFilterGenerator } from '../../../../../utils/project-filter-gene
|
|||||||
import {
|
import {
|
||||||
CREATE_FEATURE,
|
CREATE_FEATURE,
|
||||||
UPDATE_FEATURE,
|
UPDATE_FEATURE,
|
||||||
} from '../../../../AccessProvider/permissions';
|
} from '../../../../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
||||||
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
|
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
|
||||||
import FeatureProjectSelect from './FeatureProjectSelect/FeatureProjectSelect';
|
import FeatureProjectSelect from './FeatureProjectSelect/FeatureProjectSelect';
|
||||||
|
@ -20,7 +20,7 @@ import NoItems from '../../../../common/NoItems/NoItems';
|
|||||||
import ResponsiveButton from '../../../../common/ResponsiveButton/ResponsiveButton';
|
import ResponsiveButton from '../../../../common/ResponsiveButton/ResponsiveButton';
|
||||||
import { Add } from '@material-ui/icons';
|
import { Add } from '@material-ui/icons';
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
||||||
import useQueryParams from '../../../../../hooks/useQueryParams';
|
import useQueryParams from '../../../../../hooks/useQueryParams';
|
||||||
|
|
||||||
const FeatureStrategiesEnvironments = () => {
|
const FeatureStrategiesEnvironments = () => {
|
||||||
@ -178,8 +178,12 @@ const FeatureStrategiesEnvironments = () => {
|
|||||||
|
|
||||||
// Check groupId
|
// Check groupId
|
||||||
|
|
||||||
const cacheParamKeys = Object.keys(cachedStrategy?.parameters || {});
|
const cacheParamKeys = Object.keys(
|
||||||
const strategyParamKeys = Object.keys(strategy?.parameters || {});
|
cachedStrategy?.parameters || {}
|
||||||
|
);
|
||||||
|
const strategyParamKeys = Object.keys(
|
||||||
|
strategy?.parameters || {}
|
||||||
|
);
|
||||||
// Check length of parameters
|
// Check length of parameters
|
||||||
if (cacheParamKeys.length !== strategyParamKeys.length) {
|
if (cacheParamKeys.length !== strategyParamKeys.length) {
|
||||||
equal = false;
|
equal = false;
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
UPDATE_STRATEGY_BUTTON_ID,
|
UPDATE_STRATEGY_BUTTON_ID,
|
||||||
} from '../../../../../../testIds';
|
} from '../../../../../../testIds';
|
||||||
import AccessContext from '../../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../../contexts/AccessContext';
|
||||||
import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
|
||||||
import useFeatureApi from '../../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
import useFeatureApi from '../../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
|
|
||||||
interface IFeatureStrategyEditable {
|
interface IFeatureStrategyEditable {
|
||||||
|
@ -8,10 +8,9 @@ import classnames from 'classnames';
|
|||||||
import { Button, IconButton, Tooltip, useMediaQuery } from '@material-ui/core';
|
import { Button, IconButton, Tooltip, useMediaQuery } from '@material-ui/core';
|
||||||
import { DoubleArrow } from '@material-ui/icons';
|
import { DoubleArrow } from '@material-ui/icons';
|
||||||
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
||||||
import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
|
|
||||||
|
|
||||||
const FeatureStrategiesList = () => {
|
const FeatureStrategiesList = () => {
|
||||||
const smallScreen = useMediaQuery('(max-width:700px)');
|
const smallScreen = useMediaQuery('(max-width:700px)');
|
||||||
const { expandedSidebar, setExpandedSidebar } = useContext(
|
const { expandedSidebar, setExpandedSidebar } = useContext(
|
||||||
@ -53,7 +52,7 @@ const FeatureStrategiesList = () => {
|
|||||||
const iconClasses = classnames(styles.icon, {
|
const iconClasses = classnames(styles.icon, {
|
||||||
[styles.expandedIcon]: expandedSidebar,
|
[styles.expandedIcon]: expandedSidebar,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={classes}>
|
<section className={classes}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -65,7 +64,14 @@ const FeatureStrategiesList = () => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Tooltip title={hasAccess(UPDATE_FEATURE) ? 'Click to open.' : 'You don\'t have access to perform this operation'} arrow>
|
<Tooltip
|
||||||
|
title={
|
||||||
|
hasAccess(UPDATE_FEATURE)
|
||||||
|
? 'Click to open.'
|
||||||
|
: "You don't have access to perform this operation"
|
||||||
|
}
|
||||||
|
arrow
|
||||||
|
>
|
||||||
<span className={styles.iconButtonWrapper}>
|
<span className={styles.iconButtonWrapper}>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={styles.iconButton}
|
className={styles.iconButton}
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
getFeatureStrategyIcon,
|
getFeatureStrategyIcon,
|
||||||
getHumanReadbleStrategyName,
|
getHumanReadbleStrategyName,
|
||||||
} from '../../../../../../utils/strategy-names';
|
} from '../../../../../../utils/strategy-names';
|
||||||
import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../../common/ConditionallyRender';
|
||||||
import { useStyles } from './FeatureStrategyCard.styles';
|
import { useStyles } from './FeatureStrategyCard.styles';
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ const FeatureStrategyCard = ({
|
|||||||
FeatureStrategiesUIContext
|
FeatureStrategiesUIContext
|
||||||
);
|
);
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const canUpdateFeature = hasAccess(UPDATE_FEATURE)
|
const canUpdateFeature = hasAccess(UPDATE_FEATURE);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
const strategy = getStrategyObject(strategies, name, featureId);
|
const strategy = getStrategyObject(strategies, name, featureId);
|
||||||
@ -83,7 +83,9 @@ const FeatureStrategyCard = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
className={styles.addButton}
|
className={styles.addButton}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
data-test={`${ADD_NEW_STRATEGY_CARD_BUTTON_ID}-${index + 1}`}
|
data-test={`${ADD_NEW_STRATEGY_CARD_BUTTON_ID}-${
|
||||||
|
index + 1
|
||||||
|
}`}
|
||||||
disabled={!canUpdateFeature}
|
disabled={!canUpdateFeature}
|
||||||
>
|
>
|
||||||
<Add />
|
<Add />
|
||||||
|
@ -19,7 +19,7 @@ import FeatureStrategiesSeparator from '../../FeatureStrategiesEnvironments/Feat
|
|||||||
import DefaultStrategy from '../../common/DefaultStrategy/DefaultStrategy';
|
import DefaultStrategy from '../../common/DefaultStrategy/DefaultStrategy';
|
||||||
import { ADD_CONSTRAINT_ID } from '../../../../../../testIds';
|
import { ADD_CONSTRAINT_ID } from '../../../../../../testIds';
|
||||||
import AccessContext from '../../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../../contexts/AccessContext';
|
||||||
import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
interface IFeatureStrategyAccordionBodyProps {
|
interface IFeatureStrategyAccordionBodyProps {
|
||||||
strategy: IFeatureStrategy;
|
strategy: IFeatureStrategy;
|
||||||
|
@ -19,7 +19,7 @@ import { useParams } from 'react-router';
|
|||||||
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
import FeatureVariantListItem from './FeatureVariantsListItem/FeatureVariantsListItem';
|
import FeatureVariantListItem from './FeatureVariantsListItem/FeatureVariantsListItem';
|
||||||
import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
||||||
import useUnleashContext from '../../../../../hooks/api/getters/useUnleashContext/useUnleashContext';
|
import useUnleashContext from '../../../../../hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||||
import GeneralSelect from '../../../../common/GeneralSelect/GeneralSelect';
|
import GeneralSelect from '../../../../common/GeneralSelect/GeneralSelect';
|
||||||
@ -121,8 +121,8 @@ const FeatureOverviewVariants = () => {
|
|||||||
style={{ display: 'block', marginTop: '0.5rem' }}
|
style={{ display: 'block', marginTop: '0.5rem' }}
|
||||||
>
|
>
|
||||||
By overriding the stickiness you can control which parameter
|
By overriding the stickiness you can control which parameter
|
||||||
is used to ensure consistent traffic
|
is used to ensure consistent traffic allocation across
|
||||||
allocation across variants.{' '}
|
variants.{' '}
|
||||||
<a
|
<a
|
||||||
href="https://docs.getunleash.io/advanced/toggle_variants"
|
href="https://docs.getunleash.io/advanced/toggle_variants"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -8,7 +8,7 @@ import useProject from '../../../hooks/api/getters/useProject/useProject';
|
|||||||
import useTabs from '../../../hooks/useTabs';
|
import useTabs from '../../../hooks/useTabs';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import { IFeatureViewParams } from '../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../interfaces/params';
|
||||||
import { UPDATE_FEATURE } from '../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../providers/AccessProvider/permissions';
|
||||||
import Dialogue from '../../common/Dialogue';
|
import Dialogue from '../../common/Dialogue';
|
||||||
import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton';
|
||||||
import FeatureLog from './FeatureLog/FeatureLog';
|
import FeatureLog from './FeatureLog/FeatureLog';
|
||||||
@ -106,8 +106,8 @@ const FeatureView2 = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
The feature <strong>{featureId.substring(0,30)}</strong> does not exist. Do
|
The feature <strong>{featureId.substring(0, 30)}</strong>{' '}
|
||||||
you want to
|
does not exist. Do you want to
|
||||||
<Link to={getCreateTogglePath(projectId)}>create it</Link>
|
<Link to={getCreateTogglePath(projectId)}>create it</Link>
|
||||||
?
|
?
|
||||||
</p>
|
</p>
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
CF_NAME_ID,
|
CF_NAME_ID,
|
||||||
CF_TYPE_ID,
|
CF_TYPE_ID,
|
||||||
} from '../../../../testIds';
|
} from '../../../../testIds';
|
||||||
import { CREATE_FEATURE } from '../../../AccessProvider/permissions';
|
import { CREATE_FEATURE } from '../../../providers/AccessProvider/permissions';
|
||||||
import { projectFilterGenerator } from '../../../../utils/project-filter-generator';
|
import { projectFilterGenerator } from '../../../../utils/project-filter-generator';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import useQueryParams from '../../../../hooks/useQueryParams';
|
import useQueryParams from '../../../../hooks/useQueryParams';
|
||||||
|
@ -8,7 +8,7 @@ import { useStyles } from './StrategyCardHeader.styles.js';
|
|||||||
import { ReactComponent as ReorderIcon } from '../../../../../assets/icons/reorder.svg';
|
import { ReactComponent as ReorderIcon } from '../../../../../assets/icons/reorder.svg';
|
||||||
import ConditionallyRender from '../../../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
const StrategyCardHeader = ({
|
const StrategyCardHeader = ({
|
||||||
name,
|
name,
|
||||||
|
@ -11,11 +11,11 @@ import {
|
|||||||
ADMIN,
|
ADMIN,
|
||||||
DELETE_FEATURE,
|
DELETE_FEATURE,
|
||||||
UPDATE_FEATURE,
|
UPDATE_FEATURE,
|
||||||
} from '../../../AccessProvider/permissions';
|
} from '../../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
import theme from '../../../../themes/main-theme';
|
import theme from '../../../../themes/main-theme';
|
||||||
import { createFakeStore } from '../../../../accessStoreFake';
|
import { createFakeStore } from '../../../../accessStoreFake';
|
||||||
import AccessProvider from '../../../AccessProvider/AccessProvider';
|
import AccessProvider from '../../../providers/AccessProvider/AccessProvider';
|
||||||
|
|
||||||
jest.mock('../update-strategies-container', () => ({
|
jest.mock('../update-strategies-container', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
|
@ -14,7 +14,7 @@ import { ReactComponent as UnleashLogo } from '../../../assets/img/logo-dark-wit
|
|||||||
import { useStyles } from './Header.styles';
|
import { useStyles } from './Header.styles';
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { useCommonStyles } from '../../../common.styles';
|
import { useCommonStyles } from '../../../common.styles';
|
||||||
import { ADMIN } from '../../AccessProvider/permissions';
|
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
||||||
import useUser from '../../../hooks/api/getters/useUser/useUser';
|
import useUser from '../../../hooks/api/getters/useUser/useUser';
|
||||||
import { IPermission } from '../../../interfaces/user';
|
import { IPermission } from '../../../interfaces/user';
|
||||||
import NavigationMenu from './NavigationMenu/NavigationMenu';
|
import NavigationMenu from './NavigationMenu/NavigationMenu';
|
||||||
|
@ -28,7 +28,7 @@ import AdminApi from '../../page/admin/api';
|
|||||||
import AdminUsers from '../../page/admin/users';
|
import AdminUsers from '../../page/admin/users';
|
||||||
import AdminInvoice from '../../page/admin/invoice';
|
import AdminInvoice from '../../page/admin/invoice';
|
||||||
import AdminAuth from '../../page/admin/auth';
|
import AdminAuth from '../../page/admin/auth';
|
||||||
import Login from '../user/Login';
|
import Login from '../user/Login/Login';
|
||||||
import { P, C, E, EEA } from '../common/flags';
|
import { P, C, E, EEA } from '../common/flags';
|
||||||
import NewUser from '../user/NewUser';
|
import NewUser from '../user/NewUser';
|
||||||
import ResetPassword from '../user/ResetPassword/ResetPassword';
|
import ResetPassword from '../user/ResetPassword/ResetPassword';
|
||||||
@ -40,7 +40,7 @@ import RedirectArchive from '../feature/RedirectArchive/RedirectArchive';
|
|||||||
import EnvironmentList from '../environments/EnvironmentList/EnvironmentList';
|
import EnvironmentList from '../environments/EnvironmentList/EnvironmentList';
|
||||||
import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment';
|
import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment';
|
||||||
import FeatureView2 from '../feature/FeatureView2/FeatureView2';
|
import FeatureView2 from '../feature/FeatureView2/FeatureView2';
|
||||||
import FeatureCreate from '../feature/FeatureCreate/FeatureCreate'
|
import FeatureCreate from '../feature/FeatureCreate/FeatureCreate';
|
||||||
|
|
||||||
export const routes = [
|
export const routes = [
|
||||||
// Project
|
// Project
|
||||||
@ -164,7 +164,7 @@ export const routes = [
|
|||||||
layout: 'main',
|
layout: 'main',
|
||||||
menu: { mobile: true },
|
menu: { mobile: true },
|
||||||
},
|
},
|
||||||
|
|
||||||
// Applications
|
// Applications
|
||||||
{
|
{
|
||||||
path: '/applications/:name',
|
path: '/applications/:name',
|
||||||
@ -305,8 +305,8 @@ export const routes = [
|
|||||||
menu: {},
|
menu: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Addons
|
// Addons
|
||||||
{
|
{
|
||||||
path: '/addons/create/:provider',
|
path: '/addons/create/:provider',
|
||||||
parent: '/addons',
|
parent: '/addons',
|
||||||
title: 'Create',
|
title: 'Create',
|
||||||
|
@ -14,7 +14,7 @@ import PageContent from '../../../common/PageContent';
|
|||||||
import ResponsiveButton from '../../../common/ResponsiveButton/ResponsiveButton';
|
import ResponsiveButton from '../../../common/ResponsiveButton/ResponsiveButton';
|
||||||
import FeatureToggleListNew from '../../../feature/FeatureToggleListNew/FeatureToggleListNew';
|
import FeatureToggleListNew from '../../../feature/FeatureToggleListNew/FeatureToggleListNew';
|
||||||
import { useStyles } from './ProjectFeatureToggles.styles';
|
import { useStyles } from './ProjectFeatureToggles.styles';
|
||||||
import { CREATE_FEATURE } from '../../../AccessProvider/permissions';
|
import { CREATE_FEATURE } from '../../../providers/AccessProvider/permissions';
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
|
||||||
interface IProjectFeatureToggles {
|
interface IProjectFeatureToggles {
|
||||||
@ -61,7 +61,10 @@ const ProjectFeatureToggles = ({
|
|||||||
<ResponsiveButton
|
<ResponsiveButton
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
history.push(
|
history.push(
|
||||||
getCreateTogglePath(id, uiConfig.flags.E)
|
getCreateTogglePath(
|
||||||
|
id,
|
||||||
|
uiConfig.flags.E
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
maxWidth="700px"
|
maxWidth="700px"
|
||||||
|
@ -6,7 +6,7 @@ import useLoading from '../../../hooks/useLoading';
|
|||||||
import PageContent from '../../common/PageContent';
|
import PageContent from '../../common/PageContent';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import HeaderTitle from '../../common/HeaderTitle';
|
import HeaderTitle from '../../common/HeaderTitle';
|
||||||
import { UPDATE_PROJECT } from '../../AccessProvider/permissions';
|
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';
|
||||||
@ -19,7 +19,6 @@ import EnvironmentDisableConfirm from './EnvironmentDisableConfirm/EnvironmentDi
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
|
|
||||||
|
|
||||||
export interface ProjectEnvironment {
|
export interface ProjectEnvironment {
|
||||||
name: string;
|
name: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -51,7 +50,6 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
|
|||||||
const ref = useLoading(loading);
|
const ref = useLoading(loading);
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
refetchEnvs();
|
refetchEnvs();
|
||||||
refetchProject();
|
refetchProject();
|
||||||
@ -164,52 +162,62 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
|
|||||||
headerContent={
|
headerContent={
|
||||||
<HeaderTitle
|
<HeaderTitle
|
||||||
title={`Configure environments for "${project?.name}" project`}
|
title={`Configure environments for "${project?.name}" project`}
|
||||||
/>}
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ConditionallyRender condition={uiConfig.flags.E} show={
|
<ConditionallyRender
|
||||||
<div className={styles.container}>
|
condition={uiConfig.flags.E}
|
||||||
<ConditionallyRender condition={error} show={renderError()} />
|
show={
|
||||||
<Alert severity="info" style={{marginBottom: '20px'}}>
|
<div className={styles.container}>
|
||||||
<b>Important!</b> In order for your application to retrieve configured activation strategies for a specific environment,
|
<ConditionallyRender
|
||||||
the application<br/> must use an environment specific API key. You can look up the environment-specific API keys {' '}
|
condition={error}
|
||||||
<Link
|
show={renderError()}
|
||||||
to='/admin/api'
|
/>
|
||||||
|
<Alert
|
||||||
|
severity="info"
|
||||||
|
style={{ marginBottom: '20px' }}
|
||||||
>
|
>
|
||||||
here.
|
<b>Important!</b> In order for your application
|
||||||
</Link>
|
to retrieve configured activation strategies for
|
||||||
<br/>
|
a specific environment, the application
|
||||||
<br/>
|
<br /> must use an environment specific API key.
|
||||||
Your administrator can configure an environment-specific API key to be used in the SDK.
|
You can look up the environment-specific API
|
||||||
If you are an administrator you can {' '}
|
keys <Link to="/admin/api">here.</Link>
|
||||||
<Link
|
<br />
|
||||||
to='/admin/api'
|
<br />
|
||||||
>
|
Your administrator can configure an
|
||||||
create a new API key.
|
environment-specific API key to be used in the
|
||||||
</Link>
|
SDK. If you are an administrator you can{' '}
|
||||||
|
<Link to="/admin/api">
|
||||||
|
create a new API key.
|
||||||
|
</Link>
|
||||||
|
</Alert>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={environments.length < 1 && !loading}
|
||||||
|
show={<div>No environments available.</div>}
|
||||||
|
elseShow={renderEnvironments()}
|
||||||
|
/>
|
||||||
|
<EnvironmentDisableConfirm
|
||||||
|
env={selectedEnv}
|
||||||
|
open={!!selectedEnv}
|
||||||
|
handleDisableEnvironment={
|
||||||
|
handleDisableEnvironment
|
||||||
|
}
|
||||||
|
handleCancelDisableEnvironment={
|
||||||
|
handleCancelDisableEnvironment
|
||||||
|
}
|
||||||
|
confirmName={confirmName}
|
||||||
|
setConfirmName={setConfirmName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<Alert security="success">
|
||||||
|
This feature has not been Unleashed for you yet.
|
||||||
</Alert>
|
</Alert>
|
||||||
<ConditionallyRender
|
}
|
||||||
condition={environments.length < 1 && !loading}
|
/>
|
||||||
show={<div>No environments available.</div>}
|
|
||||||
elseShow={renderEnvironments()}
|
|
||||||
/>
|
|
||||||
<EnvironmentDisableConfirm
|
|
||||||
env={selectedEnv}
|
|
||||||
open={!!selectedEnv}
|
|
||||||
handleDisableEnvironment={handleDisableEnvironment}
|
|
||||||
handleCancelDisableEnvironment={
|
|
||||||
handleCancelDisableEnvironment
|
|
||||||
}
|
|
||||||
confirmName={confirmName}
|
|
||||||
setConfirmName={setConfirmName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
} elseShow={
|
|
||||||
<Alert security="success">
|
|
||||||
This feature has not been Unleashed for you yet.
|
|
||||||
</Alert>
|
|
||||||
} />
|
|
||||||
|
|
||||||
{toast}
|
{toast}
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@ import PageContent from '../../common/PageContent';
|
|||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import HeaderTitle from '../../common/HeaderTitle';
|
import HeaderTitle from '../../common/HeaderTitle';
|
||||||
import ResponsiveButton from '../../common/ResponsiveButton/ResponsiveButton';
|
import ResponsiveButton from '../../common/ResponsiveButton/ResponsiveButton';
|
||||||
import { CREATE_PROJECT } from '../../AccessProvider/permissions';
|
import { CREATE_PROJECT } from '../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
import { Add } from '@material-ui/icons';
|
import { Add } from '@material-ui/icons';
|
||||||
import ApiError from '../../common/ApiError/ApiError';
|
import ApiError from '../../common/ApiError/ApiError';
|
||||||
@ -26,21 +26,21 @@ type projectMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function resolveCreateButtonData(isOss: boolean, hasAccess: boolean) {
|
function resolveCreateButtonData(isOss: boolean, hasAccess: boolean) {
|
||||||
if(isOss) {
|
if (isOss) {
|
||||||
return {
|
return {
|
||||||
title: 'You must be on a paid subscription to create new projects',
|
title: 'You must be on a paid subscription to create new projects',
|
||||||
disabled: true
|
disabled: true,
|
||||||
}
|
};
|
||||||
} else if (!hasAccess) {
|
} else if (!hasAccess) {
|
||||||
return {
|
return {
|
||||||
title: 'You do not have permissions create new projects',
|
title: 'You do not have permission to create new projects',
|
||||||
disabled: true
|
disabled: true,
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
title: 'Click to create a new project',
|
title: 'Click to create a new project',
|
||||||
disabled: false
|
disabled: false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,10 @@ const ProjectListNew = () => {
|
|||||||
setFetchedProjects(prev => ({ ...prev, [projectId]: true }));
|
setFetchedProjects(prev => ({ ...prev, [projectId]: true }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const createButtonData = resolveCreateButtonData(isOss(), hasAccess(CREATE_PROJECT));
|
const createButtonData = resolveCreateButtonData(
|
||||||
|
isOss(),
|
||||||
|
hasAccess(CREATE_PROJECT)
|
||||||
|
);
|
||||||
|
|
||||||
const renderError = () => {
|
const renderError = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -9,7 +9,7 @@ import { trim } from '../common/util';
|
|||||||
import PageContent from '../common/PageContent/PageContent';
|
import PageContent from '../common/PageContent/PageContent';
|
||||||
import AccessContext from '../../contexts/AccessContext';
|
import AccessContext from '../../contexts/AccessContext';
|
||||||
import ConditionallyRender from '../common/ConditionallyRender';
|
import ConditionallyRender from '../common/ConditionallyRender';
|
||||||
import { CREATE_PROJECT } from '../AccessProvider/permissions';
|
import { CREATE_PROJECT } from '../providers/AccessProvider/permissions';
|
||||||
import HeaderTitle from '../common/HeaderTitle';
|
import HeaderTitle from '../common/HeaderTitle';
|
||||||
import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
@ -28,29 +28,28 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
|
|||||||
const { editMode } = props;
|
const { editMode } = props;
|
||||||
|
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const [project, setProject ] = useState(props.project || {});
|
const [project, setProject] = useState(props.project || {});
|
||||||
const [errors, setErrors ] = useState<any>({});
|
const [errors, setErrors] = useState<any>({});
|
||||||
const { isOss, loading } = useUiConfig();
|
const { isOss, loading } = useUiConfig();
|
||||||
const ref = useLoading(loading);
|
const ref = useLoading(loading);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!project.id && props.project.id) {
|
if (!project.id && props.project.id) {
|
||||||
setProject(props.project);
|
setProject(props.project);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [props.project]);
|
}, [props.project]);
|
||||||
|
|
||||||
const setValue = (field: string, value: string) => {
|
const setValue = (field: string, value: string) => {
|
||||||
const p = {...project}
|
const p = { ...project };
|
||||||
p[field] = value;
|
p[field] = value;
|
||||||
setProject(p)
|
setProject(p);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateId = async (id: string) => {
|
const validateId = async (id: string) => {
|
||||||
if (editMode) return true;
|
if (editMode) return true;
|
||||||
|
|
||||||
const e = {...errors};
|
const e = { ...errors };
|
||||||
try {
|
try {
|
||||||
await props.validateId(id);
|
await props.validateId(id);
|
||||||
e.id = undefined;
|
e.id = undefined;
|
||||||
@ -58,14 +57,14 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
|
|||||||
e.id = err.message;
|
e.id = err.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrors(e)
|
setErrors(e);
|
||||||
if (e.id) return false;
|
if (e.id) return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateName = () => {
|
const validateName = () => {
|
||||||
if (project.name.length === 0) {
|
if (project.name.length === 0) {
|
||||||
setErrors({...errors, name: 'Name can not be empty.' })
|
setErrors({ ...errors, name: 'Name can not be empty.' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -111,91 +110,103 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<ConditionallyRender
|
||||||
<ConditionallyRender condition={isOss()} show={
|
condition={isOss()}
|
||||||
<Alert data-loading severity="error">
|
show={
|
||||||
{submitText} project requires a paid version of Unleash.
|
<Alert data-loading severity="error">
|
||||||
Check out <a href="https://www.getunleash.io" target="_blank" rel="noreferrer">getunleash.io</a>{' '}
|
{submitText} project requires a paid version of
|
||||||
to learn more.
|
Unleash. Check out{' '}
|
||||||
</Alert>
|
<a
|
||||||
} elseShow={
|
href="https://www.getunleash.io"
|
||||||
<>
|
target="_blank"
|
||||||
<Typography
|
rel="noreferrer"
|
||||||
variant="subtitle1"
|
>
|
||||||
style={{ marginBottom: '0.5rem' }}
|
getunleash.io
|
||||||
>
|
</a>{' '}
|
||||||
Projects allows you to group feature toggles together in the
|
to learn more.
|
||||||
management UI.
|
</Alert>
|
||||||
</Typography>
|
}
|
||||||
<form
|
elseShow={
|
||||||
data-loading
|
<>
|
||||||
onSubmit={onSubmit}
|
<Typography
|
||||||
className={classnames(
|
variant="subtitle1"
|
||||||
commonStyles.contentSpacing,
|
style={{ marginBottom: '0.5rem' }}
|
||||||
styles.formContainer
|
>
|
||||||
)}
|
Projects allows you to group feature toggles
|
||||||
>
|
together in the management UI.
|
||||||
<TextField
|
</Typography>
|
||||||
label="Project Id"
|
<form
|
||||||
name="id"
|
data-loading
|
||||||
placeholder="A-unique-key"
|
onSubmit={onSubmit}
|
||||||
value={project.id}
|
className={classnames(
|
||||||
error={!!errors.id}
|
commonStyles.contentSpacing,
|
||||||
helperText={errors.id}
|
styles.formContainer
|
||||||
disabled={editMode}
|
)}
|
||||||
variant="outlined"
|
>
|
||||||
size="small"
|
<TextField
|
||||||
onBlur={v => validateId(v.target.value)}
|
label="Project Id"
|
||||||
onChange={v =>
|
name="id"
|
||||||
setValue('id', trim(v.target.value))
|
placeholder="A-unique-key"
|
||||||
}
|
value={project.id}
|
||||||
/>
|
error={!!errors.id}
|
||||||
<br />
|
helperText={errors.id}
|
||||||
<TextField
|
disabled={editMode}
|
||||||
label="Name"
|
variant="outlined"
|
||||||
name="name"
|
size="small"
|
||||||
placeholder="Project name"
|
onBlur={v => validateId(v.target.value)}
|
||||||
value={project.name}
|
onChange={v =>
|
||||||
error={!!errors.name}
|
setValue('id', trim(v.target.value))
|
||||||
variant="outlined"
|
}
|
||||||
size="small"
|
/>
|
||||||
helperText={errors.name}
|
<br />
|
||||||
onChange={v => setValue('name', v.target.value)}
|
<TextField
|
||||||
/>
|
label="Name"
|
||||||
<TextField
|
name="name"
|
||||||
className={commonStyles.fullwidth}
|
placeholder="Project name"
|
||||||
placeholder="A short description"
|
value={project.name}
|
||||||
maxRows={2}
|
error={!!errors.name}
|
||||||
label="Description"
|
variant="outlined"
|
||||||
error={!!errors.description}
|
size="small"
|
||||||
helperText={errors.description}
|
helperText={errors.name}
|
||||||
variant="outlined"
|
onChange={v =>
|
||||||
size="small"
|
setValue('name', v.target.value)
|
||||||
multiline
|
}
|
||||||
value={project.description}
|
/>
|
||||||
onChange={v =>
|
<TextField
|
||||||
setValue('description', v.target.value)
|
className={commonStyles.fullwidth}
|
||||||
}
|
placeholder="A short description"
|
||||||
/>
|
maxRows={2}
|
||||||
|
label="Description"
|
||||||
|
error={!!errors.description}
|
||||||
|
helperText={errors.description}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
multiline
|
||||||
|
value={project.description}
|
||||||
|
onChange={v =>
|
||||||
|
setValue('description', v.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={hasAccess(CREATE_PROJECT)}
|
condition={hasAccess(CREATE_PROJECT)}
|
||||||
show={
|
show={
|
||||||
<div className={styles.formButtons}>
|
<div className={styles.formButtons}>
|
||||||
<FormButtons
|
<FormButtons
|
||||||
submitText={submitText}
|
submitText={submitText}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
} />
|
}
|
||||||
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
ProjectFormComponent.propTypes = {
|
ProjectFormComponent.propTypes = {
|
||||||
project: PropTypes.object.isRequired,
|
project: PropTypes.object.isRequired,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
|
||||||
import AccessContext from '../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import { ADMIN } from './permissions';
|
import { ADMIN } from './permissions';
|
||||||
|
|
||||||
// TODO: Type up redux store
|
// TODO: Type up redux store
|
67
frontend/src/component/providers/SWRProvider/SWRProvider.tsx
Normal file
67
frontend/src/component/providers/SWRProvider/SWRProvider.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { USER_CACHE_KEY } from '../../../hooks/api/getters/useUser/useUser';
|
||||||
|
import { mutate, SWRConfig, useSWRConfig } from 'swr';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import { IToast } from '../../../hooks/useToast';
|
||||||
|
|
||||||
|
interface ISWRProviderProps {
|
||||||
|
setToastData: (toastData: IToast) => void;
|
||||||
|
isUnauthorized: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SWRProvider: React.FC<ISWRProviderProps> = ({
|
||||||
|
children,
|
||||||
|
setToastData,
|
||||||
|
isUnauthorized,
|
||||||
|
}) => {
|
||||||
|
const { cache } = useSWRConfig();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const handleFetchError = error => {
|
||||||
|
if (error.status === 401) {
|
||||||
|
cache.clear();
|
||||||
|
const path = location.pathname;
|
||||||
|
|
||||||
|
mutate(USER_CACHE_KEY, { ...error.info }, false);
|
||||||
|
if (path === '/login') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push('/login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isUnauthorized()) {
|
||||||
|
setToastData({
|
||||||
|
show: true,
|
||||||
|
type: 'error',
|
||||||
|
text: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SWRConfig
|
||||||
|
value={{
|
||||||
|
onErrorRetry: (
|
||||||
|
error,
|
||||||
|
_key,
|
||||||
|
_config,
|
||||||
|
revalidate,
|
||||||
|
{ retryCount }
|
||||||
|
) => {
|
||||||
|
// Never retry on 404 or 401.
|
||||||
|
if (error.status < 499) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => revalidate({ retryCount }), 5000);
|
||||||
|
},
|
||||||
|
onError: handleFetchError,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SWRConfig>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SWRProvider;
|
@ -24,7 +24,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
CREATE_STRATEGY,
|
CREATE_STRATEGY,
|
||||||
DELETE_STRATEGY,
|
DELETE_STRATEGY,
|
||||||
} from '../../AccessProvider/permissions';
|
} from '../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import PageContent from '../../common/PageContent/PageContent';
|
import PageContent from '../../common/PageContent/PageContent';
|
||||||
|
@ -5,9 +5,9 @@ import { ThemeProvider } from '@material-ui/core';
|
|||||||
import StrategiesListComponent from '../StrategiesList/StrategiesList';
|
import StrategiesListComponent from '../StrategiesList/StrategiesList';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import theme from '../../../themes/main-theme';
|
import theme from '../../../themes/main-theme';
|
||||||
import AccessProvider from '../../AccessProvider/AccessProvider';
|
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
||||||
import { createFakeStore } from '../../../accessStoreFake';
|
import { createFakeStore } from '../../../accessStoreFake';
|
||||||
import { ADMIN } from '../../AccessProvider/permissions';
|
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
test('renders correctly with one strategy', () => {
|
test('renders correctly with one strategy', () => {
|
||||||
const strategy = {
|
const strategy = {
|
||||||
|
@ -5,7 +5,7 @@ import renderer from 'react-test-renderer';
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import theme from '../../../themes/main-theme';
|
import theme from '../../../themes/main-theme';
|
||||||
import { createFakeStore } from '../../../accessStoreFake';
|
import { createFakeStore } from '../../../accessStoreFake';
|
||||||
import AccessProvider from '../../AccessProvider/AccessProvider';
|
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
||||||
|
|
||||||
test('renders correctly with one strategy', () => {
|
test('renders correctly with one strategy', () => {
|
||||||
const strategy = {
|
const strategy = {
|
||||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { Grid, Typography } from '@material-ui/core';
|
import { Grid, Typography } from '@material-ui/core';
|
||||||
import ShowStrategy from './show-strategy-component';
|
import ShowStrategy from './show-strategy-component';
|
||||||
import EditStrategy from './CreateStrategy';
|
import EditStrategy from './CreateStrategy';
|
||||||
import { UPDATE_STRATEGY } from '../AccessProvider/permissions';
|
import { UPDATE_STRATEGY } from '../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
|
||||||
import TabNav from '../common/TabNav/TabNav';
|
import TabNav from '../common/TabNav/TabNav';
|
||||||
import PageContent from '../common/PageContent/PageContent';
|
import PageContent from '../common/PageContent/PageContent';
|
||||||
|
@ -19,7 +19,7 @@ import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyR
|
|||||||
import {
|
import {
|
||||||
CREATE_TAG_TYPE,
|
CREATE_TAG_TYPE,
|
||||||
DELETE_TAG_TYPE,
|
DELETE_TAG_TYPE,
|
||||||
} from '../../AccessProvider/permissions';
|
} from '../../providers/AccessProvider/permissions';
|
||||||
import Dialogue from '../../common/Dialogue/Dialogue';
|
import Dialogue from '../../common/Dialogue/Dialogue';
|
||||||
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||||
|
|
||||||
|
@ -3,12 +3,12 @@ import { ThemeProvider } from '@material-ui/core';
|
|||||||
import TagTypes from '../form-tag-type-component';
|
import TagTypes from '../form-tag-type-component';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import theme from '../../../themes/main-theme';
|
import theme from '../../../themes/main-theme';
|
||||||
import AccessProvider from '../../AccessProvider/AccessProvider';
|
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
||||||
import { createFakeStore } from '../../../accessStoreFake';
|
import { createFakeStore } from '../../../accessStoreFake';
|
||||||
import {
|
import {
|
||||||
CREATE_TAG_TYPE,
|
CREATE_TAG_TYPE,
|
||||||
UPDATE_TAG_TYPE,
|
UPDATE_TAG_TYPE,
|
||||||
} from '../../AccessProvider/permissions';
|
} from '../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
jest.mock('@material-ui/core/TextField');
|
jest.mock('@material-ui/core/TextField');
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@ import { MemoryRouter } from 'react-router-dom';
|
|||||||
import { ThemeProvider } from '@material-ui/styles';
|
import { ThemeProvider } from '@material-ui/styles';
|
||||||
import theme from '../../../themes/main-theme';
|
import theme from '../../../themes/main-theme';
|
||||||
import { createFakeStore } from '../../../accessStoreFake';
|
import { createFakeStore } from '../../../accessStoreFake';
|
||||||
import AccessProvider from '../../AccessProvider/AccessProvider';
|
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ADMIN,
|
ADMIN,
|
||||||
CREATE_TAG_TYPE,
|
CREATE_TAG_TYPE,
|
||||||
UPDATE_TAG_TYPE,
|
UPDATE_TAG_TYPE,
|
||||||
DELETE_TAG_TYPE,
|
DELETE_TAG_TYPE,
|
||||||
} from '../../AccessProvider/permissions';
|
} from '../../providers/AccessProvider/permissions';
|
||||||
|
|
||||||
test('renders an empty list correctly', () => {
|
test('renders an empty list correctly', () => {
|
||||||
const tree = renderer.create(
|
const tree = renderer.create(
|
||||||
|
@ -12,7 +12,7 @@ import AccessContext from '../../contexts/AccessContext';
|
|||||||
import {
|
import {
|
||||||
CREATE_TAG_TYPE,
|
CREATE_TAG_TYPE,
|
||||||
UPDATE_TAG_TYPE,
|
UPDATE_TAG_TYPE,
|
||||||
} from '../AccessProvider/permissions';
|
} from '../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../common/ConditionallyRender';
|
import ConditionallyRender from '../common/ConditionallyRender';
|
||||||
|
|
||||||
const AddTagTypeComponent = ({
|
const AddTagTypeComponent = ({
|
||||||
|
@ -14,7 +14,10 @@ import {
|
|||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import { Add, Label, Delete } from '@material-ui/icons';
|
import { Add, Label, Delete } from '@material-ui/icons';
|
||||||
|
|
||||||
import { CREATE_TAG, DELETE_TAG } from '../../AccessProvider/permissions';
|
import {
|
||||||
|
CREATE_TAG,
|
||||||
|
DELETE_TAG,
|
||||||
|
} from '../../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import HeaderTitle from '../../common/HeaderTitle';
|
import HeaderTitle from '../../common/HeaderTitle';
|
||||||
import PageContent from '../../common/PageContent/PageContent';
|
import PageContent from '../../common/PageContent/PageContent';
|
||||||
|
102
frontend/src/component/user/Authentication/Authentication.tsx
Normal file
102
frontend/src/component/user/Authentication/Authentication.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import SimpleAuth from '../SimpleAuth/SimpleAuth';
|
||||||
|
import AuthenticationCustomComponent from '../authentication-custom-component';
|
||||||
|
import PasswordAuth from '../PasswordAuth/PasswordAuth';
|
||||||
|
import HostedAuth from '../HostedAuth/HostedAuth';
|
||||||
|
import DemoAuth from '../DemoAuth';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SIMPLE_TYPE,
|
||||||
|
DEMO_TYPE,
|
||||||
|
PASSWORD_TYPE,
|
||||||
|
HOSTED_TYPE,
|
||||||
|
} from '../../../constants/authTypes';
|
||||||
|
import SecondaryLoginActions from '../common/SecondaryLoginActions/SecondaryLoginActions';
|
||||||
|
import useUser from '../../../hooks/api/getters/useUser/useUser';
|
||||||
|
import { IUser } from '../../../interfaces/user';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import useQueryParams from '../../../hooks/useQueryParams';
|
||||||
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
|
import { Alert } from '@material-ui/lab';
|
||||||
|
|
||||||
|
interface IAuthenticationProps {
|
||||||
|
insecureLogin: (path: string, user: IUser) => void;
|
||||||
|
passwordLogin: (path: string, user: IUser) => void;
|
||||||
|
demoLogin: (path: string, user: IUser) => void;
|
||||||
|
history: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Authentication = ({
|
||||||
|
insecureLogin,
|
||||||
|
passwordLogin,
|
||||||
|
demoLogin,
|
||||||
|
}: IAuthenticationProps) => {
|
||||||
|
const { authDetails } = useUser();
|
||||||
|
const history = useHistory();
|
||||||
|
const params = useQueryParams();
|
||||||
|
|
||||||
|
const error = params.get('errorMsg');
|
||||||
|
|
||||||
|
if (!authDetails) return null;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (authDetails.type === PASSWORD_TYPE) {
|
||||||
|
content = (
|
||||||
|
<>
|
||||||
|
<PasswordAuth
|
||||||
|
passwordLogin={passwordLogin}
|
||||||
|
authDetails={authDetails}
|
||||||
|
history={history}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={!authDetails.disableDefault}
|
||||||
|
show={<SecondaryLoginActions />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else if (authDetails.type === SIMPLE_TYPE) {
|
||||||
|
content = (
|
||||||
|
<SimpleAuth
|
||||||
|
insecureLogin={insecureLogin}
|
||||||
|
authDetails={authDetails}
|
||||||
|
history={history}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (authDetails.type === DEMO_TYPE) {
|
||||||
|
content = (
|
||||||
|
<DemoAuth
|
||||||
|
demoLogin={demoLogin}
|
||||||
|
authDetails={authDetails}
|
||||||
|
history={history}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (authDetails.type === HOSTED_TYPE) {
|
||||||
|
content = (
|
||||||
|
<>
|
||||||
|
<HostedAuth
|
||||||
|
passwordLogin={passwordLogin}
|
||||||
|
authDetails={authDetails}
|
||||||
|
history={history}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={!authDetails.disableDefault}
|
||||||
|
show={<SecondaryLoginActions />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = <AuthenticationCustomComponent authDetails={authDetails} />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ maxWidth: '350px' }}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(error)}
|
||||||
|
show={<Alert severity="error">{error}</Alert>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{content}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Authentication;
|
@ -1,10 +1,10 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import AuthenticationComponent from './authentication-component';
|
import AuthenticationComponent from './Authentication';
|
||||||
import {
|
import {
|
||||||
insecureLogin,
|
insecureLogin,
|
||||||
passwordLogin,
|
passwordLogin,
|
||||||
demoLogin,
|
demoLogin,
|
||||||
} from '../../store/user/actions';
|
} from '../../../store/user/actions';
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, props) => ({
|
const mapDispatchToProps = (dispatch, props) => ({
|
||||||
demoLogin: (path, user) => demoLogin(path, user)(dispatch),
|
demoLogin: (path, user) => demoLogin(path, user)(dispatch),
|
||||||
@ -12,12 +12,4 @@ const mapDispatchToProps = (dispatch, props) => ({
|
|||||||
passwordLogin: (path, user) => passwordLogin(path, user)(dispatch),
|
passwordLogin: (path, user) => passwordLogin(path, user)(dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
export default connect(null, mapDispatchToProps)(AuthenticationComponent);
|
||||||
user: state.user.toJS(),
|
|
||||||
flags: state.uiConfig.toJS().flags,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(AuthenticationComponent);
|
|
@ -79,50 +79,58 @@ const HostedAuth = ({ authDetails, passwordLogin }) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} action={authDetails.path}>
|
<ConditionallyRender
|
||||||
<Typography variant="subtitle2" className={styles.apiError}>
|
condition={!authDetails.disableDefault}
|
||||||
{apiError}
|
show={
|
||||||
</Typography>
|
<form onSubmit={handleSubmit} action={authDetails.path}>
|
||||||
<div
|
<Typography
|
||||||
className={classnames(
|
variant="subtitle2"
|
||||||
styles.contentContainer,
|
className={styles.apiError}
|
||||||
commonStyles.contentSpacingY
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<TextField
|
|
||||||
label="Username or email"
|
|
||||||
name="username"
|
|
||||||
type="string"
|
|
||||||
onChange={evt => setUsername(evt.target.value)}
|
|
||||||
value={username}
|
|
||||||
error={!!usernameError}
|
|
||||||
helperText={usernameError}
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
label="Password"
|
|
||||||
onChange={evt => setPassword(evt.target.value)}
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
error={!!passwordError}
|
|
||||||
helperText={passwordError}
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<Grid container>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
type="submit"
|
|
||||||
className={styles.button}
|
|
||||||
>
|
>
|
||||||
Sign in
|
{apiError}
|
||||||
</Button>
|
</Typography>
|
||||||
</Grid>
|
<div
|
||||||
</div>
|
className={classnames(
|
||||||
</form>
|
styles.contentContainer,
|
||||||
|
commonStyles.contentSpacingY
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
label="Username or email"
|
||||||
|
name="username"
|
||||||
|
type="string"
|
||||||
|
onChange={evt => setUsername(evt.target.value)}
|
||||||
|
value={username}
|
||||||
|
error={!!usernameError}
|
||||||
|
helperText={usernameError}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Password"
|
||||||
|
onChange={evt => setPassword(evt.target.value)}
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
error={!!passwordError}
|
||||||
|
helperText={passwordError}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Grid container>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
className={styles.button}
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import AuthenticationContainer from '../authentication-container';
|
import AuthenticationContainer from '../Authentication';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
|
|
||||||
import { useStyles } from './Login.styles';
|
import { useStyles } from './Login.styles';
|
||||||
@ -8,22 +8,21 @@ import useQueryParams from '../../../hooks/useQueryParams';
|
|||||||
import ResetPasswordSuccess from '../common/ResetPasswordSuccess/ResetPasswordSuccess';
|
import ResetPasswordSuccess from '../common/ResetPasswordSuccess/ResetPasswordSuccess';
|
||||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
||||||
import { DEMO_TYPE } from '../../../constants/authTypes';
|
import { DEMO_TYPE } from '../../../constants/authTypes';
|
||||||
|
import useUser from '../../../hooks/api/getters/useUser/useUser';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
|
||||||
const Login = ({ history, user, fetchUser }) => {
|
const Login = () => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
const { permissions, authDetails } = useUser();
|
||||||
const query = useQueryParams();
|
const query = useQueryParams();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUser();
|
if (permissions?.length > 0) {
|
||||||
/* eslint-disable-next-line */
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (user.permissions.length > 0) {
|
|
||||||
history.push('features');
|
history.push('features');
|
||||||
}
|
}
|
||||||
/* eslint-disable-next-line */
|
/* eslint-disable-next-line */
|
||||||
}, [user.permissions]);
|
}, [permissions.length]);
|
||||||
|
|
||||||
const resetPassword = query.get('reset') === 'true';
|
const resetPassword = query.get('reset') === 'true';
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ const Login = ({ history, user, fetchUser }) => {
|
|||||||
<StandaloneLayout>
|
<StandaloneLayout>
|
||||||
<div className={styles.loginFormContainer}>
|
<div className={styles.loginFormContainer}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={user?.authDetails?.type !== DEMO_TYPE}
|
condition={authDetails?.type !== DEMO_TYPE}
|
||||||
show={
|
show={
|
||||||
<h2 className={styles.title}>
|
<h2 className={styles.title}>
|
||||||
Login to continue the great work
|
Login to continue the great work
|
@ -1,14 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { fetchUser } from '../../../store/user/actions';
|
|
||||||
import Login from './Login';
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
user: state.user.toJS(),
|
|
||||||
flags: state.uiConfig.toJS().flags,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
fetchUser,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Login);
|
|
@ -79,59 +79,67 @@ const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
|||||||
const { usernameError, passwordError, apiError } = errors;
|
const { usernameError, passwordError, apiError } = errors;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} action={authDetails.path}>
|
<ConditionallyRender
|
||||||
<ConditionallyRender
|
condition={!authDetails.disableDefault}
|
||||||
condition={apiError}
|
show={
|
||||||
show={
|
<form onSubmit={handleSubmit} action={authDetails.path}>
|
||||||
<Alert severity="error" className={styles.apiError}>
|
<ConditionallyRender
|
||||||
{apiError}
|
condition={apiError}
|
||||||
</Alert>
|
show={
|
||||||
}
|
<Alert
|
||||||
/>
|
severity="error"
|
||||||
|
className={styles.apiError}
|
||||||
|
>
|
||||||
|
{apiError}
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
styles.contentContainer,
|
styles.contentContainer,
|
||||||
commonStyles.contentSpacingY
|
commonStyles.contentSpacingY
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
label="Username or email"
|
label="Username or email"
|
||||||
name="username"
|
name="username"
|
||||||
type="string"
|
type="string"
|
||||||
onChange={evt => setUsername(evt.target.value)}
|
onChange={evt => setUsername(evt.target.value)}
|
||||||
value={username}
|
value={username}
|
||||||
error={!!usernameError}
|
error={!!usernameError}
|
||||||
helperText={usernameError}
|
helperText={usernameError}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
autoComplete="true"
|
autoComplete="true"
|
||||||
size="small"
|
size="small"
|
||||||
data-test={LOGIN_EMAIL_ID}
|
data-test={LOGIN_EMAIL_ID}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Password"
|
label="Password"
|
||||||
onChange={evt => setPassword(evt.target.value)}
|
onChange={evt => setPassword(evt.target.value)}
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
error={!!passwordError}
|
error={!!passwordError}
|
||||||
helperText={passwordError}
|
helperText={passwordError}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
autoComplete="true"
|
autoComplete="true"
|
||||||
size="small"
|
size="small"
|
||||||
data-test={LOGIN_PASSWORD_ID}
|
data-test={LOGIN_PASSWORD_ID}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
style={{ width: '150px', margin: '1rem auto' }}
|
style={{ width: '150px', margin: '1rem auto' }}
|
||||||
data-test={LOGIN_BUTTON}
|
data-test={LOGIN_BUTTON}
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import SimpleAuth from './SimpleAuth/SimpleAuth';
|
|
||||||
import AuthenticationCustomComponent from './authentication-custom-component';
|
|
||||||
import PasswordAuth from './PasswordAuth/PasswordAuth';
|
|
||||||
import HostedAuth from './HostedAuth/HostedAuth';
|
|
||||||
import DemoAuth from './DemoAuth';
|
|
||||||
|
|
||||||
import {
|
|
||||||
SIMPLE_TYPE,
|
|
||||||
DEMO_TYPE,
|
|
||||||
PASSWORD_TYPE,
|
|
||||||
HOSTED_TYPE,
|
|
||||||
} from '../../constants/authTypes';
|
|
||||||
import SecondaryLoginActions from './common/SecondaryLoginActions/SecondaryLoginActions';
|
|
||||||
|
|
||||||
class AuthComponent extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
user: PropTypes.object.isRequired,
|
|
||||||
demoLogin: PropTypes.func.isRequired,
|
|
||||||
insecureLogin: PropTypes.func.isRequired,
|
|
||||||
passwordLogin: PropTypes.func.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const authDetails = this.props.user.authDetails;
|
|
||||||
|
|
||||||
if (!authDetails) return null;
|
|
||||||
|
|
||||||
let content;
|
|
||||||
if (authDetails.type === PASSWORD_TYPE) {
|
|
||||||
content = (
|
|
||||||
<>
|
|
||||||
<PasswordAuth
|
|
||||||
passwordLogin={this.props.passwordLogin}
|
|
||||||
authDetails={authDetails}
|
|
||||||
history={this.props.history}
|
|
||||||
/>
|
|
||||||
<SecondaryLoginActions />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else if (authDetails.type === SIMPLE_TYPE) {
|
|
||||||
content = (
|
|
||||||
<SimpleAuth
|
|
||||||
insecureLogin={this.props.insecureLogin}
|
|
||||||
authDetails={authDetails}
|
|
||||||
history={this.props.history}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (authDetails.type === DEMO_TYPE) {
|
|
||||||
content = (
|
|
||||||
<DemoAuth
|
|
||||||
demoLogin={this.props.demoLogin}
|
|
||||||
authDetails={authDetails}
|
|
||||||
history={this.props.history}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (authDetails.type === HOSTED_TYPE) {
|
|
||||||
content = (
|
|
||||||
<>
|
|
||||||
<HostedAuth
|
|
||||||
passwordLogin={this.props.passwordLogin}
|
|
||||||
authDetails={authDetails}
|
|
||||||
history={this.props.history}
|
|
||||||
/>
|
|
||||||
<SecondaryLoginActions />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
content = (
|
|
||||||
<AuthenticationCustomComponent authDetails={authDetails} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <>{content}</>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AuthComponent;
|
|
@ -1,6 +1,8 @@
|
|||||||
const handleErrorResponses = (target: string) => async (res: Response) => {
|
const handleErrorResponses = (target: string) => async (res: Response) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const error = new Error(`An error occurred while trying to get ${target}`);
|
const error = new Error(
|
||||||
|
`An error occurred while trying to get ${target}`
|
||||||
|
);
|
||||||
// Try to resolve body, but don't rethrow res.json is not a function
|
// Try to resolve body, but don't rethrow res.json is not a function
|
||||||
try {
|
try {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -16,6 +18,6 @@ const handleErrorResponses = (target: string) => async (res: Response) => {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default handleErrorResponses;
|
export default handleErrorResponses;
|
||||||
|
@ -4,20 +4,23 @@ import { formatApiPath } from '../../../../utils/format-path';
|
|||||||
import { IPermission } from '../../../../interfaces/user';
|
import { IPermission } from '../../../../interfaces/user';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
|
export const USER_CACHE_KEY = `api/admin/user`;
|
||||||
|
|
||||||
const useUser = () => {
|
const useUser = () => {
|
||||||
const KEY = `api/admin/user`;
|
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/user`);
|
const path = formatApiPath(`api/admin/user`);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}).then(handleErrorResponses('User info')).then(res => res.json());
|
})
|
||||||
|
.then(handleErrorResponses('User info'))
|
||||||
|
.then(res => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data, error } = useSWR(KEY, fetcher);
|
const { data, error } = useSWR(USER_CACHE_KEY, fetcher);
|
||||||
const [loading, setLoading] = useState(!error && !data);
|
const [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
mutate(KEY);
|
mutate(USER_CACHE_KEY);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -28,6 +31,7 @@ const useUser = () => {
|
|||||||
user: data?.user || {},
|
user: data?.user || {},
|
||||||
permissions: (data?.permissions || []) as IPermission[],
|
permissions: (data?.permissions || []) as IPermission[],
|
||||||
feedback: data?.feedback || [],
|
feedback: data?.feedback || [],
|
||||||
|
authDetails: data || {},
|
||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
refetch,
|
refetch,
|
||||||
|
@ -15,11 +15,10 @@ import { StylesProvider } from '@material-ui/core/styles';
|
|||||||
|
|
||||||
import mainTheme from './themes/main-theme';
|
import mainTheme from './themes/main-theme';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import MetricsPoller from './metrics-poller';
|
|
||||||
import App from './component/AppContainer';
|
import App from './component/AppContainer';
|
||||||
import ScrollToTop from './component/scroll-to-top';
|
import ScrollToTop from './component/scroll-to-top';
|
||||||
import { writeWarning } from './security-logger';
|
import { writeWarning } from './security-logger';
|
||||||
import AccessProvider from './component/AccessProvider/AccessProvider';
|
import AccessProvider from './component/providers/AccessProvider/AccessProvider';
|
||||||
import { getBasePath } from './utils/format-path';
|
import { getBasePath } from './utils/format-path';
|
||||||
|
|
||||||
let composeEnhancers;
|
let composeEnhancers;
|
||||||
@ -38,8 +37,6 @@ const unleashStore = createStore(
|
|||||||
store,
|
store,
|
||||||
composeEnhancers(applyMiddleware(thunkMiddleware))
|
composeEnhancers(applyMiddleware(thunkMiddleware))
|
||||||
);
|
);
|
||||||
const metricsPoller = new MetricsPoller(unleashStore);
|
|
||||||
metricsPoller.start();
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={unleashStore}>
|
<Provider store={unleashStore}>
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import { fetchFeatureMetrics } from './store/feature-metrics/actions';
|
|
||||||
|
|
||||||
class MetricsPoller {
|
|
||||||
constructor(store) {
|
|
||||||
this.store = store;
|
|
||||||
this.timer = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.store.subscribe(() => {
|
|
||||||
const features = this.store.getState().features;
|
|
||||||
if (!this.timer && features.size > 0) {
|
|
||||||
this.timer = setInterval(this.fetchMetrics.bind(this), 5000);
|
|
||||||
this.fetchMetrics();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchMetrics() {
|
|
||||||
this.store.dispatch(fetchFeatureMetrics());
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
if (this.timer) {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
this.timer = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MetricsPoller;
|
|
@ -1,10 +1,16 @@
|
|||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, FormControlLabel, Grid, Switch, TextField } from '@material-ui/core';
|
import {
|
||||||
|
Button,
|
||||||
|
FormControlLabel,
|
||||||
|
Grid,
|
||||||
|
Switch,
|
||||||
|
TextField,
|
||||||
|
} from '@material-ui/core';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import { ADMIN } from '../../../component/AccessProvider/permissions';
|
import { ADMIN } from '../../../component/providers/AccessProvider/permissions';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@ -93,12 +99,14 @@ function GoogleAuth({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6} style={{ padding: '20px' }}>
|
<Grid item xs={6} style={{ padding: '20px' }}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={ <Switch
|
control={
|
||||||
onChange={updateEnabled}
|
<Switch
|
||||||
value={data.enabled}
|
onChange={updateEnabled}
|
||||||
name="enabled"
|
value={data.enabled}
|
||||||
checked={data.enabled}
|
name="enabled"
|
||||||
/>}
|
checked={data.enabled}
|
||||||
|
/>
|
||||||
|
}
|
||||||
label={data.enabled ? 'Enabled' : 'Disabled'}
|
label={data.enabled ? 'Enabled' : 'Disabled'}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, FormControlLabel, Grid, Switch, TextField } from '@material-ui/core';
|
import {
|
||||||
|
Button,
|
||||||
|
FormControlLabel,
|
||||||
|
Grid,
|
||||||
|
Switch,
|
||||||
|
TextField,
|
||||||
|
} from '@material-ui/core';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import { ADMIN } from '../../../component/AccessProvider/permissions';
|
import { ADMIN } from '../../../component/providers/AccessProvider/permissions';
|
||||||
import AutoCreateForm from './AutoCreateForm/AutoCreateForm';
|
import AutoCreateForm from './AutoCreateForm/AutoCreateForm';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@ -57,7 +63,7 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) {
|
|||||||
...data,
|
...data,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const onSubmit = async e => {
|
const onSubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -100,12 +106,14 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6} style={{ padding: '20px' }}>
|
<Grid item md={6} style={{ padding: '20px' }}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={ <Switch
|
control={
|
||||||
onChange={updateEnabled}
|
<Switch
|
||||||
value={data.enabled}
|
onChange={updateEnabled}
|
||||||
name="enabled"
|
value={data.enabled}
|
||||||
checked={data.enabled}
|
name="enabled"
|
||||||
/>}
|
checked={data.enabled}
|
||||||
|
/>
|
||||||
|
}
|
||||||
label={data.enabled ? 'Enabled' : 'Disabled'}
|
label={data.enabled ? 'Enabled' : 'Disabled'}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -125,7 +133,6 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) {
|
|||||||
style={{ width: '400px' }}
|
style={{ width: '400px' }}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -151,7 +158,9 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) {
|
|||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item md={5}>
|
<Grid item md={5}>
|
||||||
<strong>Client secret</strong>
|
<strong>Client secret</strong>
|
||||||
<p>(Required) Client secret of your OpenID application. </p>
|
<p>
|
||||||
|
(Required) Client secret of your OpenID application.{' '}
|
||||||
|
</p>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6}>
|
<Grid item md={6}>
|
||||||
<TextField
|
<TextField
|
||||||
@ -171,18 +180,27 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) {
|
|||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item md={5}>
|
<Grid item md={5}>
|
||||||
<strong>(Optional) Enable Single Sign-Out</strong>
|
<strong>(Optional) Enable Single Sign-Out</strong>
|
||||||
<p>If you enable Single Sign-Out Unleash will redirect the user to the IDP as part of the Sign-out process.</p>
|
<p>
|
||||||
|
If you enable Single Sign-Out Unleash will redirect
|
||||||
|
the user to the IDP as part of the Sign-out process.
|
||||||
|
</p>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6} style={{ padding: '20px' }}>
|
<Grid item md={6} style={{ padding: '20px' }}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={ <Switch
|
control={
|
||||||
onChange={updateSingleSignOut}
|
<Switch
|
||||||
value={data.enableSingleSignOut}
|
onChange={updateSingleSignOut}
|
||||||
disabled={!data.enabled}
|
value={data.enableSingleSignOut}
|
||||||
name="enableSingleSignOut"
|
disabled={!data.enabled}
|
||||||
checked={data.enableSingleSignOut}
|
name="enableSingleSignOut"
|
||||||
/>}
|
checked={data.enableSingleSignOut}
|
||||||
label={data.enableSingleSignOut ? 'Enabled' : 'Disabled'}
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
data.enableSingleSignOut
|
||||||
|
? 'Enabled'
|
||||||
|
: 'Disabled'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -199,7 +217,7 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) {
|
|||||||
Save
|
Save
|
||||||
</Button>{' '}
|
</Button>{' '}
|
||||||
<small>{info}</small>
|
<small>{info}</small>
|
||||||
<small style={{color: 'red'}}>{error}</small>
|
<small style={{ color: 'red' }}>{error}</small>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, FormControlLabel, Grid, Switch, TextField } from '@material-ui/core';
|
import {
|
||||||
|
Button,
|
||||||
|
FormControlLabel,
|
||||||
|
Grid,
|
||||||
|
Switch,
|
||||||
|
TextField,
|
||||||
|
} from '@material-ui/core';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import { ADMIN } from '../../../component/AccessProvider/permissions';
|
import { ADMIN } from '../../../component/providers/AccessProvider/permissions';
|
||||||
import AutoCreateForm from './AutoCreateForm/AutoCreateForm';
|
import AutoCreateForm from './AutoCreateForm/AutoCreateForm';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@ -51,7 +57,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
|
|||||||
...data,
|
...data,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const onSubmit = async e => {
|
const onSubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -92,12 +98,14 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6}>
|
<Grid item md={6}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={ <Switch
|
control={
|
||||||
onChange={updateEnabled}
|
<Switch
|
||||||
value={data.enabled}
|
onChange={updateEnabled}
|
||||||
name="enabled"
|
value={data.enabled}
|
||||||
checked={data.enabled}
|
name="enabled"
|
||||||
/>}
|
checked={data.enabled}
|
||||||
|
/>
|
||||||
|
}
|
||||||
label={data.enabled ? 'Enabled' : 'Disabled'}
|
label={data.enabled ? 'Enabled' : 'Disabled'}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -136,7 +144,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
|
|||||||
name="signOnUrl"
|
name="signOnUrl"
|
||||||
value={data.signOnUrl || ''}
|
value={data.signOnUrl || ''}
|
||||||
disabled={!data.enabled}
|
disabled={!data.enabled}
|
||||||
style={{ width: '400px'}}
|
style={{ width: '400px' }}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
required
|
required
|
||||||
@ -158,12 +166,13 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
|
|||||||
name="certificate"
|
name="certificate"
|
||||||
value={data.certificate || ''}
|
value={data.certificate || ''}
|
||||||
disabled={!data.enabled}
|
disabled={!data.enabled}
|
||||||
style={{width: '100%'}}
|
style={{ width: '100%' }}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
fontSize: '0.6em',
|
fontSize: '0.6em',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
}}}
|
},
|
||||||
|
}}
|
||||||
multiline
|
multiline
|
||||||
rows={14}
|
rows={14}
|
||||||
rowsMax={14}
|
rowsMax={14}
|
||||||
@ -189,7 +198,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
|
|||||||
name="signOutUrl"
|
name="signOutUrl"
|
||||||
value={data.signOutUrl || ''}
|
value={data.signOutUrl || ''}
|
||||||
disabled={!data.enabled}
|
disabled={!data.enabled}
|
||||||
style={{ width: '400px'}}
|
style={{ width: '400px' }}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
@ -199,8 +208,10 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
|
|||||||
<Grid item md={5}>
|
<Grid item md={5}>
|
||||||
<strong>Service Provider X.509 Certificate</strong>
|
<strong>Service Provider X.509 Certificate</strong>
|
||||||
<p>
|
<p>
|
||||||
(Optional) The private certificate used by the Service Provider used to sign the SAML 2.0
|
(Optional) The private certificate used by the
|
||||||
request towards the IDP. E.g. used to sign single logout requests (SLO).
|
Service Provider used to sign the SAML 2.0 request
|
||||||
|
towards the IDP. E.g. used to sign single logout
|
||||||
|
requests (SLO).
|
||||||
</p>
|
</p>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={7}>
|
<Grid item md={7}>
|
||||||
@ -210,12 +221,13 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
|
|||||||
name="spCertificate"
|
name="spCertificate"
|
||||||
value={data.spCertificate || ''}
|
value={data.spCertificate || ''}
|
||||||
disabled={!data.enabled}
|
disabled={!data.enabled}
|
||||||
style={{width: '100%'}}
|
style={{ width: '100%' }}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
fontSize: '0.6em',
|
fontSize: '0.6em',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
}}}
|
},
|
||||||
|
}}
|
||||||
multiline
|
multiline
|
||||||
rows={14}
|
rows={14}
|
||||||
rowsMax={14}
|
rowsMax={14}
|
||||||
|
@ -2,7 +2,7 @@ import { useContext } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import InvoiceList from './invoice-container';
|
import InvoiceList from './invoice-container';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import { ADMIN } from '../../../component/AccessProvider/permissions';
|
import { ADMIN } from '../../../component/providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../../component/common/ConditionallyRender';
|
import ConditionallyRender from '../../../component/common/ConditionallyRender';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
|
|
||||||
@ -13,17 +13,13 @@ const InvoiceAdminPage = ({ history }) => {
|
|||||||
<div>
|
<div>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={hasAccess(ADMIN)}
|
condition={hasAccess(ADMIN)}
|
||||||
show={
|
show={<InvoiceList />}
|
||||||
<InvoiceList />
|
|
||||||
}
|
|
||||||
elseShow={
|
elseShow={
|
||||||
<Alert severity="error">
|
<Alert severity="error">
|
||||||
You need to be instance admin to access this section.
|
You need to be instance admin to access this section.
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import { Edit, Lock, Delete } from '@material-ui/icons';
|
import { Edit, Lock, Delete } from '@material-ui/icons';
|
||||||
import { SyntheticEvent, useContext } from 'react';
|
import { SyntheticEvent, useContext } from 'react';
|
||||||
import { ADMIN } from '../../../../../component/AccessProvider/permissions';
|
import { ADMIN } from '../../../../../component/providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../../../../component/common/ConditionallyRender';
|
import ConditionallyRender from '../../../../../component/common/ConditionallyRender';
|
||||||
import { formatDateWithLocale } from '../../../../../component/common/util';
|
import { formatDateWithLocale } from '../../../../../component/common/util';
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
|
@ -14,7 +14,7 @@ import UpdateUser from '../update-user-component';
|
|||||||
import DelUser from '../del-user-component';
|
import DelUser from '../del-user-component';
|
||||||
import ConditionallyRender from '../../../../component/common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../../../component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import AccessContext from '../../../../contexts/AccessContext';
|
import AccessContext from '../../../../contexts/AccessContext';
|
||||||
import { ADMIN } from '../../../../component/AccessProvider/permissions';
|
import { ADMIN } from '../../../../component/providers/AccessProvider/permissions';
|
||||||
import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded';
|
import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded';
|
||||||
import useUsers from '../../../../hooks/api/getters/useUsers/useUsers';
|
import useUsers from '../../../../hooks/api/getters/useUsers/useUsers';
|
||||||
import useAdminUsersApi from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
import useAdminUsersApi from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
||||||
|
@ -5,7 +5,7 @@ import AdminMenu from '../admin-menu';
|
|||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import ConditionallyRender from '../../../component/common/ConditionallyRender';
|
import ConditionallyRender from '../../../component/common/ConditionallyRender';
|
||||||
import { ADMIN } from '../../../component/AccessProvider/permissions';
|
import { ADMIN } from '../../../component/providers/AccessProvider/permissions';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import HeaderTitle from '../../../component/common/HeaderTitle';
|
import HeaderTitle from '../../../component/common/HeaderTitle';
|
||||||
import { Button } from '@material-ui/core';
|
import { Button } from '@material-ui/core';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { ADMIN } from '../../component/AccessProvider/permissions';
|
import { ADMIN } from '../../component/providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../component/common/ConditionallyRender';
|
import ConditionallyRender from '../../component/common/ConditionallyRender';
|
||||||
import HistoryComponent from '../../component/history/EventHistory';
|
import HistoryComponent from '../../component/history/EventHistory';
|
||||||
import AccessContext from '../../contexts/AccessContext';
|
import AccessContext from '../../contexts/AccessContext';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ADMIN } from '../component/AccessProvider/permissions';
|
import { ADMIN } from '../component/providers/AccessProvider/permissions';
|
||||||
import IAuthStatus, { IPermission } from '../interfaces/user';
|
import IAuthStatus, { IPermission } from '../interfaces/user';
|
||||||
|
|
||||||
type objectIdx = {
|
type objectIdx = {
|
||||||
@ -24,6 +24,6 @@ export const projectFilterGenerator = (
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
return (projectId: string) => {
|
return (projectId: string) => {
|
||||||
return admin || permissionMap[projectId]
|
return admin || permissionMap[projectId];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user