diff --git a/src/locales/en.ts b/src/locales/en.ts
index e583163..c723011 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -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",
diff --git a/src/locales/ru.ts b/src/locales/ru.ts
index aade65b..0e59497 100644
--- a/src/locales/ru.ts
+++ b/src/locales/ru.ts
@@ -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: "Что-то пошло не так",
diff --git a/src/pages/AccessSettingsPage.tsx b/src/pages/AccessSettingsPage.tsx
index 1e6f8d4..f13566f 100644
--- a/src/pages/AccessSettingsPage.tsx
+++ b/src/pages/AccessSettingsPage.tsx
@@ -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
if (isError || !data) return
if (!isAdmin) return
- const rolesSelect: OneSelectItem[] = data.map(role => ({ value: role.id, label: role.name }))
const handleSelectRole = (value: string) => {
setRoleId(value)
diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx
index a2318ad..c6b8780 100644
--- a/src/pages/SettingsPage.tsx
+++ b/src/pages/SettingsPage.tsx
@@ -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(false)
+ const [allRoles, setAllRoles] = useState()
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:
- })
- },
- onError: (e) => {
- notifications.show({
- id: e.message,
- withCloseButton: true,
- autoClose: false,
- title: "Error",
- message: e.message,
- color: 'red',
- icon: ,
- })
+ 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) => {
- 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
- if (configPending || adminLoading) return
- if (configError) return
+ if (adminLoading) return
return (
{!isMobile ?
< Space w='20%' />
- : <>>
+ : null
}
-
+
+ {
+ showRoles && allRoles && allRoles.length > 0 ?
+
+ : null
+ }
{!isMobile ?
- : <>>
+ : null
}
);
diff --git a/src/services/frigate.proxy/frigate.api.ts b/src/services/frigate.proxy/frigate.api.ts
index 6b8ec98..7a3f03f 100644
--- a/src/services/frigate.proxy/frigate.api.ts
+++ b/src/services/frigate.proxy/frigate.api.ts
@@ -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('apiv1/config').then(res => res.data),
- putConfig: (config: PutConfig[]) => instanceApi.put('apiv1/config', config).then(res => res.data),
+ getAllConfig: () => instanceApi.get('apiv1/config').then(res => res.data),
+ getConfig: (key: string) => instanceApi.get(`apiv1/config/${key}`).then(res => res.data),
+ getOIDPConfig: () => instanceApi.get('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('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',
diff --git a/src/services/frigate.proxy/frigate.schema.ts b/src/services/frigate.proxy/frigate.schema.ts
index d19fce7..ce5860a 100644
--- a/src/services/frigate.proxy/frigate.schema.ts
+++ b/src/services/frigate.proxy/frigate.schema.ts
@@ -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
export type PutConfig = z.infer
+export type OIDPConfig = z.infer
export type GetFrigateHost = z.infer
// export type GetFrigateHostWithCameras = z.infer
export type GetFrigateHostWConfig = GetFrigateHost & { config: FrigateConfig }
diff --git a/src/shared/components/filters/RoleSelectFilter.tsx b/src/shared/components/filters/RoleSelectFilter.tsx
index 7ccf4c1..2c94eac 100644
--- a/src/shared/components/filters/RoleSelectFilter.tsx
+++ b/src/shared/components/filters/RoleSelectFilter.tsx
@@ -33,9 +33,9 @@ const RoleSelectFilter: React.FC = ({
return (
);
diff --git a/src/shared/components/inputs/FloatingLabelInput.tsx b/src/shared/components/inputs/FloatingLabelInput.tsx
index e8d9cec..abff035 100644
--- a/src/shared/components/inputs/FloatingLabelInput.tsx
+++ b/src/shared/components/inputs/FloatingLabelInput.tsx
@@ -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 = ({
value: propVal,
onChangeValue,
encrypted,
- ecryptedValue,
...rest
}) => {
const [focused, setFocused] = useState(false);
diff --git a/src/widgets/OIDPSettingsForm.tsx b/src/widgets/OIDPSettingsForm.tsx
new file mode 100644
index 0000000..21d17a7
--- /dev/null
+++ b/src/widgets/OIDPSettingsForm.tsx
@@ -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 = ({
+ isConfigValid
+}) => {
+ const { t } = useTranslation()
+ const queryClient = useQueryClient()
+
+ const [config, setConfig] = useState({
+ clientId: '',
+ clientSecret: '',
+ clientUsername: '',
+ clientPassword: '',
+ clientURL: ''
+ });
+
+ const { isPending, isError, data, refetch } = useQuery({
+ 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:
+ })
+ },
+ onError: (e) => {
+ notifications.show({
+ id: e.message,
+ withCloseButton: true,
+ autoClose: false,
+ title: t('error'),
+ message: e.message,
+ color: 'red',
+ icon: ,
+ })
+ }
+ })
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setConfig((prevConfig) => ({
+ ...prevConfig,
+ [name]: value,
+ }));
+ };
+
+ const handleSubmit = (event: React.FormEvent) => {
+ event.preventDefault()
+ mutation.mutate(config);
+ }
+
+ const handleDiscard = () => {
+ if (!isProduction) console.log('Discard changes')
+ refetch()
+ if (data) setConfig(data)
+ }
+
+ if (isPending) return
+ if (isError) return
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default OIDPSettingsForm;
\ No newline at end of file
diff --git a/src/widgets/RolesSettingsForm.tsx b/src/widgets/RolesSettingsForm.tsx
new file mode 100644
index 0000000..69342cc
--- /dev/null
+++ b/src/widgets/RolesSettingsForm.tsx
@@ -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 = ({
+ allRoles
+}) => {
+ const [roles, setRoles] = useState()
+ 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:
+ })
+ },
+ onError: (e) => {
+ notifications.show({
+ id: e.message,
+ withCloseButton: true,
+ autoClose: false,
+ title: t('error'),
+ message: e.message,
+ color: 'red',
+ icon: ,
+ })
+ }
+ })
+
+ 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
+ if (isError) return
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+};
+
+export default RolesSettingsForm;
\ No newline at end of file