fix roles

add state for useAdmin
add new request to cameras
add BlobImage
This commit is contained in:
NlightN22 2024-02-28 03:45:36 +07:00
parent 4ed2ac9e2e
commit 505dd09b43
12 changed files with 104 additions and 43 deletions

View File

@ -1,20 +1,31 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { frigateQueryKeys, frigateApi } from "../services/frigate.proxy/frigate.api"; import { frigateQueryKeys, frigateApi } from "../services/frigate.proxy/frigate.api";
import { useRealmAccessRoles } from "./useRealmAccessRoles"; import { useRealmAccessRoles } from "./useRealmAccessRoles";
import { useEffect, useState } from "react";
export const useAdminRole = () => { export const useAdminRole = () => {
const { data: adminConfig, isError, isPending } = useQuery({ const { data: adminConfig, isError, isFetching } = useQuery({
queryKey: [frigateQueryKeys.getAdminRole], queryKey: [frigateQueryKeys.getAdminRole],
queryFn: frigateApi.getAdminRole queryFn: frigateApi.getAdminRole,
staleTime: 1000 * 60 * 60,
gcTime: 1000 * 60 * 60 * 24,
}) })
const roles = useRealmAccessRoles() const roles = useRealmAccessRoles()
const [initialized, setInitialized] = useState(false)
const isLoading = isFetching || roles === undefined
if (isPending) return { isAdmin: undefined, isLoading: true } useEffect(() => {
if (isError) return { isAdmin: false, isError: true } if (!isLoading) {
if (!adminConfig) return { isAdmin: true } setInitialized(true);
if (adminConfig && !adminConfig.value) return { isAdmin: true } }
}, [isLoading]);
if (!initialized || isLoading) return { isAdmin: undefined, isLoading: true }
if (isError) return { isAdmin: false, isError: true, isLoading: false }
if (!adminConfig) return { isAdmin: true, isLoading: false }
if (adminConfig && !adminConfig.value) return { isAdmin: true, isLoading: false }
const isAdmin = roles.some(role => role === adminConfig.value) const isAdmin = roles.some(role => role === adminConfig.value)
return { isAdmin } return { isAdmin, isLoading: false }
} }

View File

@ -1,12 +1,11 @@
import React, { useCallback, useContext, useEffect, useRef } from 'react'; import React, { useCallback, useContext, useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Context } from '..'; import { Context } from '..';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api'; import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
import { Button, Flex, Text, useMantineTheme } from '@mantine/core'; import { Button, Flex, useMantineTheme } from '@mantine/core';
import { useClipboard } from '@mantine/hooks'; import { useClipboard } from '@mantine/hooks';
import { configureMonacoYaml } from "monaco-yaml"; import Editor, { Monaco } from '@monaco-editor/react'
import Editor, { DiffEditor, useMonaco, loader, Monaco } from '@monaco-editor/react'
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import CenterLoader from '../shared/components/loaders/CenterLoader'; import CenterLoader from '../shared/components/loaders/CenterLoader';
import RetryErrorPage from './RetryErrorPage'; import RetryErrorPage from './RetryErrorPage';

View File

