fix tags handlers and schemas

This commit is contained in:
NlightN22 2024-09-30 15:41:11 +07:00
parent 90114ac7a6
commit f50bfa6cb6
9 changed files with 260 additions and 29 deletions

View File

@ -132,8 +132,6 @@ const EditCameraPage = () => {
const handleSave = () => {
if (!selectedMask || !points) return
console.log('type', selectedMask?.type)
console.log('save', points)
mutate()
}

View File

@ -5,7 +5,9 @@ import {
GetCameraWHostWConfig, GetRole,
GetRoleWCameras, GetExportedFile, recordingSchema,
oidpConfig,
OIDPConfig
OIDPConfig,
GetCameraWHost,
GetCamera
} from "./frigate.schema";
import { FrigateConfig } from "../../types/frigateConfig";
import { RecordSummary } from "../../types/record";
@ -40,21 +42,13 @@ export const frigateApi = {
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
}),
getHost: (id: string) => instanceApi.get<GetFrigateHost>(`apiv1/frigate-hosts/${id}`).then(res => {
return res.data
}),
getHosts: () => instanceApi.get<GetFrigateHost[]>('apiv1/frigate-hosts').then(res => res.data),
getHost: (id: string) => instanceApi.get<GetFrigateHost>(`apiv1/frigate-hosts/${id}`).then(res => res.data),
getCamerasByHostId: (hostId: string) => instanceApi.get<GetCameraWHostWConfig[]>(`apiv1/cameras/host/${hostId}`).then(res => res.data),
getCamerasWHost: () => instanceApi.get<GetCameraWHostWConfig[]>(`apiv1/cameras`).then(res => res.data),
getCameraWHost: (id: string) => instanceApi.get<GetCameraWHostWConfig>(`apiv1/cameras/${id}`).then(res => { return res.data }),
putHosts: (hosts: PutFrigateHost[]) => instanceApi.put<GetFrigateHost[]>('apiv1/frigate-hosts', hosts).then(res => {
return res.data
}),
deleteHosts: (hosts: DeleteFrigateHost[]) => instanceApi.delete<GetFrigateHost[]>('apiv1/frigate-hosts', { data: hosts }).then(res => {
return res.data
}),
getCameraWHost: (id: string) => instanceApi.get<GetCameraWHostWConfig>(`apiv1/cameras/${id}`).then(res => res.data),
putHosts: (hosts: PutFrigateHost[]) => instanceApi.put<GetFrigateHost[]>('apiv1/frigate-hosts', hosts).then(res => res.data),
deleteHosts: (hosts: DeleteFrigateHost[]) => instanceApi.delete<GetFrigateHost[]>('apiv1/frigate-hosts', { data: hosts }).then(res => res.data),
getRoles: () => instanceApi.get<GetRole[]>('apiv1/roles').then(res => res.data),
putRoles: () => instanceApi.put<GetRole[]>('apiv1/roles').then(res => res.data),
putRoleWCameras: (roleId: string, cameraIDs: string[]) => instanceApi.put<GetRoleWCameras>(`apiv1/roles/${roleId}/cameras`,
@ -64,7 +58,9 @@ export const frigateApi = {
getAdminRole: () => instanceApi.get<GetConfig>('apiv1/config/admin').then(res => res.data),
getUserTags: () => instanceApi.get<GetUserTag[]>('apiv1/tags').then(res => res.data),
putUserTag: (tag: PutUserTag) => instanceApi.put<GetUserTag>('apiv1/tags', tag).then(res => res.data),
delUserTag: (tagId: string) => instanceApi.delete<GetUserTag>(`apiv1/tags/${tagId}`).then(res => res.data)
delUserTag: (tagId: string) => instanceApi.delete<GetUserTag>(`apiv1/tags/${tagId}`).then(res => res.data),
putTagToCamera: (cameraId: string, tagId: string) => instanceApi.put<GetCamera>(`apiv1/cameras/${cameraId}/tag/${tagId}`).then(res => res.data),
deleteTagFromCamera: (cameraId: string, tagId: string) => instanceApi.delete<GetCamera>(`apiv1/cameras/${cameraId}/tag/${tagId}`).then(res => res.data),
}
export const proxyPrefix = `${proxyURL.protocol}//${proxyURL.host}/proxy/`

View File

@ -1,5 +1,6 @@
import { z } from "zod";
import { CameraConfig, FrigateConfig } from "../../types/frigateConfig";
import { cameraTag } from "../../types/tags";
export const putConfigSchema = z.object({
key: z.string(),
@ -48,6 +49,7 @@ const getCameraSchema = z.object({
name: z.string(),
url: z.string(),
state: z.boolean().nullable(),
tags: cameraTag.array()
});
export const getRoleSchema = z.object({
@ -127,6 +129,7 @@ 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 }
export type GetCamera = z.infer<typeof getCameraSchema>
export type GetCameraWHost = z.infer<typeof getCameraWithHostSchema>
export type GetCameraWHostWConfig = GetCameraWHost & { config?: CameraConfig }
export type PutFrigateHost = z.infer<typeof putFrigateHostSchema>

View File

@ -0,0 +1,71 @@
import { ActionIcon, Badge, Button, Menu, rem } from '@mantine/core';
import { IconPlus } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import { frigateQueryKeys, frigateApi } from '../../services/frigate.proxy/frigate.api';
import CogwheelLoader from './loaders/CogwheelLoader';
import RetryError from './RetryError';
interface AddBadgeProps {
onClick?(tagId: string): void,
}
const AddBadge: React.FC<AddBadgeProps> = ({
onClick
}) => {
const { data, isPending, isError, refetch } = useQuery({
queryKey: [frigateQueryKeys.getUserTags],
queryFn: frigateApi.getUserTags
})
const handleClick = (tagId: string) => {
if (onClick) onClick(tagId)
}
if (isPending) return <CogwheelLoader />
if (isError) return <RetryError onRetry={refetch} />
if (!data || data.length < 1) return (
<Badge
mt='0.2rem'
variant="outline"
>
<ActionIcon size="xs" color="blue" radius="xl" variant="transparent">
<IconPlus size={rem(20)} />
</ActionIcon>
</Badge>
)
return (
<Menu
shadow="md"
width={200}
transitionProps={{ transition: 'pop-top-right' }}
position="top-end"
withinPortal
>
<Menu.Target>
<Badge
mt='0.2rem'
variant="outline"
>
<ActionIcon size="xs" color="blue" radius="xl" variant="transparent">
<IconPlus size={rem(20)} />
</ActionIcon>
</Badge>
</Menu.Target>
<Menu.Dropdown>
{data.map((item) => (
<Menu.Item key={item.id} onClick={() => handleClick(item.id)}>
{item.value}
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
);
};
export default AddBadge;

View File

@ -0,0 +1,49 @@
import { ActionIcon, rem, Badge } from '@mantine/core';
import { IconX } from '@tabler/icons-react';
import React from 'react';
const RemoveButton = ({ onClick }: { onClick(): void }) => {
return (
<ActionIcon onClick={onClick} size="xs" color="blue" radius="xl" variant="transparent">
<IconX size={rem(10)} />
</ActionIcon>
);
};
interface TagBadgeProps {
value: string,
label: string,
onClick?(value: string): void,
onClose?(value: string): void,
}
const TagBadge: React.FC<TagBadgeProps> = ({
value,
label,
onClick,
onClose,
}) => {
const handleClick = (value: string) => {
if (onClick) onClick(value)
}
const handleClose = () => {
if (onClose) onClose(value)
}
return (
<Badge
mt='0.2rem'
mr='0.3rem'
variant="outline"
pr={3}
rightSection={<RemoveButton onClick={handleClose} />}
onClick={() => handleClick}
>
{label}
</Badge>
);
};
export default TagBadge;

View File

@ -43,7 +43,7 @@ const UserTagsFilter = () => {
return true
}
const { mutate } = useMutation({
const { mutate: addTag } = useMutation({
mutationFn: (newTag: PutUserTag) => frigateApi.putUserTag(newTag)
.catch(error => {
if (error.response && error.response.data) {
@ -52,8 +52,8 @@ const UserTagsFilter = () => {
return Promise.reject(error)
}),
onSuccess: (data) => {
setSelectedList([...selectedList, data.id])
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getUserTags] })
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getCamerasWHost] })
},
onError: (e) => {
if (e && e.message) {
@ -67,7 +67,6 @@ const UserTagsFilter = () => {
icon: <IconAlertCircle />,
})
}
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
}
})
@ -81,6 +80,7 @@ const UserTagsFilter = () => {
}),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getUserTags] })
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getCamerasWHost] })
const updatedList = selectedList.filter(item => data.id === item)
setSelectedList(updatedList)
},
@ -103,9 +103,8 @@ const UserTagsFilter = () => {
const saveNewTag = (value: string) => {
const newTag: PutUserTag = {
value,
cameraIds: []
}
mutate(newTag)
addTag(newTag)
}
const onCreate = (query: string | SelectItem | null | undefined) => {
@ -128,7 +127,6 @@ const UserTagsFilter = () => {
if (isError) return <RetryError onRetry={refetch} />
const handleOnChange = (value: string[]) => {
console.log('cahnged:', value)
const updatedList = selectedList.filter(item => value.includes(item))
const newItems = value.filter(item => !selectedList.includes(item))
setSelectedList([...updatedList, ...newItems])

View File

@ -3,7 +3,6 @@ import { z } from "zod";
export const putUserTag = z.object({
value: z.string(),
cameraIds: z.string().array()
})
export const getUserTag = z.object({
@ -12,9 +11,14 @@ export const getUserTag = z.object({
updatedAt: z.string().datetime(),
value: z.string(),
userId: z.string(),
cameraIds: z.string().array(),
})
export const cameraTag = z.object({
id: z.string(),
value: z.string(),
})
export type CameraTag = z.infer<typeof cameraTag>
export type GetUserTag = z.infer<typeof getUserTag>
export type PutUserTag = z.infer<typeof putUserTag>
@ -24,3 +28,4 @@ export const mapUserTagsToSelectItems = (tags: GetUserTag[]): SelectItem[] => {
label: tag.value
}))
}

View File

@ -1,14 +1,15 @@
import { Button, Card, Flex, Grid, Group, Text, createStyles } from '@mantine/core';
import { useIntersection } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useAdminRole } from '../hooks/useAdminRole';
import { recordingsPageQuery } from '../pages/RecordingsPage';
import { routesPath } from '../router/routes.path';
import { mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
import AutoUpdatedImage from '../shared/components/images/AutoUpdatedImage';
import { useTranslation } from 'react-i18next';
import { useAdminRole } from '../hooks/useAdminRole';
import CameraTagsList from './CameraTagsList';
const useStyles = createStyles((theme) => ({
mainCard: {
@ -55,7 +56,7 @@ const CameraCard = ({
useEffect(() => {
if (entry?.isIntersecting)
setRenderImage(true)
}, [entry?.isIntersecting])
}, [entry?.isIntersecting])
const handleOpenLiveView = () => {
const url = routesPath.LIVE_PATH.replace(':id', camera.id)
@ -73,6 +74,8 @@ const CameraCard = ({
}
}
return (
<Grid.Col md={6} lg={3} p='0.2rem'>
<Card ref={ref} h='100%' radius="lg" padding='0.5rem' className={classes.mainCard}>
@ -87,6 +90,7 @@ const CameraCard = ({
{!isAdmin ? null : <Button size='sm' onClick={handleOpenEditCamera}>{t('edit')}</Button>}
</Flex>
</Group>
<CameraTagsList camera={camera} />
</Card>
</Grid.Col >
);

View File

@ -0,0 +1,107 @@
import { Flex } from '@mantine/core';
import { dataTagSymbol, useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
import AddBadge from '../shared/components/AddBadge';
import TagBadge from '../shared/components/TagBadge';
import { CameraTag, PutUserTag } from '../types/tags';
import { notifications } from '@mantine/notifications';
import { IconAlertCircle } from '@tabler/icons-react';
interface CameraTagsListProps {
camera: GetCameraWHostWConfig
}
const CameraTagsList: React.FC<CameraTagsListProps> = ({
camera
}) => {
const [tagsList, setTagsList] = useState<CameraTag[]>(camera.tags)
useEffect(()=>{
setTagsList(camera.tags)
},[camera])
const { mutate: addTagToCamera } = useMutation({
mutationFn: async (tagId: string) => frigateApi.putTagToCamera(camera.id, tagId)
.catch(error => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data)
}
return Promise.reject(error)
}),
onSuccess: (data) => {
setTagsList(data.tags)
},
onError: (e) => {
if (e && e.message) {
notifications.show({
id: e.message,
withCloseButton: true,
autoClose: 5000,
title: "Error",
message: e.message,
color: 'red',
icon: <IconAlertCircle />,
})
}
}
})
const { mutate: deleteTagFromCamera } = useMutation({
mutationFn: (tagId: string) => frigateApi.deleteTagFromCamera(camera.id, tagId)
.catch(error => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data)
}
return Promise.reject(error)
}),
onSuccess: (data) => setTagsList(data.tags),
onError: (e) => {
if (e && e.message) {
notifications.show({
id: e.message,
withCloseButton: true,
autoClose: 5000,
title: "Error",
message: e.message,
color: 'red',
icon: <IconAlertCircle />,
})
}
}
})
const handleAddTagClick = (tagId: string) => {
if (tagId) addTagToCamera(tagId)
}
const handleDeleteTagClick = (tagId: string) => {
if (tagId) deleteTagFromCamera(tagId)
}
return (
<Flex justify='end' w='100%' wrap="wrap">
{
tagsList && tagsList.length > 0 ? tagsList.map(tag => (
<TagBadge
key={tag.id}
value={tag.id}
label={tag.value}
onClose={handleDeleteTagClick}
/>
))
: <></>
}
<AddBadge
onClick={handleAddTagClick}
/>
</Flex>
);
};
export default CameraTagsList;