change settings page
This commit is contained in:
parent
2dff7fa5f6
commit
131f5efac6
@ -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",
|
||||||
|
|||||||
@ -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: "Что-то пошло не так",
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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 }
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
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