fix tags handlers and schemas
This commit is contained in:
parent
90114ac7a6
commit
f50bfa6cb6
@ -132,8 +132,6 @@ const EditCameraPage = () => {
|
||||
|
||||
const handleSave = () => {
|
||||
if (!selectedMask || !points) return
|
||||
console.log('type', selectedMask?.type)
|
||||
console.log('save', points)
|
||||
mutate()
|
||||
}
|
||||
|
||||
|
||||
@ -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/`
|
||||
|
||||
@ -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>
|
||||
|
||||
71
src/shared/components/AddBadge.tsx
Normal file
71
src/shared/components/AddBadge.tsx
Normal 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;
|
||||
49
src/shared/components/TagBadge.tsx
Normal file
49
src/shared/components/TagBadge.tsx
Normal 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;
|
||||
@ -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])
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -23,4 +27,5 @@ export const mapUserTagsToSelectItems = (tags: GetUserTag[]): SelectItem[] => {
|
||||
value: tag.id,
|
||||
label: tag.value
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 >
|
||||
);
|
||||
|
||||
107
src/widgets/CameraTagsList.tsx
Normal file
107
src/widgets/CameraTagsList.tsx
Normal 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;
|
||||
Loading…
Reference in New Issue
Block a user