mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: add CORS instance settings (#1239)
* feat: add CORS instance settings * refactor: hide the CORS page when embedProxy is false
This commit is contained in:
parent
337e7888d2
commit
e6b72ff4a0
@ -5,6 +5,7 @@ import {
|
|||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Typography,
|
Typography,
|
||||||
|
Box,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { KeyboardArrowDownOutlined } from '@mui/icons-material';
|
import { KeyboardArrowDownOutlined } from '@mui/icons-material';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -16,6 +17,9 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|||||||
import { SelectProjectInput } from './SelectProjectInput/SelectProjectInput';
|
import { SelectProjectInput } from './SelectProjectInput/SelectProjectInput';
|
||||||
import { ApiTokenFormErrorType } from './useApiTokenForm';
|
import { ApiTokenFormErrorType } from './useApiTokenForm';
|
||||||
import { useStyles } from './ApiTokenForm.styles';
|
import { useStyles } from './ApiTokenForm.styles';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { TokenType } from 'interfaces/token';
|
||||||
|
import { CorsTokenAlert } from 'component/admin/cors/CorsTokenAlert';
|
||||||
|
|
||||||
interface IApiTokenFormProps {
|
interface IApiTokenFormProps {
|
||||||
username: string;
|
username: string;
|
||||||
@ -48,7 +52,6 @@ const ApiTokenForm: React.FC<IApiTokenFormProps> = ({
|
|||||||
errors,
|
errors,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
const TYPE_ADMIN = 'ADMIN';
|
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
const { environments } = useEnvironments();
|
const { environments } = useEnvironments();
|
||||||
@ -56,21 +59,21 @@ const ApiTokenForm: React.FC<IApiTokenFormProps> = ({
|
|||||||
|
|
||||||
const selectableTypes = [
|
const selectableTypes = [
|
||||||
{
|
{
|
||||||
key: 'CLIENT',
|
key: TokenType.CLIENT,
|
||||||
label: 'Server-side SDK (CLIENT)',
|
label: `Server-side SDK (${TokenType.CLIENT})`,
|
||||||
title: 'Connect server-side SDK or Unleash Proxy',
|
title: 'Connect server-side SDK or Unleash Proxy',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'ADMIN',
|
key: TokenType.ADMIN,
|
||||||
label: 'ADMIN',
|
label: TokenType.ADMIN,
|
||||||
title: 'Full access for managing Unleash',
|
title: 'Full access for managing Unleash',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (uiConfig.embedProxy) {
|
if (uiConfig.embedProxy) {
|
||||||
selectableTypes.splice(1, 0, {
|
selectableTypes.splice(1, 0, {
|
||||||
key: 'FRONTEND',
|
key: TokenType.FRONTEND,
|
||||||
label: 'Client-side SDK (FRONTEND)',
|
label: `Client-side SDK (${TokenType.FRONTEND})`,
|
||||||
title: 'Connect web and mobile SDK directly to Unleash',
|
title: 'Connect web and mobile SDK directly to Unleash',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -81,7 +84,7 @@ const ApiTokenForm: React.FC<IApiTokenFormProps> = ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const selectableEnvs =
|
const selectableEnvs =
|
||||||
type === TYPE_ADMIN
|
type === TokenType.ADMIN
|
||||||
? [{ key: '*', label: 'ALL' }]
|
? [{ key: '*', label: 'ALL' }]
|
||||||
: environments.map(environment => ({
|
: environments.map(environment => ({
|
||||||
key: environment.name,
|
key: environment.name,
|
||||||
@ -143,7 +146,7 @@ const ApiTokenForm: React.FC<IApiTokenFormProps> = ({
|
|||||||
Which project do you want to give access to?
|
Which project do you want to give access to?
|
||||||
</p>
|
</p>
|
||||||
<SelectProjectInput
|
<SelectProjectInput
|
||||||
disabled={type === TYPE_ADMIN}
|
disabled={type === TokenType.ADMIN}
|
||||||
options={selectableProjects}
|
options={selectableProjects}
|
||||||
defaultValue={projects}
|
defaultValue={projects}
|
||||||
onChange={setProjects}
|
onChange={setProjects}
|
||||||
@ -154,7 +157,7 @@ const ApiTokenForm: React.FC<IApiTokenFormProps> = ({
|
|||||||
Which environment should the token have access to?
|
Which environment should the token have access to?
|
||||||
</p>
|
</p>
|
||||||
<GeneralSelect
|
<GeneralSelect
|
||||||
disabled={type === TYPE_ADMIN}
|
disabled={type === TokenType.ADMIN}
|
||||||
options={selectableEnvs}
|
options={selectableEnvs}
|
||||||
value={environment}
|
value={environment}
|
||||||
onChange={setEnvironment}
|
onChange={setEnvironment}
|
||||||
@ -172,6 +175,14 @@ const ApiTokenForm: React.FC<IApiTokenFormProps> = ({
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={type === TokenType.FRONTEND}
|
||||||
|
show={
|
||||||
|
<Box sx={{ mt: 4 }}>
|
||||||
|
<CorsTokenAlert />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
23
frontend/src/component/admin/cors/CorsForm.test.tsx
Normal file
23
frontend/src/component/admin/cors/CorsForm.test.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
parseInputValue,
|
||||||
|
formatInputValue,
|
||||||
|
} from 'component/admin/cors/CorsForm';
|
||||||
|
|
||||||
|
test('parseInputValue', () => {
|
||||||
|
const fn = parseInputValue;
|
||||||
|
expect(fn('')).toEqual([]);
|
||||||
|
expect(fn('a')).toEqual(['a']);
|
||||||
|
expect(fn('a\nb,,c,d,')).toEqual(['a', 'b', 'c', 'd']);
|
||||||
|
expect(fn('http://localhost:8080')).toEqual(['http://localhost:8080']);
|
||||||
|
expect(fn('https://example.com')).toEqual(['https://example.com']);
|
||||||
|
expect(fn('https://example.com/')).toEqual(['https://example.com']);
|
||||||
|
expect(fn('https://example.com/')).toEqual(['https://example.com']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formatInputValue', () => {
|
||||||
|
const fn = formatInputValue;
|
||||||
|
expect(fn(undefined)).toEqual('');
|
||||||
|
expect(fn([])).toEqual('');
|
||||||
|
expect(fn(['a'])).toEqual('a');
|
||||||
|
expect(fn(['a', 'b', 'c', 'd'])).toEqual('a\nb\nc\nd');
|
||||||
|
});
|
74
frontend/src/component/admin/cors/CorsForm.tsx
Normal file
74
frontend/src/component/admin/cors/CorsForm.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { TextField, Box } from '@mui/material';
|
||||||
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
|
import { useUiConfigApi } from 'hooks/api/actions/useUiConfigApi/useUiConfigApi';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import { useId } from 'hooks/useId';
|
||||||
|
|
||||||
|
interface ICorsFormProps {
|
||||||
|
frontendApiOrigins: string[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CorsForm = ({ frontendApiOrigins }: ICorsFormProps) => {
|
||||||
|
const { setFrontendSettings } = useUiConfigApi();
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
const [value, setValue] = useState(formatInputValue(frontendApiOrigins));
|
||||||
|
const inputFieldId = useId();
|
||||||
|
const helpTextId = useId();
|
||||||
|
|
||||||
|
const onSubmit = async (event: React.FormEvent) => {
|
||||||
|
try {
|
||||||
|
const split = parseInputValue(value);
|
||||||
|
event.preventDefault();
|
||||||
|
await setFrontendSettings(split);
|
||||||
|
setValue(formatInputValue(split));
|
||||||
|
setToastData({ title: 'Settings saved', type: 'success' });
|
||||||
|
} catch (error) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<Box sx={{ display: 'grid', gap: 1 }}>
|
||||||
|
<label htmlFor={inputFieldId}>
|
||||||
|
Which origins should be allowed to call the Frontend API
|
||||||
|
(one per line)?
|
||||||
|
</label>
|
||||||
|
<TextField
|
||||||
|
id={inputFieldId}
|
||||||
|
aria-describedby={helpTextId}
|
||||||
|
placeholder={textareaDomainsPlaceholder}
|
||||||
|
value={value}
|
||||||
|
onChange={event => setValue(event.target.value)}
|
||||||
|
multiline
|
||||||
|
rows={12}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
style: { fontFamily: 'monospace', fontSize: '0.8em' },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<UpdateButton permission={ADMIN} />
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseInputValue = (value: string): string[] => {
|
||||||
|
return value
|
||||||
|
.split(/[,\n\s]+/) // Split by commas/newlines/spaces.
|
||||||
|
.map(value => value.replace(/\/$/, '')) // Remove trailing slashes.
|
||||||
|
.filter(Boolean); // Remove empty values from (e.g.) double newlines.
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatInputValue = (values: string[] | undefined): string => {
|
||||||
|
return values?.join('\n') ?? '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const textareaDomainsPlaceholder = [
|
||||||
|
'https://example.com',
|
||||||
|
'https://example.org',
|
||||||
|
].join('\n');
|
22
frontend/src/component/admin/cors/CorsHelpAlert.tsx
Normal file
22
frontend/src/component/admin/cors/CorsHelpAlert.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Alert } from '@mui/material';
|
||||||
|
|
||||||
|
export const CorsHelpAlert = () => {
|
||||||
|
return (
|
||||||
|
<Alert severity="info">
|
||||||
|
<p>
|
||||||
|
Use this page to configure allowed CORS origins for the Frontend
|
||||||
|
API (<code>/api/frontend</code>).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This configuration will not affect the Admin API (
|
||||||
|
<code>/api/admin</code>) nor the Client API (
|
||||||
|
<code>/api/client</code>).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
An asterisk (<code>*</code>) may be used to allow API calls from
|
||||||
|
any origin.
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
17
frontend/src/component/admin/cors/CorsTokenAlert.tsx
Normal file
17
frontend/src/component/admin/cors/CorsTokenAlert.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { TokenType } from 'interfaces/token';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Alert } from '@mui/material';
|
||||||
|
|
||||||
|
export const CorsTokenAlert = () => {
|
||||||
|
return (
|
||||||
|
<Alert sx={{ mt: 4 }} severity="info">
|
||||||
|
By default, all {TokenType.FRONTEND} tokens may be used from any
|
||||||
|
CORS origin. If you'd like to configure a strict set of origins,
|
||||||
|
please use the{' '}
|
||||||
|
<Link to="/admin/cors" target="_blank">
|
||||||
|
CORS origins configuration page
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
50
frontend/src/component/admin/cors/index.tsx
Normal file
50
frontend/src/component/admin/cors/index.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import AdminMenu from '../menu/AdminMenu';
|
||||||
|
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import AccessContext from 'contexts/AccessContext';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { CorsHelpAlert } from 'component/admin/cors/CorsHelpAlert';
|
||||||
|
import { CorsForm } from 'component/admin/cors/CorsForm';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
|
||||||
|
export const CorsAdmin = () => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const showAdminMenu = pathname.includes('/admin/');
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={showAdminMenu}
|
||||||
|
show={<AdminMenu />}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={hasAccess(ADMIN)}
|
||||||
|
show={<CorsPage />}
|
||||||
|
elseShow={<AdminAlert />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CorsPage = () => {
|
||||||
|
const { uiConfig, loading } = useUiConfig();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContent header={<PageHeader title="CORS origins" />}>
|
||||||
|
<Box sx={{ display: 'grid', gap: 4 }}>
|
||||||
|
<CorsHelpAlert />
|
||||||
|
<CorsForm frontendApiOrigins={uiConfig.frontendApiOrigins} />
|
||||||
|
</Box>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
};
|
@ -77,7 +77,6 @@ function AdminMenu() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
value="/admin/api"
|
value="/admin/api"
|
||||||
label={
|
label={
|
||||||
@ -86,6 +85,19 @@ function AdminMenu() {
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{uiConfig.embedProxy && (
|
||||||
|
<Tab
|
||||||
|
value="/admin/cors"
|
||||||
|
label={
|
||||||
|
<NavLink
|
||||||
|
to="/admin/cors"
|
||||||
|
style={createNavLinkStyle}
|
||||||
|
>
|
||||||
|
CORS origins
|
||||||
|
</NavLink>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Tab
|
<Tab
|
||||||
value="/admin/auth"
|
value="/admin/auth"
|
||||||
label={
|
label={
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
import { weightTypes } from '../feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/enums';
|
import { weightTypes } from '../feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/enums';
|
||||||
import { IFlags } from 'interfaces/uiConfig';
|
import { IUiConfig } from 'interfaces/uiConfig';
|
||||||
import { IRoute } from 'interfaces/route';
|
import { IRoute } from 'interfaces/route';
|
||||||
import { IFeatureVariant } from 'interfaces/featureToggle';
|
import { IFeatureVariant } from 'interfaces/featureToggle';
|
||||||
import { format, isValid } from 'date-fns';
|
import { format, isValid } from 'date-fns';
|
||||||
|
|
||||||
export const filterByFlags = (flags: IFlags) => (r: IRoute) => {
|
export const filterByConfig = (config: IUiConfig) => (r: IRoute) => {
|
||||||
if (!r.flag) {
|
if (r.flag) {
|
||||||
return true;
|
// Check if the route's `flag` is enabled in IUiConfig.flags.
|
||||||
|
const flags = config.flags as unknown as Record<string, boolean>;
|
||||||
|
return Boolean(flags[r.flag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (flags as unknown as Record<string, boolean>)[r.flag];
|
if (r.configFlag) {
|
||||||
|
// Check if the route's `configFlag` is enabled in IUiConfig.
|
||||||
|
return Boolean(config[r.configFlag]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const scrollToTop = () => {
|
export const scrollToTop = () => {
|
||||||
|
@ -18,7 +18,7 @@ import { IPermission } from 'interfaces/user';
|
|||||||
import { NavigationMenu } from './NavigationMenu/NavigationMenu';
|
import { NavigationMenu } from './NavigationMenu/NavigationMenu';
|
||||||
import { getRoutes } from 'component/menu/routes';
|
import { getRoutes } from 'component/menu/routes';
|
||||||
import { KeyboardArrowDown } from '@mui/icons-material';
|
import { KeyboardArrowDown } from '@mui/icons-material';
|
||||||
import { filterByFlags } from 'component/common/util';
|
import { filterByConfig } from 'component/common/util';
|
||||||
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
|
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
|
||||||
import { useStyles } from './Header.styles';
|
import { useStyles } from './Header.styles';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -34,10 +34,7 @@ const Header: VFC = () => {
|
|||||||
|
|
||||||
const [admin, setAdmin] = useState(false);
|
const [admin, setAdmin] = useState(false);
|
||||||
const { permissions } = useAuthPermissions();
|
const { permissions } = useAuthPermissions();
|
||||||
const {
|
const { uiConfig, isOss } = useUiConfig();
|
||||||
uiConfig: { links, name, flags },
|
|
||||||
isOss,
|
|
||||||
} = useUiConfig();
|
|
||||||
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
const { classes: themeStyles } = useThemeStyles();
|
const { classes: themeStyles } = useThemeStyles();
|
||||||
@ -64,10 +61,10 @@ const Header: VFC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const filteredMainRoutes = {
|
const filteredMainRoutes = {
|
||||||
mainNavRoutes: routes.mainNavRoutes.filter(filterByFlags(flags)),
|
mainNavRoutes: routes.mainNavRoutes.filter(filterByConfig(uiConfig)),
|
||||||
mobileRoutes: routes.mobileRoutes.filter(filterByFlags(flags)),
|
mobileRoutes: routes.mobileRoutes.filter(filterByConfig(uiConfig)),
|
||||||
adminRoutes: routes.adminRoutes
|
adminRoutes: routes.adminRoutes
|
||||||
.filter(filterByFlags(flags))
|
.filter(filterByConfig(uiConfig))
|
||||||
.filter(filterByEnterprise),
|
.filter(filterByEnterprise),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,9 +84,9 @@ const Header: VFC = () => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DrawerMenu
|
<DrawerMenu
|
||||||
title={name}
|
title={uiConfig.name}
|
||||||
flags={flags}
|
flags={uiConfig.flags}
|
||||||
links={links}
|
links={uiConfig.links}
|
||||||
open={openDrawer}
|
open={openDrawer}
|
||||||
toggleDrawer={toggleDrawer}
|
toggleDrawer={toggleDrawer}
|
||||||
admin={admin}
|
admin={admin}
|
||||||
|
@ -447,6 +447,17 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
"title": "Single sign-on",
|
"title": "Single sign-on",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"component": [Function],
|
||||||
|
"configFlag": "embedProxy",
|
||||||
|
"menu": {
|
||||||
|
"adminSettings": true,
|
||||||
|
},
|
||||||
|
"parent": "/admin",
|
||||||
|
"path": "/admin/cors",
|
||||||
|
"title": "CORS origins",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"menu": {},
|
"menu": {},
|
||||||
|
@ -56,6 +56,7 @@ import { Group } from 'component/admin/groups/Group/Group';
|
|||||||
import { CreateGroup } from 'component/admin/groups/CreateGroup/CreateGroup';
|
import { CreateGroup } from 'component/admin/groups/CreateGroup/CreateGroup';
|
||||||
import { EditGroup } from 'component/admin/groups/EditGroup/EditGroup';
|
import { EditGroup } from 'component/admin/groups/EditGroup/EditGroup';
|
||||||
import { LazyPlayground } from 'component/playground/Playground/LazyPlayground';
|
import { LazyPlayground } from 'component/playground/Playground/LazyPlayground';
|
||||||
|
import { CorsAdmin } from 'component/admin/cors';
|
||||||
|
|
||||||
export const routes: IRoute[] = [
|
export const routes: IRoute[] = [
|
||||||
// Splash
|
// Splash
|
||||||
@ -489,6 +490,15 @@ export const routes: IRoute[] = [
|
|||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: { adminSettings: true },
|
menu: { adminSettings: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/cors',
|
||||||
|
parent: '/admin',
|
||||||
|
title: 'CORS origins',
|
||||||
|
component: CorsAdmin,
|
||||||
|
type: 'protected',
|
||||||
|
configFlag: 'embedProxy',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/billing',
|
path: '/admin/billing',
|
||||||
parent: '/admin',
|
parent: '/admin',
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import useAPI from '../useApi/useApi';
|
||||||
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
|
|
||||||
|
export const useUiConfigApi = () => {
|
||||||
|
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||||
|
propagateErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setFrontendSettings = async (
|
||||||
|
frontendApiOrigins: string[]
|
||||||
|
): Promise<void> => {
|
||||||
|
const payload = {
|
||||||
|
frontendSettings: { frontendApiOrigins },
|
||||||
|
};
|
||||||
|
const req = createRequest(
|
||||||
|
formatApiPath('api/admin/ui-config'),
|
||||||
|
{ method: 'POST', body: JSON.stringify(payload) },
|
||||||
|
'setFrontendSettings'
|
||||||
|
);
|
||||||
|
await makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
setFrontendSettings,
|
||||||
|
loading,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
};
|
@ -1,4 +1,5 @@
|
|||||||
import { VoidFunctionComponent } from 'react';
|
import { VoidFunctionComponent } from 'react';
|
||||||
|
import { IUiConfig } from 'interfaces/uiConfig';
|
||||||
|
|
||||||
export interface IRoute {
|
export interface IRoute {
|
||||||
path: string;
|
path: string;
|
||||||
@ -7,6 +8,7 @@ export interface IRoute {
|
|||||||
layout?: string;
|
layout?: string;
|
||||||
parent?: string;
|
parent?: string;
|
||||||
flag?: string;
|
flag?: string;
|
||||||
|
configFlag?: keyof IUiConfig;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
enterprise?: boolean;
|
enterprise?: boolean;
|
||||||
component: VoidFunctionComponent;
|
component: VoidFunctionComponent;
|
||||||
|
5
frontend/src/interfaces/token.ts
Normal file
5
frontend/src/interfaces/token.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum TokenType {
|
||||||
|
ADMIN = 'ADMIN',
|
||||||
|
CLIENT = 'CLIENT',
|
||||||
|
FRONTEND = 'FRONTEND',
|
||||||
|
}
|
@ -16,6 +16,7 @@ export interface IUiConfig {
|
|||||||
toast?: IProclamationToast;
|
toast?: IProclamationToast;
|
||||||
segmentValuesLimit?: number;
|
segmentValuesLimit?: number;
|
||||||
strategySegmentsLimit?: number;
|
strategySegmentsLimit?: number;
|
||||||
|
frontendApiOrigins?: string[];
|
||||||
embedProxy?: boolean;
|
embedProxy?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user