add admin role control
This commit is contained in:
parent
093cdb97e7
commit
4ed2ac9e2e
@ -28,6 +28,7 @@
|
|||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"embla-carousel-react": "^8.0.0-rc10",
|
"embla-carousel-react": "^8.0.0-rc10",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"mantine-react-table": "^1.0.0-beta.25",
|
"mantine-react-table": "^1.0.0-beta.25",
|
||||||
"mobx": "^6.9.0",
|
"mobx": "^6.9.0",
|
||||||
"mobx-react-lite": "^3.4.3",
|
"mobx-react-lite": "^3.4.3",
|
||||||
|
|||||||
15
src/App.tsx
15
src/App.tsx
@ -7,10 +7,18 @@ import { getCookie, setCookie } from 'cookies-next';
|
|||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import AppBody from './AppBody';
|
import AppBody from './AppBody';
|
||||||
import Forbidden from './pages/403';
|
import Forbidden from './pages/403';
|
||||||
import { QueryClient } from '@tanstack/react-query';
|
|
||||||
import { Notifications } from '@mantine/notifications';
|
import { Notifications } from '@mantine/notifications';
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
refetchOnWindowFocus: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const systemColorScheme = useColorScheme()
|
const systemColorScheme = useColorScheme()
|
||||||
const [colorScheme, setColorScheme] = useState<ColorScheme>(getCookie('mantine-color-scheme') as ColorScheme || systemColorScheme);
|
const [colorScheme, setColorScheme] = useState<ColorScheme>(getCookie('mantine-color-scheme') as ColorScheme || systemColorScheme);
|
||||||
@ -20,7 +28,8 @@ function App() {
|
|||||||
setCookie('mantine-color-scheme', nextColorScheme, { maxAge: 60 * 60 * 24 * 30 });
|
setCookie('mantine-color-scheme', nextColorScheme, { maxAge: 60 * 60 * 24 * 30 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const auth = useAuth();
|
const auth = useAuth()
|
||||||
|
|
||||||
// automatically sign-in
|
// automatically sign-in
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasAuthParams() &&
|
if (!hasAuthParams() &&
|
||||||
@ -38,6 +47,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
|
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
|
||||||
<MantineProvider
|
<MantineProvider
|
||||||
@ -69,6 +79,7 @@ function App() {
|
|||||||
</MantineProvider >
|
</MantineProvider >
|
||||||
</ColorSchemeProvider>
|
</ColorSchemeProvider>
|
||||||
</div>
|
</div>
|
||||||
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@ -1,28 +1,19 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { AppShell, useMantineTheme, } from "@mantine/core"
|
import { AppShell, useMantineTheme, } from "@mantine/core"
|
||||||
import { HeaderAction } from './widgets/header/HeaderAction';
|
import { HeaderAction } from './widgets/header/HeaderAction';
|
||||||
import { headerLinks } from './widgets/header/header.links';
|
import { headerLinks } from './widgets/header/header.links';
|
||||||
import AppRouter from './router/AppRouter';
|
import AppRouter from './router/AppRouter';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
||||||
import { Context } from '.';
|
import { Context } from '.';
|
||||||
import SideBar from './shared/components/SideBar';
|
import SideBar from './shared/components/SideBar';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
queries: {
|
|
||||||
refetchOnWindowFocus: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const AppBody = () => {
|
const AppBody = () => {
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
|
||||||
const [leftSideBar, setLeftSidebar] = useState(false)
|
const [leftSideBar, setLeftSidebar] = useState(false)
|
||||||
const [rightSideBar, setRightSidebar] = useState(false)
|
const [rightSideBar, setRightSidebar] = useState(false)
|
||||||
|
|
||||||
|
|
||||||
const leftSideBarIsHidden = (isHidden: boolean) => {
|
const leftSideBarIsHidden = (isHidden: boolean) => {
|
||||||
setLeftSidebar(!isHidden)
|
setLeftSidebar(!isHidden)
|
||||||
}
|
}
|
||||||
@ -34,7 +25,6 @@ const AppBody = () => {
|
|||||||
|
|
||||||
console.log("render Main")
|
console.log("render Main")
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<AppShell
|
<AppShell
|
||||||
styles={{
|
styles={{
|
||||||
main: {
|
main: {
|
||||||
@ -56,7 +46,6 @@ const AppBody = () => {
|
|||||||
>
|
>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
</AppShell>
|
</AppShell>
|
||||||
</QueryClientProvider>
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
20
src/hooks/useAdminRole.ts
Normal file
20
src/hooks/useAdminRole.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { frigateQueryKeys, frigateApi } from "../services/frigate.proxy/frigate.api";
|
||||||
|
import { useRealmAccessRoles } from "./useRealmAccessRoles";
|
||||||
|
|
||||||
|
|
||||||
|
export const useAdminRole = () => {
|
||||||
|
const { data: adminConfig, isError, isPending } = useQuery({
|
||||||
|
queryKey: [frigateQueryKeys.getAdminRole],
|
||||||
|
queryFn: frigateApi.getAdminRole
|
||||||
|
})
|
||||||
|
|
||||||
|
const roles = useRealmAccessRoles()
|
||||||
|
|
||||||
|
if (isPending) return { isAdmin: undefined, isLoading: true }
|
||||||
|
if (isError) return { isAdmin: false, isError: true }
|
||||||
|
if (!adminConfig) return { isAdmin: true }
|
||||||
|
if (adminConfig && !adminConfig.value) return { isAdmin: true }
|
||||||
|
const isAdmin = roles.some(role => role === adminConfig.value)
|
||||||
|
return { isAdmin }
|
||||||
|
}
|
||||||
30
src/hooks/useRealmAccessRoles.ts
Normal file
30
src/hooks/useRealmAccessRoles.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useAuth } from "react-oidc-context";
|
||||||
|
|
||||||
|
interface CustomJwtPayload {
|
||||||
|
realm_access?: {
|
||||||
|
roles: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRealmAccessRoles = () => {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const [roles, setRoles] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
try {
|
||||||
|
const decoded = jwtDecode<CustomJwtPayload>(user.access_token);
|
||||||
|
const realmAccess = decoded.realm_access;
|
||||||
|
if (realmAccess && realmAccess.roles) {
|
||||||
|
setRoles(realmAccess.roles);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error decoding token:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
};
|
||||||
@ -10,6 +10,8 @@ import { dimensions } from '../shared/dimensions/dimensions';
|
|||||||
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import { strings } from '../shared/strings/strings';
|
import { strings } from '../shared/strings/strings';
|
||||||
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
|
import Forbidden from './403';
|
||||||
|
|
||||||
const AccessSettings = () => {
|
const AccessSettings = () => {
|
||||||
const { data, isPending, isError, refetch } = useQuery({
|
const { data, isPending, isError, refetch } = useQuery({
|
||||||
@ -17,7 +19,7 @@ const AccessSettings = () => {
|
|||||||
queryFn: frigateApi.getRoles
|
queryFn: frigateApi.getRoles
|
||||||
})
|
})
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sideBarsStore.rightVisible = false
|
sideBarsStore.rightVisible = false
|
||||||
@ -29,8 +31,9 @@ const AccessSettings = () => {
|
|||||||
const [roleId, setRoleId] = useState<string>()
|
const [roleId, setRoleId] = useState<string>()
|
||||||
|
|
||||||
|
|
||||||
if (isPending) return <CenterLoader />
|
if (isPending || adminLoading) return <CenterLoader />
|
||||||
if (isError || !data) return <RetryErrorPage />
|
if (isError || !data) return <RetryErrorPage />
|
||||||
|
if (!isAdmin) return <Forbidden />
|
||||||
const rolesSelect: OneSelectItem[] = data.map(role => ({ value: role.id, label: role.name }))
|
const rolesSelect: OneSelectItem[] = data.map(role => ({ value: role.id, label: role.name }))
|
||||||
|
|
||||||
const handleSelectRole = (value: string) => {
|
const handleSelectRole = (value: string) => {
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { Context } from '..';
|
|||||||
import { strings } from '../shared/strings/strings';
|
import { strings } from '../shared/strings/strings';
|
||||||
import { Button, Flex } from '@mantine/core';
|
import { Button, Flex } from '@mantine/core';
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
|
import Forbidden from './403';
|
||||||
|
|
||||||
const FrigateHostsPage = observer(() => {
|
const FrigateHostsPage = observer(() => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@ -24,6 +26,7 @@ const FrigateHostsPage = observer(() => {
|
|||||||
sideBarsStore.setRightChildren(null)
|
sideBarsStore.setRightChildren(null)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
const [pageData, setPageData] = useState(data)
|
const [pageData, setPageData] = useState(data)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -70,8 +73,10 @@ const FrigateHostsPage = observer(() => {
|
|||||||
if (data) setPageData([...data])
|
if (data) setPageData([...data])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hostsPending) return <CenterLoader />
|
if (hostsPending || adminLoading) return <CenterLoader />
|
||||||
|
if (!isAdmin) return <Forbidden />
|
||||||
if (hostsError) return <RetryErrorPage />
|
if (hostsError) return <RetryErrorPage />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -10,11 +10,15 @@ import Editor, { DiffEditor, useMonaco, loader, Monaco } from '@monaco-editor/re
|
|||||||
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';
|
||||||
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
|
import Forbidden from './403';
|
||||||
|
|
||||||
|
|
||||||
const HostConfigPage = () => {
|
const HostConfigPage = () => {
|
||||||
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
|
||||||
let { id } = useParams<'id'>()
|
let { id } = useParams<'id'>()
|
||||||
const queryClient = useQueryClient()
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
const { isPending: configPending, error: configError, data: config, refetch } = useQuery({
|
const { isPending: configPending, error: configError, data: config, refetch } = useQuery({
|
||||||
queryKey: [frigateQueryKeys.getFrigateHost, id],
|
queryKey: [frigateQueryKeys.getFrigateHost, id],
|
||||||
@ -27,6 +31,11 @@ const HostConfigPage = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sideBarsStore.rightVisible = false
|
||||||
|
sideBarsStore.setLeftChildren(null)
|
||||||
|
sideBarsStore.setRightChildren(null)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const clipboard = useClipboard({ timeout: 500 })
|
const clipboard = useClipboard({ timeout: 500 })
|
||||||
|
|
||||||
@ -74,9 +83,10 @@ const HostConfigPage = () => {
|
|||||||
console.log('save config', save_option)
|
console.log('save config', save_option)
|
||||||
}, [editorRef])
|
}, [editorRef])
|
||||||
|
|
||||||
if (configPending) return <CenterLoader />
|
if (configPending || adminLoading) return <CenterLoader />
|
||||||
|
|
||||||
if (configError) return <RetryErrorPage onRetry={refetch} />
|
if (configError) return <RetryErrorPage onRetry={refetch} />
|
||||||
|
if (!isAdmin) return <Forbidden />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction='column' h='100%' w='100%' justify='stretch'>
|
<Flex direction='column' h='100%' w='100%' justify='stretch'>
|
||||||
|
|||||||
@ -1,9 +1,23 @@
|
|||||||
import React from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { Context } from '..';
|
||||||
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
|
import Forbidden from './403';
|
||||||
|
|
||||||
const HostStoragePage = () => {
|
const HostStoragePage = () => {
|
||||||
let { id } = useParams<'id'>()
|
let { id } = useParams<'id'>()
|
||||||
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sideBarsStore.rightVisible = false
|
||||||
|
sideBarsStore.setLeftChildren(null)
|
||||||
|
sideBarsStore.setRightChildren(null)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!isAdmin) return <Forbidden />
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
Storage Page - NOT YET IMPLEMENTED
|
Storage Page - NOT YET IMPLEMENTED
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
import React from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { Context } from '..';
|
||||||
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
|
import Forbidden from './403';
|
||||||
|
|
||||||
const HostSystemPage = () => {
|
const HostSystemPage = () => {
|
||||||
let { id } = useParams<'id'>()
|
let { id } = useParams<'id'>()
|
||||||
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sideBarsStore.rightVisible = false
|
||||||
|
sideBarsStore.setLeftChildren(null)
|
||||||
|
sideBarsStore.setRightChildren(null)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!isAdmin) return <Forbidden />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
useQuery,
|
useQuery,
|
||||||
useMutation,
|
useMutation,
|
||||||
@ -13,6 +13,9 @@ import { strings } from '../shared/strings/strings';
|
|||||||
import { dimensions } from '../shared/dimensions/dimensions';
|
import { dimensions } from '../shared/dimensions/dimensions';
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
import { GetConfig } from '../services/frigate.proxy/frigate.schema';
|
import { GetConfig } from '../services/frigate.proxy/frigate.schema';
|
||||||
|
import { Context } from '..';
|
||||||
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
|
import Forbidden from './403';
|
||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@ -21,6 +24,16 @@ const SettingsPage = () => {
|
|||||||
queryFn: frigateApi.getConfig,
|
queryFn: frigateApi.getConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
useEffect(() => {
|
||||||
|
sideBarsStore.rightVisible = false
|
||||||
|
sideBarsStore.setLeftChildren(null)
|
||||||
|
sideBarsStore.setRightChildren(null)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
|
|
||||||
|
|
||||||
const ecryptedValue = '**********'
|
const ecryptedValue = '**********'
|
||||||
const mapEncryptedToView = (data: GetConfig[] | undefined): GetConfig[] | undefined => {
|
const mapEncryptedToView = (data: GetConfig[] | undefined): GetConfig[] | undefined => {
|
||||||
return data?.map(item => {
|
return data?.map(item => {
|
||||||
@ -81,9 +94,9 @@ const SettingsPage = () => {
|
|||||||
mutation.mutate(configsToUpdate);
|
mutation.mutate(configsToUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configPending) return <CenterLoader />
|
if (configPending || adminLoading) return <CenterLoader />
|
||||||
|
|
||||||
if (configError) return <RetryErrorPage onRetry={refetch} />
|
if (configError) return <RetryErrorPage onRetry={refetch} />
|
||||||
|
if (!isAdmin) return <Forbidden />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex h='100%'>
|
<Flex h='100%'>
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { EventFrigate } from "../../types/event";
|
|||||||
import { keycloakConfig } from "../..";
|
import { keycloakConfig } from "../..";
|
||||||
|
|
||||||
|
|
||||||
const getToken = (): string | undefined => {
|
export const getToken = (): string | undefined => {
|
||||||
const key = `oidc.user:${keycloakConfig.authority}:${keycloakConfig.client_id}`;
|
const key = `oidc.user:${keycloakConfig.authority}:${keycloakConfig.client_id}`;
|
||||||
const stored = sessionStorage.getItem(key);
|
const stored = sessionStorage.getItem(key);
|
||||||
const storedObject = stored ? JSON.parse(stored) : null;
|
const storedObject = stored ? JSON.parse(stored) : null;
|
||||||
@ -42,9 +42,9 @@ 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 => {
|
// getHostsWithCameras: () => instanceApi.get<GetFrigateHostWithCameras[]>('apiv1/frigate-hosts', { params: { include: 'cameras' } }).then(res => {
|
||||||
return res.data
|
// return res.data
|
||||||
}),
|
// }),
|
||||||
getHost: (id: string) => instanceApi.get<GetFrigateHostWithCameras>(`apiv1/frigate-hosts/${id}`).then(res => {
|
getHost: (id: string) => instanceApi.get<GetFrigateHostWithCameras>(`apiv1/frigate-hosts/${id}`).then(res => {
|
||||||
return res.data
|
return res.data
|
||||||
}),
|
}),
|
||||||
@ -57,12 +57,11 @@ export const frigateApi = {
|
|||||||
return res.data
|
return res.data
|
||||||
}),
|
}),
|
||||||
getRoles: () => instanceApi.get<GetRole[]>('apiv1/roles').then(res => res.data),
|
getRoles: () => instanceApi.get<GetRole[]>('apiv1/roles').then(res => res.data),
|
||||||
getUsersByRole: (roleName: string) => instanceApi.get<GetUserByRole[]>(`apiv1/users/${roleName}`).then(res => res.data),
|
|
||||||
getRoleWCameras: (roleId: string) => instanceApi.get<GetRoleWCameras>(`apiv1/roles/${roleId}`).then(res => res.data),
|
|
||||||
putRoleWCameras: (roleId: string, cameraIDs: string[]) => instanceApi.put<GetRoleWCameras>(`apiv1/roles/${roleId}/cameras`,
|
putRoleWCameras: (roleId: string, cameraIDs: string[]) => instanceApi.put<GetRoleWCameras>(`apiv1/roles/${roleId}/cameras`,
|
||||||
{
|
{
|
||||||
cameraIDs: cameraIDs
|
cameraIDs: cameraIDs
|
||||||
}).then(res => res.data)
|
}).then(res => res.data),
|
||||||
|
getAdminRole: () => instanceApi.get<GetConfig>('apiv1/config/admin').then(res => res.data),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const proxyPrefix = `${proxyURL.protocol}//${proxyURL.host}/proxy/`
|
export const proxyPrefix = `${proxyURL.protocol}//${proxyURL.host}/proxy/`
|
||||||
@ -71,14 +70,15 @@ export const proxyApi = {
|
|||||||
getHostConfigRaw: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/config/raw`).then(res => res.data),
|
getHostConfigRaw: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/config/raw`).then(res => res.data),
|
||||||
getHostConfig: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/config`).then(res => res.data),
|
getHostConfig: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/config`).then(res => res.data),
|
||||||
getImageFrigate: async (imageUrl: string) => {
|
getImageFrigate: async (imageUrl: string) => {
|
||||||
const response = await axios.get<Blob>(imageUrl, {
|
const response = await instanceApi.get<Blob>(imageUrl, {
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
getVideoFrigate: async (videoUrl: string, onProgress: (percentage: number | undefined) => void) => {
|
getVideoFrigate: async (videoUrl: string, onProgress: (percentage: number | undefined) => void) => {
|
||||||
const response = await axios.get<Blob>(videoUrl, {
|
const response = await instanceApi.get<Blob>(videoUrl, {
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
|
timeout: 10 * 60 * 1000,
|
||||||
onDownloadProgress: (progressEvent) => {
|
onDownloadProgress: (progressEvent) => {
|
||||||
const total = progressEvent.total
|
const total = progressEvent.total
|
||||||
const current = progressEvent.loaded;
|
const current = progressEvent.loaded;
|
||||||
@ -188,4 +188,5 @@ export const frigateQueryKeys = {
|
|||||||
getRoles: 'roles',
|
getRoles: 'roles',
|
||||||
getRoleWCameras: 'roles-cameras',
|
getRoleWCameras: 'roles-cameras',
|
||||||
getUsersByRole: 'users-role',
|
getUsersByRole: 'users-role',
|
||||||
|
getAdminRole: 'admin-role',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react';
|
|||||||
import videojs from 'video.js';
|
import videojs from 'video.js';
|
||||||
import Player from 'video.js/dist/types/player';
|
import Player from 'video.js/dist/types/player';
|
||||||
import 'video.js/dist/video-js.css'
|
import 'video.js/dist/video-js.css'
|
||||||
|
import { getToken } from '../../../services/frigate.proxy/frigate.api';
|
||||||
|
|
||||||
interface VideoPlayerProps {
|
interface VideoPlayerProps {
|
||||||
videoUrl: string
|
videoUrl: string
|
||||||
@ -12,6 +13,16 @@ const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
|
|||||||
const playerRef = useRef<Player | null>(null);
|
const playerRef = useRef<Player | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
videojs.Vhs.xhr.beforeRequest = function(options: any) {
|
||||||
|
options.headers = {
|
||||||
|
...options.headers,
|
||||||
|
Authorization: `Bearer ${getToken()}`,
|
||||||
|
};
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
preload: 'auto',
|
preload: 'auto',
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
@ -19,6 +30,7 @@ const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
|
|||||||
{
|
{
|
||||||
src: videoUrl,
|
src: videoUrl,
|
||||||
type: 'application/vnd.apple.mpegurl',
|
type: 'application/vnd.apple.mpegurl',
|
||||||
|
withCredentials: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
controls: true,
|
controls: true,
|
||||||
@ -70,3 +82,4 @@ const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default VideoPlayer;
|
export default VideoPlayer;
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import ColorSchemeToggle from "../../shared/components/buttons/ColorSchemeToggle
|
|||||||
import Logo from "../../shared/components/images/LogoImage";
|
import Logo from "../../shared/components/images/LogoImage";
|
||||||
import { routesPath } from "../../router/routes.path";
|
import { routesPath } from "../../router/routes.path";
|
||||||
import DrawerMenu from "../../shared/components/menu/DrawerMenu";
|
import DrawerMenu from "../../shared/components/menu/DrawerMenu";
|
||||||
|
import { useAdminRole } from "../../hooks/useAdminRole";
|
||||||
|
|
||||||
const HEADER_HEIGHT = rem(60)
|
const HEADER_HEIGHT = rem(60)
|
||||||
|
|
||||||
@ -55,7 +56,8 @@ const useStyles = createStyles((theme) => ({
|
|||||||
|
|
||||||
export interface LinkItem {
|
export interface LinkItem {
|
||||||
label: string
|
label: string
|
||||||
link: string
|
link: string,
|
||||||
|
admin?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HeaderActionProps {
|
export interface HeaderActionProps {
|
||||||
@ -67,16 +69,17 @@ export const HeaderAction = ({ links }: HeaderActionProps) => {
|
|||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
|
const { isAdmin } = useAdminRole()
|
||||||
|
|
||||||
const handleNavigate = (link: string) => {
|
const handleNavigate = (link: string) => {
|
||||||
navigate(link)
|
navigate(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = links.map(item =>
|
const items = links.filter(link => !(link.admin && !isAdmin)).map(link =>
|
||||||
<Menu key={item.label} trigger="hover" transitionProps={{ exitDuration: 0 }} withinPortal>
|
<Menu key={link.label} trigger="hover" transitionProps={{ exitDuration: 0 }} withinPortal>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<Button variant="subtle" uppercase onClick={() => handleNavigate(item.link)}>
|
<Button variant="subtle" uppercase onClick={() => handleNavigate(link.link)}>
|
||||||
{item.label}
|
{link.label}
|
||||||
</Button>
|
</Button>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import { HeaderActionProps, LinkItem } from "./HeaderAction";
|
|||||||
|
|
||||||
export const headerLinks: LinkItem[] = [
|
export const headerLinks: LinkItem[] = [
|
||||||
{ link: routesPath.MAIN_PATH, label: headerMenu.home },
|
{ link: routesPath.MAIN_PATH, label: headerMenu.home },
|
||||||
{ link: routesPath.SETTINGS_PATH, label: headerMenu.settings },
|
{ link: routesPath.SETTINGS_PATH, label: headerMenu.settings, admin: true },
|
||||||
{ link: routesPath.RECORDINGS_PATH, label: headerMenu.recordings },
|
{ link: routesPath.RECORDINGS_PATH, label: headerMenu.recordings },
|
||||||
{ link: routesPath.HOSTS_PATH, label: headerMenu.hostsConfig },
|
{ link: routesPath.HOSTS_PATH, label: headerMenu.hostsConfig, admin: true },
|
||||||
{ link: routesPath.ACCESS_PATH, label: headerMenu.acessSettings },
|
{ link: routesPath.ACCESS_PATH, label: headerMenu.acessSettings, admin: true },
|
||||||
]
|
]
|
||||||
@ -6737,6 +6737,11 @@ jwt-decode@^3.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
||||||
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
|
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
|
||||||
|
|
||||||
|
jwt-decode@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
|
||||||
|
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
|
||||||
|
|
||||||
keycode@2.2.0:
|
keycode@2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
|
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user