From 354c57a040f456492279df877e7d6b8efd05b919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o?= <leopold.chappuis@savoirfairelinux.com> Date: Mon, 10 Feb 2025 15:16:17 -0500 Subject: [PATCH] admin-panel: UI refactor with layout wrapper and enhancements Introduced a layout wrapper for the settings page. Improved overall UI for better user experience. Change-Id: I60c483c3f2b8aca923b2ef40c8ab4c4cb677f6c2 --- .../AdminAccountConfiguration.tsx | 130 +++++++++--------- .../AdminSettings/ConfigureOpenId.tsx | 55 ++++++-- .../AdminSettings/DownloadLimit.tsx | 30 ++-- .../AdminSettings/GuestAuthMethod.tsx | 53 ++++--- .../AdminSettings/JamsAuthMethod.tsx | 109 +++++++-------- .../AdminSettings/LocalAuthMethod.tsx | 77 +++++------ .../AdminSettings/OpenIdAuthMethod.tsx | 45 +----- client/src/components/LayoutSettings.tsx | 64 +++++++++ server/locale/en/translation.json | 13 +- server/src/routers/admin-router.ts | 9 ++ server/src/storage/admin-account.ts | 4 + 11 files changed, 315 insertions(+), 274 deletions(-) create mode 100644 client/src/components/LayoutSettings.tsx diff --git a/client/src/components/AdminSettings/AdminAccountConfiguration.tsx b/client/src/components/AdminSettings/AdminAccountConfiguration.tsx index 669e6473..03bb1e75 100644 --- a/client/src/components/AdminSettings/AdminAccountConfiguration.tsx +++ b/client/src/components/AdminSettings/AdminAccountConfiguration.tsx @@ -15,12 +15,13 @@ * License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ -import { Box, Button, Input, Typography } from '@mui/material' +import { Box, Button, Input } from '@mui/material' import { AnimatePresence, motion } from 'framer-motion' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useUpdateAdminPasswordMutation } from '../../services/adminQueries' +import LayoutSettings from '../LayoutSettings' export default function AuthMethods() { const { t } = useTranslation() @@ -59,68 +60,71 @@ export default function AuthMethods() { } return ( - <Box - sx={{ marginTop: '10%', overflow: 'hidden', display: 'flex', flexDirection: 'column', justifyContent: 'center' }} - > - <Typography variant="h5" sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'center' }}> - {' '} - {t('setting_change_admin_password')}{' '} - </Typography> - <AnimatePresence> - <motion.div - style={{ - display: 'flex', - flexDirection: 'row', - justifyContent: 'center', - alignContent: 'center', - marginTop: '2%', - }} - initial={{ x: -20 }} - animate={{ x: 0 }} - exit={{ x: 20 }} - transition={{ duration: 0.5 }} - > - <Box sx={{ display: 'flex', flexDirection: 'column' }}> - <Input - type="password" - value={oldPassword} - onChange={onOldPasswordChange} - placeholder={t('admin_page_password_old_placeholder')} - ></Input> - <Input - type="password" - value={newPassword} - onChange={onNewPasswordChange} - placeholder={t('admin_page_password_placeholder')} - ></Input> - {newPassword !== repeatPassword && newPassword !== '' && repeatPassword !== '' && ( - <Box sx={{ fontSize: '11px', color: '#CC0022' }}>{t('password_input_helper_text_not_match')}</Box> - )} - <Input - type="password" - value={repeatPassword} - onChange={onRepeatPasswordChange} - placeholder={t('admin_page_password_repeat_placeholder')} - ></Input> - <Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'center', marginTop: '10%' }}> - <Button - variant="contained" - type="submit" - onClick={validatePasswordChange} - disabled={ - oldPassword === '' || - newPassword === '' || - repeatPassword === '' || - newPassword !== repeatPassword || - oldPassword === newPassword - } - > - {t('admin_page_password_submit')} - </Button> + <LayoutSettings title={t('setting_change_admin_password')} text={t('setting_change_admin_password_text')}> + <Box + sx={{ + overflow: 'hidden', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + marginTop: '-20px', + }} + > + <AnimatePresence> + <motion.div + style={{ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + }} + initial={{ x: -20 }} + animate={{ x: 0 }} + exit={{ x: 20 }} + transition={{ duration: 0.5 }} + > + <Box sx={{ display: 'flex', flexDirection: 'column' }}> + <Input + type="password" + value={oldPassword} + onChange={onOldPasswordChange} + placeholder={t('admin_page_password_old_placeholder')} + ></Input> + <Input + type="password" + value={newPassword} + onChange={onNewPasswordChange} + placeholder={t('admin_page_password_placeholder')} + ></Input> + {newPassword !== repeatPassword && newPassword !== '' && repeatPassword !== '' && ( + <Box sx={{ fontSize: '11px', color: '#CC0022' }}>{t('password_input_helper_text_not_match')}</Box> + )} + <Input + type="password" + value={repeatPassword} + onChange={onRepeatPasswordChange} + placeholder={t('admin_page_password_repeat_placeholder')} + ></Input> + <Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'center', marginTop: '10%' }}> + <Button + variant="contained" + type="submit" + onClick={validatePasswordChange} + disabled={ + oldPassword === '' || + newPassword === '' || + repeatPassword === '' || + newPassword !== repeatPassword || + oldPassword === newPassword + } + > + {t('admin_page_password_submit')} + </Button> + </Box> </Box> - </Box> - </motion.div> - </AnimatePresence> - </Box> + </motion.div> + </AnimatePresence> + </Box> + </LayoutSettings> ) } diff --git a/client/src/components/AdminSettings/ConfigureOpenId.tsx b/client/src/components/AdminSettings/ConfigureOpenId.tsx index f54dcec9..345aadda 100644 --- a/client/src/components/AdminSettings/ConfigureOpenId.tsx +++ b/client/src/components/AdminSettings/ConfigureOpenId.tsx @@ -17,6 +17,7 @@ */ import ContentCopyIcon from '@mui/icons-material/ContentCopy' +import DescriptionIcon from '@mui/icons-material/Description' import { Box, Button, @@ -28,6 +29,8 @@ import { Input, Switch, Typography, + useMediaQuery, + useTheme, } from '@mui/material' import { AdminSettingSelection } from 'jami-web-common' import { useContext, useState } from 'react' @@ -46,6 +49,8 @@ export default function ConfigureOpenId() { const { setAlertContent } = useContext(AlertSnackbarContext) const [clientId, setClientId] = useState<string>() const [clientSecret, setClientSecret] = useState('') + const theme = useTheme() + const isMobile = useMediaQuery(theme.breakpoints.down('sm')) const providerName = location.pathname.split('/').at(-1) @@ -129,22 +134,35 @@ export default function ConfigureOpenId() { <Box sx={{ display: 'flex', height: '100%', justifyContent: 'center', alignItems: 'center', alignContent: 'center' }} > - <Card sx={{ width: '420px', height: '580px', marginTop: '-50px' }}> + <Card + sx={{ + width: '420px', + height: '580px', + marginTop: '-50px', + boxShadow: isMobile ? 'none' : undefined, + border: isMobile ? 'none' : undefined, + }} + > <CardHeader title={ - <Typography sx={{ marginLeft: '-8px' }} variant="h3"> - {provider.data.displayName} - </Typography> - } - subheader={ - <Box sx={{ display: 'flex', flexDirection: 'column' }}> - {'OAuth2 Provider'} - <a href={provider.data.documentation} target="_blank" rel="noopener noreferrer"> - {'Documentation'} - </a> + <Box + sx={{ + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignContent: 'center', + alignItems: 'center', + gap: '10px', + }} + > + <Typography sx={{ marginLeft: '-8px' }} variant="h3"> + {provider.data.displayName} + </Typography> + <img src={provider.data.icon} style={{ width: '40px', height: '40px', marginTop: '5px' }} /> </Box> } - ></CardHeader> + subheader={<Box sx={{ display: 'flex', flexDirection: 'column' }}>{t('oauth2_provider')}</Box>} + /> <CardContent sx={{ display: 'flex', flexDirection: 'column', marginTop: '-16px' }}> <Box sx={{ marginBottom: '24px' }}> <Typography>{t('configure_oauth_provider_info')}</Typography> @@ -161,7 +179,7 @@ export default function ConfigureOpenId() { }} > <span style={{ paddingRight: '10%' }}> - {t('setting_auth_change', { authMethod: AuthenticationMethods.OPENID })} + {t('setting_auth_change', { authMethod: provider.data.displayName })} </span> <FormGroup style={{ alignItems: 'center' }}> <FormControlLabel @@ -204,9 +222,16 @@ export default function ConfigureOpenId() { ></Input> <ContentCopyIcon sx={{ ...contentCopyIconStyle, visibility: 'hidden' }} /> </Box> - <Box sx={{ width: '100%', display: 'flex', justifyContent: 'flex-end', marginTop: '4px' }}> + <Box sx={{ width: '100%', display: 'flex', justifyContent: 'space-between', marginTop: '20px' }}> + <Button + color="primary" + onClick={() => window.open(provider.data.documentation, '_blank', 'noopener,noreferrer')} + > + <DescriptionIcon /> + {t('documentation')} + </Button> <Button variant="contained" color="primary" onClick={onValidate}> - {t('submit')} + {t('save_button')} </Button> </Box> </CardContent> diff --git a/client/src/components/AdminSettings/DownloadLimit.tsx b/client/src/components/AdminSettings/DownloadLimit.tsx index 04732b35..e5d4f61a 100644 --- a/client/src/components/AdminSettings/DownloadLimit.tsx +++ b/client/src/components/AdminSettings/DownloadLimit.tsx @@ -16,13 +16,14 @@ * <https://www.gnu.org/licenses/>. */ -import { Box, Button, Input, Typography, useMediaQuery, useTheme } from '@mui/material' +import { Box, Button, Input } from '@mui/material' import { useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { AlertSnackbarContext } from '../../contexts/AlertSnackbarProvider' import { useSetAutoDownloadLimit } from '../../services/adminQueries' import { useGetAutoDownloadLimit } from '../../services/dataTransferQueries' +import LayoutSettings from '../LayoutSettings' import ProcessingRequest from '../ProcessingRequest' export default function DownloadLimit() { @@ -31,8 +32,6 @@ export default function DownloadLimit() { const { setAlertContent } = useContext(AlertSnackbarContext) const setAutoDownloadLimitMutation = useSetAutoDownloadLimit() const { t } = useTranslation() - const theme = useTheme() - const isMobile = useMediaQuery(theme.breakpoints.down('md')) const configuredLimit = limitData.data @@ -70,27 +69,14 @@ export default function DownloadLimit() { } return ( - <Box - sx={{ - display: 'flex', - flexDirection: 'column', - width: '100%', - justifyContent: 'center', - marginTop: '5%', - alignItems: 'center', - }} - > - <Typography variant="h3" sx={{ textAlign: 'center' }}> - {t('download_limit')} - </Typography> - <Typography variant="body2" sx={{ width: isMobile ? '300px' : '500px', pt: '1%', pb: '1%' }}> - {t('download_limit_details')} - </Typography> - <Box> + <LayoutSettings title={t('download_limit')} text={t('download_limit_details')}> + <Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', gap: '10px' }}> {' '} <Input type="number" inputProps={{ min: '0' }} value={limit} onChange={handleLimitChange}></Input> - <Button onClick={onSave}>{t('save_button')}</Button> + <Button variant="contained" onClick={onSave}> + {t('save_button')} + </Button> </Box> - </Box> + </LayoutSettings> ) } diff --git a/client/src/components/AdminSettings/GuestAuthMethod.tsx b/client/src/components/AdminSettings/GuestAuthMethod.tsx index 88a2073b..e37649a9 100644 --- a/client/src/components/AdminSettings/GuestAuthMethod.tsx +++ b/client/src/components/AdminSettings/GuestAuthMethod.tsx @@ -16,11 +16,11 @@ * <https://www.gnu.org/licenses/>. */ -import { Box, FormControl, FormControlLabel, FormGroup, Switch, Typography } from '@mui/material' +import { Box, FormControl, FormControlLabel, FormGroup, Switch } from '@mui/material' import { useTranslation } from 'react-i18next' -import { AuthenticationMethods } from '../../enums/authenticationMethods' import { useGetAdminConfigQuery, useUpdateAdminConfigMutation } from '../../services/adminQueries' +import LayoutSettings from '../LayoutSettings' import ProcessingRequest from '../ProcessingRequest' export default function GuestAuthMethod() { @@ -38,33 +38,30 @@ export default function GuestAuthMethod() { } return ( - <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80%', width: '100%' }}> - <Box sx={{ display: 'flex', flexDirection: 'column', width: '300px', gap: '20px' }}> - <Box> - <Typography variant="h6">{t('guest_authentication')}</Typography> - </Box> - <Box - sx={{ - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - alignContent: 'center', - height: '3rem', - }} - > - <span style={{ paddingRight: '10%' }}> - {t('setting_auth_change', { authMethod: AuthenticationMethods.GUEST })} - </span> - <FormControl> - <FormGroup style={{ marginTop: '2%', alignItems: 'center' }}> - <FormControlLabel - control={<Switch checked={data.guestAccessEnabled} onChange={handleChange} />} - label={undefined} - /> - </FormGroup> - </FormControl> + <LayoutSettings title={t('guest_authentication')} text={t('guest_authentication_info')}> + <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80%', width: '100%' }}> + <Box sx={{ display: 'flex', flexDirection: 'column', width: '300px', gap: '20px' }}> + <Box + sx={{ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignContent: 'center', + height: '3rem', + }} + > + <span style={{ paddingRight: '10%' }}>{t('setting_auth_change', { authMethod: t('guest') })}</span> + <FormControl> + <FormGroup style={{ marginTop: '2%', alignItems: 'center' }}> + <FormControlLabel + control={<Switch checked={data.guestAccessEnabled} onChange={handleChange} />} + label={undefined} + /> + </FormGroup> + </FormControl> + </Box> </Box> </Box> - </Box> + </LayoutSettings> ) } diff --git a/client/src/components/AdminSettings/JamsAuthMethod.tsx b/client/src/components/AdminSettings/JamsAuthMethod.tsx index e1f93054..670576c5 100644 --- a/client/src/components/AdminSettings/JamsAuthMethod.tsx +++ b/client/src/components/AdminSettings/JamsAuthMethod.tsx @@ -33,14 +33,13 @@ import { useTranslation } from 'react-i18next' import { Form, Navigate } from 'react-router-dom' import { AlertSnackbarContext } from '../../contexts/AlertSnackbarProvider' -import { AuthenticationMethods } from '../../enums/authenticationMethods' import { useGetAdminConfigQuery, useGetJamsUrl, - useRemoveJamsUrl, useSetJamsUrl, useUpdateAdminConfigMutation, } from '../../services/adminQueries' +import LayoutSettings from '../LayoutSettings' import ProcessingRequest from '../ProcessingRequest' const JamsAuthMethod = () => { @@ -51,7 +50,6 @@ const JamsAuthMethod = () => { const theme: Theme = useTheme() const setJamsUrlMutation = useSetJamsUrl() const jamsUrlQuery = useGetJamsUrl() - const removeJamsUrlMutation = useRemoveJamsUrl() const [jamsUrl, setJamsUrl] = useState<string>('') const { setAlertContent } = useContext(AlertSnackbarContext) const isWrapping = useMediaQuery(theme.breakpoints.down(500)) @@ -81,14 +79,6 @@ const JamsAuthMethod = () => { setJamsUrl(event.target.value) } - const removeJamsUrl = async () => { - removeJamsUrlMutation.mutate() - jamsUrlQuery.refetch() - if (jamsCurrentUrl !== undefined) { - setJamsUrl(jamsCurrentUrl) - } - } - const submitNewJamsUrl = async () => { // verifies if the string is a server adress or name server const regexValidation = /^(https?:\/\/)?(localhost|([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})(?::\d{1,5})?(\/[^\s]*)?$/.test( @@ -115,60 +105,53 @@ const JamsAuthMethod = () => { } return ( - <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80%' }}> - <Box sx={{ textAlign: 'start', marginTop: '8%' }}> - <Box - sx={{ - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - alignContent: 'center', - height: '3rem', - }} - > - <span style={{ paddingRight: '10%' }}> - {t('setting_auth_change', { authMethod: AuthenticationMethods.JAMS })} - </span> - <FormControl> - <FormGroup style={{ marginTop: '2%', alignItems: 'center' }}> - <FormControlLabel - control={<Switch checked={data.jamsAuthEnabled} onChange={handleChange} />} - label={undefined} - /> - </FormGroup> - </FormControl> - </Box> - <Box sx={{ textAlign: 'start', alignItems: 'start' }}> - <Typography>JAMS URL </Typography> - <Form> - <Input value={jamsUrl} onChange={handleNameChangeJamsUrl} placeholder={t('enter_jams_server')} /> - <Button - variant="contained" - type="submit" - onClick={submitNewJamsUrl} - sx={{ - mt: theme.typography.pxToRem(20), - marginLeft: theme.typography.pxToRem(10), - marginTop: isWrapping ? '5px' : '-15px', - }} - > - {t('save_button')} - </Button> - <Button - variant="contained" - onClick={removeJamsUrl} - sx={{ - mt: theme.typography.pxToRem(20), - marginLeft: theme.typography.pxToRem(10), - marginTop: isWrapping ? '5px' : '-15px', - }} - > - {t('remove_jams_server')} - </Button> - </Form> + <LayoutSettings title={t('jams_authentication')} text={t('jams_authentication_info')}> + <Box + sx={{ display: 'flex', justifyContent: 'start', alignItems: 'center', height: '80%', paddingBottom: '20px' }} + > + <Box sx={{ textAlign: 'start', width: '100%', minWidth: '300px' }}> + <Box + sx={{ + width: '100%', + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignContent: 'start', + }} + > + {t('setting_auth_change', { authMethod: t('jams') })} + <FormControl> + <FormGroup style={{ alignItems: 'start' }}> + <FormControlLabel + control={<Switch checked={data.jamsAuthEnabled} onChange={handleChange} />} + label={undefined} + /> + </FormGroup> + </FormControl> + </Box> + <Box sx={{ textAlign: 'start', alignItems: 'start', marginTop: '-40px' }}> + <Typography>{t('jams_url')}</Typography> + <Form> + <Input value={jamsUrl} onChange={handleNameChangeJamsUrl} placeholder={t('enter_jams_server')} /> + </Form> + <Box sx={{ width: '100%', display: 'flex', justifyContent: 'flex-end', marginTop: '20px' }}> + <Button + variant="contained" + type="submit" + onClick={submitNewJamsUrl} + sx={{ + mt: theme.typography.pxToRem(20), + marginLeft: theme.typography.pxToRem(10), + marginTop: isWrapping ? '5px' : '-15px', + }} + > + {t('save_button')} + </Button> + </Box> + </Box> </Box> </Box> - </Box> + </LayoutSettings> ) } export default JamsAuthMethod diff --git a/client/src/components/AdminSettings/LocalAuthMethod.tsx b/client/src/components/AdminSettings/LocalAuthMethod.tsx index 780aa0d9..81679f7e 100644 --- a/client/src/components/AdminSettings/LocalAuthMethod.tsx +++ b/client/src/components/AdminSettings/LocalAuthMethod.tsx @@ -33,7 +33,6 @@ import { useTranslation } from 'react-i18next' import { Form, Navigate } from 'react-router-dom' import { AlertSnackbarContext } from '../../contexts/AlertSnackbarProvider' -import { AuthenticationMethods } from '../../enums/authenticationMethods' import { useGetAdminConfigQuery, useGetNameServer, @@ -41,6 +40,7 @@ import { useSetNameServer, useUpdateAdminConfigMutation, } from '../../services/adminQueries' +import LayoutSettings from '../LayoutSettings' import ProcessingRequest from '../ProcessingRequest' const LocalAuthMethod = () => { @@ -104,64 +104,63 @@ const LocalAuthMethod = () => { } return ( - <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80%' }}> - <Box sx={{ textAlign: 'start' }}> - <Box - sx={{ - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - alignContent: 'center', - height: '3rem', - }} - > - <span style={{ paddingRight: '10%' }}> - {t('setting_auth_change', { authMethod: AuthenticationMethods.JAMI })} - </span> - <FormControl> - <FormGroup style={{ marginTop: '2%', alignItems: 'center' }}> - <FormControlLabel - control={<Switch checked={data.localAuthEnabled} onChange={handleChange} />} - label={undefined} - /> - </FormGroup> - </FormControl> - </Box> - <Box sx={{ textAlign: 'start', alignItems: 'start' }}> - <Typography>{t('setting_name_server')}</Typography> - <Form> - <Input value={nameServer} onChange={handleNameChangeNameServer} /> + <LayoutSettings title={t('jami_authentication')} text={t('jami_authentication_info')}> + <Box sx={{ display: 'flex', justifyContent: 'start', alignItems: 'start', paddingBottom: '30px' }}> + <Box sx={{ textAlign: 'start', width: '100%', minWidth: '300px' }}> + <Box + sx={{ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignContent: 'start', + marginTop: '-30px', + }} + > + <span style={{ paddingRight: '10%' }}>{t('setting_auth_change', { authMethod: t('jami') })}</span> + <FormControl> + <FormGroup style={{ alignItems: 'start' }}> + <FormControlLabel + control={<Switch checked={data.localAuthEnabled} onChange={handleChange} />} + label={undefined} + /> + </FormGroup> + </FormControl> + </Box> + <Box sx={{ textAlign: 'start', alignItems: 'start', marginTop: '-40px' }}> + <Typography>{t('setting_name_server')}</Typography> + <Form> + <Input value={nameServer} onChange={handleNameChangeNameServer} /> + </Form> + </Box> + <Box sx={{ display: 'flex', justifyContent: 'flex-end', marginTop: '30px' }}> <Button variant="contained" - type="submit" - onClick={submitNewNameServer} + onClick={deleteNameServer} sx={{ mt: theme.typography.pxToRem(20), marginLeft: theme.typography.pxToRem(10), marginTop: isWrapping ? '5px' : '-15px', }} - disabled={nameServer === nameServerConfigured} > - {t('save_button')} + {t('reset_nameserver_button')} </Button> <Button variant="contained" - onClick={deleteNameServer} + type="submit" + onClick={submitNewNameServer} sx={{ mt: theme.typography.pxToRem(20), marginLeft: theme.typography.pxToRem(10), marginTop: isWrapping ? '5px' : '-15px', }} + disabled={nameServer === nameServerConfigured} > - {t('reset_nameserver_button')} + {t('save_button')} </Button> - </Form> + </Box> </Box> - {nameServer === nameServerConfigured && ( - <Box style={{ fontSize: 'smaller', marginTop: '2%' }}>{t('nameserver_already_set')}</Box> - )} </Box> - </Box> + </LayoutSettings> ) } diff --git a/client/src/components/AdminSettings/OpenIdAuthMethod.tsx b/client/src/components/AdminSettings/OpenIdAuthMethod.tsx index 59205c9f..2e895451 100644 --- a/client/src/components/AdminSettings/OpenIdAuthMethod.tsx +++ b/client/src/components/AdminSettings/OpenIdAuthMethod.tsx @@ -16,18 +16,7 @@ * <https://www.gnu.org/licenses/>. */ -import { - Box, - Divider, - FormControl, - FormControlLabel, - FormGroup, - List, - ListItem, - ListItemText, - Switch, - Typography, -} from '@mui/material' +import { Box, Divider, List, ListItem, ListItemText, Typography } from '@mui/material' import { AnimatePresence, motion } from 'framer-motion' import { AdminSettingSelection } from 'jami-web-common' import { useTranslation } from 'react-i18next' @@ -35,15 +24,10 @@ import { useNavigate } from 'react-router-dom' import { AuthenticationMethods } from '../../enums/authenticationMethods' import rightChevron from '../../icons/rightChevron.svg' -import { - useGetAdminConfigQuery, - useGetAllOauthClients, - useUpdateAdminConfigMutation, -} from '../../services/adminQueries' +import { useGetAdminConfigQuery, useGetAllOauthClients } from '../../services/adminQueries' import ProcessingRequest from '../ProcessingRequest' export default function OpenIdAuthMethod() { - const saveAdminConfigMutation = useUpdateAdminConfigMutation() const navigate = useNavigate() const { t } = useTranslation() const { data } = useGetAdminConfigQuery() @@ -83,10 +67,6 @@ export default function OpenIdAuthMethod() { const disabledColor = '#CC0022' const enabledColor = '#00796B' - const handleChange = () => { - saveAdminConfigMutation.mutate({ openIdAuthEnabled: !data.openIdAuthEnabled }) - } - const handleSelection = (provider: string) => { navigate(`/admin/${AdminSettingSelection.AUTHENTICATION}/${AuthenticationMethods.OPENID}/${provider}`) } @@ -97,27 +77,6 @@ export default function OpenIdAuthMethod() { <Box> <Typography variant="h6">{t('openid_authentication')}</Typography> </Box> - <Box - sx={{ - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - alignContent: 'center', - height: '3rem', - }} - > - <span style={{ paddingRight: '10%' }}> - {t('setting_auth_change', { authMethod: AuthenticationMethods.OPENID })} - </span> - <FormControl> - <FormGroup style={{ marginTop: '2%', alignItems: 'center' }}> - <FormControlLabel - control={<Switch checked={data.openIdAuthEnabled} onChange={handleChange} />} - label={undefined} - /> - </FormGroup> - </FormControl> - </Box> <AnimatePresence> <motion.div style={{ diff --git a/client/src/components/LayoutSettings.tsx b/client/src/components/LayoutSettings.tsx new file mode 100644 index 00000000..a44550f5 --- /dev/null +++ b/client/src/components/LayoutSettings.tsx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022-2025 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see + * <https://www.gnu.org/licenses/>. + */ + +import { Box, Card, CardHeader, Typography, useMediaQuery, useTheme } from '@mui/material' + +interface LayoutSettingsProps { + children: React.ReactNode + title: string + text?: string +} + +export default function LayoutSettings({ children, title, text }: LayoutSettingsProps) { + const theme = useTheme() + const isMobile = useMediaQuery(theme.breakpoints.down('sm')) + + return ( + <Box + sx={{ + display: 'flex', + width: '100%', + minHeight: '420px', + maxHeight: '600px', + justifyContent: 'center', + marginTop: isMobile ? '0px' : '80px', + }} + > + <Card + sx={{ + maxWidth: '400px', + padding: '20px', + boxShadow: isMobile ? 'none' : undefined, + border: isMobile ? 'none' : undefined, + }} + > + <CardHeader + title={<Typography variant="h5">{title}</Typography>} + subheader={ + <Box sx={{ mt: '40px' }}> + <Typography variant="body2">{text}</Typography> + </Box> + } + /> + <Box sx={{ marginTop: '50px', maxWidth: '100%', display: 'flex', justifyContent: 'center' }}> + <Box sx={{ maxWidth: '90%' }}>{children}</Box> + </Box> + </Card> + </Box> + ) +} diff --git a/server/locale/en/translation.json b/server/locale/en/translation.json index 9912fae5..99674004 100644 --- a/server/locale/en/translation.json +++ b/server/locale/en/translation.json @@ -15,10 +15,12 @@ "admin_account_configuration": "Admin configuration", "admin_config_auth_methods_title": "Authentication methods", "admin_login_to_main": "User login", + "admin_page_accounts_overview": "Accounts overview", "admin_page_accounts_overview_title": "Accounts overview - {{count}} active accounts", "admin_page_accounts_overview_title_auth": "Authentication method", "admin_page_accounts_overview_title_username": "Username", "admin_page_accounts_overview_title_storage": "Storage used", + "admin_page_authentication_methods": "Authentication methods", "admin_page_create_button": "Create admin account", "admin_page_login_title": "Jami Admin", "admin_page_login_subtitle": "Log in to access the admin panel", @@ -118,6 +120,7 @@ "dialog_close": "Close", "dialog_confirm_title_default": "Confirm action", "disable_tips": "Disable tips", + "documentation": "Documentation", "download_limit": "Auto download limit", "download_limit_details": "The automatic download limit is the maximum file size (in MB) that the application downloads automatically. If a file exceeds the limit, the user is required to accept the download.", "edited_message": "Edited ", @@ -140,6 +143,7 @@ "go_to_login_page": "Go to login page", "guest": "Guest", "guest_authentication": "Guest authentication", + "guest_authentication_info": "Guest authentication enables users to access a temporary account that is automatically deleted shortly after. This allows them to start chatting without the need to provide any information or credentials.", "incoming_call": "Incoming call", "incoming_call_audio": "Incoming audio call from {{member0}}", "incoming_call_video": "Incoming video call from {{member0}}", @@ -148,8 +152,13 @@ "invited": "Invited", "jami": "Jami", "jami_account": "Jami Account", + "jami_authentication": "Jami Local Authentication", + "jami_authentication_info": "Local Authentication allows users to register an account by providing a username. The account is created and managed directly by the server. If the server's data is deleted, the account and its access will be permanently lost.", "jami_user_id": "Jami user ID", "jams": "JAMS", + "jams_authentication": "JAMS authentication", + "jams_authentication_info": "JAMS is the authentication service provided by Savoir Faire Linux, enabling companies to manage secure access and user authentication efficiently.", + "jams_url": "JAMS URL", "jams_url_already_set": "JAMS server is already set.", "link_new_device": "Link new device", "limit_cannot_be_negative": "The limit cannot be negative", @@ -193,6 +202,7 @@ "ongoing_call_unmuted": "Ongoing call", "openid": "OpenID", "openid_authentication": "OpenID authentication", + "oauth2_provider": "OAuth2 Provider", "outgoing_call": "Outgoing call", "password_input_helper_text": "", "password_input_helper_text_empty": "The password is missing.", @@ -242,7 +252,8 @@ "search_results": "Search Results", "see_all_devices": "See all devices", "select_placeholder": "Select an option", - "setting_auth_change": "{{authMethod}} enabled", + "setting_change_admin_password_text": "Change the administrator password", + "setting_auth_change": "{{authMethod}} authentication", "setting_auth_jami_change": "Enable Jami local", "setting_can_be_changed_later": "*You will still have the option to modify this parameter later in the admin configuration panel.", "setting_auto_download_limit_error": "An error occurred while updating the auto download limit", diff --git a/server/src/routers/admin-router.ts b/server/src/routers/admin-router.ts index 12463199..cfb95168 100644 --- a/server/src/routers/admin-router.ts +++ b/server/src/routers/admin-router.ts @@ -461,6 +461,7 @@ adminRouter.get( redirectUri: baseUrl + providersSupport.getRedirectUri(provider), documentation: providerConfig.documentation, displayName: providerConfig.displayName, + icon: providerConfig.icon, } res.send(data) @@ -497,10 +498,18 @@ adminRouter.patch( } } + if (adminAccount.getNumberOfEnabledProviders() === 0) { + adminAccount.updateConfig({ openIdAuthEnabled: false }) + } else { + adminAccount.updateConfig({ openIdAuthEnabled: true }) + } + adminAccount.save() + res.sendStatus(HttpStatusCode.Ok) }), ) + adminRouter.post('/autoDownloadLimit', (req, res) => { const downloadLimit = Number(req.body.limit) if (!downloadLimit) { diff --git a/server/src/storage/admin-account.ts b/server/src/storage/admin-account.ts index 1f13cbf1..e263ea81 100644 --- a/server/src/storage/admin-account.ts +++ b/server/src/storage/admin-account.ts @@ -137,6 +137,10 @@ export class AdminAccount { } } + getNumberOfEnabledProviders() { + return Object.values(this.getOpenIdProviders()).filter((provider) => provider.isActive).length + } + getLastWizardState(): string { return this.adminAccount.lastWizardState } -- GitLab