add admin role control
This commit is contained in:
parent
093cdb97e7
commit
4ed2ac9e2e
@ -28,6 +28,7 @@
|
||||
"dayjs": "^1.11.9",
|
||||
"embla-carousel-react": "^8.0.0-rc10",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mantine-react-table": "^1.0.0-beta.25",
|
||||
"mobx": "^6.9.0",
|
||||
"mobx-react-lite": "^3.4.3",
|
||||
|
||||
77
src/App.tsx
77
src/App.tsx
@ -7,10 +7,18 @@ import { getCookie, setCookie } from 'cookies-next';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import AppBody from './AppBody';
|
||||
import Forbidden from './pages/403';
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function App() {
|
||||
const systemColorScheme = useColorScheme()
|
||||
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 });
|
||||
}
|
||||
|
||||
const auth = useAuth();
|
||||
const auth = useAuth()
|
||||
|
||||
// automatically sign-in
|
||||
useEffect(() => {
|
||||
if (!hasAuthParams() &&
|
||||
@ -38,37 +47,39 @@ function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
|
||||
<MantineProvider
|
||||
withGlobalStyles
|
||||
withNormalizeCSS
|
||||
theme={{
|
||||
// fontFamily: '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji', //default system fonts
|
||||
colorScheme: colorScheme,
|
||||
components: {
|
||||
Button: {
|
||||
defaultProps: {
|
||||
radius: "xl",
|
||||
}
|
||||
},
|
||||
// Image: {
|
||||
// styles: (theme) => ({
|
||||
// placeholder: {
|
||||
// backgroundColor: 'transparent',
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
}
|
||||
}}
|
||||
>
|
||||
<BrowserRouter>
|
||||
<Notifications />
|
||||
<AppBody />
|
||||
</BrowserRouter>
|
||||
</MantineProvider >
|
||||
</ColorSchemeProvider>
|
||||
</div>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<div className="App">
|
||||
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
|
||||
<MantineProvider
|
||||
withGlobalStyles
|
||||
withNormalizeCSS
|
||||
theme={{
|
||||
// fontFamily: '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji', //default system fonts
|
||||
colorScheme: colorScheme,
|
||||
components: {
|
||||
Button: {
|
||||
defaultProps: {
|
||||
radius: "xl",
|
||||
}
|
||||
},
|
||||
// Image: {
|
||||
// styles: (theme) => ({
|
||||
// placeholder: {
|
||||
// backgroundColor: 'transparent',
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
}
|
||||
}}
|
||||
>
|
||||
<BrowserRouter>
|
||||
<Notifications />
|
||||
<AppBody />
|
||||
</BrowserRouter>
|
||||
</MantineProvider >
|
||||
</ColorSchemeProvider>
|
||||
</div>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
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 { HeaderAction } from './widgets/header/HeaderAction';
|
||||
import { headerLinks } from './widgets/header/header.links';
|
||||
import AppRouter from './router/AppRouter';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { Context } from '.';
|
||||
import SideBar from './shared/components/SideBar';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const AppBody = () => {
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
|
||||
const [leftSideBar, setLeftSidebar] = useState(false)
|
||||
const [rightSideBar, setRightSidebar] = useState(false)
|
||||
|
||||
|
||||
const leftSideBarIsHidden = (isHidden: boolean) => {
|
||||
setLeftSidebar(!isHidden)
|
||||
}
|
||||
@ -34,29 +25,27 @@ const AppBody = () => {
|
||||
|
||||
console.log("render Main")
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppShell
|
||||
styles={{
|
||||
main: {
|
||||
paddingLeft: !leftSideBar ? "1rem" : '',
|
||||
paddingRight: !rightSideBar ? '1rem' : '',
|
||||
background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : undefined,
|
||||
},
|
||||
}}
|
||||
navbarOffsetBreakpoint="sm"
|
||||
asideOffsetBreakpoint="sm"
|
||||
<AppShell
|
||||
styles={{
|
||||
main: {
|
||||
paddingLeft: !leftSideBar ? "1rem" : '',
|
||||
paddingRight: !rightSideBar ? '1rem' : '',
|
||||
background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : undefined,
|
||||
},
|
||||
}}
|
||||
navbarOffsetBreakpoint="sm"
|
||||
asideOffsetBreakpoint="sm"
|
||||
|
||||
header={
|
||||
<HeaderAction links={headerLinks} />
|
||||
}
|
||||
aside={
|
||||
!sideBarsStore.rightVisible ? <></> :
|
||||
<SideBar isHidden = { rightSideBarIsHidden } side = "right" />
|
||||
}
|
||||
>
|
||||
<AppRouter />
|
||||
</AppShell>
|
||||
</QueryClientProvider>
|
||||
header={
|
||||
<HeaderAction links={headerLinks} />
|
||||
}
|
||||
aside={
|
||||
!sideBarsStore.rightVisible ? <></> :
|
||||
<SideBar isHidden={rightSideBarIsHidden} side="right" />
|
||||
}
|
||||
>
|
||||
<AppRouter />
|
||||
</AppShell>
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
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 { Context } from '..';
|
||||
import { strings } from '../shared/strings/strings';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import Forbidden from './403';
|
||||
|
||||
const AccessSettings = () => {
|
||||
const { data, isPending, isError, refetch } = useQuery({
|
||||
@ -17,7 +19,7 @@ const AccessSettings = () => {
|
||||
queryFn: frigateApi.getRoles
|
||||
})
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
|
||||
useEffect(() => {
|
||||
sideBarsStore.rightVisible = false
|
||||
@ -29,8 +31,9 @@ const AccessSettings = () => {
|
||||
const [roleId, setRoleId] = useState<string>()
|
||||
|
||||
|
||||
if (isPending) return <CenterLoader />
|
||||
if (isPending || adminLoading) return <CenterLoader />
|
||||
if (isError || !data) return <RetryErrorPage />
|
||||
if (!isAdmin) return <Forbidden />
|
||||
const rolesSelect: OneSelectItem[] = data.map(role => ({ value: role.id, label: role.name }))
|
||||
|
||||
const handleSelectRole = (value: string) => {
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import FrigateHostsTable from '../widgets/FrigateHostsTable';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
import { deleteFrigateHostSchema, GetFrigateHost, putFrigateHostSchema} from '../services/frigate.proxy/frigate.schema';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
import { deleteFrigateHostSchema, GetFrigateHost, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
|
||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import { Context } from '..';
|
||||
import { strings } from '../shared/strings/strings';
|
||||
import { Button, Flex } from '@mantine/core';
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import Forbidden from './403';
|
||||
|
||||
const FrigateHostsPage = observer(() => {
|
||||
const queryClient = useQueryClient()
|
||||
@ -24,6 +26,7 @@ const FrigateHostsPage = observer(() => {
|
||||
sideBarsStore.setRightChildren(null)
|
||||
}, [])
|
||||
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
const [pageData, setPageData] = useState(data)
|
||||
|
||||
useEffect(() => {
|
||||
@ -70,8 +73,10 @@ const FrigateHostsPage = observer(() => {
|
||||
if (data) setPageData([...data])
|
||||
}
|
||||
|
||||
if (hostsPending) return <CenterLoader />
|
||||
if (hostsPending || adminLoading) return <CenterLoader />
|
||||
if (!isAdmin) return <Forbidden />
|
||||
if (hostsError) return <RetryErrorPage />
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
|
||||
@ -10,11 +10,15 @@ import Editor, { DiffEditor, useMonaco, loader, Monaco } from '@monaco-editor/re
|
||||
import * as monaco from "monaco-editor";
|
||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import Forbidden from './403';
|
||||
|
||||
|
||||
const HostConfigPage = () => {
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
|
||||
let { id } = useParams<'id'>()
|
||||
const queryClient = useQueryClient()
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
const theme = useMantineTheme();
|
||||
const { isPending: configPending, error: configError, data: config, refetch } = useQuery({
|
||||
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 })
|
||||
|
||||
@ -74,9 +83,10 @@ const HostConfigPage = () => {
|
||||
console.log('save config', save_option)
|
||||
}, [editorRef])
|
||||
|
||||
if (configPending) return <CenterLoader />
|
||||
if (configPending || adminLoading) return <CenterLoader />
|
||||
|
||||
if (configError) return <RetryErrorPage onRetry={refetch} />
|
||||
if (!isAdmin) return <Forbidden />
|
||||
|
||||
return (
|
||||
<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 { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import Forbidden from './403';
|
||||
|
||||
const HostStoragePage = () => {
|
||||
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 (
|
||||
<div>
|
||||
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 { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import Forbidden from './403';
|
||||
|
||||
const HostSystemPage = () => {
|
||||
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 (
|
||||
<div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
useQuery,
|
||||
useMutation,
|
||||
@ -13,6 +13,9 @@ import { strings } from '../shared/strings/strings';
|
||||
import { dimensions } from '../shared/dimensions/dimensions';
|
||||
import { useMediaQuery } from '@mantine/hooks';
|
||||
import { GetConfig } from '../services/frigate.proxy/frigate.schema';
|
||||
import { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import Forbidden from './403';
|
||||
|
||||
const SettingsPage = () => {
|
||||
const queryClient = useQueryClient()
|
||||
@ -20,6 +23,16 @@ const SettingsPage = () => {
|
||||
queryKey: [frigateQueryKeys.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 mapEncryptedToView = (data: GetConfig[] | undefined): GetConfig[] | undefined => {
|
||||
@ -81,9 +94,9 @@ const SettingsPage = () => {
|
||||
mutation.mutate(configsToUpdate);
|
||||
}
|
||||
|
||||
if (configPending) return <CenterLoader />
|
||||
|
||||
if (configPending || adminLoading) return <CenterLoader />
|
||||
if (configError) return <RetryErrorPage onRetry={refetch} />
|
||||
if (!isAdmin) return <Forbidden />
|
||||
|
||||
return (
|
||||
<Flex h='100%'>
|
||||
|
||||
@ -13,7 +13,7 @@ import { EventFrigate } from "../../types/event";
|
||||
import { keycloakConfig } from "../..";
|
||||
|
||||
|
||||
const getToken = (): string | undefined => {
|
||||
export const getToken = (): string | undefined => {
|
||||
const key = `oidc.user:${keycloakConfig.authority}:${keycloakConfig.client_id}`;
|
||||
const stored = sessionStorage.getItem(key);
|
||||
const storedObject = stored ? JSON.parse(stored) : null;
|
||||
@ -27,14 +27,14 @@ const instanceApi = axios.create({
|
||||
|
||||
instanceApi.interceptors.request.use(
|
||||
config => {
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => Promise.reject(error)
|
||||
);
|
||||
);
|
||||
|
||||
export const frigateApi = {
|
||||
getConfig: () => instanceApi.get<GetConfig[]>('apiv1/config').then(res => res.data),
|
||||
@ -42,9 +42,9 @@ export const frigateApi = {
|
||||
getHosts: () => instanceApi.get<GetFrigateHost[]>('apiv1/frigate-hosts').then(res => {
|
||||
return res.data
|
||||
}),
|
||||
getHostsWithCameras: () => instanceApi.get<GetFrigateHostWithCameras[]>('apiv1/frigate-hosts', { params: { include: 'cameras' } }).then(res => {
|
||||
return res.data
|
||||
}),
|
||||
// getHostsWithCameras: () => instanceApi.get<GetFrigateHostWithCameras[]>('apiv1/frigate-hosts', { params: { include: 'cameras' } }).then(res => {
|
||||
// return res.data
|
||||
// }),
|
||||
getHost: (id: string) => instanceApi.get<GetFrigateHostWithCameras>(`apiv1/frigate-hosts/${id}`).then(res => {
|
||||
return res.data
|
||||
}),
|
||||
@ -57,12 +57,11 @@ export const frigateApi = {
|
||||
return 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`,
|
||||
{
|
||||
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/`
|
||||
@ -71,14 +70,15 @@ export const proxyApi = {
|
||||
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),
|
||||
getImageFrigate: async (imageUrl: string) => {
|
||||
const response = await axios.get<Blob>(imageUrl, {
|
||||
const response = await instanceApi.get<Blob>(imageUrl, {
|
||||
responseType: 'blob'
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
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',
|
||||
timeout: 10 * 60 * 1000,
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
const total = progressEvent.total
|
||||
const current = progressEvent.loaded;
|
||||
@ -188,4 +188,5 @@ export const frigateQueryKeys = {
|
||||
getRoles: 'roles',
|
||||
getRoleWCameras: 'roles-cameras',
|
||||
getUsersByRole: 'users-role',
|
||||
getAdminRole: 'admin-role',
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react';
|
||||
import videojs from 'video.js';
|
||||
import Player from 'video.js/dist/types/player';
|
||||
import 'video.js/dist/video-js.css'
|
||||
import { getToken } from '../../../services/frigate.proxy/frigate.api';
|
||||
|
||||
interface VideoPlayerProps {
|
||||
videoUrl: string
|
||||
@ -12,6 +13,16 @@ const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
|
||||
const playerRef = useRef<Player | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
//@ts-ignore
|
||||
videojs.Vhs.xhr.beforeRequest = function(options: any) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
};
|
||||
return options;
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
preload: 'auto',
|
||||
autoplay: true,
|
||||
@ -19,6 +30,7 @@ const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
|
||||
{
|
||||
src: videoUrl,
|
||||
type: 'application/vnd.apple.mpegurl',
|
||||
withCredentials: true,
|
||||
},
|
||||
],
|
||||
controls: true,
|
||||
@ -69,4 +81,5 @@ 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 { routesPath } from "../../router/routes.path";
|
||||
import DrawerMenu from "../../shared/components/menu/DrawerMenu";
|
||||
import { useAdminRole } from "../../hooks/useAdminRole";
|
||||
|
||||
const HEADER_HEIGHT = rem(60)
|
||||
|
||||
@ -55,7 +56,8 @@ const useStyles = createStyles((theme) => ({
|
||||
|
||||
export interface LinkItem {
|
||||
label: string
|
||||
link: string
|
||||
link: string,
|
||||
admin?: boolean
|
||||
}
|
||||
|
||||
export interface HeaderActionProps {
|
||||
@ -67,16 +69,17 @@ export const HeaderAction = ({ links }: HeaderActionProps) => {
|
||||
const { classes } = useStyles();
|
||||
const navigate = useNavigate()
|
||||
const auth = useAuth()
|
||||
const { isAdmin } = useAdminRole()
|
||||
|
||||
const handleNavigate = (link: string) => {
|
||||
navigate(link)
|
||||
}
|
||||
|
||||
const items = links.map(item =>
|
||||
<Menu key={item.label} trigger="hover" transitionProps={{ exitDuration: 0 }} withinPortal>
|
||||
const items = links.filter(link => !(link.admin && !isAdmin)).map(link =>
|
||||
<Menu key={link.label} trigger="hover" transitionProps={{ exitDuration: 0 }} withinPortal>
|
||||
<Menu.Target>
|
||||
<Button variant="subtle" uppercase onClick={() => handleNavigate(item.link)}>
|
||||
{item.label}
|
||||
<Button variant="subtle" uppercase onClick={() => handleNavigate(link.link)}>
|
||||
{link.label}
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
</Menu>
|
||||
|
||||
@ -4,8 +4,8 @@ import { HeaderActionProps, LinkItem } from "./HeaderAction";
|
||||
|
||||
export const headerLinks: LinkItem[] = [
|
||||
{ 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.HOSTS_PATH, label: headerMenu.hostsConfig },
|
||||
{ link: routesPath.ACCESS_PATH, label: headerMenu.acessSettings },
|
||||
{ link: routesPath.HOSTS_PATH, label: headerMenu.hostsConfig, admin: true },
|
||||
{ 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"
|
||||
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:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user