fix roles
add state for useAdmin add new request to cameras add BlobImage
This commit is contained in:
parent
4ed2ac9e2e
commit
505dd09b43
@ -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 }
|
}
|
||||||
const isAdmin = roles.some(role => role === adminConfig.value)
|
}, [isLoading]);
|
||||||
return { isAdmin }
|
|
||||||
|
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)
|
||||||
|
return { isAdmin, isLoading: false }
|
||||||
}
|
}
|
||||||
@ -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';
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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 }
|
||||||
|
|||||||
@ -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>
|
||||||
|
{!event.data?.score? <></> :
|
||||||
<Text>{strings.player.rating}: {(event.data.score * 100).toFixed(2)}%</Text>
|
<Text>{strings.player.rating}: {(event.data.score * 100).toFixed(2)}%</Text>
|
||||||
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
51
src/shared/components/images/BlobImage.tsx
Normal file
51
src/shared/components/images/BlobImage.tsx
Normal 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;
|
||||||
@ -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 (
|
||||||
|
|||||||
@ -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'
|
||||||
|
|||||||
@ -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[];
|
||||||
|
|||||||
@ -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])
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user