change settings page
This commit is contained in:
parent
2dff7fa5f6
commit
131f5efac6
@ -1,3 +1,5 @@
|
||||
import { error } from "console"
|
||||
|
||||
const en = {
|
||||
editCameraPage: {
|
||||
notFrigateCamera: 'Not frigate camera',
|
||||
@ -14,6 +16,20 @@ const en = {
|
||||
saveAndRestart: 'Save & Restart',
|
||||
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: {
|
||||
cameraStats: 'Cameras stats',
|
||||
storageStats: 'Storages stats',
|
||||
@ -110,6 +126,9 @@ const en = {
|
||||
goToMainPage: "Return to main page",
|
||||
retry: "Retry",
|
||||
youCanRetryOrGoToMain: "You can retry or return to the main page",
|
||||
successfully: "Sucessfully",
|
||||
successfullySaved: "Sucessfully saved",
|
||||
error: "Error",
|
||||
errors: {
|
||||
emptyResponse: 'Empty response',
|
||||
somthingGoesWrong: "Something went wrong",
|
||||
|
||||
@ -14,6 +14,19 @@ const ru = {
|
||||
saveAndRestart: 'Сохранить & Перезагрузить',
|
||||
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: {
|
||||
cameraStats: 'Статистика Камер',
|
||||
storageStats: 'Статистика Хранения',
|
||||
@ -110,6 +123,9 @@ const ru = {
|
||||
goToMainPage: "Вернуться на главную",
|
||||
retry: "Повторить",
|
||||
youCanRetryOrGoToMain: "Вы можете повторить или вернуться на главную",
|
||||
successfully: "Успешно",
|
||||
successfullySaved: "Успешно сохранено",
|
||||
error: "Ошибка",
|
||||
errors: {
|
||||
emptyResponse: 'Пустой ответ',
|
||||
somthingGoesWrong: "Что-то пошло не так",
|
||||
|
||||
@ -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 { useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
@ -8,13 +8,12 @@ import { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
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 { dimensions } from '../shared/dimensions/dimensions';
|
||||
import { isProduction } from '../shared/env.const';
|
||||
import Forbidden from './403';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import RoleSelectFilter from '../shared/components/filters/RoleSelectFilter';
|
||||
|
||||
const AccessSettings = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -42,7 +41,6 @@ const AccessSettings = () => {
|
||||
if (isPending || adminLoading) return <CenterLoader />
|
||||
if (isError || !data) return <RetryErrorPage onRetry={refetch} />
|
||||
if (!isAdmin) return <Forbidden />
|
||||
const rolesSelect: OneSelectItem[] = data.map(role => ({ value: role.id, label: role.name }))
|
||||
|
||||
const handleSelectRole = (value: string) => {
|
||||
setRoleId(value)
|
||||
|
||||
@ -1,35 +1,25 @@
|
||||
import { Button, Flex, Space } from '@mantine/core';
|
||||
import { Flex, Space } from '@mantine/core';
|
||||
import { useMediaQuery } from '@mantine/hooks';
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
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 { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
import { GetConfig, PutConfig } from '../services/frigate.proxy/frigate.schema';
|
||||
import { FloatingLabelInput } from '../shared/components/inputs/FloatingLabelInput';
|
||||
import { frigateApi } from '../services/frigate.proxy/frigate.api';
|
||||
import { GetRole } from '../services/frigate.proxy/frigate.schema';
|
||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||
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 RetryErrorPage from './RetryErrorPage';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { v4 } from 'uuid';
|
||||
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
||||
|
||||
const SettingsPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
const queryClient = useQueryClient()
|
||||
const { isPending: configPending, error: configError, data, refetch } = useQuery({
|
||||
queryKey: [frigateQueryKeys.getConfig],
|
||||
queryFn: frigateApi.getConfig,
|
||||
})
|
||||
|
||||
const [showRoles, setShowRoles] = useState<boolean>(false)
|
||||
const [allRoles, setAllRoles] = useState<GetRole[]>()
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
useEffect(() => {
|
||||
@ -43,127 +33,40 @@ const SettingsPage = () => {
|
||||
|
||||
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 mutation = useMutation({
|
||||
mutationFn: (config: PutConfig[]) => frigateApi.putConfig(config).catch(error => {
|
||||
if (error.response && error.response.data) {
|
||||
return Promise.reject(error.response.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 />,
|
||||
})
|
||||
mutationFn: frigateApi.getRoles,
|
||||
onSuccess: (data) => {
|
||||
setAllRoles(data)
|
||||
}
|
||||
})
|
||||
|
||||
const handleDiscard = () => {
|
||||
if (!isProduction) console.log('Discard changes')
|
||||
refetch()
|
||||
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);
|
||||
const handleOIDPConfigValid = (valid: boolean) => {
|
||||
setShowRoles(valid)
|
||||
mutation.mutate()
|
||||
}
|
||||
|
||||
if (!isAdmin) return <Forbidden />
|
||||
if (configPending || adminLoading) return <CenterLoader />
|
||||
if (configError) return <RetryErrorPage onRetry={refetch} />
|
||||
if (adminLoading) return <CenterLoader />
|
||||
|
||||
return (
|
||||
<Flex h='100%'>
|
||||
{!isMobile ?
|
||||
< Space w='20%' />
|
||||
: <></>
|
||||
: null
|
||||
}
|
||||
<Flex direction='column' h='100%' w='100%' justify='stretch'>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{!configs ? <></>
|
||||
:
|
||||
configs.map((config) => (
|
||||
<FloatingLabelInput
|
||||
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>
|
||||
<OIDPSettingsForm isConfigValid={handleOIDPConfigValid} />
|
||||
{
|
||||
showRoles && allRoles && allRoles.length > 0 ?
|
||||
<RolesSettingsForm allRoles={allRoles} />
|
||||
: null
|
||||
}
|
||||
</Flex>
|
||||
{!isMobile ?
|
||||
<Space w='20%' />
|
||||
: <></>
|
||||
: null
|
||||
}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@ -3,7 +3,9 @@ import { proxyURL } from "../../shared/env.const"
|
||||
import {
|
||||
GetConfig, DeleteFrigateHost, GetFrigateHost, PutConfig, PutFrigateHost,
|
||||
GetCameraWHostWConfig, GetRole,
|
||||
GetRoleWCameras, GetExportedFile, recordingSchema
|
||||
GetRoleWCameras, GetExportedFile, recordingSchema,
|
||||
oidpConfig,
|
||||
OIDPConfig
|
||||
} from "./frigate.schema";
|
||||
import { FrigateConfig } from "../../types/frigateConfig";
|
||||
import { RecordSummary } from "../../types/record";
|
||||
@ -31,8 +33,12 @@ instanceApi.interceptors.request.use(
|
||||
);
|
||||
|
||||
export const frigateApi = {
|
||||
getConfig: () => instanceApi.get<GetConfig[]>('apiv1/config').then(res => res.data),
|
||||
putConfig: (config: PutConfig[]) => instanceApi.put('apiv1/config', config).then(res => res.data),
|
||||
getAllConfig: () => instanceApi.get<GetConfig[]>('apiv1/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 => {
|
||||
return res.data
|
||||
}),
|
||||
@ -212,7 +218,9 @@ export const mapHostToHostname = (host?: GetFrigateHost): string | undefined =>
|
||||
}
|
||||
|
||||
export const frigateQueryKeys = {
|
||||
getConfig: 'config',
|
||||
getAllConfig: 'getAllConfig',
|
||||
getConfig: 'getConfig',
|
||||
getOIDPConfig: 'OIDPconfig',
|
||||
getFrigateHosts: 'frigate-hosts',
|
||||
getFrigateHostsConfigs: 'frigate-hosts-configs',
|
||||
getFrigateHost: 'frigate-host',
|
||||
|
||||
@ -6,6 +6,14 @@ export const putConfigSchema = z.object({
|
||||
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({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
@ -115,6 +123,7 @@ export const recordingSchema = z.object({
|
||||
|
||||
export type GetConfig = z.infer<typeof getConfigSchema>
|
||||
export type PutConfig = z.infer<typeof putConfigSchema>
|
||||
export type OIDPConfig = z.infer<typeof oidpConfig>
|
||||
export type GetFrigateHost = z.infer<typeof getFrigateHostSchema>
|
||||
// export type GetFrigateHostWithCameras = z.infer<typeof getFrigateHostWithCamerasSchema>
|
||||
export type GetFrigateHostWConfig = GetFrigateHost & { config: FrigateConfig }
|
||||
|
||||
@ -33,9 +33,9 @@ const RoleSelectFilter: React.FC<RoleSelectFilterProps> = ({
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={roleId}
|
||||
{...selectProps}
|
||||
data={rolesSelect}
|
||||
value={roleId}
|
||||
onChange={handleSelectRole}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -5,7 +5,6 @@ import classes from './FloatingLabelInput.module.css';
|
||||
interface FloatingLabelInputProps extends TextInputProps {
|
||||
value?: string
|
||||
encrypted?: boolean
|
||||
ecryptedValue?: string
|
||||
onChangeValue?: (key: string, value: string) => void
|
||||
}
|
||||
|
||||
@ -13,7 +12,6 @@ export const FloatingLabelInput: React.FC<FloatingLabelInputProps> = ({
|
||||
value: propVal,
|
||||
onChangeValue,
|
||||
encrypted,
|
||||
ecryptedValue,
|
||||
...rest
|
||||
}) => {
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
152
src/widgets/OIDPSettingsForm.tsx
Normal file
152
src/widgets/OIDPSettingsForm.tsx
Normal 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;
|
||||
171
src/widgets/RolesSettingsForm.tsx
Normal file
171
src/widgets/RolesSettingsForm.tsx
Normal 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;
|
||||
Loading…
Reference in New Issue
Block a user