feat: introduce admin app configs page (#1335)
This commit is contained in:
@@ -12,6 +12,7 @@ import UserInterface from 'pages/UserInterface';
|
|||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import Can from 'components/Can';
|
import Can from 'components/Can';
|
||||||
import AdminApplications from 'pages/AdminApplications';
|
import AdminApplications from 'pages/AdminApplications';
|
||||||
|
import AdminApplication from 'pages/AdminApplication';
|
||||||
|
|
||||||
// TODO: consider introducing redirections to `/` as fallback
|
// TODO: consider introducing redirections to `/` as fallback
|
||||||
export default (
|
export default (
|
||||||
@@ -119,6 +120,17 @@ export default (
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={`${URLS.ADMIN_APP_PATTERN}/*`}
|
||||||
|
element={
|
||||||
|
<Can I="update" a="App">
|
||||||
|
<AdminSettingsLayout>
|
||||||
|
<AdminApplication />
|
||||||
|
</AdminSettingsLayout>
|
||||||
|
</Can>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={URLS.ADMIN_SETTINGS}
|
path={URLS.ADMIN_SETTINGS}
|
||||||
element={<Navigate to={URLS.USERS} replace />}
|
element={<Navigate to={URLS.USERS} replace />}
|
||||||
|
123
packages/web/src/components/AdminApplicationSettings/index.tsx
Normal file
123
packages/web/src/components/AdminApplicationSettings/index.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import useAppConfig from 'hooks/useAppConfig.ee';
|
||||||
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
import { useMutation } from '@apollo/client';
|
||||||
|
import { useSnackbar } from 'notistack';
|
||||||
|
|
||||||
|
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
||||||
|
import { UPDATE_APP_CONFIG } from 'graphql/mutations/update-app-config';
|
||||||
|
|
||||||
|
import Form from 'components/Form';
|
||||||
|
import { Switch } from './style';
|
||||||
|
|
||||||
|
type AdminApplicationSettingsProps = {
|
||||||
|
appKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function AdminApplicationSettings(
|
||||||
|
props: AdminApplicationSettingsProps
|
||||||
|
): React.ReactElement {
|
||||||
|
const { appConfig, loading } = useAppConfig(props.appKey);
|
||||||
|
const [createAppConfig, { loading: loadingCreateAppConfig }] = useMutation(
|
||||||
|
CREATE_APP_CONFIG,
|
||||||
|
{
|
||||||
|
refetchQueries: ['GetAppConfig'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const [updateAppConfig, { loading: loadingUpdateAppConfig }] = useMutation(
|
||||||
|
UPDATE_APP_CONFIG,
|
||||||
|
{
|
||||||
|
refetchQueries: ['GetAppConfig'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const formatMessage = useFormatMessage();
|
||||||
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
|
const handleSubmit = async (values: any) => {
|
||||||
|
try {
|
||||||
|
if (!appConfig) {
|
||||||
|
await createAppConfig({
|
||||||
|
variables: {
|
||||||
|
input: { key: props.appKey, ...values },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await updateAppConfig({
|
||||||
|
variables: {
|
||||||
|
input: { id: appConfig.id, ...values },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
enqueueSnackbar(formatMessage('adminAppsSettings.successfullySaved'), {
|
||||||
|
variant: 'success',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Failed while saving!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
allowCustomConnection: appConfig?.allowCustomConnection || false,
|
||||||
|
shared: appConfig?.shared || false,
|
||||||
|
disabled: appConfig?.disabled || false,
|
||||||
|
}),
|
||||||
|
[appConfig]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
defaultValues={defaultValues}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
render={({ formState: { isDirty } }) => (
|
||||||
|
<Paper sx={{ p: 2, mt: 4 }}>
|
||||||
|
<Stack spacing={2} direction="column">
|
||||||
|
<Switch
|
||||||
|
name="allowCustomConnection"
|
||||||
|
label={formatMessage('adminAppsSettings.allowCustomConnection')}
|
||||||
|
FormControlLabelProps={{
|
||||||
|
labelPlacement: 'start',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
<Switch
|
||||||
|
name="shared"
|
||||||
|
label={formatMessage('adminAppsSettings.shared')}
|
||||||
|
FormControlLabelProps={{
|
||||||
|
labelPlacement: 'start',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
<Switch
|
||||||
|
name="disabled"
|
||||||
|
label={formatMessage('adminAppsSettings.disabled')}
|
||||||
|
FormControlLabelProps={{
|
||||||
|
labelPlacement: 'start',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<LoadingButton
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ boxShadow: 2, mt: 5 }}
|
||||||
|
loading={loadingCreateAppConfig || loadingUpdateAppConfig}
|
||||||
|
disabled={!isDirty || loading}
|
||||||
|
>
|
||||||
|
{formatMessage('adminAppsSettings.save')}
|
||||||
|
</LoadingButton>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
></Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminApplicationSettings;
|
@@ -0,0 +1,7 @@
|
|||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import SwitchBase from 'components/Switch';
|
||||||
|
|
||||||
|
export const Switch = styled(SwitchBase)`
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0;
|
||||||
|
`;
|
@@ -25,6 +25,7 @@ export default function Switch(props: SwitchProps): React.ReactElement {
|
|||||||
onChange,
|
onChange,
|
||||||
label,
|
label,
|
||||||
FormControlLabelProps,
|
FormControlLabelProps,
|
||||||
|
className,
|
||||||
...switchProps
|
...switchProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ export default function Switch(props: SwitchProps): React.ReactElement {
|
|||||||
},
|
},
|
||||||
}) => (
|
}) => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
className={className}
|
||||||
{...FormControlLabelProps}
|
{...FormControlLabelProps}
|
||||||
control={
|
control={
|
||||||
<MuiSwitch
|
<MuiSwitch
|
||||||
|
@@ -97,6 +97,16 @@ export const USER_INTERFACE = `${ADMIN_SETTINGS}/user-interface`;
|
|||||||
export const AUTHENTICATION = `${ADMIN_SETTINGS}/authentication`;
|
export const AUTHENTICATION = `${ADMIN_SETTINGS}/authentication`;
|
||||||
export const ADMIN_APPS = `${ADMIN_SETTINGS}/apps`;
|
export const ADMIN_APPS = `${ADMIN_SETTINGS}/apps`;
|
||||||
export const ADMIN_APP = (appKey: string) => `${ADMIN_SETTINGS}/apps/${appKey}`;
|
export const ADMIN_APP = (appKey: string) => `${ADMIN_SETTINGS}/apps/${appKey}`;
|
||||||
|
export const ADMIN_APP_PATTERN = `${ADMIN_SETTINGS}/apps/:appKey`;
|
||||||
|
export const ADMIN_APP_SETTINGS_PATTERN = `${ADMIN_SETTINGS}/apps/:appKey/settings`;
|
||||||
|
export const ADMIN_APP_AUTH_CLIENTS_PATTERN = `${ADMIN_SETTINGS}/apps/:appKey/auth-clients`;
|
||||||
|
export const ADMIN_APP_CONNECTIONS_PATTERN = `${ADMIN_SETTINGS}/apps/:appKey/connections`;
|
||||||
|
export const ADMIN_APP_CONNECTIONS = (appKey: string) =>
|
||||||
|
`${ADMIN_SETTINGS}/apps/${appKey}/connections`;
|
||||||
|
export const ADMIN_APP_SETTINGS = (appKey: string) =>
|
||||||
|
`${ADMIN_SETTINGS}/apps/${appKey}/settings`;
|
||||||
|
export const ADMIN_APP_AUTH_CLIENTS = (appKey: string) =>
|
||||||
|
`${ADMIN_SETTINGS}/apps/${appKey}/auth-clients`;
|
||||||
|
|
||||||
export const DASHBOARD = FLOWS;
|
export const DASHBOARD = FLOWS;
|
||||||
|
|
||||||
|
13
packages/web/src/graphql/mutations/create-app-config.ts
Normal file
13
packages/web/src/graphql/mutations/create-app-config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const CREATE_APP_CONFIG = gql`
|
||||||
|
mutation CreateAppConfig($input: CreateAppConfigInput) {
|
||||||
|
createAppConfig(input: $input) {
|
||||||
|
id
|
||||||
|
key
|
||||||
|
allowCustomConnection
|
||||||
|
shared
|
||||||
|
disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
13
packages/web/src/graphql/mutations/update-app-config.ts
Normal file
13
packages/web/src/graphql/mutations/update-app-config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const UPDATE_APP_CONFIG = gql`
|
||||||
|
mutation UpdateAppConfig($input: UpdateAppConfigInput) {
|
||||||
|
updateAppConfig(input: $input) {
|
||||||
|
id
|
||||||
|
key
|
||||||
|
allowCustomConnection
|
||||||
|
shared
|
||||||
|
disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@@ -248,5 +248,13 @@
|
|||||||
"roleMappingsForm.save": "Save",
|
"roleMappingsForm.save": "Save",
|
||||||
"roleMappingsForm.notFound": "No role mappings have found.",
|
"roleMappingsForm.notFound": "No role mappings have found.",
|
||||||
"roleMappingsForm.successfullySaved": "Role mappings have been saved.",
|
"roleMappingsForm.successfullySaved": "Role mappings have been saved.",
|
||||||
"adminApps.title": "Apps"
|
"adminApps.title": "Apps",
|
||||||
|
"adminApps.connections": "Connections",
|
||||||
|
"adminApps.authClients": "Auth clients",
|
||||||
|
"adminApps.settings": "Settings",
|
||||||
|
"adminAppsSettings.allowCustomConnection": "Allow custom connection",
|
||||||
|
"adminAppsSettings.shared": "Shared",
|
||||||
|
"adminAppsSettings.disabled": "Disabled",
|
||||||
|
"adminAppsSettings.save": "Save",
|
||||||
|
"adminAppsSettings.successfullySaved": "Settings have been saved."
|
||||||
}
|
}
|
||||||
|
129
packages/web/src/pages/AdminApplication/index.tsx
Normal file
129
packages/web/src/pages/AdminApplication/index.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
import {
|
||||||
|
Link,
|
||||||
|
Route,
|
||||||
|
Navigate,
|
||||||
|
Routes,
|
||||||
|
useParams,
|
||||||
|
useMatch,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Grid from '@mui/material/Grid';
|
||||||
|
import Tabs from '@mui/material/Tabs';
|
||||||
|
import Tab from '@mui/material/Tab';
|
||||||
|
|
||||||
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import { GET_APP } from 'graphql/queries/get-app';
|
||||||
|
import * as URLS from 'config/urls';
|
||||||
|
|
||||||
|
import AppIcon from 'components/AppIcon';
|
||||||
|
import Container from 'components/Container';
|
||||||
|
import PageTitle from 'components/PageTitle';
|
||||||
|
import AdminApplicationSettings from 'components/AdminApplicationSettings';
|
||||||
|
|
||||||
|
type AdminApplicationParams = {
|
||||||
|
appKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AdminApplication(): React.ReactElement | null {
|
||||||
|
const theme = useTheme();
|
||||||
|
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
|
const connectionsPathMatch = useMatch({
|
||||||
|
path: URLS.ADMIN_APP_CONNECTIONS_PATTERN,
|
||||||
|
end: false,
|
||||||
|
});
|
||||||
|
const settingsPathMatch = useMatch({
|
||||||
|
path: URLS.ADMIN_APP_SETTINGS_PATTERN,
|
||||||
|
end: false,
|
||||||
|
});
|
||||||
|
const authClientsPathMatch = useMatch({
|
||||||
|
path: URLS.ADMIN_APP_AUTH_CLIENTS_PATTERN,
|
||||||
|
end: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { appKey } = useParams() as AdminApplicationParams;
|
||||||
|
const { data, loading } = useQuery(GET_APP, { variables: { key: appKey } });
|
||||||
|
|
||||||
|
const app = data?.getApp || {};
|
||||||
|
|
||||||
|
if (loading) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<Grid container item xs={12} sm={10} md={9}>
|
||||||
|
<Grid container sx={{ mb: 3 }} alignItems="center">
|
||||||
|
<Grid item xs="auto" sx={{ mr: 3 }}>
|
||||||
|
<AppIcon
|
||||||
|
url={app.iconUrl}
|
||||||
|
color={app.primaryColor}
|
||||||
|
name={app.name}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs>
|
||||||
|
<PageTitle>{app.name}</PageTitle>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs>
|
||||||
|
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
|
||||||
|
<Tabs
|
||||||
|
variant={matchSmallScreens ? 'fullWidth' : undefined}
|
||||||
|
value={
|
||||||
|
settingsPathMatch?.pattern?.path ||
|
||||||
|
connectionsPathMatch?.pattern?.path ||
|
||||||
|
authClientsPathMatch?.pattern?.path
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
label={formatMessage('adminApps.settings')}
|
||||||
|
to={URLS.ADMIN_APP_SETTINGS(appKey)}
|
||||||
|
value={URLS.ADMIN_APP_SETTINGS_PATTERN}
|
||||||
|
component={Link}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
label={formatMessage('adminApps.authClients')}
|
||||||
|
to={URLS.ADMIN_APP_AUTH_CLIENTS(appKey)}
|
||||||
|
value={URLS.ADMIN_APP_AUTH_CLIENTS_PATTERN}
|
||||||
|
component={Link}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
label={formatMessage('adminApps.connections')}
|
||||||
|
to={URLS.ADMIN_APP_CONNECTIONS(appKey)}
|
||||||
|
value={URLS.ADMIN_APP_CONNECTIONS_PATTERN}
|
||||||
|
disabled={!app.supportsConnections}
|
||||||
|
component={Link}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path={`/settings/*`}
|
||||||
|
element={<AdminApplicationSettings appKey={appKey} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={`/auth-clients/*`}
|
||||||
|
element={<div>Auth clients</div>}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={`/connections/*`}
|
||||||
|
element={<div>App connections</div>}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
element={
|
||||||
|
<Navigate to={URLS.ADMIN_APP_SETTINGS(appKey)} replace />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user