diff --git a/Backend/src/app/frontend/frontend.controller.ts b/Backend/src/app/frontend/frontend.controller.ts index b3ba4ef..10735d0 100644 --- a/Backend/src/app/frontend/frontend.controller.ts +++ b/Backend/src/app/frontend/frontend.controller.ts @@ -57,6 +57,11 @@ const filterParams = { export class FrontendController { constructor(private readonly databaseService: DatabaseService) {} + @Get(`cameras`) + getCameras() { + return this.databaseService.getCameras(); + } + @ApiParam(filterParams) @Get(`:filter(${filters.join('|')})/aggregate`) getAggregateValues( diff --git a/Backend/src/app/login.controller.ts b/Backend/src/app/login.controller.ts index 7cf7940..a01077c 100644 --- a/Backend/src/app/login.controller.ts +++ b/Backend/src/app/login.controller.ts @@ -24,13 +24,13 @@ export class LoginController { examples: { a: { summary: 'Existing user', - value: { + value: { name: process.env.CSD_USER, password: process.env.CSD_PASSWORD, }, }, - b: { - summary: 'Non existing user', + b: { + summary: 'Non existing user', value: { name: 'non', password: 'Basic' }, }, }, diff --git a/Backend/src/camera.config.ts b/Backend/src/camera.config.ts new file mode 100644 index 0000000..8871dae --- /dev/null +++ b/Backend/src/camera.config.ts @@ -0,0 +1 @@ +export const cameraIds = [0, 1, 2, 3, 4, 5, 6, 7]; diff --git a/Backend/src/cameraStream/cameraData.ts b/Backend/src/cameraStream/cameraData.ts deleted file mode 100644 index c983f69..0000000 --- a/Backend/src/cameraStream/cameraData.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type Camera = { - id: string; - name: string; - rtspUrl: string; -}; diff --git a/Backend/src/database/database.service.ts b/Backend/src/database/database.service.ts index 833a6c8..2416fec 100644 --- a/Backend/src/database/database.service.ts +++ b/Backend/src/database/database.service.ts @@ -20,6 +20,7 @@ import DataType from '../DataType'; import { FiltersAvailable } from '../validators/filters/filters.pipe'; import * as process from 'process'; import * as bcrypt from 'bcrypt'; +import { cameraIds } from '../camera.config'; const url = `${process.env.MONGO_PROTOCOL ?? 'mongodb'}://${ process.env.MONGO_INITDB_ROOT_USERNAME @@ -76,12 +77,23 @@ export class DatabaseService { } } + // If no camera exists it automatically creates one with the default config in camera.config.ts file + async initCameras() { + const size = await this.DB.collection('camera_names').countDocuments(); + if (size == 0) { + await this.DB.collection(`camera_names`).insertMany( + cameraIds.map((id) => ({ id: id, name: 'No name' })), + ); + } + } + constructor() { const client = new MongoClient(url); this.DB = client.db('csd'); this.initDBUser(); this.initDBNvr(); + this.initCameras(); } async addData(data: DataType) { @@ -119,6 +131,10 @@ export class DatabaseService { else return res.toArray(); } + getCameras(): Promise { + return this.DB.collection('camera_names').find().toArray(); + } + aggregateCamera(filter: FiltersAvailable): Promise { return this.DB.collection('cameras') .aggregate([ @@ -243,7 +259,7 @@ export class DatabaseService { }); return { - ip: array[0].ip, + ip: process.env.NVR_IP_ADDRESS, channels: array[0].channels, }; } diff --git a/Frontend/Dockerfile b/Frontend/Dockerfile new file mode 100644 index 0000000..88045d0 --- /dev/null +++ b/Frontend/Dockerfile @@ -0,0 +1,23 @@ +# Use the official Node.js image as the base image +FROM node:14-alpine + +# Set the working directory inside the container +WORKDIR /app + +# Copy package.json and package-lock.json to the working directory +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the entire app to the working directory +COPY . . + +# Build the Next.js app +RUN npm run build + +# Expose the port that your Next.js app will run on +EXPOSE 3000 + +# Start the Next.js app +CMD ["npm", "start"] diff --git a/Frontend/src/api/axios-client.ts b/Frontend/src/api/axios-client.tsx similarity index 80% rename from Frontend/src/api/axios-client.ts rename to Frontend/src/api/axios-client.tsx index ba2a2b0..a4d6a52 100644 --- a/Frontend/src/api/axios-client.ts +++ b/Frontend/src/api/axios-client.tsx @@ -6,12 +6,8 @@ type RequestHeaderOptions = { Authorization?: string; }; -const accessToken = sessionStorage.getItem("access_token"); const headers: RequestHeaderOptions = {}; -if (accessToken) { - headers.Authorization = `Bearer ${sessionStorage.getItem("access_token")}`; -} export const axiosClient = axios.create({ baseURL: process.env.NEXT_PUBLIC_BACKEND_URL, headers: { ...headers }, diff --git a/Frontend/src/api/cameras.ts b/Frontend/src/api/cameras.ts new file mode 100644 index 0000000..197f37a --- /dev/null +++ b/Frontend/src/api/cameras.ts @@ -0,0 +1,27 @@ +import { Activity, AnyObject, Camera } from "@/types"; +import { axiosClient } from "./axios-client"; +import { endpoints } from "./endpoints"; + +type CamerasResponseDTO = Camera[]; +type CameraSettingUpdateResponseDTO = any; + +export const getCameras = () => { + return () => + axiosClient + .get(`${endpoints.getCameras}`, {}) + .then((result) => result.data); +}; + +export const updateCamera = async (configuration: AnyObject) => { + for (const key in configuration) { + try { + await axiosClient + .post(`${key}/${configuration[key]}`, {}); + } catch (error: any) { + console.log(error); + } + + } + return "Success" + +}; diff --git a/Frontend/src/api/endpoints.ts b/Frontend/src/api/endpoints.ts index 8d91f5d..0d27416 100644 --- a/Frontend/src/api/endpoints.ts +++ b/Frontend/src/api/endpoints.ts @@ -1,4 +1,11 @@ export const endpoints = { - /* authentication endpoints */ + // authentication endpoints login: "login", + + // ِRecent activites endpoints + recentActivities: "intrusionDetection", + getIntrusionCount: "intrusionDetection/aggregate", + + // Camera endpoints + getCameras: "cameras", }; diff --git a/Frontend/src/api/index.ts b/Frontend/src/api/index.ts index c88e739..b2cadf8 100644 --- a/Frontend/src/api/index.ts +++ b/Frontend/src/api/index.ts @@ -1,5 +1,7 @@ -export { ReactQueryProvider } from "./react-query-provider"; +export { ReactQueryProvider, queryClient } from "./react-query-provider"; export { axiosClient } from "./axios-client"; -/* authentication methods */ + export { login } from "./authentication/login"; +export { getRecentActivities, getRecentActivitiesCount,getActivityImage } from "./recent-activities"; +export { getCameras, updateCamera } from "./cameras"; diff --git a/Frontend/src/api/react-query-provider.tsx b/Frontend/src/api/react-query-provider.tsx index caa93ea..998ddbd 100644 --- a/Frontend/src/api/react-query-provider.tsx +++ b/Frontend/src/api/react-query-provider.tsx @@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "react-query"; -const queryClient = new QueryClient(); +export const queryClient = new QueryClient(); export const ReactQueryProvider = ({ children, diff --git a/Frontend/src/api/recent-activities.ts b/Frontend/src/api/recent-activities.ts new file mode 100644 index 0000000..038f1d6 --- /dev/null +++ b/Frontend/src/api/recent-activities.ts @@ -0,0 +1,54 @@ +import { Activity } from "@/types"; +import { axiosClient } from "./axios-client"; +import { endpoints } from "./endpoints"; + +type RecentActivitiesResponseDTO = { + data: Activity[]; +}; + +type RecentActivitiesCountResponseDTO = { + _id: string; + count: number; +}[]; + +type RecentActivityImageResponseDTO = { + data: any; +}; + +export const getRecentActivities = (top: number, skip: number) => { + return () => + axiosClient + .get( + `${endpoints.recentActivities}/${top}/${skip}`, + {} + ) + .then((result) => result.data); +}; + +export const getRecentActivitiesCount = () => { + return () => + axiosClient + .get( + `${endpoints.getIntrusionCount}`, + {} + ) + .then((result) => { + let totalCount = 0; + + result.data.forEach((item) => { + totalCount += item.count; + }); + + return totalCount; + }); +}; + +export const getActivityImage = (id: string, timestamp: string) => { + return () => + axiosClient + .get( + `${id}/${timestamp}`, + {} + ) + .then((result) => result.data); +}; diff --git a/Frontend/src/app/recent-activities/page.tsx b/Frontend/src/app/recent-activities/page.tsx index 5a308f7..ee38610 100644 --- a/Frontend/src/app/recent-activities/page.tsx +++ b/Frontend/src/app/recent-activities/page.tsx @@ -1,16 +1,10 @@ "use client"; -import { Table } from "@/components"; -import { recentActivitiesData, recentActivitiesColumns } from "@/data"; +import { RecentActivitiesContainer } from "@/containers"; export default function RecentActivities() { return (
- + ); } diff --git a/Frontend/src/app/settings/page.tsx b/Frontend/src/app/settings/page.tsx new file mode 100644 index 0000000..d0ff3c5 --- /dev/null +++ b/Frontend/src/app/settings/page.tsx @@ -0,0 +1,10 @@ +"use client"; +import { SettingsContainer } from "@/containers"; + +export default function Settings() { + return ( +
+ +
+ ); +} diff --git a/Frontend/src/components/button/view-screenshot.tsx b/Frontend/src/components/button/view-screenshot.tsx new file mode 100644 index 0000000..913baf7 --- /dev/null +++ b/Frontend/src/components/button/view-screenshot.tsx @@ -0,0 +1,75 @@ +import { Button } from "./button"; +import { CameraOutlined } from "@ant-design/icons"; +import { useModalSlice, useNotificationSlice } from "@/hooks"; +import { useQuery } from "react-query"; +import { getActivityImage } from "@/api"; +import { Spin } from "antd"; +import { useEffect } from "react"; + +type PropsType = { + cameraId: string; + timestamp: string; +}; + +/** This component renders screenshot button */ +export const ViewScreenshotButton: React.FC = ({ + cameraId, + timestamp, +}) => { + const { openModal, closeModal } = useModalSlice(); + const { openNotification } = useNotificationSlice(); + + // Get total number of recent activities + const { + isLoading: isLoadingImage, + error: isErrorImage, + data: imageData, + refetch: fetchImage, + } = useQuery( + ["recentActivitiesCount", cameraId], + getActivityImage(cameraId, timestamp), + { enabled: false } + ); + + const onButtonClick = () => { + fetchImage(); + }; + + useEffect(() => { + if (imageData) { + openModal({ + title: timestamp, + modalContent: imageData.data, + isLoading: true, + }); + } + }, [imageData]); + + useEffect(() => { + if (isLoadingImage) { + openModal({ + title: timestamp, + modalContent: "", + isLoading: true, + }); + } + }, [isLoadingImage]); + + useEffect(() => { + if (isErrorImage) { + openNotification({ + type: "error", + message: "Could not fetch image.", + }); + closeModal(); + } + }, [isErrorImage]); + + return ( +
+ {/*
*/} diff --git a/Frontend/src/containers/analytics-container/recent-unauthorized/recent-unauthorized.tsx b/Frontend/src/containers/analytics-container/recent-unauthorized/recent-unauthorized.tsx index e8d5b43..597c049 100644 --- a/Frontend/src/containers/analytics-container/recent-unauthorized/recent-unauthorized.tsx +++ b/Frontend/src/containers/analytics-container/recent-unauthorized/recent-unauthorized.tsx @@ -4,14 +4,14 @@ import { Card, Table } from "@/components"; import { recentActivitiesData, recentActivitiesColumns } from "@/data"; export const RecentUnauthorizedContainer: FC = () => { - const authorizedEntities = recentActivitiesData - .filter((item) => item.entity === undefined) - .slice(0, 5); + // const authorizedEntities = recentActivitiesData + // .filter((item) => item.entity === undefined) + // .slice(0, 5); return ( <>
-
+ {/*
*/} diff --git a/Frontend/src/containers/analytics-container/unauthorized-counter/unauthorized-counter.tsx b/Frontend/src/containers/analytics-container/unauthorized-counter/unauthorized-counter.tsx index aa0fee1..73b6013 100644 --- a/Frontend/src/containers/analytics-container/unauthorized-counter/unauthorized-counter.tsx +++ b/Frontend/src/containers/analytics-container/unauthorized-counter/unauthorized-counter.tsx @@ -45,13 +45,13 @@ export const UnauthorizedCounterContainer: FC = () => {
No of attempts on:
- + /> */}
diff --git a/Frontend/src/containers/analytics-container/user-area-bar/user-area-bar.tsx b/Frontend/src/containers/analytics-container/user-area-bar/user-area-bar.tsx index 4d0e545..5241b1d 100644 --- a/Frontend/src/containers/analytics-container/user-area-bar/user-area-bar.tsx +++ b/Frontend/src/containers/analytics-container/user-area-bar/user-area-bar.tsx @@ -24,7 +24,7 @@ import type { AnyObject, AuthorizedEntity, Camera } from "@/types"; export const UserAreaBarContainer: FC = () => { const cameraOptions = cameras.map((item) => ({ label: item.name, - value: item.key, + value: item.id, })); const entityOptions = authorizedEntitiesData.map((item) => ({ label: item.name, @@ -70,7 +70,7 @@ export const UserAreaBarContainer: FC = () => { }; const handleCameraSelectChange = (value: string[]) => { - const selectedCamerasObject = getObjectArray(value, "key", cameras); + const selectedCamerasObject = getObjectArray(value, "id", cameras); setSelectedCameras(selectedCamerasObject); }; @@ -130,13 +130,13 @@ export const UserAreaBarContainer: FC = () => {
Date: - + /> */}
diff --git a/Frontend/src/containers/index.ts b/Frontend/src/containers/index.ts index 184436b..c4ee5ab 100644 --- a/Frontend/src/containers/index.ts +++ b/Frontend/src/containers/index.ts @@ -7,3 +7,6 @@ export { AboutUsTeamContainer } from "./about-us-container/about-us-team-contain export { AboutUsMissionContainer } from "./about-us-container/about-us-mission-container"; export { AnalyticsContainer } from "./analytics-container/analytics-container"; export { LoginContainer } from "./login-container"; +export { RecentActivitiesContainer } from "./recent-activities-container"; +export { SettingsContainer } from "./settings-container"; +export { ModalContainer } from "./modal-container"; diff --git a/Frontend/src/containers/layout-container.tsx b/Frontend/src/containers/layout-container.tsx index 6f5a70e..6cdc7b9 100644 --- a/Frontend/src/containers/layout-container.tsx +++ b/Frontend/src/containers/layout-container.tsx @@ -6,10 +6,12 @@ import { loggedInNavBarItems, guestNavBarItems } from "@/data"; import { antTheme } from "../../theme"; import type { NavBarItem } from "@/types"; import { getCurrentNav } from "@/utils"; -import { BellOutlined } from "@ant-design/icons"; import { useSessionSlice, useCameraSlice } from "@/hooks"; import { ProtectionContainer } from "./protection-container"; import { NotificationContainer } from "./notification-container"; +import { useQuery } from "react-query"; +import { axiosClient, getCameras } from "@/api"; +import { ModalContainer } from "./modal-container"; const { Header, Content, Footer, Sider } = Layout; export const LayoutContainer = ({ @@ -18,13 +20,16 @@ export const LayoutContainer = ({ children: React.ReactNode; }) => { /* state to check if ant design styled loaded */ - const { session, logOut } = useSessionSlice(); - const { isFullScreenGrid } = useCameraSlice(); + const { session, logOut, logIn } = useSessionSlice(); + const { isFullScreenGrid, setCameras } = useCameraSlice(); const [antStyleLoaded, setAntStyleLoaded] = useState(false); const [currentNavMenu, setCurrentNavMenu] = useState([]); const router = useRouter(); const pathname = usePathname(); + // Initialize camera + const { data: camerasFetchedData } = useQuery("cameras", getCameras()); + /* event handler */ const onMenuClick = (info: any) => { const selectedItem = currentNavMenu.find((item) => item.key === info.key); @@ -39,8 +44,21 @@ export const LayoutContainer = ({ /* useEffect */ useEffect(() => { setAntStyleLoaded(true); + const accessToken = sessionStorage.getItem("access_token"); + if (accessToken) { + axiosClient.defaults.headers.common[ + "Authorization" + ] = `Bearer ${sessionStorage.getItem("access_token")}`; + logIn({ accessToken: accessToken }); + } }, []); + useEffect(() => { + if (camerasFetchedData && session) { + setCameras(camerasFetchedData); + } + }, [camerasFetchedData, session]); + useEffect(() => { /* navbar menu */ if (session) { @@ -79,11 +97,7 @@ export const LayoutContainer = ({ /> -
- {session && ( - - )} -
+
)} + ); diff --git a/Frontend/src/containers/login-container.tsx b/Frontend/src/containers/login-container.tsx index db681c2..0bf3dc9 100644 --- a/Frontend/src/containers/login-container.tsx +++ b/Frontend/src/containers/login-container.tsx @@ -31,12 +31,6 @@ export const LoginContainer: FC = () => { const result = await login(data); logIn({ accessToken: result.data.access_token, - user: { - firstName: "Nabil Mohammed", - lastName: "Khelifa", - email: "nabil.nablotech@gmail.com", - mobileNumber: "393513117160", - }, }); } catch (error: any) { openNotification({ diff --git a/Frontend/src/containers/modal-container.tsx b/Frontend/src/containers/modal-container.tsx new file mode 100644 index 0000000..a843aa1 --- /dev/null +++ b/Frontend/src/containers/modal-container.tsx @@ -0,0 +1,26 @@ +"use client"; +import { useModalSlice } from "@/hooks"; +import { Modal, Spin } from "antd"; +import { useEffect } from "react";; + +export const ModalContainer = () => { + const { isModalOpen, closeModal, modalContent, isLoading , title} = useModalSlice(); + /* onClose or cancel */ + const onCancel = () => { + closeModal(); + } + + return ( + <> + + {modalContent} + {isLoading && + + } + {!isLoading && + Ima + } + + + ); +}; diff --git a/Frontend/src/containers/recent-activities-container.tsx b/Frontend/src/containers/recent-activities-container.tsx new file mode 100644 index 0000000..d9cacac --- /dev/null +++ b/Frontend/src/containers/recent-activities-container.tsx @@ -0,0 +1,89 @@ +"use client"; +import type { FC } from "react"; +import React, { useEffect, useState } from "react"; +import { Table } from "@/components"; +import { recentActivitiesData, recentActivitiesColumns } from "@/data"; +import { getRecentActivities, getRecentActivitiesCount } from "@/api"; +import { useQuery } from "react-query"; +import { openNotification } from "@/store"; +import { useCameraSlice } from "@/hooks"; + +/* This container renders recent activites table */ + +type PropsType = {}; + +export const RecentActivitiesContainer: FC = () => { + const [pageNumber, setPageNumber] = useState(1); + const { cameras } = useCameraSlice(); + + // Get total number of recent activities + const { + isLoading: isLoadingRecentActivitiesCount, + error: isErrorRecentActivitiesCount, + data: recentActivitiesCountFetchedData, + } = useQuery("recentActivitiesCount", getRecentActivitiesCount()); + + // get paginated recent activities + const { + isLoading: isLoadingRecentActivities, + error: isErrorRecentActivities, + data: recentActivitiesFetchedData, + refetch: refechRecentActivites, + } = useQuery( + ["recentActivities", pageNumber], + getRecentActivities(10, pageNumber - 1), + { keepPreviousData: true, enabled: false } + ); + + useEffect(() => { + if (recentActivitiesCountFetchedData) { + console.log(recentActivitiesCountFetchedData); + refechRecentActivites(); + } + }, [recentActivitiesCountFetchedData]); + + useEffect(() => { + if (isErrorRecentActivities) { + const error: any = isErrorRecentActivities; + openNotification({ + type: "error", + message: error?.response?.data + ? error?.response?.data?.message + : error?.message, + }); + } + + if (isErrorRecentActivitiesCount) { + const error: any = isErrorRecentActivitiesCount; + openNotification({ + type: "error", + message: error?.response?.data + ? error?.response?.data?.message + : error?.message, + }); + } + }, [isErrorRecentActivities, isErrorRecentActivitiesCount]); + + return ( + <> +
({ + ...event, + cameraName: cameras.find((item) => item.id == event.cameraId) + ?.name, + }) + ) + } + pagination={{ + total: recentActivitiesCountFetchedData, + onChange: setPageNumber, + }} + rowKey={"_id"} + loading={isLoadingRecentActivitiesCount || isLoadingRecentActivities} + /> + + ); +}; diff --git a/Frontend/src/containers/settings-container.tsx b/Frontend/src/containers/settings-container.tsx new file mode 100644 index 0000000..37509e3 --- /dev/null +++ b/Frontend/src/containers/settings-container.tsx @@ -0,0 +1,100 @@ +"use client"; +import { useEffect, useState } from "react"; +import { useForm, Controller } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import type { FC } from "react"; +import React from "react"; +import { generateSettingFormSchema } from "@/utils"; +import { Input, Button } from "@/components"; +import { updateCamera, queryClient } from "@/api"; +import { useCameraSlice, useNotificationSlice } from "@/hooks"; +import { useMutation } from "react-query"; + +/* This container renders setting form */ + +type PropsType = {}; + +export const SettingsContainer: FC = () => { + /* state*/ + const [isLoading, setIsLoading] = useState(false); + const { cameras } = useCameraSlice(); + const { + control, + handleSubmit, + formState: { errors, isValid }, + setValue + } = useForm({ + resolver: yupResolver( + generateSettingFormSchema(cameras.map((item) => item.id)) + ), + }); + const { openNotification } = useNotificationSlice(); + const { mutate } = useMutation(updateCamera, { + onSuccess: () => { + queryClient.invalidateQueries('cameras') + }, + }); + + + /* event handlers */ + const onSubmit = async (data: any) => { + try { + setIsLoading(true); + console.log(data); + mutate(data); + openNotification({ + type: "success", + message: "Updated Successfully.", + }); + } catch (error: any) { + openNotification({ + type: "error", + message: error?.response?.data + ? error.response?.data?.message + : error.message, + }); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + if (cameras.length > 0) { + cameras.forEach( + (camera) => { setValue(camera.id.toString(), camera.name) } + ) + } + + }, [cameras]) + + return ( +
+
+ {cameras.map((camera) => ( + ( + + )} + /> + ))} + +
+ +
+ +
+ ); +}; diff --git a/Frontend/src/containers/video-stream-container.tsx b/Frontend/src/containers/video-stream-container.tsx index d07ed35..37f2b14 100644 --- a/Frontend/src/containers/video-stream-container.tsx +++ b/Frontend/src/containers/video-stream-container.tsx @@ -17,16 +17,6 @@ const webSocketURL = process.env.NEXT_PUBLIC_BACKEND_URL ? process.env.NEXT_PUBLIC_BACKEND_URL : ""; -const socket = io(webSocketURL, { - transportOptions: { - polling: { - extraHeaders: { - Authorization: `Bearer ${sessionStorage.getItem("access_token")}`, - }, - }, - }, -}); - /* This container renders different video recording screens */ export const VideoStreamContainer: FC = ({ sizePerScreen = 9 }) => { const [subscribers, setSubscribers] = useState([]); @@ -40,18 +30,36 @@ export const VideoStreamContainer: FC = ({ sizePerScreen = 9 }) => { const elem = document.documentElement; if (elem.requestFullscreen && !isFullScreenGrid) { elem.requestFullscreen(); + toggleIsFullScreenGrid(true); + console.log("Setting true"); } if (document.exitFullscreen && isFullScreenGrid) { document.exitFullscreen(); + toggleIsFullScreenGrid(false); + console.log("Setting false"); } + }; - toggleIsFullScreenGrid(); + const onExitFullScreenEscape = () => { + if (!document.fullscreen) { + document.body.style.overflow = "auto"; + toggleIsFullScreenGrid(false); + } }; /* useEffect hooks */ useEffect(() => { - setCameras(camerasData); + const socket = io(webSocketURL, { + transportOptions: { + polling: { + extraHeaders: { + Authorization: `Bearer ${sessionStorage.getItem("access_token")}`, + }, + }, + }, + }); + const openVidu = new OpenVidu(); const session = openVidu.initSession(); @@ -79,12 +87,18 @@ export const VideoStreamContainer: FC = ({ sizePerScreen = 9 }) => { // On every Stream destroyed... session.on("streamDestroyed", (event) => { // Remove the stream from 'subscribers' array + console.log(event); const streamManager = event.stream.streamManager; setSubscribers( subscribers.filter((subscriber) => subscriber != streamManager) ); }); + // On every Stream destroyed... + session.on("streamPropertyChanged", (event) => { + console.log(event); + }); + // On every asynchronous exception... session.on("exception", (exception) => { console.error(exception); @@ -149,7 +163,12 @@ export const VideoStreamContainer: FC = ({ sizePerScreen = 9 }) => { console.log(data); }); - return () => window.addEventListener("beforeunload", disconnectSession); + document.addEventListener("fullscreenchange", onExitFullScreenEscape); + + return () => { + document.removeEventListener("fullscreenchange", onExitFullScreenEscape); + window.addEventListener("beforeunload", disconnectSession); + }; }, []); useEffect(() => { @@ -160,7 +179,6 @@ export const VideoStreamContainer: FC = ({ sizePerScreen = 9 }) => { ); }, [subscribers]); - return ( <>
diff --git a/Frontend/src/data/camera-data.tsx b/Frontend/src/data/camera-data.tsx index a13f157..921c1c3 100644 --- a/Frontend/src/data/camera-data.tsx +++ b/Frontend/src/data/camera-data.tsx @@ -2,39 +2,51 @@ import { Camera } from "@/types"; export const cameras: Camera[] = [ { - key: "1", + id: "0", name: "Backyard", isActive: true, url: "http://localhost:8080/hls/1", }, { - key: "main-road", + id: "1", name: "Main road", isActive: false, url: "https://www.youtube.com/embed/yNQmth5kUZ0?modestbranding=1&showinfo=0&controls=0&autoplay=1&mute=1", }, { - key: "door", + id: "2", name: "Door", isActive: false, url: "https://www.youtube.com/embed/dV9ngLCKE7k?modestbranding=1&showinfo=0&controls=0&autoplay=1&mute=1", }, { - key: "pet-room", + id: "3", name: "Pet room", isActive: false, url: "https://www.youtube.com/embed/ewEW_xAKRMg?modestbranding=1&showinfo=0&controls=0&autoplay=1&mute=1", }, { - key: "basement", + id: "4", name: "Basement", isActive: false, url: "https://www.youtube.com/embed/ewEW_xAKRMg?modestbranding=1&showinfo=0&controls=0&autoplay=1&mute=1", }, { - key: "baby-room", + id: "5", name: "Baby room", isActive: false, url: "https://www.youtube.com/embed/ewEW_xAKRMg?modestbranding=1&showinfo=0&controls=0&autoplay=1&mute=1", }, + { + id: "6", + name: "Dinning room", + isActive: false, + url: "https://www.youtube.com/embed/ewEW_xAKRMg?modestbranding=1&showinfo=0&controls=0&autoplay=1&mute=1", + }, + { + id: "7", + name: "Kitchen", + isActive: false, + url: "https://www.youtube.com/embed/ewEW_xAKRMg?modestbranding=1&showinfo=0&controls=0&autoplay=1&mute=1", + }, ]; diff --git a/Frontend/src/data/form-schema-data.tsx b/Frontend/src/data/form-schema-data.tsx index 95af96f..5529e93 100644 --- a/Frontend/src/data/form-schema-data.tsx +++ b/Frontend/src/data/form-schema-data.tsx @@ -6,3 +6,11 @@ export const loginFormSchema = yup password: yup.string().required("This field is required"), }) .required(); + + + export const settingFormSchema = yup + .object({ + name: yup.string().required("This field is required"), + password: yup.string().required("This field is required"), + }) + .required(); diff --git a/Frontend/src/data/navbar-data.tsx b/Frontend/src/data/navbar-data.tsx index 9bf41a6..4bd0720 100644 --- a/Frontend/src/data/navbar-data.tsx +++ b/Frontend/src/data/navbar-data.tsx @@ -19,34 +19,34 @@ export const loggedInNavBarItems: NavBarItem[] = [ route: "/video-stream", icon: VideoCameraOutlined, }, - { - key: "authorized-entites", - label: "Authorized Entities", - route: "/authorized-entities", - icon: UserOutlined, - }, + // { + // key: "authorized-entites", + // label: "Authorized Entities", + // route: "/authorized-entities", + // icon: UserOutlined, + // }, { key: "recent-activities", label: "Recent Activities", route: "/recent-activities", icon: HistoryOutlined, }, - { - key: "playback", - label: "Playback", - route: "/playback", - icon: PlayCircleOutlined, - }, - { - key: "analytics", - label: "Analytics", - route: "/analytics", - icon: BarChartOutlined, - }, + // { + // key: "playback", + // label: "Playback", + // route: "/playback", + // icon: PlayCircleOutlined, + // }, + // { + // key: "analytics", + // label: "Analytics", + // route: "/analytics", + // icon: BarChartOutlined, + // }, { key: "setting", label: "Setting", - route: "/setting", + route: "/settings", icon: SettingOutlined, }, { diff --git a/Frontend/src/data/recent-activities-data.tsx b/Frontend/src/data/recent-activities-data.tsx index 8dca1e4..38af6c1 100644 --- a/Frontend/src/data/recent-activities-data.tsx +++ b/Frontend/src/data/recent-activities-data.tsx @@ -1,11 +1,9 @@ "use client"; import { Activity } from "@/types"; -import { Button } from "@/components"; +import { ViewScreenshotButton } from "@/components"; import { Space } from "antd"; -import { CameraOutlined, VideoCameraOutlined } from "@ant-design/icons"; import type { ColumnsType } from "antd/es/table"; import { cameras } from "./camera-data"; -import { authorizedEntitiesData } from "./authorized-entities-data"; export const recentActivitiesColumns: ColumnsType = [ { @@ -15,28 +13,22 @@ export const recentActivitiesColumns: ColumnsType = [ }, { title: "Camera", - dataIndex: "camera", - render: (_, record) => {record.camera.name}, - }, - { - title: "Entity", - dataIndex: "entity", - render: (_, record) => ( - - {record.entity ? record.entity.name : "Unkown"} - - ), + dataIndex: "cameraName", + render: (_, record) => {record.cameraName}, }, { title: "Action", key: "action", render: (_, record) => ( -