feat: introduce admin app configs page (#1335)

This commit is contained in:
kattoczko
2023-10-24 11:19:37 +01:00
committed by GitHub
parent 0a36101da1
commit 8c859f9408
9 changed files with 318 additions and 1 deletions

View File

@@ -12,6 +12,7 @@ import UserInterface from 'pages/UserInterface';
import * as URLS from 'config/urls';
import Can from 'components/Can';
import AdminApplications from 'pages/AdminApplications';
import AdminApplication from 'pages/AdminApplication';
// TODO: consider introducing redirections to `/` as fallback
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
path={URLS.ADMIN_SETTINGS}
element={<Navigate to={URLS.USERS} replace />}

View 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;

View File

@@ -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;
`;

View File

@@ -25,6 +25,7 @@ export default function Switch(props: SwitchProps): React.ReactElement {
onChange,
label,
FormControlLabelProps,
className,
...switchProps
} = props;
@@ -45,6 +46,7 @@ export default function Switch(props: SwitchProps): React.ReactElement {
},
}) => (
<FormControlLabel
className={className}
{...FormControlLabelProps}
control={
<MuiSwitch

View File

@@ -97,6 +97,16 @@ export const USER_INTERFACE = `${ADMIN_SETTINGS}/user-interface`;
export const AUTHENTICATION = `${ADMIN_SETTINGS}/authentication`;
export const ADMIN_APPS = `${ADMIN_SETTINGS}/apps`;
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;

View 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
}
}
`;

View 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
}
}
`;

View File

@@ -248,5 +248,13 @@
"roleMappingsForm.save": "Save",
"roleMappingsForm.notFound": "No role mappings have found.",
"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."
}

View 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>
);
}