@ -1,13 +1,11 @@
import axios from "axios" import axios from "axios"
import { proxyURL } from "../../shared/env.const" import { proxyURL } from "../../shared/env.const"
import { z } from "zod"
import { import {
GetConfig, DeleteFrigateHost, GetFrigateHost, PutConfig, PutFrigateHost, GetConfig, DeleteFrigateHost, GetFrigateHost, PutConfig, PutFrigateHost,
GetFrigateHostWithCameras, GetCameraWHost, GetCameraWHostWConfig, GetRole, GetCameraWHostWConfig, GetRole,
GetUserByRole, GetRoleWCameras, GetExportedFile GetRoleWCameras, GetExportedFile
} from "./frigate.schema"; } from "./frigate.schema";
import { FrigateConfig } from "../../types/frigateConfig"; import { FrigateConfig } from "../../types/frigateConfig";
import { url } from "inspector";
import { RecordSummary } from "../../types/record"; import { RecordSummary } from "../../types/record";
import { EventFrigate } from "../../types/event"; import { EventFrigate } from "../../types/event";
import { keycloakConfig } from "../.."; import { keycloakConfig } from "../..";
@ -42,12 +40,11 @@ export const frigateApi = {
getHosts: () => instanceApi.get<GetFrigateHost[]>('apiv1/frigate-hosts').then(res => { getHosts: () => instanceApi.get<GetFrigateHost[]>('apiv1/frigate-hosts').then(res => {
return res.data return res.data
}), }),
// getHostsWithCameras: () => instanceApi.get<GetFrigateHostWithCameras[]>('apiv1/frigate-hosts', { params: { include: 'cameras' } }).then(res => { // TODO change request to cameras
// return res.data getHost: (id: string) => instanceApi.get<GetFrigateHost>(`apiv1/frigate-hosts/${id}`).then(res => {
// }),
getHost: (id: string) => instanceApi.get<GetFrigateHostWithCameras>(`apiv1/frigate-hosts/${id}`).then(res => {
return res.data return 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), getCamerasWHost: () => instanceApi.get<GetCameraWHostWConfig[]>(`apiv1/cameras`).then(res => res.data),
getCameraWHost: (id: string) => instanceApi.get<GetCameraWHostWConfig>(`apiv1/cameras/${id}`).then(res => { return 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 => { putHosts: (hosts: PutFrigateHost[]) => instanceApi.put<GetFrigateHost[]>('apiv1/frigate-hosts', hosts).then(res => {
@ -181,6 +178,7 @@ export const frigateQueryKeys = {
getFrigateHost: 'frigate-host', getFrigateHost: 'frigate-host',
getCamerasWHost: 'cameras-frigate-host', getCamerasWHost: 'cameras-frigate-host',
getCameraWHost: 'camera-frigate-host', getCameraWHost: 'camera-frigate-host',
getCameraByHostId: 'camera-by-hostId',
getHostConfig: 'host-config', getHostConfig: 'host-config',
getRecordingsSummary: 'recordings-frigate-summary', getRecordingsSummary: 'recordings-frigate-summary',
getRecordings: 'recordings-frigate', getRecordings: 'recordings-frigate',

View File

@ -108,7 +108,7 @@ export const getExpotedFile = 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 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 }
export type GetCameraWHost = z.infer<typeof getCameraWithHostSchema> export type GetCameraWHost = z.infer<typeof getCameraWithHostSchema>
export type GetCameraWHostWConfig = GetCameraWHost & { config?: CameraConfig } export type GetCameraWHostWConfig = GetCameraWHost & { config?: CameraConfig }

View File

@ -6,6 +6,7 @@ import { strings } from '../../strings/strings';
import { unixTimeToDate, getDurationFromTimestamps } from '../../utils/dateUtil'; import { unixTimeToDate, getDurationFromTimestamps } from '../../utils/dateUtil';
import VideoPlayer from '../players/VideoPlayer'; import VideoPlayer from '../players/VideoPlayer';
import { EventFrigate } from '../../../types/event'; import { EventFrigate } from '../../../types/event';
import BlobImage from '../images/BlobImage';
interface EventPanelProps { interface EventPanelProps {
event: EventFrigate event: EventFrigate
@ -25,7 +26,7 @@ const EventPanel = ({
{playedValue === event.id && playerUrl ? <VideoPlayer videoUrl={playerUrl} /> : <></>} {playedValue === event.id && playerUrl ? <VideoPlayer videoUrl={playerUrl} /> : <></>}
<Flex w='100%' justify='space-between'> <Flex w='100%' justify='space-between'>
{!hostName ? <></> : {!hostName ? <></> :
<Image <BlobImage
maw={200} maw={200}
fit="contain" fit="contain"
withPlaceholder withPlaceholder
@ -48,7 +49,9 @@ const EventPanel = ({
<Text>{strings.player.object}: {event.label}</Text> <Text>{strings.player.object}: {event.label}</Text>
<Text>{strings.player.startTime}: {unixTimeToDate(event.start_time)}</Text> <Text>{strings.player.startTime}: {unixTimeToDate(event.start_time)}</Text>
<Text>{strings.player.duration}: {getDurationFromTimestamps(event.start_time, event.end_time)}</Text> <Text>{strings.player.duration}: {getDurationFromTimestamps(event.start_time, event.end_time)}</Text>
<Text>{strings.player.rating}: {(event.data.score * 100).toFixed(2)}%</Text> {!event.data?.score? <></> :
<Text>{strings.player.rating}: {(event.data.score * 100).toFixed(2)}%</Text>
}
</Flex> </Flex>
</Flex> </Flex>
</> </>

View File

@ -19,15 +19,15 @@ const CameraSelectFilter = ({
const { recordingsStore: recStore } = useContext(Context) const { recordingsStore: recStore } = useContext(Context)
const { data, isError, isPending, isSuccess, refetch } = useQuery({ const { data, isError, isPending, isSuccess, refetch } = useQuery({
queryKey: [frigateQueryKeys.getFrigateHost, selectedHostId], queryKey: [frigateQueryKeys.getCameraByHostId, selectedHostId],
queryFn: () => frigateApi.getHost(selectedHostId) queryFn: () => frigateApi.getCamerasByHostId(selectedHostId)
}) })
useEffect(() => { useEffect(() => {
if (!data) return if (!data) return
if (recStore.cameraIdParam) { if (recStore.cameraIdParam) {
console.log('change camera by param') console.log('change camera by param')
recStore.filteredCamera = data.cameras.find( camera => camera.id === recStore.cameraIdParam) recStore.filteredCamera = data.find( camera => camera.id === recStore.cameraIdParam)
recStore.cameraIdParam = undefined recStore.cameraIdParam = undefined
} }
}, [isSuccess]) }, [isSuccess])
@ -36,10 +36,10 @@ const CameraSelectFilter = ({
if (isError) return <RetryError onRetry={refetch}/> if (isError) return <RetryError onRetry={refetch}/>
if (!data) return null if (!data) return null
const camerasItems: OneSelectItem[] = data.cameras.map(camera => ({ value: camera.id, label: camera.name })) const camerasItems: OneSelectItem[] = data.map(camera => ({ value: camera.id, label: camera.name }))
const handleSelect = (value: string) => { const handleSelect = (value: string) => {
const camera = data.cameras.find(camera => camera.id === value) const camera = data.find(camera => camera.id === value)
if (!camera) { if (!camera) {
recStore.filteredCamera = undefined recStore.filteredCamera = undefined
return return

View File

@ -0,0 +1,51 @@
import { Image, ImageProps, Loader } from '@mantine/core';
import React, { useEffect, useState } from 'react';
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
import { useIntersection } from '@mantine/hooks';
import { useQuery } from '@tanstack/react-query';
import RetryError from '../RetryError';
interface BlobImageProps extends ImageProps {
src: string
}
const BlobImage = ({
src,
...rest
}: BlobImageProps) => {
const [imageSrc, setImageSrc] = useState<string | null>(null);
const { ref, entry } = useIntersection({ threshold: 0.1, })
const isVisible = entry?.isIntersecting
const { data: imageBlob, refetch, isPending, isError } = useQuery({
queryKey: [src],
queryFn: () => proxyApi.getImageFrigate(src),
staleTime: 60 * 1000,
gcTime: Infinity,
refetchInterval: isVisible ? 30 * 1000 : undefined,
});
useEffect(() => {
if (imageBlob && imageBlob instanceof Blob) {
const objectURL = URL.createObjectURL(imageBlob);
setImageSrc(objectURL);
return () => {
if (objectURL) {
URL.revokeObjectURL(objectURL);
}
}
}
}, [imageBlob])
if (isPending || !imageSrc) return <Loader />
if (isError) return <RetryError onRetry={refetch} />
return (
<Image src={imageSrc} {...rest} />
);
};
export default BlobImage;

View File

@ -2,8 +2,8 @@ import { ImageProps, Image, Center } from '@mantine/core';
import { IconPhotoOff } from '@tabler/icons-react'; import { IconPhotoOff } from '@tabler/icons-react';
import React from 'react'; import React from 'react';
const ImageWithPlaceHolder = (props: ImageProps & React.RefAttributes<HTMLDivElement>) => { const ImageWithPlaceHolder = (props: ImageProps) => {
if (props.src) return ( if (props.src && (typeof props.src === 'string')) return (
<Image {...props} /> <Image {...props} />
) )
return ( return (

View File

@ -1,6 +1,6 @@
import { makeAutoObservable } from "mobx" import { makeAutoObservable } from "mobx"
import { z } from "zod" import { z } from "zod"
import { GetCameraWHostWConfig, GetFrigateHost, GetFrigateHostWithCameras } from "../../services/frigate.proxy/frigate.schema" import { GetCameraWHostWConfig, GetFrigateHost } from "../../services/frigate.proxy/frigate.schema"
export type RecordForPlay = { export type RecordForPlay = {
hostName?: string // format 'localhost:4000' hostName?: string // format 'localhost:4000'

View File

@ -13,9 +13,9 @@ export interface EventFrigate {
retain_indefinitely: boolean; retain_indefinitely: boolean;
plus_id?: string; plus_id?: string;
model_hash?: string; model_hash?: string;
data: { data?: {
top_score: number; top_score?: number;
score: number; score?: number;
sub_label_score?: number; sub_label_score?: number;
region: number[]; region: number[];
box: number[]; box: number[];

View File

@ -26,7 +26,6 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
const [sortedName, setSortedName] = useState<string | null>(null) const [sortedName, setSortedName] = useState<string | null>(null)
useEffect(() => { useEffect(() => {
console.log('data changed')
setTableData(data) setTableData(data)
}, [data]) }, [data])

View File

@ -21,19 +21,19 @@ const SelectedHostList = ({
const { recordingsStore: recStore } = useContext(Context) const { recordingsStore: recStore } = useContext(Context)
const [openCameraId, setOpenCameraId] = useState<string | null>(null) const [openCameraId, setOpenCameraId] = useState<string | null>(null)
const { data: host, isPending: hostPending, isError: hostError, refetch: hostRefetch } = useQuery({ const { data: camerasQuery, isPending: hostPending, isError: hostError, refetch: hostRefetch } = useQuery({
queryKey: [frigateQueryKeys.getFrigateHost, hostId], queryKey: [frigateQueryKeys.getCameraByHostId, hostId],
queryFn: async () => { queryFn: async () => {
if (hostId) { if (hostId) {
return frigateApi.getHost(hostId) return frigateApi.getCamerasByHostId(hostId)
} }
return null return []
} }
}) })
const handleOnChange = (cameraId: string | null) => { const handleOnChange = (cameraId: string | null) => {
setOpenCameraId(openCameraId === cameraId ? null : cameraId) setOpenCameraId(openCameraId === cameraId ? null : cameraId)
recStore.openedCamera = host?.cameras.find( camera => camera.id === cameraId) recStore.openedCamera = camerasQuery?.find( camera => camera.id === cameraId)
} }
const handleRetry = () => { const handleRetry = () => {
@ -43,9 +43,9 @@ const SelectedHostList = ({
if (hostPending) return <CenterLoader /> if (hostPending) return <CenterLoader />
if (hostError) return <RetryErrorPage onRetry={handleRetry} /> if (hostError) return <RetryErrorPage onRetry={handleRetry} />
if (!host || host.cameras.length < 1) return null if (!camerasQuery || camerasQuery.length < 1) return null
const cameras = host.cameras.map(camera => { const camerasItems = camerasQuery.map(camera => {
return ( return (
<Accordion.Item key={camera.id + 'Item'} value={camera.id}> <Accordion.Item key={camera.id + 'Item'} value={camera.id}>
<Accordion.Control key={camera.id + 'Control'}>{strings.camera}: {camera.name}</Accordion.Control> <Accordion.Control key={camera.id + 'Control'}>{strings.camera}: {camera.name}</Accordion.Control>
@ -62,13 +62,13 @@ const SelectedHostList = ({
return ( return (
<Flex w='100%' h='100%' direction='column' align='center'> <Flex w='100%' h='100%' direction='column' align='center'>
<Text>{strings.host}: {host.name}</Text> <Text>{strings.host}: {camerasQuery[0].frigateHost?.name}</Text>
<Accordion <Accordion
mt='1rem' mt='1rem'
variant='separated' variant='separated'
radius="md" w='100%' radius="md" w='100%'
onChange={(value) => handleOnChange(value)}> onChange={(value) => handleOnChange(value)}>
{cameras} {camerasItems}
</Accordion> </Accordion>
</Flex> </Flex>
) )