diff --git a/.env b/.env index e4a7fd9..fc6bcfa 100644 --- a/.env +++ b/.env @@ -1,6 +1,7 @@ VITE_API_URL=http://localhost:3000/api/v1 NODE_ENV=development +#NODE_ENV=production #Database Configuration DB_NAME=ve_router_db diff --git a/db-scripts/02-seed_common_data.sql b/db-scripts/02-seed_common_data.sql index c7e79bb..7935f99 100644 --- a/db-scripts/02-seed_common_data.sql +++ b/db-scripts/02-seed_common_data.sql @@ -32,3 +32,6 @@ BEGIN END // DELIMITER ; + +-- Automatically call the procedure after creation +CALL seed_common_router_data(); diff --git a/router-dashboard/.env b/router-dashboard/.env index 5132f4a..587d23a 100644 --- a/router-dashboard/.env +++ b/router-dashboard/.env @@ -1,2 +1,3 @@ VITE_API_URL=http://${SERVER_IP:-localhost}:3000/api/v1 -VITE_NODE_ENV=development \ No newline at end of file +VITE_NODE_ENV=development +#VITE_NODE_ENV=production \ No newline at end of file diff --git a/router-dashboard/src/components/Login.tsx b/router-dashboard/src/components/Login.tsx index eaf9682..176bfff 100644 --- a/router-dashboard/src/components/Login.tsx +++ b/router-dashboard/src/components/Login.tsx @@ -5,71 +5,86 @@ import { useNavigate } from 'react-router-dom'; const Login: React.FC = () => { const navigate = useNavigate(); - const { setIsAuthenticated } = useAuth(); // Access the setIsAuthenticated function + const { setIsAuthenticated } = useAuth(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [usernameError, setUsernameError] = useState(''); const [passwordError, setPasswordError] = useState(''); const [generalError, setGeneralError] = useState(''); + const [loading, setLoading] = useState(true); // Start with loading true to show spinner initially - const [sessionExpiredMessage, setSessionExpiredMessage] = useState(null); // State to store the session expired message + const [sessionExpiredMessage, setSessionExpiredMessage] = useState(null); + // Simulate initial loading state or check for session expiry useEffect(() => { - // Check if the URL has the sessionExpired query parameter const urlParams = new URLSearchParams(window.location.search); const sessionExpired = urlParams.get('sessionExpired'); if (sessionExpired) { setSessionExpiredMessage('Session expired or something went wrong. Please log in again.'); } - }, []); // Empty dependency array means this will run only on component mount + + // Simulate a delay to show loading spinner for a brief moment (or wait for other async checks) + setTimeout(() => { + setLoading(false); // Set loading to false after a certain period + }, 1000); // You can adjust the delay (in ms) to suit your needs + }, []); const handleLogin = async (event: React.FormEvent) => { event.preventDefault(); - + + setLoading(true); // Set loading to true when login starts + let hasError = false; - - // Validate username and password + if (!username.trim()) { setUsernameError('Username is required'); hasError = true; } else { setUsernameError(''); } - + if (!password.trim()) { setPasswordError('Password is required'); hasError = true; } else { setPasswordError(''); } - - // Stop execution if there are validation errors - if (hasError) return; - + + if (hasError) { + setLoading(false); // If there are errors, stop the loading state + return; + } + try { const result = await apiService.login(username, password); if (result) { - // Save tokens and user details in localStorage localStorage.setItem('accessToken', result.accessToken); localStorage.setItem('refreshToken', result.refreshToken); localStorage.setItem('user', JSON.stringify(result.user)); localStorage.setItem('isLoggedIn', 'true'); - - // Update the authentication state in the context + setIsAuthenticated(true); - - // Redirect to Dashboard - //navigate('/'); - window.location.href = '/'; + navigate('/', { replace: true }) } else { setGeneralError('Invalid username or password'); } } catch (error: any) { setGeneralError(error.message || 'An unexpected error occurred'); + } finally { + setLoading(false); // Stop loading once the login attempt is finished } }; - + + // Render loading spinner initially + if (loading) { + return ( +
{/* Adjusted margin-top */} +
+
+ ); + } + return (
{/* Login Content */} diff --git a/router-dashboard/src/components/dashboard/DashboardLayout.tsx b/router-dashboard/src/components/dashboard/DashboardLayout.tsx index a36fadc..91499ae 100644 --- a/router-dashboard/src/components/dashboard/DashboardLayout.tsx +++ b/router-dashboard/src/components/dashboard/DashboardLayout.tsx @@ -8,6 +8,7 @@ import { RouterData } from '../../types'; import { apiService } from '../../services/api.service'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; // Import the useAuth hook +import useIdleTimeout from '../../utils/useIdleTimeout'; interface User { name: string; @@ -107,12 +108,17 @@ const DashboardLayout: React.FC = () => { // Redirect to login page //navigate('/login'); - window.location.href = '/login'; + //window.location.href = '/login'; + navigate('/login', { replace: true }); } catch (error) { console.error('Error during logout:', error); } }; + // Use the idle timeout hook + useIdleTimeout(handleLogout); + + const getSummary = (routerData: RouterData[]) => { return { total: routerData.length, @@ -129,7 +135,6 @@ const DashboardLayout: React.FC = () => { }; - const renderContent = () => { if (loading) { return ( @@ -269,6 +274,7 @@ const DashboardLayout: React.FC = () => {
); + }; export default DashboardLayout; \ No newline at end of file diff --git a/router-dashboard/src/components/dashboard/RouterTableRow.tsx b/router-dashboard/src/components/dashboard/RouterTableRow.tsx index 6c2c2ce..7748211 100644 --- a/router-dashboard/src/components/dashboard/RouterTableRow.tsx +++ b/router-dashboard/src/components/dashboard/RouterTableRow.tsx @@ -58,8 +58,7 @@ export const RouterTableRow: React.FC = ({
Facility AET: {router.facilityAET}
-
OpenVPN IP: {router.openvpnIp}
-
Router VM Primary IP: {router.routerVmPrimaryIp}
+
Router VM IP: {router.routerVmPrimaryIp}
diff --git a/router-dashboard/src/services/api.service.ts b/router-dashboard/src/services/api.service.ts index 25f0103..c727722 100644 --- a/router-dashboard/src/services/api.service.ts +++ b/router-dashboard/src/services/api.service.ts @@ -1,7 +1,13 @@ // router-dashboard/src/services/api.service.ts import { RouterData, FilterType, BackendRouter } from '../types'; -const API_BASE_URL = 'http://localhost:3000/api/v1'; +const API_BASE_URL = process.env.NODE_ENV === 'development' + ? 'http://localhost:3000/api/v1' + : '/api/v1'; + + +console.log("process.env.NODE_ENV", process.env.NODE_ENV); +console.log("API_BASE_URL", API_BASE_URL); // Default request options for all API calls const DEFAULT_OPTIONS = { diff --git a/router-dashboard/src/utils/useIdleTimeout.ts b/router-dashboard/src/utils/useIdleTimeout.ts new file mode 100644 index 0000000..ced3b0b --- /dev/null +++ b/router-dashboard/src/utils/useIdleTimeout.ts @@ -0,0 +1,62 @@ +import { useEffect, useRef } from 'react'; + +/** + * Custom hook to manage user inactivity and trigger a timeout action. + * @param onTimeout - Function to execute when the timeout is reached. + * @param timeout - Duration (in ms) before timeout. Default: 30 minutes. + */ +const useIdleTimeout = (onTimeout: () => void, timeout: number = 30 * 60 * 1000) => { + // Ref to persist the timeout timer and prevent resets during re-renders + const timeoutTimerRef = useRef(null); + + // Function to reset the timer + const resetTimer = () => { + if (timeoutTimerRef.current) { + clearTimeout(timeoutTimerRef.current); + } + timeoutTimerRef.current = setTimeout(onTimeout, timeout); + }; + + useEffect(() => { + // Event listener for user activity (mousemove, keydown, click) + const handleUserActivity = () => { + resetTimer(); + }; + + // Event listener for tab visibility (page hidden or visible) + const handleVisibilityChange = () => { + if (document.hidden) { + if (timeoutTimerRef.current) { + clearTimeout(timeoutTimerRef.current); // Pause the timer if the tab is not visible + } + } else { + resetTimer(); // Restart the timer when the tab becomes visible again + } + }; + + // Add event listeners for user activity + window.addEventListener('mousemove', handleUserActivity); + window.addEventListener('keydown', handleUserActivity); + window.addEventListener('click', handleUserActivity); + + // Add event listener for page visibility change + document.addEventListener('visibilitychange', handleVisibilityChange); + + // Initialize the timer when the component mounts + resetTimer(); + + // Cleanup function to remove listeners and clear the timer + return () => { + if (timeoutTimerRef.current) { + clearTimeout(timeoutTimerRef.current); // Cleanup the timeout + } + window.removeEventListener('mousemove', handleUserActivity); + window.removeEventListener('keydown', handleUserActivity); + window.removeEventListener('click', handleUserActivity); + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; + }, [onTimeout, timeout]); // Dependency array to only re-run on changes to onTimeout or timeout + +}; + +export default useIdleTimeout; diff --git a/ve-router-backend/src/app.ts b/ve-router-backend/src/app.ts index 1a77b3c..949aac7 100644 --- a/ve-router-backend/src/app.ts +++ b/ve-router-backend/src/app.ts @@ -19,7 +19,7 @@ app.use((req, res, next) => { // CORS configuration app.use(cors({ - origin: ['http://localhost:5173', 'http://localhost:3000'], + origin: ['http://localhost:5173', 'https://router-dashboard.dev.vitalengine.com:3000', 'https://router-dashboard.vitalengine.com:3000'], credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] diff --git a/ve-router-backend/src/repositories/RouterRepository.ts b/ve-router-backend/src/repositories/RouterRepository.ts index aa50422..9cb28a4 100644 --- a/ve-router-backend/src/repositories/RouterRepository.ts +++ b/ve-router-backend/src/repositories/RouterRepository.ts @@ -162,18 +162,45 @@ export class RouterRepository { return vpnStatus === 'VPN_CONNECTED' && appStatus === 'CONTAINER_RUNNING' && vmStatus === 'NET_ONLINE' ? 'CONNECTED' : 'DISCONNECTED'; }; + async updateRouterAndContainerStatus(id: number, vpnStatus: string, appStatus: string): Promise { + // Update router status + await pool.query( + `UPDATE routers SET vpn_status_code = ?, app_status_code = ?, updated_at = NOW() WHERE id = ?`, + [vpnStatus, appStatus, id] + ); + + // Update container status + await pool.query( + `UPDATE container_status SET status_code = ?, updated_at = NOW() WHERE router_id = ?`, + ["CONTAINER_STOPPED", id] + ); + } + private async transformDatabaseRouter(dbRouter: any, index: number): Promise { try { - const studies = await this.getRouterStudies(dbRouter.id); - //const vms = await this.getRouterVMs(dbRouter.id); - const vms:VM[] = []; - const containers = await this.getRouterContainers(dbRouter.id); + const lastSeen = new Date(dbRouter.last_seen).toISOString(); const vpnStatus = dbRouter.vpn_status_code.toString(); const appStatus = dbRouter.app_status_code.toString(); const vmStatus = await this.calculateVMStatus(lastSeen); const routerStatus = await this.calculateSystemStatus(vpnStatus, appStatus, vmStatus); + // Update vpnStatus and appStatus based on vmStatus + let updatedVpnStatus = vpnStatus; + let updatedAppStatus = appStatus; + + if (vmStatus === 'NET_OFFLINE') { + updatedVpnStatus = 'VPN_DISCONNECTED'; + updatedAppStatus = 'CONTAINER_STOPPED'; + + await this.updateRouterAndContainerStatus(dbRouter.id, updatedVpnStatus, updatedAppStatus); + } + + const studies = await this.getRouterStudies(dbRouter.id); + //const vms = await this.getRouterVMs(dbRouter.id); + const vms:VM[] = []; + const containers = await this.getRouterContainers(dbRouter.id); + return { id: dbRouter.id, slNo: index + 1, @@ -192,8 +219,8 @@ export class RouterRepository { studies }, systemStatus: { - vpnStatus: vpnStatus, - appStatus: appStatus, + vpnStatus: updatedVpnStatus, + appStatus: updatedAppStatus, vmStatus: vmStatus, routerStatus: routerStatus, vms, diff --git a/ve-router-backend/src/services/AuthService.ts b/ve-router-backend/src/services/AuthService.ts index 21508f5..ff48155 100644 --- a/ve-router-backend/src/services/AuthService.ts +++ b/ve-router-backend/src/services/AuthService.ts @@ -19,8 +19,8 @@ export class AuthService { return jwt.sign( { userId: user.id, username: user.username, role: user.role }, process.env.JWT_SECRET as string, - //{ expiresIn: '30m' } - { expiresIn: '1m' } + { expiresIn: '30m' } + //{ expiresIn: '1m' } ); }; @@ -29,8 +29,8 @@ export class AuthService { const accessToken = jwt.sign( { userId: user.id, username: user.username, role: user.role }, process.env.JWT_SECRET as string, - //{ expiresIn: '30m' } - { expiresIn: '1m' } + { expiresIn: '30m' } + //{ expiresIn: '1m' } ); const refreshToken = jwt.sign( diff --git a/ve-router-backend/src/services/SetupService.ts b/ve-router-backend/src/services/SetupService.ts index 7eeb55a..a8ed04d 100644 --- a/ve-router-backend/src/services/SetupService.ts +++ b/ve-router-backend/src/services/SetupService.ts @@ -18,7 +18,8 @@ export class SetupService { { name: 'Administrator', username: 'admin', email: 'admin@ve.com', password: 'admin@@007', role: 'admin' }, { name: 'Maqbool Patel', username: 'maqbool', email: 'maqbool@ve.com', password: 'maqbool@@210', role: 'admin' }, { name: 'Kavya Raghunath', username: 'kavya', email: 'kavya@ve.com', password: 'kavya@@124', role: 'viewer' }, - { name: 'Reid McKenzie', username: 'reid', email: 'reid@ve.com', password: 'reid@@321', role: 'viewer' } + { name: 'Reid McKenzie', username: 'reid', email: 'reid@ve.com', password: 'reid@@321', role: 'viewer' }, + { name: 'Guest', username: 'guest', email: 'guest@ve.com', password: 'guest@@012', role: 'viewer' } ]; const createdUsers = [];