Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intrusion detection #16

Merged
merged 11 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Backend/src/app/frontend/frontend.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 3 additions & 3 deletions Backend/src/app/login.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
},
},
Expand Down
1 change: 1 addition & 0 deletions Backend/src/camera.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const cameraIds = [0, 1, 2, 3, 4, 5, 6, 7];
5 changes: 0 additions & 5 deletions Backend/src/cameraStream/cameraData.ts

This file was deleted.

21 changes: 19 additions & 2 deletions Backend/src/database/database.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -59,11 +60,22 @@ 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.initCameras();
}

async addData(data: DataType<number>) {
Expand Down Expand Up @@ -101,6 +113,10 @@ export class DatabaseService {
else return res.toArray();
}

getCameras(): Promise<Document[]> {
return this.DB.collection('camera_names').find().toArray();
}

aggregateCamera(filter?: FiltersAvailable): Promise<Document[]> {
return this.DB.collection('cameras')
.aggregate()
Expand Down Expand Up @@ -214,11 +230,12 @@ export class DatabaseService {
{
name: 'NVR',
ip: process.env.NVR_IP_ADDRESS,
channels: [0, 1, 2, 3, 4, 5, 6, 7],
channels: cameraIds,
},
);

return {
ip: array[0].ip,
ip: process.env.NVR_IP_ADDRESS,
channels: array[0].channels,
};

Expand Down
23 changes: 23 additions & 0 deletions Frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
27 changes: 27 additions & 0 deletions Frontend/src/api/cameras.ts
Original file line number Diff line number Diff line change
@@ -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<CamerasResponseDTO>(`${endpoints.getCameras}`, {})
.then((result) => result.data);
};

export const updateCamera = async (configuration: AnyObject) => {
for (const key in configuration) {
try {
await axiosClient
.post<CamerasResponseDTO>(`${key}/${configuration[key]}`, {});
} catch (error: any) {
console.log(error);
}

}
return "Success"

};
9 changes: 8 additions & 1 deletion Frontend/src/api/endpoints.ts
Original file line number Diff line number Diff line change
@@ -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",
};
6 changes: 4 additions & 2 deletions Frontend/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -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";
2 changes: 1 addition & 1 deletion Frontend/src/api/react-query-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();
export const queryClient = new QueryClient();

export const ReactQueryProvider = ({
children,
Expand Down
54 changes: 54 additions & 0 deletions Frontend/src/api/recent-activities.ts
Original file line number Diff line number Diff line change
@@ -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<RecentActivitiesResponseDTO>(
`${endpoints.recentActivities}/${top}/${skip}`,
{}
)
.then((result) => result.data);
};

export const getRecentActivitiesCount = () => {
return () =>
axiosClient
.get<RecentActivitiesCountResponseDTO>(
`${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<RecentActivityImageResponseDTO>(
`${id}/${timestamp}`,
{}
)
.then((result) => result.data);
};
10 changes: 2 additions & 8 deletions Frontend/src/app/recent-activities/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
"use client";
import { Table } from "@/components";
import { recentActivitiesData, recentActivitiesColumns } from "@/data";
import { RecentActivitiesContainer } from "@/containers";

export default function RecentActivities() {
return (
<div>
<Table
columns={recentActivitiesColumns}
data={recentActivitiesData}
pagination={{ total: recentActivitiesData.length }}
rowKey={"id"}
/>
<RecentActivitiesContainer />
</div>
);
}
10 changes: 10 additions & 0 deletions Frontend/src/app/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";
import { SettingsContainer } from "@/containers";

export default function Settings() {
return (
<div>
<SettingsContainer />
</div>
);
}
75 changes: 75 additions & 0 deletions Frontend/src/components/button/view-screenshot.tsx
Original file line number Diff line number Diff line change
@@ -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<PropsType> = ({
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 (
<Button
toolTipText="View screenshot"
icon={<CameraOutlined />}
onClick={onButtonClick}
/>
);
};
1 change: 1 addition & 0 deletions Frontend/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { VideoRecordingScreen } from "./video-recording-screen/video-recording-screen";
export { Table } from "./table/table";
export { Button } from "./button/button";
export { ViewScreenshotButton } from "./button/view-screenshot";
export { Card } from "./card/card";
export { Input } from "./input/input";
2 changes: 1 addition & 1 deletion Frontend/src/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type PropsType = {

/** This component renders a input field */
export const Input = React.forwardRef<HTMLInputElement, PropsType>(
({ label, password = false, error = undefined, ...props }, ref) => {
({ label, password = false, error = undefined, ...props }, ref:any) => {
return (
<div className="py-1">
<div className={"font-bold pb-2"}>{label}</div>
Expand Down
2 changes: 1 addition & 1 deletion Frontend/src/components/table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Table as AntTable } from "antd";
import { AnyObject } from "@/types";

type PropsType<T extends AnyObject> = {
data: T[];
data: T[] | undefined;
columns: ColumnsType<T>;
pagination?: TablePaginationConfig;
loading?: boolean;
Expand Down
Loading
Loading