Skip to content

Commit

Permalink
Add device and camera management features, update layout #1
Browse files Browse the repository at this point in the history
  • Loading branch information
hcwinsemius committed Jan 14, 2025
1 parent 3577103 commit 1cafc6d
Show file tree
Hide file tree
Showing 12 changed files with 471 additions and 43 deletions.
10 changes: 6 additions & 4 deletions dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ cd portal
npm install

# Get axios dependency
npm install axios
npm install react-router-dom
# Start dev server
npm run dev
npm install axios # for API connection
npm install react-router-dom # for organizing the router
npm install dotenv # for setting environment variables

# Start dev server, ensuring the API url is added as environment variable
VITE_API_BASE_URL="http://<name-of-server>:<port-of-server>" npm run dev
```

Currently, two official plugins are available:
Expand Down
13 changes: 13 additions & 0 deletions dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"axios": "^1.7.9",
"dotenv": "^16.4.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.1.1",
Expand Down
25 changes: 23 additions & 2 deletions dashboard/src/App.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
padding: 0 auto;
// text-align: center;
height: 100%;
}
.app-container {
display: flex;
flex-direction: column; /* Stack Navbar, Main Content, and Footer */
height: 100vh; /* Ensure the container fills the entire viewport */
overflow: hidden; /* prevent body from scrolling */
}
.main-content {
flex: 1; /* This makes the main content area take up the remaining space */
overflow-y: auto; /* scrollable with overflowing content */
padding: 1rem; /* Optional: Add some padding */
}


.logo {
height: 6em;
Expand Down Expand Up @@ -40,3 +53,11 @@
.read-the-docs {
color: #888;
}

a {
color: #FFFFFF;
}
a:hover {
color: #FFFFFF;
font-weight: bold;
}
16 changes: 12 additions & 4 deletions dashboard/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import reactLogo from './assets/react.svg'
import reactLogo from '/react.svg'
import orcLogo from '/orc_favicon.svg'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

import './App.css'
import Navbar from './nav/Navbar';
import Footer from './nav/Footer';
import Home from './views/home';
import DeviceForm from './views/deviceForm';
import Device from './views/device';
import DiskManagement from './views/diskManagement'
import CameraAim from './views/cameraAim'
import api from './api'

const App = () => {
return (
<Router>
<div>
<div className="app-container">
{/* Navbar appears everywhere */}
<Navbar />
{/* Define the route structure */}
<div className="main-content">
<Routes>
<Route path="*" element={<div>Snap!! 404 Page Not Found</div>} />
<Route path="/" element={<Home />} />
<Route path="/device_form" element={<DeviceForm />} />
<Route path="/device" element={<Device />} />
<Route path="/disk_management" element={<DiskManagement />} />
<Route path="/camera_aim" element={<CameraAim />} />
</Routes>
</div>
<Footer />
</div>
</Router>
)
Expand Down
14 changes: 13 additions & 1 deletion dashboard/src/api.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
//window.process = {};
import axios from 'axios'
//import dotenv from 'dotenv';
//dotenv.config();

// Ensure API_BASE_URL is defined
if (!import.meta.env.VITE_API_BASE_URL) {
throw new Error('VITE_API_BASE_URL environment variable is not defined!');
}

const api = axios.create({
baseURL: 'http://framework:8000'
baseURL: import.meta.env.VITE_API_BASE_URL, // Load from environment variable
});

//const api = axios.create({
// baseURL: 'http://framework:8000'
//});


export default api;
22 changes: 22 additions & 0 deletions dashboard/src/nav/Footer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.footer {
background-color: #2c3e50; /* Consistent color */
color: white;
text-align: center;
padding: 1rem;
position: relative; /* Static positioning by default */
z-index: 10;
bottom: 0; /* Will always remain at the bottom with flex layout */
width: 100%; /* Ensure the footer spans the full width of the screen */
}

.footer-content img {
display: inline-block;
}

.footer-content p {
margin: 0;
display: inline-block;
font-size: 0.9rem
align-items: center;
}
*/
15 changes: 15 additions & 0 deletions dashboard/src/nav/Footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import './Footer.css'

const Footer = () => {
return (
<footer className="footer bg-primary">
<div className="footer-content">
<img src="./public/orc_favicon.svg" alt="ORC Logo" width="20" className="footer-logo"/>
{' '}
<p>© {new Date().getFullYear()} <a href="https://rainbowsensing.com">Rainbowsensing</a>. All rights reserved.</p>
</div>
</footer>
);
};

export default Footer;
27 changes: 24 additions & 3 deletions dashboard/src/nav/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import React from 'react';
import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';

const Navbar = () => {
const [isOpen, setIsOpen] = useState(false); // track if navbar is open / closed
const handleToggle = () => {
setIsOpen(!isOpen); // Toggles the `isOpen` state
};
const handleClose = () => {
setIsOpen(false); // Closes the navbar when called
};
return (
<nav className='navbar navbar-dark bg-primary'>
<div className='container-fluid'>
Expand All @@ -24,8 +31,22 @@ const Navbar = () => {
<li className="nav-item">
<NavLink
className={({ isActive }) => isActive ? "nav-link active" : "nav-link"}
to="/device_form">
Device info
to="/device">
Device information
</NavLink>
</li>
<li className="nav-item">
<NavLink
className={({ isActive }) => isActive ? "nav-link active" : "nav-link"}
to="/disk_management">
Disk management
</NavLink>
</li>
<li className="nav-item">
<NavLink
className={({ isActive }) => isActive ? "nav-link active" : "nav-link"}
to="/camera_aim">
Camera aiming
</NavLink>
</li>
</ul>
Expand Down
113 changes: 113 additions & 0 deletions dashboard/src/views/cameraAim.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, {useState, useEffect} from 'react';

import reactLogo from '/react.svg'
import orcLogo from '/orc_favicon.svg'
import api from "../api"

const CameraAim = () => {
const [count, setCount] = useState(0)
const [videoFeedUrl, setVideoFeedUrl] = useState("");
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);

// submit form to display stream
const handleFormSubmit = async (event) => {
event.preventDefault();
setIsLoading(true);
setError(null); // reset errors before doing a new check
const getVideoFeed = async () => {
try {
console.log(event);
const videoUrl = event.target.videoUrl.value;
// const videoUrl = "rtsp://nodeorcpi:8554/cam";
const feedUrl = `${api.defaults.baseURL}/video-feed/?video_url=${encodeURIComponent(videoUrl)}`; // Dynamically get it from Axios
// test the feed by doing an API call
const response = await api.head('/video-feed/?video_url=' + encodeURIComponent(videoUrl));
if (response.status === 200) {
setVideoFeedUrl(feedUrl); // Set the dynamically generated URL
console.log("Setting load status to false");
setIsLoading(false);

} else {
console.log("We have an error")
throw new Error(`Invalid video feed. Status Code: ${response.status}`);
}
} catch (error) {
setError('Failed to load video feed. Ensure the camera is connected and available.');
console.error("Error generating video feed URL:", error);
} finally {
console.log("Setting load status to false")
setIsLoading(false);
}

};

getVideoFeed();
};

// Dynamically generate the stream URL
// useEffect(() => {
// setIsLoading(true);
// setError(null); // reset errors before doing a new check
// const getVideoFeed = async () => {
// try {
// const feedUrl = `${api.defaults.baseURL}/video-feed/`; // Dynamically get it from Axios
// // test the feed by doing an API call
// const response = await api.head('/video-feed/');
// console.log(response);
// if (response.status === 200) {
// setVideoFeedUrl(feedUrl); // Set the dynamically generated URL
// console.log("Setting load status to false");
// setIsLoading(false);
//
// } else {
// console.log("We have an error")
// throw new Error(`Invalid video feed. Status Code: ${response.status}`);
// }
// } catch (error) {
// setError('Failed to load video feed. Ensure the camera is connected and available.');
// console.error("Error generating video feed URL:", error);
// } finally {
// console.log("Setting load status to false")
// setIsLoading(false);
// }
//
// };
//
// getVideoFeed();
// }, []); // Empty dependency to run this once after the component is mounted

return (
<>
<h1>NodeORC configuration</h1>
{isLoading && <p>Loading video feed...</p>}
{error ? (
<p className="text-danger">{error}</p>
) : (
videoFeedUrl && (
<img
src={videoFeedUrl} // Dynamically set the URL
alt="Live Video Stream"
style={{ maxWidth: "100%", height: "auto" }}
/>
)
)}
<div className='container'>
<form onSubmit={handleFormSubmit}>
<div className='mb-3 mt-3'>
<label htmlFor='name' className='form-label'>
Video stream URL (e.g. rtsp://... or http://)
</label>
<input type='text' className='form-control' id='videoUrl' name='videoUrl'/>
</div>
<button type='submit' className='btn btn-primary'>
Submit
</button>
</form>
</div>
</>
)
}


export default CameraAim;
Loading

0 comments on commit 1cafc6d

Please sign in to comment.