change settings page

This commit is contained in:
NlightN22 2024-06-24 03:15:25 +07:00
parent 2dff7fa5f6
commit 131f5efac6
10 changed files with 407 additions and 133 deletions

View File

@ -1,3 +1,5 @@
import { error } from "console"
const en = { const en = {
editCameraPage: { editCameraPage: {
notFrigateCamera: 'Not frigate camera', notFrigateCamera: 'Not frigate camera',
@ -14,6 +16,20 @@ const en = {
saveAndRestart: 'Save & Restart', saveAndRestart: 'Save & Restart',
editorNotExist: 'Editor does not exists', editorNotExist: 'Editor does not exists',
}, },
settingsPage: {
oidpClientId: 'OIDP Client ID',
oidpClientIdPH: 'frigate-cli',
clientSecret: 'OIDP Client secret',
clientSecretPH: 'super secret from OIDP server client',
clientUsername: 'OIDP Client username',
clientUsernamePH: 'frigate-admin@yourmail.com',
clientPassword: 'OIDP Client password',
clientPasswordPH: 'User password on OIDP server',
realmUrl: 'OIDP realm URL path',
realmUrlPH: 'https://your.oidp.server.com/realms/frigate-realm',
adminRole: 'Select admin role',
birdseyeRole: 'Select birds eye role user',
},
systemPage: { systemPage: {
cameraStats: 'Cameras stats', cameraStats: 'Cameras stats',
storageStats: 'Storages stats', storageStats: 'Storages stats',
@ -110,6 +126,9 @@ const en = {
goToMainPage: "Return to main page", goToMainPage: "Return to main page",
retry: "Retry", retry: "Retry",
youCanRetryOrGoToMain: "You can retry or return to the main page", youCanRetryOrGoToMain: "You can retry or return to the main page",
successfully: "Sucessfully",
successfullySaved: "Sucessfully saved",
error: "Error",
errors: { errors: {
emptyResponse: 'Empty response', emptyResponse: 'Empty response',
somthingGoesWrong: "Something went wrong", somthingGoesWrong: "Something went wrong",

View File

@ -14,6 +14,19 @@ const ru = {
saveAndRestart: 'Сохранить & Перезагрузить', saveAndRestart: 'Сохранить & Перезагрузить',
editorNotExist: 'Редактор не найден', editorNotExist: 'Редактор не найден',
}, },
settingsPage: {
oidpClientId: 'OIDP ID клиента',
oidpClientIdPH: 'frigate-cli',
clientSecret: 'OIDP секрет клиента',
clientSecretPH: 'супер секретный пароль от клиента OIDP сервера',
clientUsername: 'OIDP имя пользователя',
clientUsernamePH: 'frigate-admin@yourmail.com',
clientPassword: 'OIDP пароль',
clientPasswordPH: 'Пароль пользователя на OIDP сервере',
realmUrl: 'OIDP realm URL путь',
realmUrlPH: 'https://your.oidp.server.com/realms/frigate-realm',
adminRole: 'Выбери роль администратора',
birdseyeRole: 'Выбери роль birdseye пользователя',},
systemPage: { systemPage: {
cameraStats: 'Статистика Камер', cameraStats: 'Статистика Камер',
storageStats: 'Статистика Хранения', storageStats: 'Статистика Хранения',
@ -110,6 +123,9 @@ const ru = {
goToMainPage: "Вернуться на главную", goToMainPage: "Вернуться на главную",
retry: "Повторить", retry: "Повторить",
youCanRetryOrGoToMain: "Вы можете повторить или вернуться на главную", youCanRetryOrGoToMain: "Вы можете повторить или вернуться на главную",
successfully: "Успешно",
successfullySaved: "Успешно сохранено",
error: "Ошибка",
errors: { errors: {
emptyResponse: 'Пустой ответ', emptyResponse: 'Пустой ответ',
somthingGoesWrong: "Что-то пошло не так", somthingGoesWrong: "Что-то пошло не так",

View File

@ -1,4 +1,4 @@
import { Flex, Group, Select, Text } from '@mantine/core'; import { Flex, Group, Text } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks'; import { useMediaQuery } from '@mantine/hooks';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
@ -8,13 +8,12 @@ import { Context } from '..';
import { useAdminRole } from '../hooks/useAdminRole'; import { useAdminRole } from '../hooks/useAdminRole';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import CamerasTransferList from '../shared/components/CamerasTransferList'; import CamerasTransferList from '../shared/components/CamerasTransferList';
import { OneSelectItem } from '../shared/components/filters/OneSelectFilter'; import RoleSelectFilter from '../shared/components/filters/RoleSelectFilter';
import CenterLoader from '../shared/components/loaders/CenterLoader'; import CenterLoader from '../shared/components/loaders/CenterLoader';
import { dimensions } from '../shared/dimensions/dimensions'; import { dimensions } from '../shared/dimensions/dimensions';
import { isProduction } from '../shared/env.const'; import { isProduction } from '../shared/env.const';
import Forbidden from './403'; import Forbidden from './403';
import RetryErrorPage from './RetryErrorPage'; import RetryErrorPage from './RetryErrorPage';
import RoleSelectFilter from '../shared/components/filters/RoleSelectFilter';
const AccessSettings = () => { const AccessSettings = () => {
const { t } = useTranslation() const { t } = useTranslation()
@ -42,7 +41,6 @@ const AccessSettings = () => {
if (isPending || adminLoading) return <CenterLoader /> if (isPending || adminLoading) return <CenterLoader />
if (isError || !data) return <RetryErrorPage onRetry={refetch} /> if (isError || !data) return <RetryErrorPage onRetry={refetch} />
if (!isAdmin) return <Forbidden /> if (!isAdmin) return <Forbidden />
const rolesSelect: OneSelectItem[] = data.map(role => ({ value: role.id, label: role.name }))
const handleSelectRole = (value: string) => { const handleSelectRole = (value: string) => {
setRoleId(value) setRoleId(value)

View File

@ -1,35 +1,25 @@
import { Button, Flex, Space } from '@mantine/core'; import { Flex, Space } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks'; import { useMediaQuery } from '@mantine/hooks';
import { import { useMutation } from '@tanstack/react-query';
useMutation,
useQuery,
useQueryClient,
} from '@tanstack/react-query';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import React, { useContext, useEffect, useRef, useState } from 'react'; import { useContext, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Context } from '..'; import { Context } from '..';
import { useAdminRole } from '../hooks/useAdminRole'; import { useAdminRole } from '../hooks/useAdminRole';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { frigateApi } from '../services/frigate.proxy/frigate.api';
import { GetConfig, PutConfig } from '../services/frigate.proxy/frigate.schema'; import { GetRole } from '../services/frigate.proxy/frigate.schema';
import { FloatingLabelInput } from '../shared/components/inputs/FloatingLabelInput';
import CenterLoader from '../shared/components/loaders/CenterLoader'; import CenterLoader from '../shared/components/loaders/CenterLoader';
import { dimensions } from '../shared/dimensions/dimensions'; import { dimensions } from '../shared/dimensions/dimensions';
import { isProduction } from '../shared/env.const'; import OIDPSettingsForm from '../widgets/OIDPSettingsForm';
import RolesSettingsForm from '../widgets/RolesSettingsForm';
import Forbidden from './403'; import Forbidden from './403';
import RetryErrorPage from './RetryErrorPage';
import { notifications } from '@mantine/notifications';
import { v4 } from 'uuid';
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
const SettingsPage = () => { const SettingsPage = () => {
const { t } = useTranslation() const { t } = useTranslation()
const executed = useRef(false) const executed = useRef(false)
const queryClient = useQueryClient()
const { isPending: configPending, error: configError, data, refetch } = useQuery({ const [showRoles, setShowRoles] = useState<boolean>(false)
queryKey: [frigateQueryKeys.getConfig], const [allRoles, setAllRoles] = useState<GetRole[]>()
queryFn: frigateApi.getConfig,
})
const { sideBarsStore } = useContext(Context) const { sideBarsStore } = useContext(Context)
useEffect(() => { useEffect(() => {
@ -43,127 +33,40 @@ const SettingsPage = () => {
const { isAdmin, isLoading: adminLoading } = useAdminRole() const { isAdmin, isLoading: adminLoading } = useAdminRole()
const ecryptedTemplate = 'encrypted value is exist'
const mapEncryptedToView = (data: GetConfig[] | undefined): GetConfig[] | undefined => {
return data?.map(item => {
const { value, encrypted, ...rest } = item
if (encrypted && value) return { value: ecryptedTemplate, encrypted, ...rest }
return item
})
}
const [configs, setConfigs] = useState(data)
const isMobile = useMediaQuery(dimensions.mobileSize) const isMobile = useMediaQuery(dimensions.mobileSize)
const mutation = useMutation({ const mutation = useMutation({
mutationFn: (config: PutConfig[]) => frigateApi.putConfig(config).catch(error => { mutationFn: frigateApi.getRoles,
if (error.response && error.response.data) { onSuccess: (data) => {
return Promise.reject(error.response.data) setAllRoles(data)
}
return Promise.reject(error)
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getConfig] })
notifications.show({
id: v4(),
withCloseButton: true,
autoClose: 5000,
title: `Sucessfully`,
message: `Sucessfully saved`,
color: 'green',
icon: <IconCircleCheck />
})
},
onError: (e) => {
notifications.show({
id: e.message,
withCloseButton: true,
autoClose: false,
title: "Error",
message: e.message,
color: 'red',
icon: <IconAlertCircle />,
})
} }
}) })
const handleDiscard = () => { const handleOIDPConfigValid = (valid: boolean) => {
if (!isProduction) console.log('Discard changes') setShowRoles(valid)
refetch() mutation.mutate()
setConfigs(data ? mapEncryptedToView(data) : [])
}
useEffect(() => {
if (!isProduction) console.log('data changed')
setConfigs(mapEncryptedToView(data))
}, [data])
useEffect(() => {
if (!isProduction) console.log('configs changed')
}, [configs])
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const formData = new FormData(event.currentTarget);
const formDataObj: any = Array.from(formData.entries()).reduce((acc, [key, value]) => ({
...acc,
[key]: value,
}), {});
const configsToUpdate = Object.keys(formDataObj).map(key => {
const value = formDataObj[key]
const currData = data?.find(val => val.key === key)
const notChangedEncrypted = value === ecryptedTemplate
if (currData && currData.encrypted && notChangedEncrypted) {
return {
key,
value: currData.value
}
}
return {
key,
value: value,
}
});
if (!isProduction) console.log('configsToUpdate', configsToUpdate)
mutation.mutate(configsToUpdate);
} }
if (!isAdmin) return <Forbidden /> if (!isAdmin) return <Forbidden />
if (configPending || adminLoading) return <CenterLoader /> if (adminLoading) return <CenterLoader />
if (configError) return <RetryErrorPage onRetry={refetch} />
return ( return (
<Flex h='100%'> <Flex h='100%'>
{!isMobile ? {!isMobile ?
< Space w='20%' /> < Space w='20%' />
: <></> : null
} }
<Flex direction='column' h='100%' w='100%' justify='stretch'> <Flex direction='column' h='100%' w='100%' justify='stretch'>
<form onSubmit={handleSubmit}> <OIDPSettingsForm isConfigValid={handleOIDPConfigValid} />
{!configs ? <></> {
: showRoles && allRoles && allRoles.length > 0 ?
configs.map((config) => ( <RolesSettingsForm allRoles={allRoles} />
<FloatingLabelInput : null
key={`${config.key}-${new Date().getTime()}`} }
name={config.key}
label={config.description}
value={config.value}
placeholder={config.description}
encrypted={config.encrypted}
ecryptedValue={ecryptedTemplate}
/>
))}
<Space h='2%' />
<Flex w='100%' justify='stretch' wrap='nowrap' align='center'>
<Button w='100%' onClick={handleDiscard} m='0.5rem'>{t('discard')}</Button>
<Button w='100%' type="submit" m='0.5rem'>{t('confirm')}</Button>
</Flex>
</form>
</Flex> </Flex>
{!isMobile ? {!isMobile ?
<Space w='20%' /> <Space w='20%' />
: <></> : null
} }
</Flex> </Flex>
); );

View File

@ -3,7 +3,9 @@ import { proxyURL } from "../../shared/env.const"
import { import {
GetConfig, DeleteFrigateHost, GetFrigateHost, PutConfig, PutFrigateHost, GetConfig, DeleteFrigateHost, GetFrigateHost, PutConfig, PutFrigateHost,
GetCameraWHostWConfig, GetRole, GetCameraWHostWConfig, GetRole,
GetRoleWCameras, GetExportedFile, recordingSchema GetRoleWCameras, GetExportedFile, recordingSchema,
oidpConfig,
OIDPConfig
} from "./frigate.schema"; } from "./frigate.schema";
import { FrigateConfig } from "../../types/frigateConfig"; import { FrigateConfig } from "../../types/frigateConfig";
import { RecordSummary } from "../../types/record"; import { RecordSummary } from "../../types/record";
@ -31,8 +33,12 @@ instanceApi.interceptors.request.use(
); );
export const frigateApi = { export const frigateApi = {
getConfig: () => instanceApi.get<GetConfig[]>('apiv1/config').then(res => res.data), getAllConfig: () => instanceApi.get<GetConfig[]>('apiv1/config').then(res => res.data),
putConfig: (config: PutConfig[]) => instanceApi.put('apiv1/config', config).then(res => res.data), getConfig: (key: string) => instanceApi.get<GetConfig>(`apiv1/config/${key}`).then(res => res.data),
getOIDPConfig: () => instanceApi.get<OIDPConfig>('apiv1/config/oidp').then(res => res.data),
putConfigs: (config: PutConfig[]) => instanceApi.put('apiv1/config', config).then(res => res.data),
putOIDPConfig: (config: OIDPConfig) => instanceApi.put('apiv1/config/oidp', config).then(res => res.data),
putOIDPConfigTest: (config: OIDPConfig) => instanceApi.put('apiv1/config/oidp/test', config).then(res => res.data),
getHosts: () => instanceApi.get<GetFrigateHost[]>('apiv1/frigate-hosts').then(res => { getHosts: () => instanceApi.get<GetFrigateHost[]>('apiv1/frigate-hosts').then(res => {
return res.data return res.data
}), }),
@ -212,7 +218,9 @@ export const mapHostToHostname = (host?: GetFrigateHost): string | undefined =>
} }
export const frigateQueryKeys = { export const frigateQueryKeys = {
getConfig: 'config', getAllConfig: 'getAllConfig',
getConfig: 'getConfig',
getOIDPConfig: 'OIDPconfig',
getFrigateHosts: 'frigate-hosts', getFrigateHosts: 'frigate-hosts',
getFrigateHostsConfigs: 'frigate-hosts-configs', getFrigateHostsConfigs: 'frigate-hosts-configs',
getFrigateHost: 'frigate-host', getFrigateHost: 'frigate-host',

View File

@ -6,6 +6,14 @@ export const putConfigSchema = z.object({
value: z.string(), value: z.string(),
}) })
export const oidpConfig = z.object({
clientId: z.string(),
clientSecret: z.string(),
clientUsername: z.string(),
clientPassword: z.string(),
clientURL: z.string(),
})
export const getConfigSchema = z.object({ export const getConfigSchema = z.object({
key: z.string(), key: z.string(),
value: z.string(), value: z.string(),
@ -115,6 +123,7 @@ export const recordingSchema = z.object({
export type GetConfig = z.infer<typeof getConfigSchema> export type GetConfig = z.infer<typeof getConfigSchema>
export type PutConfig = z.infer<typeof putConfigSchema> export type PutConfig = z.infer<typeof putConfigSchema>
export type OIDPConfig = z.infer<typeof oidpConfig>
export type GetFrigateHost = z.infer<typeof getFrigateHostSchema> export type GetFrigateHost = z.infer<typeof getFrigateHostSchema>
// export type GetFrigateHostWithCameras = z.infer<typeof getFrigateHostWithCamerasSchema> // export type GetFrigateHostWithCameras = z.infer<typeof getFrigateHostWithCamerasSchema>
export type GetFrigateHostWConfig = GetFrigateHost & { config: FrigateConfig } export type GetFrigateHostWConfig = GetFrigateHost & { config: FrigateConfig }

View File

@ -33,9 +33,9 @@ const RoleSelectFilter: React.FC<RoleSelectFilterProps> = ({
return ( return (
<Select <Select
value={roleId}
{...selectProps} {...selectProps}
data={rolesSelect} data={rolesSelect}
value={roleId}
onChange={handleSelectRole} onChange={handleSelectRole}
/> />
); );

View File

@ -5,7 +5,6 @@ import classes from './FloatingLabelInput.module.css';
interface FloatingLabelInputProps extends TextInputProps { interface FloatingLabelInputProps extends TextInputProps {
value?: string value?: string
encrypted?: boolean encrypted?: boolean
ecryptedValue?: string
onChangeValue?: (key: string, value: string) => void onChangeValue?: (key: string, value: string) => void
} }
@ -13,7 +12,6 @@ export const FloatingLabelInput: React.FC<FloatingLabelInputProps> = ({
value: propVal, value: propVal,
onChangeValue, onChangeValue,
encrypted, encrypted,
ecryptedValue,
...rest ...rest
}) => { }) => {
const [focused, setFocused] = useState(false); const [focused, setFocused] = useState(false);

View File

@ -0,0 +1,152 @@
import { notifications } from '@mantine/notifications';
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { v4 } from 'uuid';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import { OIDPConfig } from '../services/frigate.proxy/frigate.schema';
import RetryError from '../shared/components/RetryError';
import { FloatingLabelInput } from '../shared/components/inputs/FloatingLabelInput';
import CogwheelLoader from '../shared/components/loaders/CogwheelLoader';
import { Flex, Button } from '@mantine/core';
import { isProduction } from '../shared/env.const';
import { useEffect, useState } from 'react';
interface OIDPSettingsFormProps {
isConfigValid?: (valid: boolean) => void
}
const OIDPSettingsForm: React.FC<OIDPSettingsFormProps> = ({
isConfigValid
}) => {
const { t } = useTranslation()
const queryClient = useQueryClient()
const [config, setConfig] = useState<OIDPConfig>({
clientId: '',
clientSecret: '',
clientUsername: '',
clientPassword: '',
clientURL: ''
});
const { isPending, isError, data, refetch } = useQuery<OIDPConfig>({
queryKey: [frigateQueryKeys.getOIDPConfig],
queryFn: frigateApi.getOIDPConfig,
})
useEffect(() => {
if (data) {
setConfig(data)
const result = Object.values(data).every(field => field.trim() !== '')
if (isConfigValid) isConfigValid(result)
}
}, [data]);
const mutation = useMutation({
mutationFn: (config: OIDPConfig) => frigateApi.putOIDPConfig(config).catch(error => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data)
}
return Promise.reject(error)
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getOIDPConfig] })
notifications.show({
id: v4(),
withCloseButton: true,
autoClose: 5000,
title: t('successfully'),
message: t('successfullySaved'),
color: 'green',
icon: <IconCircleCheck />
})
},
onError: (e) => {
notifications.show({
id: e.message,
withCloseButton: true,
autoClose: false,
title: t('error'),
message: e.message,
color: 'red',
icon: <IconAlertCircle />,
})
}
})
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setConfig((prevConfig) => ({
...prevConfig,
[name]: value,
}));
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
mutation.mutate(config);
}
const handleDiscard = () => {
if (!isProduction) console.log('Discard changes')
refetch()
if (data) setConfig(data)
}
if (isPending) return <CogwheelLoader />
if (isError) return <RetryError onRetry={refetch} />
return (
<>
<form onSubmit={handleSubmit}>
<FloatingLabelInput
name="clientId"
label={t('settingsPage.oidpClientId')}
value={config.clientId}
placeholder={t('settingsPage.oidpClientIdPH')}
encrypted={false}
onChange={handleInputChange}
/>
<FloatingLabelInput
name="clientSecret"
label={t('settingsPage.clientSecret')}
value={config.clientSecret}
placeholder={t('settingsPage.clientSecretPH')}
encrypted={true}
onChange={handleInputChange}
/>
<FloatingLabelInput
name="clientUsername"
label={t('settingsPage.clientUsername')}
value={config.clientUsername}
placeholder={t('settingsPage.clientUsernamePH')}
encrypted={false}
onChange={handleInputChange}
/>
<FloatingLabelInput
name="clientPassword"
label={t('settingsPage.clientPassword')}
value={config.clientPassword}
placeholder={t('settingsPage.clientPasswordPH')}
encrypted={true}
onChange={handleInputChange}
/>
<FloatingLabelInput
name="clientURL"
label={t('settingsPage.realmUrl')}
value={config.clientURL}
placeholder={t('settingsPage.realmUrlPH')}
encrypted={false}
onChange={handleInputChange}
/>
<Flex w='100%' justify='stretch' wrap='nowrap' align='center' mt='lg'>
<Button w='100%' onClick={handleDiscard} m='0.5rem'>{t('discard')}</Button>
<Button w='100%' type="submit" m='0.5rem'>{t('confirm')}</Button>
</Flex>
</form>
</>
);
};
export default OIDPSettingsForm;

View File

@ -0,0 +1,171 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import RetryError from '../shared/components/RetryError';
import RoleSelectFilter from '../shared/components/filters/RoleSelectFilter';
import CogwheelLoader from '../shared/components/loaders/CogwheelLoader';
import { GetRole } from '../services/frigate.proxy/frigate.schema';
import { isProduction } from '../shared/env.const';
import { Flex, Button } from '@mantine/core';
import { notifications } from '@mantine/notifications';
import { IconCircleCheck, IconAlertCircle } from '@tabler/icons-react';
import { v4 } from 'uuid';
interface Roles {
adminRole?: {
id?: string,
name?: string,
},
birdsEyeRole?: {
id?: string,
name?: string,
}
}
interface RolesSettingsFormProps {
allRoles: GetRole[]
}
const RolesSettingsForm: React.FC<RolesSettingsFormProps> = ({
allRoles
}) => {
const [roles, setRoles] = useState<Roles>()
const { t } = useTranslation()
const queryClient = useQueryClient()
const adminRoleKey = 'adminRole'
const birdsEyeRoleKey = 'birdsEyeRole'
const { isPending, isError, data, refetch } = useQuery({
queryKey: [frigateQueryKeys.getConfig, adminRoleKey, birdsEyeRoleKey],
queryFn: async () => {
const adminConfig = await frigateApi.getConfig(adminRoleKey)
const birdsEyeRoleConfig = await frigateApi.getConfig(birdsEyeRoleKey)
return {
adminRole: {
id: allRoles.find(role => role.name === adminConfig.value)?.id,
name: allRoles.find(role => role.name === adminConfig.value)?.name
},
birdsEyeRole: {
id: allRoles.find(role => role.name === birdsEyeRoleConfig.value)?.id,
name: allRoles.find(role => role.name === birdsEyeRoleConfig.value)?.name
},
}
},
})
const save = useMutation({
mutationFn: async () => {
if (roles?.adminRole?.name) {
const adminConfig = {
value: roles.adminRole.name,
key: adminRoleKey
}
const birdsEyeConfig = {
value: roles.birdsEyeRole?.name ? roles.birdsEyeRole.name : '',
key: birdsEyeRoleKey
}
frigateApi.putConfigs([adminConfig, birdsEyeConfig]).catch(error => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data)
}
return Promise.reject(error)
})
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getConfig, adminRoleKey, birdsEyeRoleKey] })
notifications.show({
id: v4(),
withCloseButton: true,
autoClose: 5000,
title: t('successfully'),
message: t('successfullySaved'),
color: 'green',
icon: <IconCircleCheck />
})
},
onError: (e) => {
notifications.show({
id: e.message,
withCloseButton: true,
autoClose: false,
title: t('error'),
message: e.message,
color: 'red',
icon: <IconAlertCircle />,
})
}
})
useEffect(() => {
setRoles(data)
}, [data])
const handleSelectAdmin = (roleId: string) => {
const role = allRoles.find(role => role.id === roleId)
setRoles({
...roles,
adminRole: {
id: role?.id,
name: role?.name
},
})
}
const handleSelectBirdsEye = (roleId: string) => {
const role = allRoles.find(role => role.id === roleId)
setRoles({
...roles,
birdsEyeRole: {
id: role?.id,
name: role?.name
},
})
}
const handleDiscard = () => {
refetch()
if (data) setRoles(data)
}
const handleSave = () => {
save.mutate()
}
useEffect(() => {
if (!isProduction) console.log('Roles:', roles)
}, [roles])
if (isPending) return <CogwheelLoader />
if (isError) return <RetryError onRetry={refetch} />
return (
<>
<RoleSelectFilter
label={t('settingsPage.adminRole')}
mt='lg'
searchable
error={!roles?.adminRole?.id ? "Pick at least one item" : undefined}
value={roles?.adminRole?.id ? roles.adminRole.id : null}
onChange={handleSelectAdmin}
/>
<RoleSelectFilter
label={t('settingsPage.birdseyeRole')}
mt='md'
searchable
value={roles?.birdsEyeRole?.id ? roles.birdsEyeRole.id : null}
onChange={handleSelectBirdsEye}
clearable
/>
<Flex w='100%' justify='stretch' wrap='nowrap' align='center' mt='lg'>
<Button w='100%' onClick={handleDiscard} m='0.5rem'>{t('discard')}</Button>
<Button w='100%' onClick={handleSave} m='0.5rem'>{t('confirm')}</Button>
</Flex>
</>
);
};
export default RolesSettingsForm;