add admin role control

This commit is contained in:
NlightN22 2024-02-28 01:48:07 +07:00
parent 093cdb97e7
commit 4ed2ac9e2e
16 changed files with 233 additions and 102 deletions

View File

@ -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",

View File

@ -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,37 +47,39 @@ function App() {
} }
return ( return (
<div className="App"> <QueryClientProvider client={queryClient}>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}> <div className="App">
<MantineProvider <ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
withGlobalStyles <MantineProvider
withNormalizeCSS withGlobalStyles
theme={{ withNormalizeCSS
// fontFamily: '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji', //default system fonts theme={{
colorScheme: colorScheme, // fontFamily: '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji', //default system fonts
components: { colorScheme: colorScheme,
Button: { components: {
defaultProps: { Button: {
radius: "xl", defaultProps: {
} radius: "xl",
}, }
// Image: { },
// styles: (theme) => ({ // Image: {
// placeholder: { // styles: (theme) => ({
// backgroundColor: 'transparent', // placeholder: {
// } // backgroundColor: 'transparent',
// }) // }
// }, // })
} // },
}} }
> }}
<BrowserRouter> >
<Notifications /> <BrowserRouter>
<AppBody /> <Notifications />
</BrowserRouter> <AppBody />
</MantineProvider > </BrowserRouter>
</ColorSchemeProvider> </MantineProvider >
</div> </ColorSchemeProvider>
</div>
</QueryClientProvider>
); );
} }
export default App; export default App;

View File

@ -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,29 +25,27 @@ const AppBody = () => {
console.log("render Main") console.log("render Main")
return ( return (
<QueryClientProvider client={queryClient}> <AppShell
<AppShell styles={{
styles={{ main: {
main: { paddingLeft: !leftSideBar ? "1rem" : '',
paddingLeft: !leftSideBar ? "1rem" : '', paddingRight: !rightSideBar ? '1rem' : '',
paddingRight: !rightSideBar ? '1rem' : '', background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : undefined,
background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : undefined, },
}, }}
}} navbarOffsetBreakpoint="sm"
navbarOffsetBreakpoint="sm" asideOffsetBreakpoint="sm"
asideOffsetBreakpoint="sm"
header={ header={
<HeaderAction links={headerLinks} /> <HeaderAction links={headerLinks} />
} }
aside={ aside={
!sideBarsStore.rightVisible ? <></> : !sideBarsStore.rightVisible ? <></> :
<SideBar isHidden = { rightSideBarIsHidden } side = "right" /> <SideBar isHidden={rightSideBarIsHidden} side="right" />
} }
> >
<AppRouter /> <AppRouter />
</AppShell> </AppShell>
</QueryClientProvider>
) )
}; };

20
src/hooks/useAdminRole.ts Normal file
View 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 }
}

View 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;
};

View File

@ -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) => {

View File

@ -1,14 +1,16 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import FrigateHostsTable from '../widgets/FrigateHostsTable'; import FrigateHostsTable from '../widgets/FrigateHostsTable';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import { deleteFrigateHostSchema, GetFrigateHost, putFrigateHostSchema} from '../services/frigate.proxy/frigate.schema'; import { deleteFrigateHostSchema, GetFrigateHost, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
import CenterLoader from '../shared/components/loaders/CenterLoader'; import CenterLoader from '../shared/components/loaders/CenterLoader';
import RetryErrorPage from './RetryErrorPage'; import RetryErrorPage from './RetryErrorPage';
import { Context } from '..'; 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>
{ {

View File

@ -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'>

View File

@ -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

View File

@ -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>

View File

@ -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()
@ -20,6 +23,16 @@ const SettingsPage = () => {
queryKey: [frigateQueryKeys.getConfig], queryKey: [frigateQueryKeys.getConfig],
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 => {
@ -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%'>

View File

@ -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;
@ -27,14 +27,14 @@ const instanceApi = axios.create({
instanceApi.interceptors.request.use( instanceApi.interceptors.request.use(
config => { config => {
const token = getToken(); const token = getToken();
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
} }
return config; return config;
}, },
error => Promise.reject(error) error => Promise.reject(error)
); );
export const frigateApi = { export const frigateApi = {
getConfig: () => instanceApi.get<GetConfig[]>('apiv1/config').then(res => res.data), 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 => { 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',
} }

View File

@ -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,
@ -69,4 +81,5 @@ const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
); );
}; };
export default VideoPlayer; export default VideoPlayer;

View File

@ -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>

View File

@ -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 },
] ]

View File

@ -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"