Redirect to login page when no activity for 30 minutes. Show only facility AET and VM IP. Add default users for now because user management is not yet implemented.
This commit is contained in:
parent
262307f17d
commit
7c288da183
1
.env
1
.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
|
||||
|
||||
@ -32,3 +32,6 @@ BEGIN
|
||||
END //
|
||||
|
||||
DELIMITER ;
|
||||
|
||||
-- Automatically call the procedure after creation
|
||||
CALL seed_common_router_data();
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
VITE_API_URL=http://${SERVER_IP:-localhost}:3000/api/v1
|
||||
VITE_NODE_ENV=development
|
||||
#VITE_NODE_ENV=production
|
||||
@ -5,31 +5,38 @@ 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<string | null>(null); // State to store the session expired message
|
||||
const [sessionExpiredMessage, setSessionExpiredMessage] = useState<string | null>(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;
|
||||
@ -44,32 +51,40 @@ const Login: React.FC = () => {
|
||||
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 (
|
||||
<div className="p-6 flex items-center justify-center mt-40"> {/* Adjusted margin-top */}
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-gray-100">
|
||||
{/* Login Content */}
|
||||
|
||||
@ -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 = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default DashboardLayout;
|
||||
@ -58,8 +58,7 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
|
||||
<div className="bg-white p-3 rounded shadow-sm">
|
||||
<div className="grid gap-2">
|
||||
<div><span className="font-medium">Facility AET:</span> {router.facilityAET}</div>
|
||||
<div><span className="font-medium">OpenVPN IP:</span> {router.openvpnIp}</div>
|
||||
<div><span className="font-medium">Router VM Primary IP:</span> {router.routerVmPrimaryIp}</div>
|
||||
<div><span className="font-medium">Router VM IP:</span> {router.routerVmPrimaryIp}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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 = {
|
||||
|
||||
62
router-dashboard/src/utils/useIdleTimeout.ts
Normal file
62
router-dashboard/src/utils/useIdleTimeout.ts
Normal file
@ -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<NodeJS.Timeout | null>(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;
|
||||
@ -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']
|
||||
|
||||
@ -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<void> {
|
||||
// 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<RouterData> {
|
||||
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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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 = [];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user