Compare commits
No commits in common. "feature/jwt_auth" and "main" have entirely different histories.
feature/jw
...
main
7
.env
7
.env
@ -1,7 +0,0 @@
|
|||||||
VITE_API_URL=http://localhost:3000/api/v1
|
|
||||||
|
|
||||||
NODE_ENV=development
|
|
||||||
#NODE_ENV=production
|
|
||||||
|
|
||||||
#Database Configuration
|
|
||||||
DB_NAME=ve_router_db
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Wait for MySQL to become available
|
|
||||||
echo "Waiting for MySQL to become healthy..."
|
|
||||||
until mysql -h "localhost" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SHOW DATABASES;" &>/dev/null; do
|
|
||||||
echo "Waiting for MySQL..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "MySQL is up and running!"
|
|
||||||
|
|
||||||
# Run the initial common setup (always executed)
|
|
||||||
echo "Running 01-init.sql"
|
|
||||||
mysql -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE < /docker-entrypoint-initdb.d/01-init.sql
|
|
||||||
|
|
||||||
echo "Running 02-seed_common_data.sql"
|
|
||||||
mysql -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE < /docker-entrypoint-initdb.d/02-seed_common_data.sql
|
|
||||||
|
|
||||||
# Check the environment variable and execute environment-specific scripts
|
|
||||||
if [ "$ENVIRONMENT" != "production" ]; then
|
|
||||||
echo "Running 03-seed_router_data_qa.sql for QA (non-production)"
|
|
||||||
mysql -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE < /docker-entrypoint-initdb.d/03-seed_router_data_qa.sql
|
|
||||||
else
|
|
||||||
echo "Production environment detected. Skipping seeding data for QA (non-production)."
|
|
||||||
fi
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
-- Check if the procedure exists, and create it only if it does not
|
|
||||||
DROP PROCEDURE IF EXISTS `seed_common_router_data`;
|
|
||||||
|
|
||||||
DELIMITER //
|
|
||||||
|
|
||||||
CREATE PROCEDURE seed_common_router_data()
|
|
||||||
BEGIN
|
|
||||||
-- Insert Status Categories
|
|
||||||
INSERT INTO status_category (name, description)
|
|
||||||
VALUES
|
|
||||||
('Network', 'Network related statuses'),
|
|
||||||
('Disk', 'Disk related statuses'),
|
|
||||||
('VPN', 'VPN connection statuses'),
|
|
||||||
('License', 'License statuses'),
|
|
||||||
('Container', 'Container related statuses')
|
|
||||||
ON DUPLICATE KEY UPDATE id = id;
|
|
||||||
|
|
||||||
-- Insert Status Types
|
|
||||||
INSERT INTO status_type (category_id, name, code, description, severity)
|
|
||||||
VALUES
|
|
||||||
(1, 'Online', 'NET_ONLINE', 'System is online', 1),
|
|
||||||
(1, 'Offline', 'NET_OFFLINE', 'System is offline', 5),
|
|
||||||
(2, 'Normal', 'DISK_NORMAL', 'Disk usage is normal', 1),
|
|
||||||
(2, 'Warning', 'DISK_WARNING', 'Disk usage is high', 3),
|
|
||||||
(2, 'Critical', 'DISK_CRITICAL', 'Disk usage is critical', 5),
|
|
||||||
(3, 'Connected', 'VPN_CONNECTED', 'VPN is connected', 1),
|
|
||||||
(3, 'Disconnected', 'VPN_DISCONNECTED', 'VPN is disconnected', 5),
|
|
||||||
(5, 'Running', 'CONTAINER_RUNNING', 'Container is running', 1),
|
|
||||||
(5, 'Stopped', 'CONTAINER_STOPPED', 'Container is stopped', 5)
|
|
||||||
ON DUPLICATE KEY UPDATE id = id;
|
|
||||||
|
|
||||||
END //
|
|
||||||
|
|
||||||
DELIMITER ;
|
|
||||||
|
|
||||||
-- Automatically call the procedure after creation
|
|
||||||
CALL seed_common_router_data();
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
-- Check if the procedure exists, and create it only if it does not
|
|
||||||
DROP PROCEDURE IF EXISTS `seed_router_data`;
|
|
||||||
|
|
||||||
DELIMITER //
|
|
||||||
|
|
||||||
CREATE PROCEDURE seed_router_data()
|
|
||||||
BEGIN
|
|
||||||
-- Insert Routers
|
|
||||||
INSERT INTO routers (router_id, facility, router_alias, facility_aet, openvpn_ip, router_vm_primary_ip,
|
|
||||||
last_seen, vpn_status_code, disk_status_code, app_status_code, license_status, free_disk, total_disk, disk_usage)
|
|
||||||
VALUES
|
|
||||||
('RTR001', 'Main Hospital', 'MAIN_RAD', 'RTR_1', '10.8.0.101', '192.168.1.101', NOW(), 'VPN_CONNECTED', 'DISK_NORMAL', 'CONTAINER_RUNNING', 'active', 500000000000, 1000000000000, 50.00),
|
|
||||||
('RTR002', 'Emergency Center', 'ER_RAD', 'RTR_2', '10.8.0.102', '192.168.1.102', NOW(), 'VPN_DISCONNECTED', 'DISK_WARNING', 'CONTAINER_RUNNING', 'active', 400000000000, 1000000000000, 60.00),
|
|
||||||
('RTR003', 'Imaging Center', 'IMG_CENTER', 'RTR_3', '10.8.0.103', '192.168.1.103', NOW(), 'VPN_CONNECTED', 'DISK_NORMAL', 'CONTAINER_RUNNING', 'active', 600000000000, 1000000000000, 40.00)
|
|
||||||
ON DUPLICATE KEY UPDATE id = id;
|
|
||||||
|
|
||||||
-- Store Router IDs
|
|
||||||
SET @router1_id = (SELECT id FROM routers WHERE router_id = 'RTR001');
|
|
||||||
SET @router2_id = (SELECT id FROM routers WHERE router_id = 'RTR002');
|
|
||||||
SET @router3_id = (SELECT id FROM routers WHERE router_id = 'RTR003');
|
|
||||||
|
|
||||||
-- Insert Container Status
|
|
||||||
INSERT INTO container_status (router_id, container_name, status_code, created_at, updated_at)
|
|
||||||
VALUES
|
|
||||||
(@router1_id, 'router-cstore-scp', 'CONTAINER_RUNNING', NOW(), NOW()),
|
|
||||||
(@router1_id, 'router-cstore-scu', 'CONTAINER_RUNNING', NOW(), NOW()),
|
|
||||||
(@router2_id, 'router-cstore-scp', 'CONTAINER_RUNNING', NOW(), NOW()),
|
|
||||||
(@router2_id, 'router-cstore-scu', 'CONTAINER_RUNNING', NOW(), NOW()),
|
|
||||||
(@router3_id, 'router-cstore-scp', 'CONTAINER_RUNNING', NOW(), NOW())
|
|
||||||
ON DUPLICATE KEY UPDATE id = id;
|
|
||||||
|
|
||||||
-- Insert DICOM Study Overview
|
|
||||||
INSERT INTO dicom_study_overview (
|
|
||||||
router_id, study_instance_uid, patient_id, patient_name,
|
|
||||||
accession_number, study_date, modality, study_description,
|
|
||||||
series_instance_uid, procedure_code, referring_physician_name,
|
|
||||||
study_status_code
|
|
||||||
)
|
|
||||||
VALUES
|
|
||||||
(@router1_id, '1.2.840.113619.2.55.3.283116435.276.1543707218.134', 'P1', 'John Doe', 'ACC1234', '2024-03-15', 'CT', 'Chest CT', '1.2.840.113619.2.55.3.283116435.276.1543707219.135', 'CT001', 'Dr. Smith', 'idle'),
|
|
||||||
(@router2_id, '1.2.840.113619.2.55.3.283116435.276.1543707218.136', 'P2', 'Jane Doe', 'ACC1235', '2024-03-15', 'MR', 'Brain MRI', '1.2.840.113619.2.55.3.283116435.276.1543707219.137', 'MR001', 'Dr. Johnson', 'idle')
|
|
||||||
ON DUPLICATE KEY UPDATE id = id;
|
|
||||||
|
|
||||||
END //
|
|
||||||
|
|
||||||
DELIMITER ;
|
|
||||||
|
|
||||||
-- Automatically call the procedure after creation
|
|
||||||
CALL seed_router_data();
|
|
||||||
142
deploy.sh
142
deploy.sh
@ -1,142 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Function to print colored messages
|
|
||||||
print_message() {
|
|
||||||
local color=$1
|
|
||||||
local message=$2
|
|
||||||
echo -e "${color}${message}${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to check if command was successful
|
|
||||||
check_status() {
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
print_message "$GREEN" "✔ Success: $1"
|
|
||||||
else
|
|
||||||
print_message "$RED" "✘ Error: $1"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Default values
|
|
||||||
ENV="production"
|
|
||||||
SERVER_IP=""
|
|
||||||
FRONTEND_IP=""
|
|
||||||
DB_HOST="mysql"
|
|
||||||
|
|
||||||
# Help message
|
|
||||||
show_help() {
|
|
||||||
echo "Usage: ./deploy.sh [OPTIONS]"
|
|
||||||
echo "Deploy the router dashboard application"
|
|
||||||
echo
|
|
||||||
echo "Options:"
|
|
||||||
echo " -s, --server-ip Server IP address (required)"
|
|
||||||
echo " -f, --frontend-ip Frontend IP address (defaults to server IP if not provided)"
|
|
||||||
echo " -d, --db-host Database host (defaults to mysql)"
|
|
||||||
echo " -e, --environment Environment (development/staging/production, defaults to production)"
|
|
||||||
echo " -h, --help Show this help message"
|
|
||||||
echo
|
|
||||||
echo "Example:"
|
|
||||||
echo " ./deploy.sh -s 192.168.1.100 -f 192.168.1.101 -e production"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse command line arguments
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case $1 in
|
|
||||||
-s|--server-ip)
|
|
||||||
SERVER_IP="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-f|--frontend-ip)
|
|
||||||
FRONTEND_IP="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-d|--db-host)
|
|
||||||
DB_HOST="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-e|--environment)
|
|
||||||
ENV="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
show_help
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
print_message "$RED" "Unknown option: $1"
|
|
||||||
show_help
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Validate required parameters
|
|
||||||
if [ -z "$SERVER_IP" ]; then
|
|
||||||
print_message "$RED" "Error: Server IP is required"
|
|
||||||
show_help
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If frontend IP is not provided, use server IP
|
|
||||||
if [ -z "$FRONTEND_IP" ]; then
|
|
||||||
FRONTEND_IP=$SERVER_IP
|
|
||||||
print_message "$YELLOW" "Frontend IP not provided, using Server IP: $FRONTEND_IP"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Export environment variables
|
|
||||||
export SERVER_IP
|
|
||||||
export FRONTEND_IP
|
|
||||||
export DB_HOST
|
|
||||||
export NODE_ENV=$ENV
|
|
||||||
|
|
||||||
# Display deployment information
|
|
||||||
print_message "$GREEN" "\nDeployment Configuration:"
|
|
||||||
echo "Server IP: $SERVER_IP"
|
|
||||||
echo "Frontend IP: $FRONTEND_IP"
|
|
||||||
echo "Database Host: $DB_HOST"
|
|
||||||
echo "Environment: $ENV"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Confirm deployment
|
|
||||||
read -p "Do you want to continue with deployment? (y/n) " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
print_message "$YELLOW" "Deployment cancelled"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start deployment
|
|
||||||
print_message "$GREEN" "\nStarting deployment..."
|
|
||||||
|
|
||||||
# Pull latest changes
|
|
||||||
print_message "$YELLOW" "Pulling latest changes..."
|
|
||||||
git pull
|
|
||||||
check_status "Git pull"
|
|
||||||
|
|
||||||
# Stop existing containers
|
|
||||||
print_message "$YELLOW" "Stopping existing containers..."
|
|
||||||
docker-compose down
|
|
||||||
check_status "Stopping containers"
|
|
||||||
|
|
||||||
# Build containers
|
|
||||||
print_message "$YELLOW" "Building containers..."
|
|
||||||
docker-compose build
|
|
||||||
check_status "Building containers"
|
|
||||||
|
|
||||||
# Start containers
|
|
||||||
print_message "$YELLOW" "Starting containers..."
|
|
||||||
docker-compose up -d
|
|
||||||
check_status "Starting containers"
|
|
||||||
|
|
||||||
# Check container status
|
|
||||||
print_message "$YELLOW" "Checking container status..."
|
|
||||||
docker-compose ps
|
|
||||||
check_status "Container status check"
|
|
||||||
|
|
||||||
print_message "$GREEN" "\nDeployment completed successfully!"
|
|
||||||
@ -4,87 +4,57 @@ services:
|
|||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./router-dashboard
|
context: ./router-dashboard
|
||||||
dockerfile: dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
|
||||||
- SERVER_IP=${SERVER_IP:-localhost}
|
|
||||||
ports:
|
ports:
|
||||||
- "${FRONTEND_PORT:-5173}:5173"
|
- "5173:5173"
|
||||||
environment:
|
environment:
|
||||||
- SERVER_IP=${SERVER_IP:-localhost}
|
- VITE_API_URL=http://localhost:3001/api/v1
|
||||||
- FRONTEND_IP=${FRONTEND_IP:-localhost}
|
|
||||||
- VITE_API_URL=http://${SERVER_IP:-localhost}:${BACKEND_PORT:-3000}/api/v1
|
|
||||||
- VITE_NODE_ENV=${NODE_ENV:-development}
|
|
||||||
restart: always
|
|
||||||
depends_on:
|
depends_on:
|
||||||
backend:
|
- backend
|
||||||
condition: service_healthy
|
volumes:
|
||||||
container_name: router_dashboard_frontend
|
- ./router-dashboard:/app
|
||||||
networks:
|
- /app/node_modules
|
||||||
- app_network
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "wget", "--spider", "-q", "http://${SERVER_IP:-localhost}:5173"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
start_period: 30s
|
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
context: ./ve-router-backend
|
context: ./ve-router-backend
|
||||||
dockerfile: dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "${BACKEND_PORT:-3000}:3000"
|
- "3001:3000"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=${NODE_ENV:-development}
|
- NODE_ENV=development
|
||||||
- DB_HOST=${DB_HOST:-mysql}
|
- DB_HOST=host.docker.internal
|
||||||
- DB_PORT=${DB_PORT:-3306}
|
- DB_PORT=3307
|
||||||
- DB_USER=${DB_USER:-ve_router_user}
|
- DB_USER=ve_router_user
|
||||||
- DB_PASSWORD=${DB_PASSWORD:-ve_router_password}
|
- DB_PASSWORD=ve_router_password
|
||||||
- DB_NAME=${DB_NAME:-ve_router_db}
|
- DB_NAME=ve_router_db
|
||||||
- CORS_ORIGIN=http://${FRONTEND_IP:-localhost}:${FRONTEND_PORT:-5173},http://${SERVER_IP:-localhost}:${BACKEND_PORT:-3000}
|
|
||||||
restart: always
|
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql:
|
- mysql
|
||||||
condition: service_healthy
|
volumes:
|
||||||
healthcheck:
|
- ./ve-router-backend:/app
|
||||||
test: ["CMD", "nc", "-z", "${SERVER_IP:-localhost}", "${BACKEND_PORT:-3000}"]
|
- /app/node_modules
|
||||||
interval: 30s
|
|
||||||
retries: 3
|
|
||||||
start_period: 30s
|
|
||||||
timeout: 10s
|
|
||||||
networks:
|
|
||||||
- app_network
|
|
||||||
container_name: router_dashboard_backend
|
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
|
MYSQL_ROOT_PASSWORD: rootpassword
|
||||||
MYSQL_DATABASE: ${DB_NAME:-ve_router_db}
|
MYSQL_DATABASE: ve_router_db
|
||||||
MYSQL_USER: ${DB_USER:-ve_router_user}
|
MYSQL_USER: ve_router_user
|
||||||
MYSQL_PASSWORD: ${DB_PASSWORD:-ve_router_password}
|
MYSQL_PASSWORD: ve_router_password
|
||||||
ENVIRONMENT: ${NODE_ENV:-development}
|
|
||||||
volumes:
|
volumes:
|
||||||
- mysql_data:/var/lib/mysql
|
- mysql_data:/var/lib/mysql
|
||||||
- ./db-scripts:/docker-entrypoint-initdb.d:ro
|
# Correct paths for init scripts
|
||||||
|
- ./router-dashboard/sql/init.sql:/docker-entrypoint-initdb.d/01-init.sql
|
||||||
|
- ./router-dashboard/sql/seed_data.sql:/docker-entrypoint-initdb.d/02-seed_data.sql
|
||||||
ports:
|
ports:
|
||||||
- "${DB_PORT:-3306}:3306"
|
- "3307:3306"
|
||||||
command: --default-authentication-plugin=mysql_native_password
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
restart: always
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin", "ping", "-h", "${DB_HOST:-mysql}", "-u${DB_USER:-ve_router_user}", "-p${DB_PASSWORD:-ve_router_password}"]
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "ve_router_user", "-pve_router_password"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
networks:
|
|
||||||
- app_network
|
|
||||||
container_name: router_dashboard_mysql
|
|
||||||
|
|
||||||
networks:
|
|
||||||
app_network:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mysql_data:
|
mysql_data:
|
||||||
name: router_dashboard_mysql_data
|
|
||||||
38
readme.txt
38
readme.txt
@ -1,38 +0,0 @@
|
|||||||
1. Go to router-dashboard directory
|
|
||||||
2. Run below command to build the docker images, create sql schema, insert data and start containers
|
|
||||||
docker-compose up --build -d
|
|
||||||
3. When code changes done, then just run above command mentioned in point 2,
|
|
||||||
it will udpate the changes and restart containers for which code changed
|
|
||||||
4. Open below URL in web browser to verify UI
|
|
||||||
http://localhost:5173
|
|
||||||
5. Open mysql workbench/any tool to view schema details
|
|
||||||
database:ve_router_db
|
|
||||||
host: localhost
|
|
||||||
port:3306
|
|
||||||
user/password: ve_router_user/ve_router_password
|
|
||||||
|
|
||||||
6. Run below command to stop and remove containers
|
|
||||||
docker-compose down
|
|
||||||
7. Run below command to stop, remove and delete all the volumes
|
|
||||||
Caution : if mysql has volumes then all the existing data will be erasesd
|
|
||||||
docker-compose down -v
|
|
||||||
|
|
||||||
Deploy Instructions:
|
|
||||||
|
|
||||||
1. Basic usage with just server IP:
|
|
||||||
./deploy.sh -s 192.168.1.100
|
|
||||||
2.Specify different IPs for frontend and backend:
|
|
||||||
./deploy.sh -s 192.168.1.100 -f 192.168.1.101
|
|
||||||
3.Specify environment:
|
|
||||||
./deploy.sh -s 192.168.1.100 -e staging
|
|
||||||
4.Full configuration:
|
|
||||||
./deploy.sh -s 192.168.1.100 -f 192.168.1.101 -d mysql -e production
|
|
||||||
5.Run it with the help flag to see options:
|
|
||||||
./deploy.sh --help
|
|
||||||
|
|
||||||
Important notes:
|
|
||||||
|
|
||||||
In production, you should use HTTPS instead of HTTP
|
|
||||||
Make sure your firewall rules allow the necessary ports (3000, 5173, 3306)
|
|
||||||
Consider using a reverse proxy like Nginx in front of your services
|
|
||||||
The MySQL container is accessible to other containers through the service name "mysql" when using Docker networks
|
|
||||||
@ -1,3 +1,2 @@
|
|||||||
VITE_API_URL=http://${SERVER_IP:-localhost}:3000/api/v1
|
VITE_API_URL=http://localhost:3001/api/v1
|
||||||
VITE_NODE_ENV=development
|
VITE_NODE_ENV=development
|
||||||
#VITE_NODE_ENV=production
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
VITE_API_URL=http://${SERVER_IP}:3000/api/v1
|
|
||||||
VITE_NODE_ENV=production
|
|
||||||
@ -8,7 +8,7 @@ RUN npm install
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ENV VITE_API_URL=http://localhost:3000/api/v1
|
ENV VITE_API_URL=http://localhost:3001/api/v1
|
||||||
|
|
||||||
EXPOSE 5173
|
EXPOSE 5173
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<link rel="icon" type="image/png" href="ve.png" >
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<!-- <title>Vite + React + TS </title> -->
|
<title>Vite + React + TS</title>
|
||||||
<title>Router Management</title>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
"lucide-react": "^0.294.0",
|
"lucide-react": "^0.294.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.14.1",
|
|
||||||
"router-dashboard": "file:"
|
"router-dashboard": "file:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 905 B |
@ -1,36 +1,7 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
||||||
import Login from './components/Login';
|
|
||||||
import DashboardLayout from './components/dashboard/DashboardLayout';
|
import DashboardLayout from './components/dashboard/DashboardLayout';
|
||||||
import { useAuth } from './contexts/AuthContext';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { isAuthenticated } = useAuth();
|
return <DashboardLayout />;
|
||||||
|
|
||||||
return (
|
|
||||||
<Routes>
|
|
||||||
<Route
|
|
||||||
path="/"
|
|
||||||
element={
|
|
||||||
isAuthenticated ? (
|
|
||||||
<DashboardLayout />
|
|
||||||
) : (
|
|
||||||
<Navigate to="/login" replace />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/login"
|
|
||||||
element={
|
|
||||||
isAuthenticated ? (
|
|
||||||
<Navigate to="/" replace />
|
|
||||||
) : (
|
|
||||||
<Login />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB |
@ -1,99 +1 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 225.9 45.8" style="enable-background:new 0 0 225.9 45.8;" xml:space="preserve">
|
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:#231F52;}
|
|
||||||
.st1{fill:#05887A;}
|
|
||||||
.st2{fill:#87C35F;}
|
|
||||||
.st3{fill:#68C7D7;}
|
|
||||||
.st4{fill:#24767B;}
|
|
||||||
.st5{fill:#2377B5;}
|
|
||||||
.st6{fill:#069666;}
|
|
||||||
.st7{fill:#60B046;}
|
|
||||||
.st8{fill:#ECE843;}
|
|
||||||
.st9{fill:#D8DF25;}
|
|
||||||
.st10{fill:#204780;}
|
|
||||||
.st11{fill:#81BC42;}
|
|
||||||
.st12{fill:#244087;}
|
|
||||||
.st13{fill:#224480;}
|
|
||||||
.st14{fill:#148745;}
|
|
||||||
.st15{fill:#298DC9;}
|
|
||||||
.st16{fill:#17464A;}
|
|
||||||
.st17{fill:#2B67A8;}
|
|
||||||
.st18{fill:#25AC6D;}
|
|
||||||
.st19{fill:#276780;}
|
|
||||||
.st20{fill:#2783C1;}
|
|
||||||
.st21{fill:#2894CE;}
|
|
||||||
</style>
|
|
||||||
<g id="VitalEngine_Horizontal_4c" transform="translate(-135.213 -220.46)">
|
|
||||||
<g id="Group_3" transform="translate(188.083 239.605)">
|
|
||||||
<g id="Group_2" transform="translate(67.686)">
|
|
||||||
<g id="Group_1">
|
|
||||||
<path id="Path_1" class="st0" d="M14.4,22H0V0.9h14.4v2.9h-11v5.8h9.1v2.9H3.4v6.6h11V22z"/>
|
|
||||||
<path id="Path_2" class="st0" d="M18.2,22V5.7h3.1v2c2-1.7,2.7-2,4.1-2h2.6c1-0.1,2,0.2,2.8,0.9c0.6,0.6,1,1.4,1,3.7V22h-3.1
|
|
||||||
V10.5c0.1-0.6-0.1-1.1-0.4-1.6c-0.3-0.3-0.6-0.5-1.7-0.5h-2.1c-1.1,0-2.1,0.4-3,0.9V22L18.2,22L18.2,22z"/>
|
|
||||||
<path id="Path_3" class="st0" d="M39.8,26.5c-1,0.1-1.9-0.2-2.7-0.8c-0.6-0.6-0.9-1.4-0.9-3.2h3.1c0,0.7,0.1,1,0.3,1.2
|
|
||||||
c0.2,0.2,0.4,0.2,1.2,0.2h3.9c0.9,0,1.2-0.1,1.4-0.3c0.2-0.2,0.3-0.5,0.3-2v-3.4c-2.1,1.7-2.7,2-4.1,2h-2.2
|
|
||||||
c-1.7,0-2.6-0.3-3.3-1c-0.9-0.9-1.3-1.8-1.3-6.3s0.4-5.4,1.3-6.3c0.7-0.7,1.6-1,3.3-1h2.2c1.3,0,2,0.3,4.1,2v-2h3.1v15.5
|
|
||||||
c0,2.9-0.3,3.7-1,4.4c-0.5,0.5-1.3,0.9-3.2,0.9L39.8,26.5L39.8,26.5z M46.6,16.5V9.4c-1-0.6-2-0.9-3.2-1h-2.3
|
|
||||||
c-1,0-1.4,0.1-1.7,0.4C39,9.3,38.9,9.7,38.9,13s0.1,3.6,0.6,4.1c0.3,0.3,0.7,0.4,1.7,0.4h2.4C44.7,17.4,45.7,17.1,46.6,16.5
|
|
||||||
L46.6,16.5z"/>
|
|
||||||
<path id="Path_4" class="st0" d="M57.7,3.4h-3.6V0h3.6V3.4z M57.5,22h-3.1V5.7h3.1V22z"/>
|
|
||||||
<path id="Path_5" class="st0" d="M62.1,22V5.7h3.1v2c2-1.7,2.7-2,4.1-2h2.6c1-0.1,2,0.2,2.8,0.9c0.6,0.6,1,1.4,1,3.7V22h-3.1
|
|
||||||
V10.5c0.1-0.6-0.1-1.1-0.4-1.6c-0.3-0.3-0.6-0.5-1.7-0.5h-2.1c-1.1,0-2.1,0.4-3,0.9V22L62.1,22L62.1,22z"/>
|
|
||||||
<path id="Path_6" class="st0" d="M82.7,14.9c0,3.2,0.2,3.8,0.5,4.1s0.5,0.3,1.4,0.3h4.2c0.7,0,0.9-0.1,1.1-0.3
|
|
||||||
c0.2-0.2,0.3-0.6,0.3-2h3c-0.1,2.5-0.3,3.4-1.1,4.2c-0.7,0.6-1.7,0.9-2.7,0.8h-5.3c-1.2,0.1-2.3-0.2-3.2-0.9
|
|
||||||
c-1.1-1.1-1.4-2.3-1.4-7.2s0.3-6.1,1.4-7.2c0.7-0.7,1.6-0.9,3.2-0.9h4.5c1.2-0.1,2.3,0.2,3.2,0.9c1.1,1.1,1.4,2.3,1.4,7.1v0.8
|
|
||||||
c0,0.2-0.1,0.4-0.4,0.4L82.7,14.9L82.7,14.9z M82.7,12.4h7.4c0-2.7-0.2-3.3-0.5-3.6c-0.2-0.2-0.5-0.3-1.4-0.3h-3.7
|
|
||||||
c-0.9,0-1.2,0.1-1.4,0.3C82.9,9.1,82.8,9.6,82.7,12.4z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<path id="Path_7" class="st0" d="M20.8,3.4h3.6V0h-3.6V3.4z M21,22h3.1V5.7H21L21,22z M9.4,18.7H9L3.6,0.9H0l6.4,20.4
|
|
||||||
c0.2,0.7,0.5,0.8,1.2,0.8h2.9c0.8,0,1-0.1,1.2-0.8l6.4-20.4h-3.4L9.4,18.7z M49.1,5.7H44c-1-0.1-2,0.2-2.7,0.9
|
|
||||||
c-0.6,0.6-0.9,1.4-1,4h3.1c0.1-1.2,0.1-1.8,0.4-2c0.2-0.2,0.5-0.3,1.3-0.3h3.2c0.9,0,1.2,0.1,1.4,0.3c0.2,0.2,0.4,0.7,0.4,2.6v1.8
|
|
||||||
c-1.3-0.3-2.7-0.5-4-0.5c-3.9,0-4.7,0.5-5.4,1.2c-0.6,0.6-1,1.9-1,3.8c0,2,0.4,3,1,3.6c0.7,0.7,1.4,0.8,2.9,0.8h2.5
|
|
||||||
c1.4,0,2-0.4,4-2v2h3.1V11c0-2.9-0.2-3.7-0.9-4.4C51.7,6.1,50.9,5.7,49.1,5.7z M50.1,18.3c-1,0.7-2.2,1-3.4,1h-2.3
|
|
||||||
c-0.7,0-1.1,0-1.3-0.2c-0.2-0.2-0.3-0.8-0.3-2c0-1.1,0.1-1.4,0.4-1.7c0.3-0.3,0.7-0.4,2.7-0.4h4.2L50.1,18.3L50.1,18.3z M33,1.7
|
|
||||||
h-3.2v4h-2.8v2.7h2.8v10.4c-0.1,0.9,0.1,1.8,0.7,2.5c0.7,0.6,1.6,0.9,2.4,0.8c0.7,0,1.4-0.1,2-0.2l1.8-0.5v-2.1h-2.5
|
|
||||||
c-0.8,0-1.1,0-1.2-0.1C33.1,18.9,33,18.7,33,18V8.5h3.9V5.7H33L33,1.7z M62.2,19.2c-0.8,0-1.1,0-1.2-0.1c-0.1-0.1-0.2-0.4-0.2-1.1
|
|
||||||
V8.5l0,0V0.3h-3.1v8.2h0v10.4c-0.1,0.9,0.1,1.8,0.7,2.5c0.7,0.6,1.6,0.9,2.4,0.8c0.7,0,1.4-0.1,2-0.2l1.8-0.5v-2.1H62.2L62.2,19.2
|
|
||||||
z"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_4" transform="translate(135.213 220.46)">
|
|
||||||
<path id="Path_8" class="st1" d="M29.6,25.9l-4.1,7.6L17,18.4h8.4L29.6,25.9z"/>
|
|
||||||
<path id="Path_9" class="st2" d="M25.4,18.4l5-9.2h10.1l-4.2,7.6c0,0-1,0-1.5,0c-0.2,0-0.4,0.1-0.4,0.2c-0.2,0.4-0.8,1.3-0.8,1.3
|
|
||||||
L25.4,18.4z"/>
|
|
||||||
<path id="Path_10" class="st3" d="M11.9,9.3l-1.8-3.2L9.2,7.5L5.1,0l9.8,0c0.2,0,0.4,0.1,0.5,0.3c1.3,2.3,5,8.9,5,8.9L11.9,9.3z"
|
|
||||||
/>
|
|
||||||
<path id="Path_11" class="st4" d="M23.6,36.6l1.8,3.2l0.8-1.4l4.2,7.4c0,0,0,0-0.2,0c-3.2,0-6.4,0-9.7,0c-0.1,0-0.3-0.1-0.3-0.2
|
|
||||||
l-5-9L23.6,36.6z"/>
|
|
||||||
<path id="Path_12" class="st5" d="M5.1,0l4.2,7.5l-1,1.8c0,0-7.7,0-8.3,0c0-0.1,0-0.1,0-0.2c0.3-0.5,4.1-7.5,4.9-8.9
|
|
||||||
C4.9,0.2,4.9,0.1,5.1,0C5,0,5,0,5.1,0z"/>
|
|
||||||
<path id="Path_13" class="st6" d="M40.6,27.5l-5.1,9.1h-8.3l5-9.1L40.6,27.5z"/>
|
|
||||||
<path id="Path_14" class="st7" d="M30.4,9.2c0,0,4.4-8,4.8-8.8c0.1-0.1,0.2-0.2,0.3-0.3l5.1,9.1L30.4,9.2z"/>
|
|
||||||
<path id="Path_15" class="st8" d="M40.6,9.2c0,0,5-9,5.1-9.1c1.5,2.8,5,9.1,5,9.1c0,0,0,0,0,0C50.6,9.2,43.9,9.2,40.6,9.2z"/>
|
|
||||||
<path id="Path_16" class="st9" d="M45.7,0.1l-5.1,9.1l-5.1-9.1c0,0,0-0.1,0.3-0.1c3.2,0,6.3,0,9.5,0C45.5,0,45.6,0,45.7,0.1z"/>
|
|
||||||
<path id="Path_17" class="st10" d="M27.2,36.7h8.3c0,0-4.6,8.4-5,9c0,0-0.1,0.1-0.1,0.1l-4.2-7.4L27.2,36.7z"/>
|
|
||||||
<path id="Path_18" class="st11" d="M50.7,9.2c0,0-2.8,5.2-4,7.4c-0.1,0.2-0.2,0.3-0.4,0.2c-0.5,0-1.5,0-1.5,0l-4.2-7.6L50.7,9.2z"
|
|
||||||
/>
|
|
||||||
<path id="Path_19" class="st12" d="M19.5,29.2l-4.2,7.4l-5.1-9.1l8.3,0L19.5,29.2z"/>
|
|
||||||
<path id="Path_20" class="st13" d="M25.4,18.4H17l-0.9-1.6l4.2-7.6L25.4,18.4z"/>
|
|
||||||
<path id="Path_21" class="st14" d="M40.6,27.5L36.4,20c0.1-0.1,0.2-0.1,0.3-0.1h8.1L40.6,27.5z"/>
|
|
||||||
<path id="Path_22" class="st7" d="M44.8,16.8h-8.4l4.2-7.6L44.8,16.8z"/>
|
|
||||||
<path id="Path_23" class="st15" d="M16.1,16.8l-4.2-7.5l8.4-0.1L16.1,16.8z"/>
|
|
||||||
<path id="Path_24" class="st16" d="M36.4,20l4.2,7.5l-8.4,0c0,0,2.9-5.3,4-7.3C36.3,20.1,36.3,20.1,36.4,20z"/>
|
|
||||||
<path id="Path_25" class="st17" d="M14.4,20c0.2,0.4,4.2,7.5,4.2,7.5l-8.3,0C10.2,27.5,14.3,20.1,14.4,20z"/>
|
|
||||||
<path id="Path_26" class="st18" d="M25.4,18.4l8.3,0l-4.1,7.5L25.4,18.4z"/>
|
|
||||||
<path id="Path_27" class="st19" d="M19.5,29.2l4.1,7.4l-8.3,0L19.5,29.2z"/>
|
|
||||||
<path id="Path_28" class="st20" d="M6,20l4.2,7.5l4.2-7.5L6,20z"/>
|
|
||||||
<path id="Path_29" class="st21" d="M0,9.2l8.3,0l-4.1,7.5L0,9.2z"/>
|
|
||||||
<path id="Path_30" class="st3" d="M14.4,20L6,20l4.1-7.6L14.4,20z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st0" d="M218.2,16.4h-2v5.5h-1.4v-5.5h-2v-1.1h5.4V16.4z"/>
|
|
||||||
<path class="st0" d="M220.7,15.3l1.7,4.8l1.7-4.8h1.8v6.6h-1.4v-1.8l0.1-3.1l-1.8,4.9h-0.9l-1.8-4.9l0.1,3.1v1.8h-1.4v-6.6H220.7z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 4.0 KiB |
@ -1,147 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
|
||||||
import { apiService } from '../services/api.service';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Simulate initial loading state or check for session expiry
|
|
||||||
useEffect(() => {
|
|
||||||
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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
if (!username.trim()) {
|
|
||||||
setUsernameError('Username is required');
|
|
||||||
hasError = true;
|
|
||||||
} else {
|
|
||||||
setUsernameError('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!password.trim()) {
|
|
||||||
setPasswordError('Password is required');
|
|
||||||
hasError = true;
|
|
||||||
} else {
|
|
||||||
setPasswordError('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasError) {
|
|
||||||
setLoading(false); // If there are errors, stop the loading state
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await apiService.login(username, password);
|
|
||||||
if (result) {
|
|
||||||
localStorage.setItem('accessToken', result.accessToken);
|
|
||||||
localStorage.setItem('refreshToken', result.refreshToken);
|
|
||||||
localStorage.setItem('user', JSON.stringify(result.user));
|
|
||||||
localStorage.setItem('isLoggedIn', 'true');
|
|
||||||
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
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 */}
|
|
||||||
<div className="flex flex-1 items-center justify-center">
|
|
||||||
<div className="max-w-md w-full bg-white p-8 rounded-lg shadow">
|
|
||||||
<h2 className="text-2xl font-semibold text-center text-gray-700 mb-6">Login</h2>
|
|
||||||
<form onSubmit={handleLogin}>
|
|
||||||
{/* Username Field */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
|
|
||||||
Username
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="username"
|
|
||||||
type="text"
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
|
||||||
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500"
|
|
||||||
placeholder="Enter your username"
|
|
||||||
/>
|
|
||||||
{usernameError && <div className="text-red-500 text-sm mt-1">{usernameError}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Password Field */}
|
|
||||||
<div className="mb-6">
|
|
||||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500"
|
|
||||||
placeholder="Enter your password"
|
|
||||||
/>
|
|
||||||
{passwordError && <div className="text-red-500 text-sm mt-1">{passwordError}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* General Error */}
|
|
||||||
{generalError && <div className="text-red-500 text-sm mb-4">{generalError}</div>}
|
|
||||||
|
|
||||||
{/* Display the session expired message */}
|
|
||||||
{sessionExpiredMessage && <div className="text-red-500 text-sm mb-4">{sessionExpiredMessage}</div>}
|
|
||||||
|
|
||||||
{/* Login Button */}
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-150"
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Login;
|
|
||||||
@ -40,7 +40,7 @@ const Dashboard = () => {
|
|||||||
router.diskStatus === 'DISK_CRITICAL'
|
router.diskStatus === 'DISK_CRITICAL'
|
||||||
).length,
|
).length,
|
||||||
diskWarnings: data.filter(router =>
|
diskWarnings: data.filter(router =>
|
||||||
router.diskUsage >= 70
|
router.diskUsage >= 80
|
||||||
).length
|
).length
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,6 @@ import RouterTable from './RouterTable';
|
|||||||
import RouterManagement from './pages/RouterManagement';
|
import RouterManagement from './pages/RouterManagement';
|
||||||
import { RouterData } from '../../types';
|
import { RouterData } from '../../types';
|
||||||
import { apiService } from '../../services/api.service';
|
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 {
|
interface User {
|
||||||
name: string;
|
name: string;
|
||||||
@ -18,36 +15,15 @@ interface User {
|
|||||||
type FilterType = 'all' | 'active' | 'critical' | 'diskAlert';
|
type FilterType = 'all' | 'active' | 'critical' | 'diskAlert';
|
||||||
|
|
||||||
const DashboardLayout: React.FC = () => {
|
const DashboardLayout: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
|
||||||
const { isAuthenticated, setIsAuthenticated } = useAuth(); // Use AuthContext
|
|
||||||
const [activeTab, setActiveTab] = useState('dashboard');
|
const [activeTab, setActiveTab] = useState('dashboard');
|
||||||
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
|
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
|
||||||
const [routers, setRouters] = useState<RouterData[]>([]);
|
const [routers, setRouters] = useState<RouterData[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user] = useState<User>({
|
||||||
|
name: 'John Doe',
|
||||||
useEffect(() => {
|
role: 'Administrator'
|
||||||
const fetchUser = async () => {
|
});
|
||||||
if (isAuthenticated) {
|
|
||||||
try {
|
|
||||||
const storedUser = localStorage.getItem('user');
|
|
||||||
if (storedUser) {
|
|
||||||
const { name, role }: { name: string; role: string } = JSON.parse(storedUser);
|
|
||||||
setUser({ name, role });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing user from localStorage:', error);
|
|
||||||
setUser(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchUser(); // Call the async function
|
|
||||||
}, [isAuthenticated]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRouters = async () => {
|
const fetchRouters = async () => {
|
||||||
@ -60,10 +36,11 @@ const DashboardLayout: React.FC = () => {
|
|||||||
data = data.filter(router => {
|
data = data.filter(router => {
|
||||||
switch (activeFilter) {
|
switch (activeFilter) {
|
||||||
case 'active':
|
case 'active':
|
||||||
return router.systemStatus.routerStatus === 'CONNECTED' &&
|
return router.routerActivity?.studies &&
|
||||||
router.routerActivity?.studies?.some(study => study.studyStatusCode === 'Active');
|
router.routerActivity.studies.length > 0;
|
||||||
case 'critical':
|
case 'critical':
|
||||||
return router.systemStatus.routerStatus === 'DISCONNECTED' ||
|
return router.systemStatus.vpnStatus === 'VPN_DISCONNECTED' ||
|
||||||
|
router.systemStatus.appStatus === 'DISK_CRITICAL' ||
|
||||||
router.diskStatus === 'DISK_CRITICAL';
|
router.diskStatus === 'DISK_CRITICAL';
|
||||||
case 'diskAlert':
|
case 'diskAlert':
|
||||||
return router.diskUsage > 80;
|
return router.diskUsage > 80;
|
||||||
@ -88,53 +65,27 @@ const DashboardLayout: React.FC = () => {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [activeFilter]);
|
}, [activeFilter]);
|
||||||
|
|
||||||
// Handle logout functionality
|
const handleLogout = () => {
|
||||||
const handleLogout = async () => {
|
|
||||||
console.log('Logging out...');
|
console.log('Logging out...');
|
||||||
try {
|
// Add your logout logic here
|
||||||
const refreshToken = localStorage.getItem('refreshToken');
|
|
||||||
if (refreshToken) {
|
|
||||||
await apiService.logout(refreshToken); // Call logout API if available
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove only authentication-related items from localStorage
|
|
||||||
localStorage.removeItem('isLoggedIn');
|
|
||||||
localStorage.removeItem('accessToken');
|
|
||||||
localStorage.removeItem('refreshToken');
|
|
||||||
localStorage.removeItem('user');
|
|
||||||
|
|
||||||
setUser(null); // Set user state to null after logging out
|
|
||||||
setIsAuthenticated(false); // Update the authentication state in context
|
|
||||||
|
|
||||||
// Redirect to login page
|
|
||||||
//navigate('/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[]) => {
|
const getSummary = (routerData: RouterData[]) => {
|
||||||
return {
|
return {
|
||||||
total: routerData.length,
|
total: routerData.length,
|
||||||
active: routerData.filter(r =>
|
active: routerData.filter(r =>
|
||||||
r.systemStatus.routerStatus === 'CONNECTED' && // Router (VM, app, VPN) is up
|
r.routerActivity?.studies &&
|
||||||
r.routerActivity?.studies?.some(study => study.studyStatusCode === 'Active') // At least one study is active
|
r.routerActivity.studies.length > 0
|
||||||
).length,
|
).length,
|
||||||
critical: routerData.filter(r =>
|
critical: routerData.filter(r =>
|
||||||
r.systemStatus.routerStatus === 'DISCONNECTED' || // Router (VM, app, VPN) is down
|
r.systemStatus.vpnStatus === 'VPN_DISCONNECTED' ||
|
||||||
r.diskStatus === 'DISK_CRITICAL' // Disk is critical
|
r.systemStatus.appStatus === 'DISK_CRITICAL' ||
|
||||||
|
r.diskStatus === 'DISK_CRITICAL'
|
||||||
).length,
|
).length,
|
||||||
diskAlert: routerData.filter(r => r.diskUsage > 80).length // Disk usage alert
|
diskAlert: routerData.filter(r => r.diskUsage > 80).length
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@ -177,7 +128,6 @@ const DashboardLayout: React.FC = () => {
|
|||||||
activeFilter === 'active' ? 'ring-2 ring-blue-500' : ''
|
activeFilter === 'active' ? 'ring-2 ring-blue-500' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setActiveFilter('active')}
|
onClick={() => setActiveFilter('active')}
|
||||||
title="Study in transmit currently"
|
|
||||||
>
|
>
|
||||||
<h3 className="text-sm font-medium text-gray-500">Active Routers</h3>
|
<h3 className="text-sm font-medium text-gray-500">Active Routers</h3>
|
||||||
<p className="text-2xl font-semibold text-green-600 mt-1">{summary.active}</p>
|
<p className="text-2xl font-semibold text-green-600 mt-1">{summary.active}</p>
|
||||||
@ -267,14 +217,13 @@ const DashboardLayout: React.FC = () => {
|
|||||||
<div className="min-h-screen bg-gray-100">
|
<div className="min-h-screen bg-gray-100">
|
||||||
<Header user={user} onLogout={handleLogout} />
|
<Header user={user} onLogout={handleLogout} />
|
||||||
<div className="flex h-[calc(100vh-4rem)]">
|
<div className="flex h-[calc(100vh-4rem)]">
|
||||||
<Navbar activeTab={activeTab} onTabChange={setActiveTab} role={user?.role} />
|
<Navbar activeTab={activeTab} onTabChange={setActiveTab} />
|
||||||
<main className="flex-1 overflow-auto">
|
<main className="flex-1 overflow-auto">
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DashboardLayout;
|
export default DashboardLayout;
|
||||||
@ -9,15 +9,11 @@ interface User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
user: User | null; // Allow user to be null
|
user: User;
|
||||||
onLogout: () => void;
|
onLogout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header: React.FC<HeaderProps> = ({ user, onLogout }) => {
|
const Header: React.FC<HeaderProps> = ({ user, onLogout }) => {
|
||||||
if (!user) {
|
|
||||||
return null; // Don't render the header if the user is not available
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-white shadow-sm">
|
<header className="bg-white shadow-sm">
|
||||||
<div className="h-16 px-4 flex items-center justify-between">
|
<div className="h-16 px-4 flex items-center justify-between">
|
||||||
|
|||||||
@ -13,28 +13,25 @@ import {
|
|||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
onTabChange: (tab: string) => void;
|
onTabChange: (tab: string) => void;
|
||||||
role: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Tab {
|
interface Tab {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
roles: string[]; // Added roles to specify allowed roles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange, role }) => {
|
const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
||||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
const [isPinned, setIsPinned] = useState(false);
|
const [isPinned, setIsPinned] = useState(true);
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const TABS_WITH_PERMISSIONS: Tab[] = [
|
|
||||||
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard, roles: ['admin', 'operator', 'viewer'] },
|
|
||||||
{ id: 'routers', label: 'Router Management', icon: RouterIcon, roles: ['admin', 'operator'] },
|
|
||||||
{ id: 'users', label: 'User Management', icon: Users, roles: ['admin'] },
|
|
||||||
{ id: 'settings', label: 'Settings', icon: Settings, roles: ['admin'] },
|
|
||||||
];
|
|
||||||
|
|
||||||
const filteredTabs = TABS_WITH_PERMISSIONS.filter((tab) => tab.roles.includes(role));
|
const tabs: Tab[] = [
|
||||||
|
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
|
||||||
|
{ id: 'routers', label: 'Router Management', icon: RouterIcon },
|
||||||
|
{ id: 'users', label: 'User Management', icon: Users },
|
||||||
|
{ id: 'settings', label: 'Settings', icon: Settings }
|
||||||
|
];
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
if (!isPinned) {
|
if (!isPinned) {
|
||||||
@ -74,6 +71,7 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange, role }) => {
|
|||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
|
{/* Header with title and controls */}
|
||||||
<div className="p-4 border-b border-gray-700 flex items-center justify-between">
|
<div className="p-4 border-b border-gray-700 flex items-center justify-between">
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<div>
|
<div>
|
||||||
@ -81,11 +79,7 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange, role }) => {
|
|||||||
<h2 className="text-sm font-semibold">System</h2>
|
<h2 className="text-sm font-semibold">System</h2>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div className={`flex items-center gap-2 ${isCollapsed ? 'w-full justify-center' : 'justify-end'}`}>
|
||||||
className={`flex items-center gap-2 ${
|
|
||||||
isCollapsed ? 'w-full justify-center' : 'justify-end'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{(isHovered || !isCollapsed) && (
|
{(isHovered || !isCollapsed) && (
|
||||||
<button
|
<button
|
||||||
onClick={togglePin}
|
onClick={togglePin}
|
||||||
@ -96,9 +90,7 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange, role }) => {
|
|||||||
>
|
>
|
||||||
<Pin
|
<Pin
|
||||||
size={16}
|
size={16}
|
||||||
className={`transform transition-transform ${
|
className={`transform transition-transform ${isPinned ? 'rotate-45' : ''}`}
|
||||||
isPinned ? 'rotate-45' : ''
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -107,14 +99,18 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange, role }) => {
|
|||||||
className="p-1 rounded hover:bg-gray-700 transition-colors"
|
className="p-1 rounded hover:bg-gray-700 transition-colors"
|
||||||
title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
||||||
>
|
>
|
||||||
{isCollapsed ? <ChevronRight size={16} /> : <ChevronLeft size={16} />}
|
{isCollapsed ? (
|
||||||
|
<ChevronRight size={16} />
|
||||||
|
) : (
|
||||||
|
<ChevronLeft size={16} />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation Items */}
|
{/* Navigation Items */}
|
||||||
<nav className="flex-1 p-2">
|
<nav className="flex-1 p-2">
|
||||||
{filteredTabs.map((tab) => {
|
{tabs.map(tab => {
|
||||||
const Icon = tab.icon;
|
const Icon = tab.icon;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@ -123,11 +119,9 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange, role }) => {
|
|||||||
className={`
|
className={`
|
||||||
w-full flex items-center gap-3 px-3 py-2 mb-1 rounded-lg
|
w-full flex items-center gap-3 px-3 py-2 mb-1 rounded-lg
|
||||||
transition-colors duration-200 group
|
transition-colors duration-200 group
|
||||||
${
|
${activeTab === tab.id
|
||||||
activeTab === tab.id
|
|
||||||
? 'bg-blue-600 text-white'
|
? 'bg-blue-600 text-white'
|
||||||
: 'text-gray-300 hover:bg-gray-800'
|
: 'text-gray-300 hover:bg-gray-800'}
|
||||||
}
|
|
||||||
`}
|
`}
|
||||||
title={isCollapsed ? tab.label : undefined}
|
title={isCollapsed ? tab.label : undefined}
|
||||||
>
|
>
|
||||||
@ -137,6 +131,41 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange, role }) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{/* Expanded overlay when collapsed and hovered */}
|
||||||
|
{isHovered && isCollapsed && !isPinned && (
|
||||||
|
<div
|
||||||
|
className="absolute left-16 top-0 bg-gray-900 text-white h-full w-64 shadow-lg z-50 overflow-hidden"
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
<div className="p-4 border-b border-gray-700">
|
||||||
|
<h2 className="text-sm font-semibold">Router Management</h2>
|
||||||
|
<h2 className="text-sm font-semibold">System</h2>
|
||||||
|
</div>
|
||||||
|
<nav className="p-2">
|
||||||
|
{tabs.map(tab => {
|
||||||
|
const Icon = tab.icon;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => onTabChange(tab.id)}
|
||||||
|
className={`
|
||||||
|
w-full flex items-center gap-3 px-3 py-2 mb-1 rounded-lg
|
||||||
|
transition-colors duration-200
|
||||||
|
${activeTab === tab.id
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'text-gray-300 hover:bg-gray-800'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Icon size={20} />
|
||||||
|
<span>{tab.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,13 +2,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ChevronRight, ChevronDown } from 'lucide-react';
|
import { ChevronRight, ChevronDown } from 'lucide-react';
|
||||||
import { RouterData } from '../../types';
|
import { RouterData } from '../../types';
|
||||||
import { STATUS_COLORS, formatStatus, getStatusColor} from '../../utils/statusHelpers';
|
import { STATUS_COLORS, formatStatus, getStatusColor } from '../../utils/statusHelpers';
|
||||||
|
|
||||||
interface RouterTableRowProps {
|
interface RouterTableRowProps {
|
||||||
router: RouterData;
|
router: RouterData;
|
||||||
expandedRows: Set<string>;
|
expandedRows: Set<string>;
|
||||||
timeLeft: { [key: string]: number };
|
timeLeft: { [key: string]: number };
|
||||||
onToggleExpansion: (id: number, section: 'router_details' |'activity' | 'status' | 'disk') => void;
|
onToggleExpansion: (id: number, section: 'activity' | 'status' | 'disk') => void;
|
||||||
onExpandedContentHover: (id: number, section: 'activity' | 'status' | 'disk') => void;
|
onExpandedContentHover: (id: number, section: 'activity' | 'status' | 'disk') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,31 +41,6 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
|
|||||||
return 'bg-green-500';
|
return 'bg-green-500';
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderRouterDetailsPanel = () => (
|
|
||||||
<div
|
|
||||||
className="bg-gray-50 p-4 relative"
|
|
||||||
onMouseEnter={() => onExpandedContentHover(router.id, 'router_details')}
|
|
||||||
>
|
|
||||||
<div className="h-1 bg-gray-200 absolute top-0 left-0 right-0">
|
|
||||||
<div
|
|
||||||
className="h-1 bg-blue-500 transition-all duration-200"
|
|
||||||
style={{ width: `${timeLeft[`${router.id}-router_details`] || 0}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h3 className="font-semibold mb-3">Router Details</h3>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
|
||||||
<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">Router VM IP:</span> {router.routerVmPrimaryIp}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderActivityPanel = () => (
|
const renderActivityPanel = () => (
|
||||||
<div
|
<div
|
||||||
className="bg-gray-50 p-4 relative"
|
className="bg-gray-50 p-4 relative"
|
||||||
@ -113,12 +88,6 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<div>
|
|
||||||
<h4 className="font-semibold mb-2">VM Status</h4>
|
|
||||||
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.vmStatus)}`}>
|
|
||||||
{formatStatus(router.systemStatus.vmStatus)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold mb-2">VPN Status</h4>
|
<h4 className="font-semibold mb-2">VPN Status</h4>
|
||||||
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.vpnStatus)}`}>
|
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.vpnStatus)}`}>
|
||||||
@ -127,22 +96,22 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold mb-2">App Status</h4>
|
<h4 className="font-semibold mb-2">App Status</h4>
|
||||||
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.appStatus)}`}>
|
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.diskStatus)}`}>
|
||||||
{formatStatus(router.systemStatus.appStatus)}
|
{formatStatus(router.diskStatus)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold mb-2">Container Status</h4>
|
<h4 className="font-semibold mb-2">VM Status</h4>
|
||||||
{router.systemStatus.containers.length > 0 ? (
|
{router.systemStatus.vms.length > 0 ? (
|
||||||
router.systemStatus.containers.map((container, idx) => (
|
router.systemStatus.vms.map((vm, idx) => (
|
||||||
<div key={idx} className="mb-1">
|
<div key={idx} className="mb-1">
|
||||||
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(container.status)}`}>
|
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(vm.status)}`}>
|
||||||
{container.container_name}: {formatStatus(container.status)}
|
VM {vm.id}: {formatStatus(vm.status)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<span className="text-gray-500">No Containers configured</span>
|
<span className="text-gray-500">No VMs configured</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -181,19 +150,7 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
|
|||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<tr className="border-b hover:bg-gray-50">
|
<tr className="border-b hover:bg-gray-50">
|
||||||
<td className="px-4 py-2">{router.slNo}</td>
|
<td className="px-4 py-2">{router.slNo}</td>
|
||||||
<td className="px-4 py-2">
|
<td className="px-4 py-2">{router.routerId}</td>
|
||||||
<button
|
|
||||||
onClick={() => onToggleExpansion(router.id, 'router_details')}
|
|
||||||
className="flex items-center gap-1 text-blue-500 hover:text-blue-700"
|
|
||||||
>
|
|
||||||
{expandedRows.has(`${router.id}-router_details`) ? (
|
|
||||||
<ChevronDown size={16} />
|
|
||||||
) : (
|
|
||||||
<ChevronRight size={16} />
|
|
||||||
)}
|
|
||||||
<span>{router.routerId}</span>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-2">{router.routerAlias}</td>
|
<td className="px-4 py-2">{router.routerAlias}</td>
|
||||||
<td className="px-4 py-2">{router.facility}</td>
|
<td className="px-4 py-2">{router.facility}</td>
|
||||||
<td className="px-4 py-2">
|
<td className="px-4 py-2">
|
||||||
@ -206,8 +163,7 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<ChevronRight size={16} />
|
<ChevronRight size={16} />
|
||||||
)}
|
)}
|
||||||
|
View Activity ({router.routerActivity.studies.length} studies)
|
||||||
<span>{router?.routerActivity?.studies?.[0]?.studyStatusCode || "Idle"}</span>
|
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2">
|
<td className="px-4 py-2">
|
||||||
@ -220,8 +176,8 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<ChevronRight size={16} />
|
<ChevronRight size={16} />
|
||||||
)}
|
)}
|
||||||
<span className={`ml-2 px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.routerStatus)}`}>
|
<span className={`ml-2 px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.vpnStatus)}`}>
|
||||||
{formatStatus(router.systemStatus.routerStatus)}
|
{formatStatus(router.systemStatus.vpnStatus)}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@ -250,13 +206,6 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{/* Expandable Panels */}
|
{/* Expandable Panels */}
|
||||||
{expandedRows.has(`${router.id}-router_details`) && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={8}>
|
|
||||||
{renderRouterDetailsPanel()}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{expandedRows.has(`${router.id}-activity`) && (
|
{expandedRows.has(`${router.id}-activity`) && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={8}>
|
<td colSpan={8}>
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
interface Config {
|
interface Config {
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
apiUrl: import.meta.env.VITE_API_URL,
|
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3001/api/v1',
|
||||||
environment: import.meta.env.VITE_NODE_ENV || 'development',
|
environment: import.meta.env.VITE_NODE_ENV || 'development',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
interface AuthContextType {
|
|
||||||
isAuthenticated: boolean;
|
|
||||||
setIsAuthenticated: (value: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export const useAuth = (): AuthContextType => {
|
|
||||||
const context = useContext(AuthContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error("useAuth must be used within an AuthProvider");
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AuthProvider: React.FC = ({ children }) => {
|
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(() => {
|
|
||||||
// Check localStorage on initial load
|
|
||||||
return localStorage.getItem('isLoggedIn') === 'true';
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Sync authentication state with localStorage
|
|
||||||
if (isAuthenticated) {
|
|
||||||
localStorage.setItem('isLoggedIn', 'true');
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem('isLoggedIn');
|
|
||||||
localStorage.removeItem('accessToken');
|
|
||||||
localStorage.removeItem('refreshToken');
|
|
||||||
localStorage.removeItem('user');
|
|
||||||
}
|
|
||||||
}, [isAuthenticated]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthContext.Provider value={{ isAuthenticated, setIsAuthenticated }}>
|
|
||||||
{children}
|
|
||||||
</AuthContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -21,9 +21,6 @@ export const MOCK_ROUTERS: RouterData[] = [
|
|||||||
routerId: 'RTR001',
|
routerId: 'RTR001',
|
||||||
facility: 'City Hospital',
|
facility: 'City Hospital',
|
||||||
routerAlias: 'Main-Router-1',
|
routerAlias: 'Main-Router-1',
|
||||||
facilityAET: 'RTR_1',
|
|
||||||
openvpnIp: '10.8.0.101',
|
|
||||||
routerVmPrimaryIp: '192.168.0.101',
|
|
||||||
lastSeen: '2024-03-07T14:30:00Z',
|
lastSeen: '2024-03-07T14:30:00Z',
|
||||||
diskStatus: 'Normal',
|
diskStatus: 'Normal',
|
||||||
diskUsage: 45,
|
diskUsage: 45,
|
||||||
@ -67,9 +64,6 @@ export const MOCK_ROUTERS: RouterData[] = [
|
|||||||
routerId: 'RTR002',
|
routerId: 'RTR002',
|
||||||
facility: 'Medical Center',
|
facility: 'Medical Center',
|
||||||
routerAlias: 'Emergency-Router',
|
routerAlias: 'Emergency-Router',
|
||||||
facilityAET: 'RTR_2',
|
|
||||||
openvpnIp: '10.8.0.102',
|
|
||||||
routerVmPrimaryIp: '192.168.0.102',
|
|
||||||
lastSeen: '2024-03-07T14:25:00Z',
|
lastSeen: '2024-03-07T14:25:00Z',
|
||||||
diskStatus: 'Critical',
|
diskStatus: 'Critical',
|
||||||
diskUsage: 92,
|
diskUsage: 92,
|
||||||
@ -103,9 +97,6 @@ export const MOCK_ROUTERS: RouterData[] = [
|
|||||||
routerId: 'RTR003',
|
routerId: 'RTR003',
|
||||||
facility: 'Imaging Center',
|
facility: 'Imaging Center',
|
||||||
routerAlias: 'Radiology-Router',
|
routerAlias: 'Radiology-Router',
|
||||||
facilityAET: 'RTR_3',
|
|
||||||
openvpnIp: '10.8.0.103',
|
|
||||||
routerVmPrimaryIp: '192.168.0.103',
|
|
||||||
lastSeen: '2024-03-07T14:20:00Z',
|
lastSeen: '2024-03-07T14:20:00Z',
|
||||||
diskStatus: 'Warning',
|
diskStatus: 'Warning',
|
||||||
diskUsage: 78,
|
diskUsage: 78,
|
||||||
|
|||||||
@ -1,17 +1,11 @@
|
|||||||
// File: src/main.tsx
|
// File: src/main.tsx
|
||||||
import React from 'react';
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client'
|
||||||
import { BrowserRouter as Router } from 'react-router-dom'; // Import BrowserRouter for routing
|
import App from './App.tsx'
|
||||||
import App from './App.tsx'; // Ensure App is properly imported
|
import './index.css'
|
||||||
import './index.css';
|
|
||||||
import { AuthProvider } from './contexts/AuthContext'; // Import the AuthProvider
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<AuthProvider> {/* Wrap the App with AuthProvider to enable authentication context */}
|
|
||||||
<Router> {/* Wrap the App with Router to enable routing */}
|
|
||||||
<App />
|
<App />
|
||||||
</Router>
|
</React.StrictMode>,
|
||||||
</AuthProvider>
|
)
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
@ -1,13 +1,7 @@
|
|||||||
// router-dashboard/src/services/api.service.ts
|
// router-dashboard/src/services/api.service.ts
|
||||||
import { RouterData, FilterType, BackendRouter } from '../types';
|
import { RouterData, FilterType, BackendRouter } from '../types';
|
||||||
|
|
||||||
const API_BASE_URL = process.env.NODE_ENV === 'development'
|
const API_BASE_URL = 'http://localhost:3001/api/v1';
|
||||||
? '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
|
// Default request options for all API calls
|
||||||
const DEFAULT_OPTIONS = {
|
const DEFAULT_OPTIONS = {
|
||||||
@ -17,35 +11,6 @@ const DEFAULT_OPTIONS = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to get Authorization header
|
|
||||||
const getAuthHeaders = () => {
|
|
||||||
const accessToken = localStorage.getItem('accessToken');
|
|
||||||
if (!accessToken) {
|
|
||||||
console.error('No access token found in localStorage');
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function to remove tokens from localStorage
|
|
||||||
const removeTokens = () => {
|
|
||||||
localStorage.removeItem('accessToken');
|
|
||||||
localStorage.removeItem('refreshToken');
|
|
||||||
localStorage.removeItem('user');
|
|
||||||
localStorage.removeItem('isLoggedIn');
|
|
||||||
};
|
|
||||||
|
|
||||||
const redirectToLogin = (logMessage: string) => {
|
|
||||||
console.log(logMessage);
|
|
||||||
removeTokens(); // Remove all tokens if refresh token expired
|
|
||||||
|
|
||||||
// Redirect to login page with a query parameter indicating session expiry
|
|
||||||
window.location.href = '/login?sessionExpired=true'; // Redirect with the query parameter
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Helper function to log API responses in development
|
// Helper function to log API responses in development
|
||||||
const logResponse = (prefix: string, data: any) => {
|
const logResponse = (prefix: string, data: any) => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
@ -54,131 +19,9 @@ const logResponse = (prefix: string, data: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
async login(username: string, password: string): Promise<{ accessToken: string; refreshToken: string; user: any } | null> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
...DEFAULT_OPTIONS,
|
|
||||||
body: JSON.stringify({ username, password }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Invalid username or password');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
return {
|
|
||||||
accessToken: data.accessToken,
|
|
||||||
refreshToken: data.refreshToken,
|
|
||||||
user: data.user,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Login error:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh access token using the refresh token
|
|
||||||
async refreshToken(): Promise<boolean> {
|
|
||||||
const refreshToken = localStorage.getItem('refreshToken');
|
|
||||||
if (!refreshToken) {
|
|
||||||
console.error('No refresh token found in localStorage');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_BASE_URL}/auth/refresh-token`, {
|
|
||||||
method: 'POST',
|
|
||||||
...DEFAULT_OPTIONS,
|
|
||||||
body: JSON.stringify({ refreshToken }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.log('Failed to refresh token: Expired or invalid token');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
// Save the new access and refresh tokens
|
|
||||||
localStorage.setItem('accessToken', data.accessToken);
|
|
||||||
localStorage.setItem('refreshToken', data.refreshToken);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error refreshing token:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// API call with token handling for expired access token or refresh token
|
|
||||||
async fetchWithAuth(url: string, options: RequestInit = {}): Promise<Response> {
|
|
||||||
// Get the Authorization headers dynamically
|
|
||||||
const authOptions = getAuthHeaders();
|
|
||||||
|
|
||||||
// Merge the passed options with the Authorization header inside the headers object
|
|
||||||
const finalOptions = {
|
|
||||||
...options, // Include user-specified options like method, body, etc.
|
|
||||||
headers: {
|
|
||||||
...(options.headers || {}), // Include existing headers from the passed options
|
|
||||||
...authOptions, // Add Authorization header dynamically
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Print the request details before sending it
|
|
||||||
//console.log('Request Details:');
|
|
||||||
//console.log('URL:', url);
|
|
||||||
//console.log('Options:', JSON.stringify(finalOptions, null, 2)); // Pretty print the options
|
|
||||||
|
|
||||||
const response = await fetch(url, { ...finalOptions });
|
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
// Check for Access token expired
|
|
||||||
if (errorData.message === 'Access token expired') {
|
|
||||||
console.log("Access token is expired, initiating to re generate")
|
|
||||||
const refreshed = await this.refreshToken();
|
|
||||||
if (refreshed) {
|
|
||||||
// Get the Authorization headers dynamically
|
|
||||||
const authOptions = getAuthHeaders();
|
|
||||||
|
|
||||||
// Retry the original request with new access token
|
|
||||||
return fetch(url, { ...options, ...authOptions });
|
|
||||||
} else {
|
|
||||||
redirectToLogin("Refresh token is expired, removing all stored session data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for Refresh token expired
|
|
||||||
if (errorData.message === 'Refresh token expired') {
|
|
||||||
redirectToLogin("Refresh token is expired, removing all stored session data");
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectToLogin(errorData.message);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
async logout(refreshToken: string): Promise<void> {
|
|
||||||
try {
|
|
||||||
const response = await this.fetchWithAuth(`${API_BASE_URL}/auth/logout`, {
|
|
||||||
method: 'POST',
|
|
||||||
...DEFAULT_OPTIONS,
|
|
||||||
body: JSON.stringify({ refreshToken }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to logout');
|
|
||||||
}
|
|
||||||
|
|
||||||
await response.json();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Logout error:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllRouters(filter: FilterType = 'all'): Promise<RouterData[]> {
|
async getAllRouters(filter: FilterType = 'all'): Promise<RouterData[]> {
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers?filter=${filter}`, {
|
const response = await fetch(`${API_BASE_URL}/routers?filter=${filter}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
...DEFAULT_OPTIONS
|
...DEFAULT_OPTIONS
|
||||||
});
|
});
|
||||||
@ -215,9 +58,6 @@ class ApiService {
|
|||||||
routerId: router.routerId, // Changed from router.router_id
|
routerId: router.routerId, // Changed from router.router_id
|
||||||
facility: router.facility,
|
facility: router.facility,
|
||||||
routerAlias: router.routerAlias, // Changed from router.router_alias
|
routerAlias: router.routerAlias, // Changed from router.router_alias
|
||||||
facilityAET: router.facilityAET, // Changed from router.facility_aet
|
|
||||||
openvpnIp: router.openvpnIp, // Changed from router.openvpn_ip
|
|
||||||
routerVmPrimaryIp: router.routerVmPrimaryIp, // Changed from router.router_vm_primary_ip
|
|
||||||
lastSeen: router.lastSeen, // Changed from router.last_seen
|
lastSeen: router.lastSeen, // Changed from router.last_seen
|
||||||
diskStatus: router.diskStatus, // Changed from router.disk_status_code
|
diskStatus: router.diskStatus, // Changed from router.disk_status_code
|
||||||
diskUsage: router.diskUsage || 0, // Changed from router.disk_usage
|
diskUsage: router.diskUsage || 0, // Changed from router.disk_usage
|
||||||
@ -232,28 +72,18 @@ class ApiService {
|
|||||||
patientName: study.patientName,
|
patientName: study.patientName,
|
||||||
studyDate: study.studyDate,
|
studyDate: study.studyDate,
|
||||||
modality: study.modality,
|
modality: study.modality,
|
||||||
studyDescription: study.studyDescription,
|
studyDescription: study.studyDescription
|
||||||
studyStatusCode: study.studyStatusCode
|
|
||||||
|
|
||||||
}))
|
}))
|
||||||
: []
|
: []
|
||||||
},
|
},
|
||||||
systemStatus: {
|
systemStatus: {
|
||||||
vpnStatus: router.systemStatus?.vpnStatus || 'unknown',
|
vpnStatus: router.systemStatus?.vpnStatus || 'unknown',
|
||||||
appStatus: router.systemStatus?.appStatus || 'unknown',
|
appStatus: router.systemStatus?.appStatus || 'unknown',
|
||||||
vmStatus: router.systemStatus?.vmStatus || 'unknown',
|
|
||||||
routerStatus: router.systemStatus?.routerStatus || 'unknown',
|
|
||||||
vms: Array.isArray(router.systemStatus?.vms)
|
vms: Array.isArray(router.systemStatus?.vms)
|
||||||
? router.systemStatus.vms.map((vm: any) => ({
|
? router.systemStatus.vms.map((vm: any) => ({
|
||||||
id: vm.id,
|
id: vm.id,
|
||||||
status: vm.status
|
status: vm.status
|
||||||
}))
|
}))
|
||||||
: [],
|
|
||||||
containers: Array.isArray(router.systemStatus?.containers)
|
|
||||||
? router.systemStatus.containers.map((container: any) => ({
|
|
||||||
container_name: container.container_name,
|
|
||||||
status: container.status_code
|
|
||||||
}))
|
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -269,7 +99,7 @@ class ApiService {
|
|||||||
|
|
||||||
async getRouterById(id: number): Promise<RouterData | null> {
|
async getRouterById(id: number): Promise<RouterData | null> {
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers/${id}`, {
|
const response = await fetch(`${API_BASE_URL}/routers/${id}`, {
|
||||||
...DEFAULT_OPTIONS
|
...DEFAULT_OPTIONS
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -299,7 +129,7 @@ class ApiService {
|
|||||||
last_seen: new Date().toISOString(), // Set current timestamp
|
last_seen: new Date().toISOString(), // Set current timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers`, {
|
const response = await fetch(`${API_BASE_URL}/routers`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
...DEFAULT_OPTIONS,
|
...DEFAULT_OPTIONS,
|
||||||
body: JSON.stringify(backendData),
|
body: JSON.stringify(backendData),
|
||||||
@ -331,7 +161,7 @@ class ApiService {
|
|||||||
last_seen: new Date().toISOString() // Update timestamp on changes
|
last_seen: new Date().toISOString() // Update timestamp on changes
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers/${id}`, {
|
const response = await fetch(`${API_BASE_URL}/routers/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
...DEFAULT_OPTIONS,
|
...DEFAULT_OPTIONS,
|
||||||
body: JSON.stringify(backendData),
|
body: JSON.stringify(backendData),
|
||||||
@ -350,7 +180,7 @@ class ApiService {
|
|||||||
|
|
||||||
async deleteRouter(id: number): Promise<boolean> {
|
async deleteRouter(id: number): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers/${id}`, {
|
const response = await fetch(`${API_BASE_URL}/routers/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
...DEFAULT_OPTIONS
|
...DEFAULT_OPTIONS
|
||||||
});
|
});
|
||||||
@ -366,7 +196,7 @@ class ApiService {
|
|||||||
|
|
||||||
async getRoutersByFacility(facility: string): Promise<RouterData[]> {
|
async getRoutersByFacility(facility: string): Promise<RouterData[]> {
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchWithAuth(
|
const response = await fetch(
|
||||||
`${API_BASE_URL}/routers/facility/${encodeURIComponent(facility)}`,
|
`${API_BASE_URL}/routers/facility/${encodeURIComponent(facility)}`,
|
||||||
{ ...DEFAULT_OPTIONS }
|
{ ...DEFAULT_OPTIONS }
|
||||||
);
|
);
|
||||||
@ -383,7 +213,7 @@ class ApiService {
|
|||||||
|
|
||||||
async checkApiStatus(): Promise<boolean> {
|
async checkApiStatus(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers`, {
|
const response = await fetch(`${API_BASE_URL}/routers`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
...DEFAULT_OPTIONS
|
...DEFAULT_OPTIONS
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,7 +7,6 @@ export interface BackendStudy {
|
|||||||
study_date: string;
|
study_date: string;
|
||||||
modality: string;
|
modality: string;
|
||||||
study_description: string;
|
study_description: string;
|
||||||
study_status_code: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackendVM {
|
export interface BackendVM {
|
||||||
@ -22,9 +21,6 @@ export interface BackendStudy {
|
|||||||
router_id: string;
|
router_id: string;
|
||||||
facility: string;
|
facility: string;
|
||||||
router_alias: string;
|
router_alias: string;
|
||||||
facility_aet: string;
|
|
||||||
openvpn_ip: string;
|
|
||||||
router_vm_primary_ip: string;
|
|
||||||
last_seen: string;
|
last_seen: string;
|
||||||
disk_status_code: string;
|
disk_status_code: string;
|
||||||
disk_usage: number;
|
disk_usage: number;
|
||||||
|
|||||||
@ -7,7 +7,6 @@ export interface Study {
|
|||||||
studyDate: string;
|
studyDate: string;
|
||||||
modality: string;
|
modality: string;
|
||||||
studyDescription: string;
|
studyDescription: string;
|
||||||
studyStatusCode: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VM {
|
export interface VM {
|
||||||
@ -15,11 +14,6 @@ export interface VM {
|
|||||||
status: string;
|
status: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Container {
|
|
||||||
container_name: string;
|
|
||||||
status_code: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FilterType = 'all' | 'active' | 'critical' | 'diskAlert';
|
export type FilterType = 'all' | 'active' | 'critical' | 'diskAlert';
|
||||||
|
|
||||||
export interface RouterData {
|
export interface RouterData {
|
||||||
@ -28,9 +22,6 @@ export interface RouterData {
|
|||||||
routerId: string;
|
routerId: string;
|
||||||
facility: string;
|
facility: string;
|
||||||
routerAlias: string;
|
routerAlias: string;
|
||||||
facilityAET: string;
|
|
||||||
openvpnIp: string;
|
|
||||||
routerVmPrimaryIp: string;
|
|
||||||
lastSeen: string;
|
lastSeen: string;
|
||||||
diskStatus: string;
|
diskStatus: string;
|
||||||
diskUsage: number;
|
diskUsage: number;
|
||||||
@ -42,10 +33,7 @@ export interface RouterData {
|
|||||||
systemStatus: {
|
systemStatus: {
|
||||||
vpnStatus: string;
|
vpnStatus: string;
|
||||||
appStatus: string;
|
appStatus: string;
|
||||||
vmStatus: string;
|
|
||||||
routerStatus: string;
|
|
||||||
vms: VM[];
|
vms: VM[];
|
||||||
containers: Container[];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
// User Role enum
|
|
||||||
export type UserRole = 'admin' | 'operator' | 'viewer' | 'api';
|
|
||||||
|
|
||||||
// User Status enum
|
|
||||||
export type UserStatus = 'active' | 'locked' | 'disabled';
|
|
||||||
|
|
||||||
// User Interface
|
|
||||||
export interface User {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
password_hash: string;
|
|
||||||
role: UserRole;
|
|
||||||
status: UserStatus;
|
|
||||||
failed_login_attempts: number;
|
|
||||||
last_login: Date | null;
|
|
||||||
password_changed_at: Date;
|
|
||||||
created_at: Date;
|
|
||||||
updated_at: Date;
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
// src/utils/statusHelpers.ts
|
// src/utils/statusHelpers.ts
|
||||||
|
|
||||||
// Below's are for demo purpose
|
// Define all possible status values
|
||||||
|
|
||||||
export type StatusType =
|
export type StatusType =
|
||||||
| 'RUNNING'
|
| 'RUNNING'
|
||||||
| 'STOPPED'
|
| 'STOPPED'
|
||||||
@ -9,16 +8,11 @@ export type StatusType =
|
|||||||
| 'CONNECTED'
|
| 'CONNECTED'
|
||||||
| 'DISCONNECTED'
|
| 'DISCONNECTED'
|
||||||
| 'ERROR'
|
| 'ERROR'
|
||||||
| 'UNKNOWN'
|
| 'UNKNOWN';
|
||||||
| 'ONLINE'
|
|
||||||
| 'OFFLINE';
|
|
||||||
|
|
||||||
|
|
||||||
export const STATUS_COLORS: Record<StatusType | string, string> = {
|
export const STATUS_COLORS: Record<StatusType | string, string> = {
|
||||||
'RUNNING': 'bg-green-100 text-green-700',
|
'RUNNING': 'bg-green-100 text-green-700',
|
||||||
'CONNECTED': 'bg-green-100 text-green-700',
|
'CONNECTED': 'bg-green-100 text-green-700',
|
||||||
'ONLINE': 'bg-green-100 text-green-700',
|
|
||||||
'OFFLINE': 'bg-red-100 text-red-700',
|
|
||||||
'STOPPED': 'bg-red-100 text-red-700',
|
'STOPPED': 'bg-red-100 text-red-700',
|
||||||
'DISCONNECTED': 'bg-red-100 text-red-700',
|
'DISCONNECTED': 'bg-red-100 text-red-700',
|
||||||
'WARNING': 'bg-yellow-100 text-yellow-700',
|
'WARNING': 'bg-yellow-100 text-yellow-700',
|
||||||
@ -26,26 +20,11 @@ export const STATUS_COLORS: Record<StatusType | string, string> = {
|
|||||||
'UNKNOWN': 'bg-gray-100 text-gray-700'
|
'UNKNOWN': 'bg-gray-100 text-gray-700'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add this helper function
|
|
||||||
|
|
||||||
function getStatusAfterUnderscore(status: string): string {
|
|
||||||
if (status.includes('_')) {
|
|
||||||
const parts = status.split('_');
|
|
||||||
return parts[1] || ''; // Get the part after the underscore or return an empty string
|
|
||||||
}
|
|
||||||
return status; // Return the original string if no underscore is present
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getStatus = (status: string): string => {
|
|
||||||
const keywords = ['CONNECTED', 'ONLINE', 'RUNNING'];
|
|
||||||
return keywords.some((keyword) => status === keyword) ? 'Up' : 'Down';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getStatusColor = (status: string): string => {
|
|
||||||
return STATUS_COLORS[getStatusAfterUnderscore(status)] || STATUS_COLORS['UNKNOWN'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatStatus = (status: string): string => {
|
export const formatStatus = (status: string): string => {
|
||||||
return getStatus(getStatusAfterUnderscore(status));
|
return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add this helper function
|
||||||
|
export const getStatusColor = (status: string): string => {
|
||||||
|
return STATUS_COLORS[status] || STATUS_COLORS['UNKNOWN'];
|
||||||
|
};
|
||||||
@ -1,62 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -8,13 +8,9 @@ CREATE TABLE IF NOT EXISTS routers (
|
|||||||
router_id VARCHAR(10) UNIQUE NOT NULL, -- Unique router identifier
|
router_id VARCHAR(10) UNIQUE NOT NULL, -- Unique router identifier
|
||||||
facility VARCHAR(50) NOT NULL,
|
facility VARCHAR(50) NOT NULL,
|
||||||
router_alias VARCHAR(50) NOT NULL,
|
router_alias VARCHAR(50) NOT NULL,
|
||||||
facility_aet VARCHAR(50) NOT NULL,
|
|
||||||
openvpn_ip VARCHAR(15) NOT NULL,
|
|
||||||
router_vm_primary_ip VARCHAR(15) NOT NULL,
|
|
||||||
last_seen TIMESTAMP NOT NULL,
|
last_seen TIMESTAMP NOT NULL,
|
||||||
vpn_status_code VARCHAR(50) NOT NULL,
|
vpn_status_code VARCHAR(50) NOT NULL,
|
||||||
disk_status_code VARCHAR(50) NOT NULL,
|
disk_status_code VARCHAR(50) NOT NULL,
|
||||||
app_status_code VARCHAR(50) NOT NULL,
|
|
||||||
license_status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'inactive',
|
license_status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'inactive',
|
||||||
free_disk BIGINT NOT NULL CHECK (free_disk >= 0),
|
free_disk BIGINT NOT NULL CHECK (free_disk >= 0),
|
||||||
total_disk BIGINT NOT NULL CHECK (total_disk > 0),
|
total_disk BIGINT NOT NULL CHECK (total_disk > 0),
|
||||||
@ -23,19 +19,81 @@ CREATE TABLE IF NOT EXISTS routers (
|
|||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Users and Authentication tables
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
role ENUM('admin', 'operator', 'viewer') NOT NULL DEFAULT 'viewer',
|
||||||
|
status ENUM('active', 'locked', 'disabled') NOT NULL DEFAULT 'active',
|
||||||
|
failed_login_attempts INT NOT NULL DEFAULT 0,
|
||||||
|
last_login TIMESTAMP NULL,
|
||||||
|
password_changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT valid_email CHECK (email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
|
||||||
|
CONSTRAINT valid_username CHECK (username REGEXP '^[A-Za-z0-9_-]{3,50}$')
|
||||||
|
);
|
||||||
|
|
||||||
|
-- User-Router access permissions
|
||||||
|
CREATE TABLE IF NOT EXISTS user_router_access (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
router_id INT NOT NULL,
|
||||||
|
can_view BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
can_manage BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY unique_user_router_access (user_id, router_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Session management
|
||||||
|
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
session_token VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
refresh_token VARCHAR(255) NOT NULL,
|
||||||
|
ip_address VARCHAR(45) NOT NULL,
|
||||||
|
user_agent TEXT,
|
||||||
|
expires_at TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP + INTERVAL 24 HOUR),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_session_token UNIQUE(session_token),
|
||||||
|
CONSTRAINT unique_refresh_token UNIQUE(refresh_token)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- System status table
|
||||||
|
CREATE TABLE IF NOT EXISTS system_status (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
router_id INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
-- Container status table
|
-- Container status table
|
||||||
CREATE TABLE IF NOT EXISTS container_status (
|
CREATE TABLE IF NOT EXISTS container_status (
|
||||||
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
router_id varchar(50) NOT NULL,
|
system_status_id INT NOT NULL,
|
||||||
container_name varchar(50) NOT NULL,
|
container_number INT NOT NULL CHECK (container_number BETWEEN 1 AND 10),
|
||||||
status_code varchar(50) NOT NULL,
|
status_code VARCHAR(50) NOT NULL,
|
||||||
created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
UNIQUE(router_id, container_name)
|
);
|
||||||
|
|
||||||
|
-- VM details table
|
||||||
|
CREATE TABLE IF NOT EXISTS vm_details (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
router_id INT NOT NULL,
|
||||||
|
vm_number INT NOT NULL CHECK (vm_number > 0),
|
||||||
|
status_code VARCHAR(50) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_vm_per_router UNIQUE(router_id, vm_number)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- DICOM study overview table with router_id as a string reference
|
-- DICOM study overview table with router_id as a string reference
|
||||||
CREATE TABLE IF NOT EXISTS dicom_study_overview (
|
CREATE TABLE IF NOT EXISTS dicom_study_overview (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
router_id VARCHAR(10) NOT NULL, -- Matching VARCHAR(10) with the routers table
|
router_id VARCHAR(10) NOT NULL, -- Matching VARCHAR(10) with the routers table
|
||||||
study_instance_uid VARCHAR(100) UNIQUE NOT NULL,
|
study_instance_uid VARCHAR(100) UNIQUE NOT NULL,
|
||||||
@ -52,7 +110,7 @@ CREATE TABLE IF NOT EXISTS container_status (
|
|||||||
association_id VARCHAR(50) NOT NULL DEFAULT 'NEW',
|
association_id VARCHAR(50) NOT NULL DEFAULT 'NEW',
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Create tables if they don't exist
|
-- Create tables if they don't exist
|
||||||
CREATE TABLE IF NOT EXISTS status_type (
|
CREATE TABLE IF NOT EXISTS status_type (
|
||||||
@ -60,7 +118,7 @@ CREATE TABLE IF NOT EXISTS status_type (
|
|||||||
category_id VARCHAR(50),
|
category_id VARCHAR(50),
|
||||||
name VARCHAR(100),
|
name VARCHAR(100),
|
||||||
code VARCHAR(100),
|
code VARCHAR(100),
|
||||||
description VARCHAR(150),
|
description VARCHAR(20),
|
||||||
severity INT
|
severity INT
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -83,67 +141,5 @@ CREATE TABLE IF NOT EXISTS status_category (
|
|||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
-- User authentication and authorization
|
|
||||||
-- Users table
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
username VARCHAR(50) UNIQUE NOT NULL,
|
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
password_hash VARCHAR(255) NOT NULL,
|
|
||||||
role ENUM('admin', 'operator', 'viewer', 'api') NOT NULL DEFAULT 'viewer',
|
|
||||||
status ENUM('active', 'locked', 'disabled') NOT NULL DEFAULT 'active',
|
|
||||||
failed_login_attempts INT NOT NULL DEFAULT 0,
|
|
||||||
last_login TIMESTAMP NULL,
|
|
||||||
password_changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
CONSTRAINT valid_email CHECK (email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
|
|
||||||
CONSTRAINT valid_username CHECK (username REGEXP '^[A-Za-z0-9_-]{3,50}$')
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Session management
|
|
||||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
refresh_token VARCHAR(255) NOT NULL,
|
|
||||||
ip_address VARCHAR(45) NOT NULL,
|
|
||||||
user_agent TEXT,
|
|
||||||
expires_at TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP + INTERVAL 24 HOUR),
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
CONSTRAINT fk_user_session_user FOREIGN KEY (user_id)
|
|
||||||
REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
CONSTRAINT unique_refresh_token UNIQUE(refresh_token)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Authentication audit log
|
|
||||||
CREATE TABLE IF NOT EXISTS auth_log (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
user_id INT,
|
|
||||||
event_type VARCHAR(50) NOT NULL,
|
|
||||||
ip_address VARCHAR(45) NOT NULL,
|
|
||||||
user_agent TEXT,
|
|
||||||
details JSON,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
CONSTRAINT fk_auth_log_user FOREIGN KEY (user_id)
|
|
||||||
REFERENCES users(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- User-Router access permissions
|
|
||||||
CREATE TABLE IF NOT EXISTS user_router_access (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
router_id INT NOT NULL,
|
|
||||||
can_view BOOLEAN NOT NULL DEFAULT TRUE,
|
|
||||||
can_manage BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
CONSTRAINT fk_user_router_access_user FOREIGN KEY (user_id)
|
|
||||||
REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
CONSTRAINT fk_user_router_access_router FOREIGN KEY (router_id)
|
|
||||||
REFERENCES routers(id) ON DELETE CASCADE,
|
|
||||||
UNIQUE KEY unique_user_router_access (user_id, router_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Additional history and settings tables as needed...
|
-- Additional history and settings tables as needed...
|
||||||
267
sql/seed_data.sql
Normal file
267
sql/seed_data.sql
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
DELIMITER //
|
||||||
|
|
||||||
|
CREATE PROCEDURE seed_complete_router_system()
|
||||||
|
BEGIN
|
||||||
|
-- Disable foreign key checks and start fresh
|
||||||
|
SET FOREIGN_KEY_CHECKS=0;
|
||||||
|
|
||||||
|
-- Conditionally clear existing data, only if the table exists
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'auth_log') THEN
|
||||||
|
TRUNCATE TABLE auth_log;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_sessions') THEN
|
||||||
|
TRUNCATE TABLE user_sessions;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_router_access') THEN
|
||||||
|
TRUNCATE TABLE user_router_access;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'users') THEN
|
||||||
|
TRUNCATE TABLE users;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'container_status_history') THEN
|
||||||
|
TRUNCATE TABLE container_status_history;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'router_status_history') THEN
|
||||||
|
TRUNCATE TABLE router_status_history;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'container_status') THEN
|
||||||
|
TRUNCATE TABLE container_status;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'vm_details') THEN
|
||||||
|
TRUNCATE TABLE vm_details;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'dicom_study_overview') THEN
|
||||||
|
TRUNCATE TABLE dicom_study_overview;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'system_status') THEN
|
||||||
|
TRUNCATE TABLE system_status;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'router_settings_history') THEN
|
||||||
|
TRUNCATE TABLE router_settings_history;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'router_settings') THEN
|
||||||
|
TRUNCATE TABLE router_settings;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'routers') THEN
|
||||||
|
TRUNCATE TABLE routers;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'status_type') THEN
|
||||||
|
TRUNCATE TABLE status_type;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'status_category') THEN
|
||||||
|
TRUNCATE TABLE status_category;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Re-enable foreign key checks
|
||||||
|
SET FOREIGN_KEY_CHECKS=1;
|
||||||
|
|
||||||
|
-- Insert status categories
|
||||||
|
INSERT INTO status_category (name, description)
|
||||||
|
VALUES
|
||||||
|
('Network', 'Network related statuses'),
|
||||||
|
('Disk', 'Disk related statuses'),
|
||||||
|
('VPN', 'VPN connection statuses'),
|
||||||
|
('License', 'License statuses'),
|
||||||
|
('Container', 'Container related statuses')
|
||||||
|
ON DUPLICATE KEY UPDATE id = id;
|
||||||
|
|
||||||
|
-- Insert status types
|
||||||
|
INSERT INTO status_type (category_id, name, code, description, severity)
|
||||||
|
VALUES
|
||||||
|
(1, 'Online', 'NET_ONLINE', 'System is online', 1),
|
||||||
|
(1, 'Offline', 'NET_OFFLINE', 'System is offline', 5),
|
||||||
|
(2, 'Normal', 'DISK_NORMAL', 'Disk usage is normal', 1),
|
||||||
|
(2, 'Warning', 'DISK_WARNING', 'Disk usage is high', 3),
|
||||||
|
(2, 'Critical', 'DISK_CRITICAL', 'Disk usage is critical', 5),
|
||||||
|
(3, 'Connected', 'VPN_CONNECTED', 'VPN is connected', 1),
|
||||||
|
(3, 'Disconnected', 'VPN_DISCONNECTED', 'VPN is disconnected', 5),
|
||||||
|
(5, 'Running', 'CONTAINER_RUNNING', 'Container is running', 1),
|
||||||
|
(5, 'Stopped', 'CONTAINER_STOPPED', 'Container is stopped', 5)
|
||||||
|
ON DUPLICATE KEY UPDATE id = id;
|
||||||
|
|
||||||
|
-- Insert routers
|
||||||
|
INSERT INTO routers (router_id, facility, router_alias, last_seen, vpn_status_code, disk_status_code, license_status, free_disk, total_disk, disk_usage)
|
||||||
|
VALUES
|
||||||
|
('RTR001', 'Main Hospital', 'MAIN_RAD', NOW(), 'VPN_CONNECTED', 'DISK_NORMAL', 'active', 500000000000, 1000000000000, 50.00),
|
||||||
|
('RTR002', 'Emergency Center', 'ER_RAD', NOW(), 'VPN_CONNECTED', 'DISK_WARNING', 'active', 400000000000, 1000000000000, 60.00),
|
||||||
|
('RTR003', 'Imaging Center', 'IMG_CENTER', NOW(), 'VPN_CONNECTED', 'DISK_NORMAL', 'active', 600000000000, 1000000000000, 40.00)
|
||||||
|
ON DUPLICATE KEY UPDATE id = id;
|
||||||
|
|
||||||
|
-- Store router IDs for later use
|
||||||
|
SET @router1_id = (SELECT id FROM routers WHERE router_id = 'RTR001');
|
||||||
|
SET @router2_id = (SELECT id FROM routers WHERE router_id = 'RTR002');
|
||||||
|
SET @router3_id = (SELECT id FROM routers WHERE router_id = 'RTR003');
|
||||||
|
|
||||||
|
-- Insert system status
|
||||||
|
INSERT INTO system_status (router_id)
|
||||||
|
VALUES
|
||||||
|
(@router1_id),
|
||||||
|
(@router2_id),
|
||||||
|
(@router3_id)
|
||||||
|
ON DUPLICATE KEY UPDATE id = id;
|
||||||
|
|
||||||
|
-- Insert container status
|
||||||
|
INSERT INTO container_status (system_status_id, container_number, status_code)
|
||||||
|
VALUES
|
||||||
|
(1, 1, 'CONTAINER_RUNNING'),
|
||||||
|
(1, 2, 'CONTAINER_RUNNING'),
|
||||||
|
(2, 1, 'CONTAINER_RUNNING'),
|
||||||
|
(2, 2, 'CONTAINER_STOPPED'),
|
||||||
|
(3, 1, 'CONTAINER_RUNNING')
|
||||||
|
ON DUPLICATE KEY UPDATE id = id;
|
||||||
|
|
||||||
|
-- Insert VM details
|
||||||
|
INSERT INTO vm_details (router_id, vm_number, status_code)
|
||||||
|
VALUES
|
||||||
|
(@router1_id, 1, 'NET_ONLINE'),
|
||||||
|
(@router2_id, 1, 'NET_ONLINE'),
|
||||||
|
(@router3_id, 1, 'NET_ONLINE')
|
||||||
|
ON DUPLICATE KEY UPDATE id = id;
|
||||||
|
|
||||||
|
-- Insert DICOM studies
|
||||||
|
INSERT INTO dicom_study_overview (
|
||||||
|
router_id,
|
||||||
|
study_instance_uid,
|
||||||
|
patient_id,
|
||||||
|
patient_name,
|
||||||
|
accession_number,
|
||||||
|
study_date,
|
||||||
|
modality,
|
||||||
|
study_description,
|
||||||
|
series_instance_uid,
|
||||||
|
procedure_code,
|
||||||
|
referring_physician_name
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(@router1_id, '1.2.840.113619.2.55.3.283116435.276.1543707218.134', 'P1', 'John Doe', 'ACC1234', '2024-03-15', 'CT', 'Chest CT', '1.2.840.113619.2.55.3.283116435.276.1543707219.135', 'CT001', 'Dr. Smith'),
|
||||||
|
(@router2_id, '1.2.840.113619.2.55.3.283116435.276.1543707218.136', 'P2', 'Jane Doe', 'ACC1235', '2024-03-15', 'MR', 'Brain MRI', '1.2.840.113619.2.55.3.283116435.276.1543707219.137', 'MR001', 'Dr. Johnson')
|
||||||
|
ON DUPLICATE KEY UPDATE id = id;
|
||||||
|
|
||||||
|
-- Insert router settings for each router (calls to upsert_router_settings are disabled)
|
||||||
|
-- Main Hospital Router
|
||||||
|
-- CALL upsert_router_settings(
|
||||||
|
-- @router1_id,
|
||||||
|
-- 'client',
|
||||||
|
-- '{
|
||||||
|
-- "dicom": {
|
||||||
|
-- "local": {
|
||||||
|
-- "aet": "MAIN_RAD",
|
||||||
|
-- "port": 104,
|
||||||
|
-- "file_directory": "/dicom_images",
|
||||||
|
-- "wait_time": 2,
|
||||||
|
-- "receiver_wait_time": 5000
|
||||||
|
-- },
|
||||||
|
-- "association": {
|
||||||
|
-- "acse_timeout": 5,
|
||||||
|
-- "dimse_timeout": 1000,
|
||||||
|
-- "network_timeout": 1000,
|
||||||
|
-- "retry": {
|
||||||
|
-- "attempts": 3,
|
||||||
|
-- "interval": 10
|
||||||
|
-- }
|
||||||
|
-- }
|
||||||
|
-- },
|
||||||
|
-- "rabbitmq": {
|
||||||
|
-- "local": {
|
||||||
|
-- "hostname": "router-rabbitmq",
|
||||||
|
-- "port": 5672,
|
||||||
|
-- "credentials": {
|
||||||
|
-- "username": "vitalengine",
|
||||||
|
-- "password": "vitalengine"
|
||||||
|
-- },
|
||||||
|
-- "settings": {
|
||||||
|
-- "durable": true,
|
||||||
|
-- "auto_delete": false,
|
||||||
|
-- "exchange_type": "direct",
|
||||||
|
-- "heartbeat": 50
|
||||||
|
-- }
|
||||||
|
-- }
|
||||||
|
-- },
|
||||||
|
-- "scp_connections": {
|
||||||
|
-- "pacs_nodes": [
|
||||||
|
-- {
|
||||||
|
-- "host": "pacsmain.example.com",
|
||||||
|
-- "port": 104
|
||||||
|
-- },
|
||||||
|
-- {
|
||||||
|
-- "host": "pacsbackup.example.com",
|
||||||
|
-- "port": 104
|
||||||
|
-- }
|
||||||
|
-- ]
|
||||||
|
-- }
|
||||||
|
-- }',
|
||||||
|
-- 'system',
|
||||||
|
-- 'Initial client configuration for Main Hospital'
|
||||||
|
-- );
|
||||||
|
|
||||||
|
-- Emergency Center Router
|
||||||
|
-- CALL upsert_router_settings(
|
||||||
|
-- @router2_id,
|
||||||
|
-- 'client',
|
||||||
|
-- '{
|
||||||
|
-- "dicom": {
|
||||||
|
-- "local": {
|
||||||
|
-- "aet": "ER_RAD",
|
||||||
|
-- "port": 104,
|
||||||
|
-- "file_directory": "/dicom_images",
|
||||||
|
-- "wait_time": 2,
|
||||||
|
-- "receiver_wait_time": 5000
|
||||||
|
-- },
|
||||||
|
-- "association": {
|
||||||
|
-- "acse_timeout": 5,
|
||||||
|
-- "dimse_timeout": 1000,
|
||||||
|
-- "network_timeout": 1000,
|
||||||
|
-- "retry": {
|
||||||
|
-- "attempts": 3,
|
||||||
|
-- "interval": 10
|
||||||
|
-- }
|
||||||
|
-- }
|
||||||
|
-- },
|
||||||
|
-- "rabbitmq": {
|
||||||
|
-- "local": {
|
||||||
|
-- "hostname": "router-rabbitmq",
|
||||||
|
-- "port": 5672,
|
||||||
|
-- "credentials": {
|
||||||
|
-- "username": "vitalengine",
|
||||||
|
-- "password": "vitalengine"
|
||||||
|
-- },
|
||||||
|
-- "settings": {
|
||||||
|
-- "durable": true,
|
||||||
|
-- "auto_delete": false,
|
||||||
|
-- "exchange_type": "direct",
|
||||||
|
-- "heartbeat": 50
|
||||||
|
-- }
|
||||||
|
-- }
|
||||||
|
-- },
|
||||||
|
-- "scp_connections": {
|
||||||
|
-- "pacs_nodes": [
|
||||||
|
-- {
|
||||||
|
-- "host": "pacsemergency.example.com",
|
||||||
|
-- "port": 104
|
||||||
|
-- }
|
||||||
|
-- ]
|
||||||
|
-- }
|
||||||
|
-- }',
|
||||||
|
-- 'system',
|
||||||
|
-- 'Initial client configuration for Emergency Center'
|
||||||
|
-- );
|
||||||
|
|
||||||
|
-- Insert settings for other routers as needed...
|
||||||
|
|
||||||
|
END //
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
@ -1,18 +1,20 @@
|
|||||||
|
|
||||||
# Server Configuration
|
# Server Configuration
|
||||||
NODE_ENV=production
|
NODE_ENV=development
|
||||||
PORT=3000
|
PORT=3000
|
||||||
CORS_ORIGIN=http://${FRONTEND_IP}:5173,http://${SERVER_IP}:3000
|
CORS_ORIGIN=http://localhost:5173,http://localhost:3000
|
||||||
|
|
||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
DB_HOST=${DB_HOST}
|
DB_HOST=localhost
|
||||||
DB_PORT=3306
|
DB_PORT=3307
|
||||||
DB_USER=ve_router_user
|
DB_USER=root
|
||||||
DB_PASSWORD=ve_router_password
|
DB_PASSWORD=rootpassword
|
||||||
DB_NAME=ve_router_db
|
DB_NAME=ve_router_db
|
||||||
DB_CONNECTION_LIMIT=10
|
DB_CONNECTION_LIMIT=10
|
||||||
|
|
||||||
# Authentication Configuration
|
# Authentication Configuration
|
||||||
JWT_SECRET=VE_Router_JWT_Secret_2024@Key
|
JWT_SECRET=your-super-secure-jwt-secret-key
|
||||||
JWT_EXPIRES_IN=1d
|
JWT_EXPIRES_IN=1d
|
||||||
SALT_ROUNDS=10
|
SALT_ROUNDS=10
|
||||||
|
|
||||||
|
|||||||
@ -17,9 +17,7 @@
|
|||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"mysql2": "^3.2.0",
|
"mysql2": "^3.2.0",
|
||||||
"ve-router-backend": "file:",
|
"ve-router-backend": "file:",
|
||||||
"winston": "^3.16.0",
|
"winston": "^3.16.0"
|
||||||
"bcryptjs": "^2.4.3",
|
|
||||||
"jsonwebtoken": "^9.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.13",
|
"@types/cors": "^2.8.13",
|
||||||
@ -30,8 +28,6 @@
|
|||||||
"eslint": "^8.37.0",
|
"eslint": "^8.37.0",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.0.3",
|
"typescript": "^5.0.3"
|
||||||
"@types/bcryptjs": "^2.4.3",
|
|
||||||
"@types/jsonwebtoken": "^9.0.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,6 @@ import config from './config/config';
|
|||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { errorHandler } from './middleware';
|
import { errorHandler } from './middleware';
|
||||||
import logger from './utils/logger';
|
import logger from './utils/logger';
|
||||||
import pool from './config/db';
|
|
||||||
import { SetupService } from './services';
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@ -19,7 +17,7 @@ app.use((req, res, next) => {
|
|||||||
|
|
||||||
// CORS configuration
|
// CORS configuration
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: ['http://localhost:5173', 'https://router-dashboard.dev.vitalengine.com:3000', 'https://router-dashboard.vitalengine.com:3000'],
|
origin: ['http://localhost:5173', 'http://localhost:3000'],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization']
|
allowedHeaders: ['Content-Type', 'Authorization']
|
||||||
@ -54,19 +52,6 @@ app.use((req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const setupDefaultUsers = async () => {
|
|
||||||
try {
|
|
||||||
const setupService = new SetupService(pool);
|
|
||||||
await setupService.createDefaultUsers();
|
|
||||||
logger.info('Default users created successfully.');
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error creating default users:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call setupDefaultUsers during app initialization
|
|
||||||
setupDefaultUsers();
|
|
||||||
|
|
||||||
const port = config.server.port || 3000;
|
const port = config.server.port || 3000;
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
logger.info(`Server running on port ${port} in ${config.env} mode`);
|
logger.info(`Server running on port ${port} in ${config.env} mode`);
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
// src/config/config.ts
|
// src/config/config.ts
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
// Initialize dotenv at the very start
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
env: process.env.NODE_ENV || 'development',
|
env: process.env.NODE_ENV || 'development',
|
||||||
server: {
|
server: {
|
||||||
port: parseInt(process.env.PORT || '3000', 10),
|
port: parseInt(process.env.PORT || '3000', 10),
|
||||||
corsOrigin: process.env.CORS_ORIGIN?.split(',') || ['*'], // In production, replace with actual domains
|
// Update this to include your frontend URL
|
||||||
|
corsOrigin: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:5173', 'http://localhost:3000'],
|
||||||
apiPrefix: '/api/v1'
|
apiPrefix: '/api/v1'
|
||||||
},
|
},
|
||||||
db: {
|
db: {
|
||||||
host: process.env.DB_HOST || 'localhost',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: parseInt(process.env.DB_PORT || '3306', 10),
|
port: parseInt(process.env.DB_PORT || '3306', 10),
|
||||||
user: process.env.DB_USER || 've_router_user',
|
user: process.env.DB_USER || 'root',
|
||||||
password: process.env.DB_PASSWORD || 've_router_password',
|
password: process.env.DB_PASSWORD || '', // Make sure this is getting the password
|
||||||
database: process.env.DB_NAME || 've_router_db',
|
database: process.env.DB_NAME || 've_router_db',
|
||||||
connectionLimit: parseInt(process.env.DB_CONNECTION_LIMIT || '10', 10)
|
connectionLimit: parseInt(process.env.DB_CONNECTION_LIMIT || '10', 10)
|
||||||
},
|
},
|
||||||
@ -24,4 +26,13 @@ const config = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add this for debugging
|
||||||
|
console.log('Database Config:', {
|
||||||
|
host: config.db.host,
|
||||||
|
user: config.db.user,
|
||||||
|
database: config.db.database,
|
||||||
|
// Don't log the password
|
||||||
|
});
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
||||||
|
|||||||
@ -1,184 +0,0 @@
|
|||||||
// src/controllers/AuthController.ts
|
|
||||||
import { Request, Response} from 'express';
|
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
import { AuthService} from '../services';
|
|
||||||
import { CreateUserSessionDTO, UserRole } from '@/types/user';
|
|
||||||
|
|
||||||
export class AuthController {
|
|
||||||
private service: AuthService;
|
|
||||||
|
|
||||||
constructor(pool: Pool) {
|
|
||||||
this.service = new AuthService(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAuthToken = async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { username, password } = req.body;
|
|
||||||
logger.info(`Login attempt for: ${username}`);
|
|
||||||
|
|
||||||
const user = await this.service.validateUser(username, password);
|
|
||||||
|
|
||||||
const { accessToken, refreshToken} = this.service.generateTokens(user);
|
|
||||||
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days expiration
|
|
||||||
|
|
||||||
// Check for an active session for this user
|
|
||||||
const existingSession = username === 'api_user'
|
|
||||||
? await this.service.getUserSessionByIp(user.id, req.ip?? '')
|
|
||||||
: await this.service.getUserSessionByUserAndAgent(user.id, req.headers['user-agent']?? '');
|
|
||||||
if (existingSession) {
|
|
||||||
if (existingSession.expires_at > new Date()) {
|
|
||||||
// Active session found
|
|
||||||
logger.info('Reusing existing session.');
|
|
||||||
const newAccessToken = this.service.generateAccessToken(user);
|
|
||||||
res.json({
|
|
||||||
accessToken: newAccessToken,
|
|
||||||
refreshToken: existingSession.refresh_token, // Reuse
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
role: user.role,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// Expired session found, refresh it
|
|
||||||
logger.info('Updating expired session.');
|
|
||||||
await this.service.updateUserSession(existingSession.refresh_token, {
|
|
||||||
refresh_token: refreshToken,
|
|
||||||
expires_at: expiresAt
|
|
||||||
});
|
|
||||||
res.json({
|
|
||||||
accessToken,
|
|
||||||
refreshToken,
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
role: user.role,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No session matches, Create a new user sessions
|
|
||||||
const userSessionDTO: Partial<CreateUserSessionDTO> = {
|
|
||||||
user_id : user.id,
|
|
||||||
refresh_token : refreshToken,
|
|
||||||
ip_address : req.ip,
|
|
||||||
user_agent : req.headers['user-agent'],
|
|
||||||
expires_at : expiresAt
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.service.createUserSession(userSessionDTO);
|
|
||||||
|
|
||||||
// Reset login attempts
|
|
||||||
user.failed_login_attempts = 0;
|
|
||||||
user.last_login = new Date();
|
|
||||||
|
|
||||||
await this.service.updateUser(user.id, user);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
accessToken,
|
|
||||||
refreshToken,
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
role: user.role,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
|
|
||||||
if (error instanceof Error && error.message.includes('Invalid credentials')) {
|
|
||||||
logger.error(`Auth Error: ${error.message}`);
|
|
||||||
return res.status(401).json({ message: error.message });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to API error handling
|
|
||||||
logger.error('Auth error:', { message: error.message, stack: error.stack });
|
|
||||||
return res.status(500).json({ message: 'Internal Server Error' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
login = async (req: Request, res: Response) => {
|
|
||||||
this.getAuthToken(req, res);
|
|
||||||
};
|
|
||||||
|
|
||||||
refreshToken = async (req: Request, res: Response): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const { refreshToken } = req.body;
|
|
||||||
|
|
||||||
if (!refreshToken) {
|
|
||||||
res.status(401).json({ message: 'Refresh token required' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userData = await this.service.getUserAndSessionByRefreshToken(refreshToken);
|
|
||||||
if (!userData) {
|
|
||||||
res.status(401).json({ message: 'Invalid refresh token' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = {
|
|
||||||
id: userData.id,
|
|
||||||
name: userData.name,
|
|
||||||
username: userData.username,
|
|
||||||
role: userData.role as UserRole,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { accessToken, refreshToken: newRefreshToken} =
|
|
||||||
this.service.generateTokens(user);
|
|
||||||
const newExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days expiration
|
|
||||||
|
|
||||||
const userSessionData = {
|
|
||||||
refresh_token: newRefreshToken,
|
|
||||||
expires_at: newExpiresAt
|
|
||||||
}
|
|
||||||
|
|
||||||
//update new refresh token
|
|
||||||
await this.service.updateUserSession(refreshToken, userSessionData);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
accessToken,
|
|
||||||
refreshToken: newRefreshToken,
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name,
|
|
||||||
username: user.username,
|
|
||||||
role: user.role,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
logger.error('Refresh Token update error:', { message: error.message, stack: error.stack });
|
|
||||||
res.status(500).json({ message: 'Refresh Token update failed' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
logout = async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { refreshToken } = req.body;
|
|
||||||
|
|
||||||
if (refreshToken) {
|
|
||||||
await this.service.deleteUserSession(refreshToken);
|
|
||||||
} else {
|
|
||||||
return res.status(400).json({ message: "Refresh Token is required" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json({ message: 'Logged out successfully' });
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
logger.error('Logout error:', { message: error.message, stack: error.stack });
|
|
||||||
res.status(500).json({ message: 'Internal server error' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,17 +1,38 @@
|
|||||||
// src/controllers/DicomStudyController.ts
|
// src/controllers/DicomStudyController.ts
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { DicomStudyService, CommonService} from '../services';
|
import { DicomStudyService } from '../services/DicomStudyService';
|
||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
|
interface ApiError {
|
||||||
|
message: string;
|
||||||
|
code?: string;
|
||||||
|
stack?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class DicomStudyController {
|
export class DicomStudyController {
|
||||||
private service: DicomStudyService;
|
private service: DicomStudyService;
|
||||||
private commonService: CommonService;
|
|
||||||
|
|
||||||
constructor(pool:Pool) {
|
constructor() {
|
||||||
this.service = new DicomStudyService(pool);
|
this.service = new DicomStudyService();
|
||||||
this.commonService = new CommonService();
|
}
|
||||||
|
|
||||||
|
private handleError(error: unknown, message: string): ApiError {
|
||||||
|
const apiError: ApiError = {
|
||||||
|
message: message
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
logger.error(`${message}: ${error.message}`);
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
apiError.message = error.message;
|
||||||
|
apiError.stack = error.stack;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error(`${message}: Unknown error type`, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiError;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllStudies = async (req: Request, res: Response, next: NextFunction) => {
|
getAllStudies = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@ -19,7 +40,7 @@ export class DicomStudyController {
|
|||||||
const studies = await this.service.getAllStudies();
|
const studies = await this.service.getAllStudies();
|
||||||
res.json(studies);
|
res.json(studies);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const apiError = this.commonService.handleError(error, 'Failed to fetch studies');
|
const apiError = this.handleError(error, 'Failed to fetch studies');
|
||||||
res.status(500).json({ error: apiError });
|
res.status(500).json({ error: apiError });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -38,7 +59,7 @@ export class DicomStudyController {
|
|||||||
|
|
||||||
res.json(study);
|
res.json(study);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const apiError = this.commonService.handleError(error, `Failed to fetch study ${req.params.id}`);
|
const apiError = this.handleError(error, `Failed to fetch study ${req.params.id}`);
|
||||||
res.status(500).json({ error: apiError });
|
res.status(500).json({ error: apiError });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -53,7 +74,7 @@ export class DicomStudyController {
|
|||||||
const studies = await this.service.getStudiesByRouterId(routerId);
|
const studies = await this.service.getStudiesByRouterId(routerId);
|
||||||
res.json(studies);
|
res.json(studies);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const apiError = this.commonService.handleError(error, `Failed to fetch studies for router ${req.params.routerId}`);
|
const apiError = this.handleError(error, `Failed to fetch studies for router ${req.params.routerId}`);
|
||||||
// If router not found, return 404
|
// If router not found, return 404
|
||||||
if (error instanceof Error && error.message.includes('Invalid router_id')) {
|
if (error instanceof Error && error.message.includes('Invalid router_id')) {
|
||||||
return res.status(404).json({ error: 'Router not found' });
|
return res.status(404).json({ error: 'Router not found' });
|
||||||
@ -96,7 +117,7 @@ export class DicomStudyController {
|
|||||||
const study = await this.service.createStudy(req.body);
|
const study = await this.service.createStudy(req.body);
|
||||||
res.status(201).json(study);
|
res.status(201).json(study);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const apiError = this.commonService.handleError(error, 'Failed to create study');
|
const apiError = this.handleError(error, 'Failed to create study');
|
||||||
|
|
||||||
// Handle specific error cases
|
// Handle specific error cases
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@ -132,7 +153,7 @@ export class DicomStudyController {
|
|||||||
|
|
||||||
res.json(study);
|
res.json(study);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const apiError = this.commonService.handleError(error, `Failed to update study ${req.params.id}`);
|
const apiError = this.handleError(error, `Failed to update study ${req.params.id}`);
|
||||||
res.status(500).json({ error: apiError });
|
res.status(500).json({ error: apiError });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -151,7 +172,7 @@ export class DicomStudyController {
|
|||||||
|
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const apiError = this.commonService.handleError(error, `Failed to delete study ${req.params.id}`);
|
const apiError = this.handleError(error, `Failed to delete study ${req.params.id}`);
|
||||||
res.status(500).json({ error: apiError });
|
res.status(500).json({ error: apiError });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -182,7 +203,7 @@ export class DicomStudyController {
|
|||||||
|
|
||||||
res.json(studies);
|
res.json(studies);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const apiError = this.commonService.handleError(error, 'Failed to search studies');
|
const apiError = this.handleError(error, 'Failed to search studies');
|
||||||
res.status(500).json({ error: apiError });
|
res.status(500).json({ error: apiError });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,26 +1,20 @@
|
|||||||
// src/controllers/RouterController.ts
|
// src/controllers/RouterController.ts
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { RouterService, DicomStudyService, UtilityService } from '../services';
|
import { RouterService } from '../services/RouterService';
|
||||||
import { Pool } from 'mysql2/promise';
|
import { Pool } from 'mysql2/promise';
|
||||||
import { RouterData, VMUpdate, VMUpdateRequest } from '../types';
|
import { RouterData, VMUpdate, VMUpdateRequest } from '../types';
|
||||||
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
|
|
||||||
|
|
||||||
export class RouterController {
|
export class RouterController {
|
||||||
private service: RouterService;
|
private service: RouterService;
|
||||||
private dicomStudyService: DicomStudyService;
|
|
||||||
private utilityService: UtilityService;
|
|
||||||
|
|
||||||
constructor(pool: Pool) {
|
constructor(pool: Pool) {
|
||||||
this.service = new RouterService(pool);
|
this.service = new RouterService(pool);
|
||||||
this.dicomStudyService = new DicomStudyService(pool);
|
}
|
||||||
this.utilityService = new UtilityService();
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/controllers/RouterController.ts
|
// src/controllers/RouterController.ts
|
||||||
// src/controllers/RouterController.ts
|
// src/controllers/RouterController.ts
|
||||||
updateRouterVMs = async (req: Request, res: Response) => {
|
updateRouterVMs = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const routerId = req.query.router_id as string;
|
const routerId = req.query.router_id as string;
|
||||||
const { vms } = req.body;
|
const { vms } = req.body;
|
||||||
@ -56,9 +50,10 @@ export class RouterController {
|
|||||||
details: error?.message || 'Unknown error'
|
details: error?.message || 'Unknown error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getAllRouters = async (req: Request, res: Response) => {
|
|
||||||
|
getAllRouters = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const routers = await this.service.getAllRouters();
|
const routers = await this.service.getAllRouters();
|
||||||
res.json(routers);
|
res.json(routers);
|
||||||
@ -70,11 +65,13 @@ export class RouterController {
|
|||||||
res.status(500).json({ error: 'An unexpected error occurred' });
|
res.status(500).json({ error: 'An unexpected error occurred' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getRouterById = async (req: Request, res: Response) => {
|
getRouterById = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const id = parseInt(req.params.routerId);
|
const id = parseInt(req.params.id);
|
||||||
const router = await this.service.getRouterById(id);
|
const router = await this.service.getRouterById(id);
|
||||||
|
|
||||||
if (!router) {
|
if (!router) {
|
||||||
@ -89,54 +86,9 @@ export class RouterController {
|
|||||||
|
|
||||||
createRouter = async (req: Request, res: Response) => {
|
createRouter = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const routerMetrics = req.body;
|
const router = await this.service.createRouter(req.body);
|
||||||
let routerId: number;
|
res.status(201).json(router);
|
||||||
|
|
||||||
logger.info(`Initiating to create or update router: ${routerMetrics.routerId}`);
|
|
||||||
|
|
||||||
// Check for existing router
|
|
||||||
const existingRouter = await this.service.getRouterByRouterId(routerMetrics.routerId);
|
|
||||||
if (existingRouter) {
|
|
||||||
// Update existing router
|
|
||||||
const updatedRouter = await this.service.updateRouter(existingRouter.id, routerMetrics);
|
|
||||||
if (!updatedRouter) {
|
|
||||||
return res.status(404).json({ error: 'Failed to update existing router' });
|
|
||||||
}
|
|
||||||
routerId = existingRouter.id;
|
|
||||||
} else {
|
|
||||||
// Create a new router
|
|
||||||
routerMetrics.diskUsage = ((routerMetrics.totalDisk - routerMetrics.freeDisk) / routerMetrics.totalDisk) * 100;
|
|
||||||
// Get disk status
|
|
||||||
routerMetrics.diskStatus = this.utilityService.getDiskStatus(routerMetrics.diskUsage);
|
|
||||||
const router = await this.service.createRouter(routerMetrics);
|
|
||||||
if (!router) {
|
|
||||||
return res.status(404).json({ error: 'Failed to create router' });
|
|
||||||
}
|
|
||||||
routerId = router.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process studies after router processing
|
|
||||||
const studies = routerMetrics.routerActivity?.studies || [];
|
|
||||||
if (Array.isArray(studies) && studies.length > 0) {
|
|
||||||
logger.info(`Processing study for router: ${routerMetrics.routerId}`);
|
|
||||||
await this.dicomStudyService.processStudies(routerId, studies);
|
|
||||||
logger.info(`Successfully processed study for router: ${routerMetrics.routerId}`);
|
|
||||||
} else {
|
|
||||||
logger.info(`No study to process for router: ${routerMetrics.routerId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process containers after study processing
|
|
||||||
const containers = routerMetrics.systemStatus?.containers || [];
|
|
||||||
if (Array.isArray(containers) && containers.length > 0) {
|
|
||||||
logger.info(`Processing containers for router: ${routerMetrics.routerId}`);
|
|
||||||
const result = await this.service.processContainers(routerId, containers);
|
|
||||||
logger.info(`Successfully processed ${result.affectedRows} containers for router: ${routerId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(201).json({message: 'Router created successfully'});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error creating router:', error); // Log error for debugging
|
|
||||||
res.status(500).json({ error: 'Failed to create router' });
|
res.status(500).json({ error: 'Failed to create router' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
import { Request, Response} from 'express';
|
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
import { SetupService } from '../services';
|
|
||||||
|
|
||||||
export class SetupController {
|
|
||||||
private service: SetupService;
|
|
||||||
|
|
||||||
constructor(pool: Pool) {
|
|
||||||
this.service = new SetupService(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
createInitialUser = async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const user = await this.service.createDefaultUsers();
|
|
||||||
res.status(201).json({ message: 'Initial user created successfully', user });
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error
|
|
||||||
res.status(500).json({ message: error.message });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
// src/controllers/AuthController.ts
|
|
||||||
import { Request, Response} from 'express';
|
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
import { UserService } from '../services';
|
|
||||||
|
|
||||||
export class UserController {
|
|
||||||
private service: UserService;
|
|
||||||
|
|
||||||
constructor(pool: Pool) {
|
|
||||||
this.service = new UserService(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllUsers = async (req: Request, res: Response) => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
getUserById = async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { username} = req.body;
|
|
||||||
logger.info(`Get profile for: ${username}`);
|
|
||||||
|
|
||||||
const user = await this.service.getUserByUsername(username);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return res.status(404).json({ error: 'Invalid user' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
role: user.role,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
logger.error('Get profile error:', { message: error.message, stack: error.stack });
|
|
||||||
res.status(500).json({ message: 'Internal server error' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createUser = async (req: Request, res: Response) => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
updateUser = async (req: Request, res: Response) => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
deleteUser = async (req: Request, res: Response) => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
export * from './RouterController';
|
export * from './RouterController';
|
||||||
export * from './DicomStudyController';
|
export * from './DicomStudyController';
|
||||||
export * from './AuthController';
|
|
||||||
// Add more controller exports as needed
|
// Add more controller exports as needed
|
||||||
|
|||||||
@ -1,71 +0,0 @@
|
|||||||
// backend/middleware/auth.ts
|
|
||||||
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
import jwt, { JwtPayload } from 'jsonwebtoken';
|
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
|
|
||||||
// Extend Request to include a user property
|
|
||||||
declare module 'express-serve-static-core' {
|
|
||||||
interface Request {
|
|
||||||
user?: { id: number; username: string; role: string } | null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const authMiddleware = (pool: Pool) => async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
logger.info("Auth middleware triggered");
|
|
||||||
|
|
||||||
const authHeader = req.headers.authorization;
|
|
||||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
||||||
logger.warn("Authorization header missing or invalid:", { authHeader });
|
|
||||||
return res.status(401).json({ message: 'Authorization header missing or invalid' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = authHeader.split(' ')[1];
|
|
||||||
const jwtSecret = process.env.JWT_SECRET;
|
|
||||||
if (!jwtSecret) {
|
|
||||||
throw new Error("JWT_SECRET is not set in environment variables");
|
|
||||||
}
|
|
||||||
|
|
||||||
let decoded: JwtPayload;
|
|
||||||
try {
|
|
||||||
decoded = jwt.verify(token, jwtSecret) as JwtPayload;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
logger.error("JWT verification failed:", error);
|
|
||||||
if (error instanceof jwt.TokenExpiredError) {
|
|
||||||
return res.status(401).json({
|
|
||||||
message: 'Access token expired',
|
|
||||||
code: 'TOKEN_EXPIRED',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.status(401).json({ message: 'Invalid token', code: 'INVALID_TOKEN' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const userId = decoded.userId;
|
|
||||||
if (!userId || typeof userId !== 'number') {
|
|
||||||
logger.warn("Invalid or missing userId in token payload:", { decoded });
|
|
||||||
return res.status(401).json({ message: 'Invalid token payload' });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [rows] = await pool.execute(
|
|
||||||
'SELECT id, username, role FROM users WHERE id = ? AND status = "active"',
|
|
||||||
[userId]
|
|
||||||
);
|
|
||||||
const users = Array.isArray(rows) ? rows : [];
|
|
||||||
if (users.length === 0) {
|
|
||||||
logger.warn("User not found or inactive:", { userId });
|
|
||||||
return res.status(401).json({ message: 'User not found or inactive' });
|
|
||||||
}
|
|
||||||
req.user = users[0] as { id: number; username: string; role: string };
|
|
||||||
next();
|
|
||||||
} catch (dbError) {
|
|
||||||
logger.error("Database query failed:", dbError);
|
|
||||||
return res.status(500).json({ message: 'Database query error' });
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
logger.error('Error in authMiddleware:', error);
|
|
||||||
res.status(500).json({ message: 'Internal server error' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,3 +1,2 @@
|
|||||||
export * from './errorHandler';
|
export * from './errorHandler';
|
||||||
export * from './auth';
|
|
||||||
// Add more middleware exports as needed
|
// Add more middleware exports as needed
|
||||||
|
|||||||
@ -3,12 +3,8 @@
|
|||||||
import { DicomStudy, CreateDicomStudyDTO, UpdateDicomStudyDTO, DBDicomStudy, DicomStudySearchParams } from '../types/dicom';
|
import { DicomStudy, CreateDicomStudyDTO, UpdateDicomStudyDTO, DBDicomStudy, DicomStudySearchParams } from '../types/dicom';
|
||||||
import pool from '../config/db';
|
import pool from '../config/db';
|
||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
import { RowDataPacket, ResultSetHeader } from 'mysql2';
|
|
||||||
|
|
||||||
export class DicomStudyRepository {
|
export class DicomStudyRepository {
|
||||||
constructor(private pool: Pool) {} // Modified constructor
|
|
||||||
|
|
||||||
private async getRouterStringId(numericId: number): Promise<string> {
|
private async getRouterStringId(numericId: number): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const [result] = await pool.query(
|
const [result] = await pool.query(
|
||||||
@ -44,9 +40,10 @@ export class DicomStudyRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async mapDBStudyToDicomStudy(dbStudy: DBDicomStudy): Promise<DicomStudy> {
|
private async mapDBStudyToDicomStudy(dbStudy: DBDicomStudy): Promise<DicomStudy> {
|
||||||
|
const routerStringId = await this.getRouterStringId(dbStudy.router_id);
|
||||||
return {
|
return {
|
||||||
id: dbStudy.id,
|
id: dbStudy.id,
|
||||||
router_id: dbStudy.router_id.toString(),
|
router_id: routerStringId,
|
||||||
study_instance_uid: dbStudy.study_instance_uid,
|
study_instance_uid: dbStudy.study_instance_uid,
|
||||||
patient_id: dbStudy.patient_id,
|
patient_id: dbStudy.patient_id,
|
||||||
patient_name: dbStudy.patient_name,
|
patient_name: dbStudy.patient_name,
|
||||||
@ -66,16 +63,18 @@ export class DicomStudyRepository {
|
|||||||
|
|
||||||
async create(studyData: CreateDicomStudyDTO): Promise<DicomStudy> {
|
async create(studyData: CreateDicomStudyDTO): Promise<DicomStudy> {
|
||||||
try {
|
try {
|
||||||
|
// Convert string router_id to numeric id for database
|
||||||
|
const numericRouterId = await this.getRouterNumericId(studyData.router_id);
|
||||||
|
|
||||||
const [result] = await pool.query(
|
const [result] = await pool.query(
|
||||||
`INSERT INTO dicom_study_overview (
|
`INSERT INTO dicom_study_overview (
|
||||||
router_id, study_instance_uid, patient_id, patient_name,
|
router_id, study_instance_uid, patient_id, patient_name,
|
||||||
accession_number, study_date, modality, study_description,
|
accession_number, study_date, modality, study_description,
|
||||||
series_instance_uid, procedure_code, referring_physician_name,
|
series_instance_uid, procedure_code, referring_physician_name,
|
||||||
study_status_code, association_id, created_at
|
study_status_code, association_id
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`,
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
studyData.router_id,
|
numericRouterId,
|
||||||
studyData.study_instance_uid,
|
studyData.study_instance_uid,
|
||||||
studyData.patient_id,
|
studyData.patient_id,
|
||||||
studyData.patient_name,
|
studyData.patient_name,
|
||||||
@ -247,26 +246,4 @@ export class DicomStudyRepository {
|
|||||||
throw new Error('Failed to search DICOM studies');
|
throw new Error('Failed to search DICOM studies');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByStudyInstanceUid(studyInstanceUid: string): Promise<DicomStudy | null> {
|
|
||||||
try {
|
|
||||||
// Use RowDataPacket[] to align with mysql2/promise type expectations
|
|
||||||
const [rows] = await pool.query<DBDicomStudy[] & RowDataPacket[]>(`
|
|
||||||
SELECT * FROM dicom_study_overview WHERE study_instance_uid = ?`,
|
|
||||||
[studyInstanceUid]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if rows is empty
|
|
||||||
if (rows.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map the first result to your DicomStudy object
|
|
||||||
return await this.mapDBStudyToDicomStudy(rows[0]);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching DICOM study by Study Instance UID:', error);
|
|
||||||
throw new Error('Failed to fetch DICOM study');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// src/repositories/RouterRepository.ts
|
// src/repositories/RouterRepository.ts
|
||||||
import { RouterData, Study, VM, VMUpdate, Container } from '../types';
|
import { RouterData, Study, VM,VMUpdate } from '../types';
|
||||||
import pool from '../config/db';
|
import pool from '../config/db';
|
||||||
import { RowDataPacket, ResultSetHeader } from 'mysql2';
|
import { RowDataPacket, ResultSetHeader } from 'mysql2';
|
||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
@ -11,7 +11,7 @@ export class RouterRepository {
|
|||||||
|
|
||||||
// src/repositories/RouterRepository.ts
|
// src/repositories/RouterRepository.ts
|
||||||
// src/repositories/RouterRepository.ts
|
// src/repositories/RouterRepository.ts
|
||||||
async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
||||||
console.log('Repository: Starting VM update for router:', routerId);
|
console.log('Repository: Starting VM update for router:', routerId);
|
||||||
const connection = await this.pool.getConnection();
|
const connection = await this.pool.getConnection();
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ export class RouterRepository {
|
|||||||
connection.release();
|
connection.release();
|
||||||
console.log('Connection released');
|
console.log('Connection released');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getRouterStudies(routerId: number): Promise<Study[]> {
|
private async getRouterStudies(routerId: number): Promise<Study[]> {
|
||||||
try {
|
try {
|
||||||
@ -81,12 +81,10 @@ export class RouterRepository {
|
|||||||
patient_name as patientName,
|
patient_name as patientName,
|
||||||
DATE_FORMAT(study_date, '%Y-%m-%d') as studyDate,
|
DATE_FORMAT(study_date, '%Y-%m-%d') as studyDate,
|
||||||
modality,
|
modality,
|
||||||
study_description as studyDescription,
|
study_description as studyDescription
|
||||||
CONCAT(UPPER(SUBSTRING(study_status_code, 1, 1)), LOWER(SUBSTRING(study_status_code, 2))) as studyStatusCode
|
|
||||||
FROM dicom_study_overview
|
FROM dicom_study_overview
|
||||||
WHERE router_id = ?
|
WHERE router_id = ?
|
||||||
ORDER BY updated_at DESC
|
ORDER BY study_date DESC`,
|
||||||
LIMIT 1`,
|
|
||||||
[routerId]
|
[routerId]
|
||||||
);
|
);
|
||||||
return rows as Study[];
|
return rows as Study[];
|
||||||
@ -128,78 +126,10 @@ export class RouterRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getRouterContainers(routerId: number): Promise<Container[]> {
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Then use this to query vm_details
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
`SELECT
|
|
||||||
container_name,
|
|
||||||
status_code
|
|
||||||
FROM container_status
|
|
||||||
WHERE router_id = ?`,
|
|
||||||
[routerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
return rows as Container[];
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error fetching Containers for router ${routerId}:`, error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async calculateVMStatus (lastSeen: string | number | Date): Promise<string> {
|
|
||||||
const currentTime = new Date();
|
|
||||||
const lastSeenTime = new Date(lastSeen);
|
|
||||||
|
|
||||||
// Use getTime() to get timestamps in milliseconds
|
|
||||||
const diffInMinutes = (currentTime.getTime() - lastSeenTime.getTime()) / (1000 * 60);
|
|
||||||
|
|
||||||
return diffInMinutes > 1 ? 'NET_OFFLINE' : 'NET_ONLINE';
|
|
||||||
};
|
|
||||||
|
|
||||||
private async calculateSystemStatus (vpnStatus: string, appStatus: string, vmStatus: string): Promise<string> {
|
|
||||||
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> {
|
private async transformDatabaseRouter(dbRouter: any, index: number): Promise<RouterData> {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
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 studies = await this.getRouterStudies(dbRouter.id);
|
||||||
//const vms = await this.getRouterVMs(dbRouter.id);
|
const vms = await this.getRouterVMs(dbRouter.id);
|
||||||
const vms:VM[] = [];
|
|
||||||
const containers = await this.getRouterContainers(dbRouter.id);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: dbRouter.id,
|
id: dbRouter.id,
|
||||||
@ -207,10 +137,7 @@ export class RouterRepository {
|
|||||||
routerId: dbRouter.router_id,
|
routerId: dbRouter.router_id,
|
||||||
facility: dbRouter.facility,
|
facility: dbRouter.facility,
|
||||||
routerAlias: dbRouter.router_alias,
|
routerAlias: dbRouter.router_alias,
|
||||||
facilityAET: dbRouter.facility_aet,
|
lastSeen: new Date(dbRouter.last_seen).toISOString(),
|
||||||
openvpnIp: dbRouter.openvpn_ip,
|
|
||||||
routerVmPrimaryIp: dbRouter.router_vm_primary_ip,
|
|
||||||
lastSeen: lastSeen,
|
|
||||||
diskStatus: dbRouter.disk_status_code,
|
diskStatus: dbRouter.disk_status_code,
|
||||||
diskUsage: parseFloat(dbRouter.disk_usage),
|
diskUsage: parseFloat(dbRouter.disk_usage),
|
||||||
freeDisk: parseInt(dbRouter.free_disk),
|
freeDisk: parseInt(dbRouter.free_disk),
|
||||||
@ -219,12 +146,9 @@ export class RouterRepository {
|
|||||||
studies
|
studies
|
||||||
},
|
},
|
||||||
systemStatus: {
|
systemStatus: {
|
||||||
vpnStatus: updatedVpnStatus,
|
vpnStatus: dbRouter.vpn_status_code,
|
||||||
appStatus: updatedAppStatus,
|
appStatus: dbRouter.disk_status_code,
|
||||||
vmStatus: vmStatus,
|
vms
|
||||||
routerStatus: routerStatus,
|
|
||||||
vms,
|
|
||||||
containers
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -260,16 +184,6 @@ export class RouterRepository {
|
|||||||
return this.transformDatabaseRouter(rows[0], 0);
|
return this.transformDatabaseRouter(rows[0], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByRouterId(routerId: string): Promise<RouterData | null> {
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
'SELECT * FROM routers WHERE router_id = ?',
|
|
||||||
[routerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!rows.length) return null;
|
|
||||||
return this.transformDatabaseRouter(rows[0], 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findByFacility(facility: string): Promise<RouterData[]> {
|
async findByFacility(facility: string): Promise<RouterData[]> {
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
const [rows] = await pool.query<RowDataPacket[]>(
|
||||||
'SELECT * FROM routers WHERE facility = ? ORDER BY created_at DESC',
|
'SELECT * FROM routers WHERE facility = ? ORDER BY created_at DESC',
|
||||||
@ -286,20 +200,16 @@ export class RouterRepository {
|
|||||||
async create(router: Partial<RouterData>): Promise<RouterData> {
|
async create(router: Partial<RouterData>): Promise<RouterData> {
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
const [result] = await pool.query<ResultSetHeader>(
|
||||||
`INSERT INTO routers (
|
`INSERT INTO routers (
|
||||||
router_id, facility, router_alias, facility_aet, openvpn_ip, router_vm_primary_ip,
|
router_id, facility, router_alias, last_seen,
|
||||||
last_seen, vpn_status_code, disk_status_code, app_status_code,
|
vpn_status_code, disk_status_code, license_status,
|
||||||
license_status, free_disk, total_disk, disk_usage
|
free_disk, total_disk, disk_usage
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, NOW(), ?, ?, ?, 'inactive', ?, ?, ?)`,
|
) VALUES (?, ?, ?, NOW(), ?, ?, 'inactive', ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
router.routerId,
|
router.routerId,
|
||||||
router.facility,
|
router.facility,
|
||||||
router.routerAlias,
|
router.routerAlias,
|
||||||
router.facilityAET,
|
'unknown',
|
||||||
router.openvpnIp,
|
|
||||||
router.routerVmPrimaryIp,
|
|
||||||
router.systemStatus?.vpnStatus || 'unknown',
|
|
||||||
router.diskStatus || 'unknown',
|
router.diskStatus || 'unknown',
|
||||||
router.systemStatus?.appStatus || 'unknown',
|
|
||||||
router.freeDisk,
|
router.freeDisk,
|
||||||
router.totalDisk,
|
router.totalDisk,
|
||||||
router.diskUsage || 0
|
router.diskUsage || 0
|
||||||
@ -315,14 +225,8 @@ export class RouterRepository {
|
|||||||
if (router.routerId) updates.router_id = router.routerId;
|
if (router.routerId) updates.router_id = router.routerId;
|
||||||
if (router.facility) updates.facility = router.facility;
|
if (router.facility) updates.facility = router.facility;
|
||||||
if (router.routerAlias) updates.router_alias = router.routerAlias;
|
if (router.routerAlias) updates.router_alias = router.routerAlias;
|
||||||
if (router.facilityAET) updates.facility_aet = router.facilityAET;
|
|
||||||
if (router.openvpnIp) updates.openvpn_ip = router.openvpnIp;
|
|
||||||
if (router.routerVmPrimaryIp) updates.router_vm_primary_ip = router.routerVmPrimaryIp;
|
|
||||||
if (router.diskStatus) updates.disk_status_code = router.diskStatus;
|
if (router.diskStatus) updates.disk_status_code = router.diskStatus;
|
||||||
|
|
||||||
if (router.systemStatus?.vpnStatus) updates.vpn_status_code = router.systemStatus?.vpnStatus;
|
|
||||||
if (router.systemStatus?.appStatus) updates.app_status_code = router.systemStatus?.appStatus;
|
|
||||||
|
|
||||||
if (router.freeDisk !== undefined || router.totalDisk !== undefined) {
|
if (router.freeDisk !== undefined || router.totalDisk !== undefined) {
|
||||||
const existingRouter = await this.findById(id);
|
const existingRouter = await this.findById(id);
|
||||||
if (!existingRouter) return null;
|
if (!existingRouter) return null;
|
||||||
@ -338,7 +242,7 @@ export class RouterRepository {
|
|||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`UPDATE routers SET ${setClauses}, last_seen = NOW(), updated_at = NOW() WHERE id = ?`,
|
`UPDATE routers SET ${setClauses}, updated_at = NOW() WHERE id = ?`,
|
||||||
[...Object.values(updates), id]
|
[...Object.values(updates), id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -353,37 +257,4 @@ export class RouterRepository {
|
|||||||
);
|
);
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async upsertContainerStatus(routerId: string, containers: Container[]): Promise<ResultSetHeader> {
|
|
||||||
const values = containers.map((container) => [
|
|
||||||
routerId,
|
|
||||||
container.container_name,
|
|
||||||
container.status_code,
|
|
||||||
new Date(), // created_at
|
|
||||||
new Date(), // updated_at
|
|
||||||
]);
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
INSERT INTO container_status (router_id, container_name, status_code, created_at, updated_at)
|
|
||||||
VALUES ${values.map(() => "(?, ?, ?, ?, ?)").join(", ")}
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
status_code = VALUES(status_code),
|
|
||||||
updated_at = VALUES(updated_at);
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Flatten the values array manually (compatible with older TypeScript versions)
|
|
||||||
const flattenedValues = values.reduce((acc, val) => acc.concat(val), []);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [result] = await pool.query<ResultSetHeader>(query, flattenedValues);
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error inserting or updating container status:");
|
|
||||||
logger.error(`Query: ${query}`);
|
|
||||||
logger.error(`Flattened Values: ${JSON.stringify(flattenedValues)}`);
|
|
||||||
logger.error("Error Details:", error);
|
|
||||||
throw new Error("Error inserting or updating container status");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,216 +0,0 @@
|
|||||||
// src/repositories/UserRepository.ts
|
|
||||||
import { User, UpdateUser, UserWithSession, UserSession, UserStatus, CreateUserSessionDTO} from '../types/user';
|
|
||||||
import pool from '../config/db';
|
|
||||||
import { RowDataPacket, ResultSetHeader } from 'mysql2';
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
|
|
||||||
export class UserRepository {
|
|
||||||
constructor(private pool: Pool) {} // Modified constructor
|
|
||||||
|
|
||||||
async findById(id: number): Promise<User | null> {
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
'SELECT * FROM users WHERE id = ?',
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!rows.length) return null;
|
|
||||||
return rows[0] as User;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findByUsername(username: string): Promise<User | null> {
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
'SELECT * FROM users WHERE username = ? AND status = "active"',
|
|
||||||
[username]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!rows.length) return null;
|
|
||||||
return rows[0] as User;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findUserByUsernameOrEmail(username: string, email: string): Promise<User | null> {
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
'SELECT * FROM users WHERE (username = ? OR email = ?) AND status = "active"',
|
|
||||||
[username, email]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!rows.length) return null;
|
|
||||||
return rows[0] as User;
|
|
||||||
}
|
|
||||||
|
|
||||||
async create(user: Partial<User>): Promise<User> {
|
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
|
||||||
`INSERT INTO users (
|
|
||||||
name, username, email, password_hash, role
|
|
||||||
) VALUES (?, ?, ?, ?, ?)`,
|
|
||||||
[
|
|
||||||
user.name,
|
|
||||||
user.username,
|
|
||||||
user.email,
|
|
||||||
user.password_hash,
|
|
||||||
user.role
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.findById(result.insertId) as Promise<User>;
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(id: number, userData: UpdateUser): Promise<User | null> {
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Build update query dynamically based on provided fields
|
|
||||||
const updateFields: string[] = [];
|
|
||||||
const updateValues: any[] = [];
|
|
||||||
|
|
||||||
Object.entries(userData).forEach(([key, value]) => {
|
|
||||||
if (value !== undefined) {
|
|
||||||
updateFields.push(`${key} = ?`);
|
|
||||||
updateValues.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (updateFields.length > 0) {
|
|
||||||
// Add updated_at timestamp
|
|
||||||
updateFields.push('updated_at = CURRENT_TIMESTAMP');
|
|
||||||
|
|
||||||
// Add id for WHERE clause
|
|
||||||
updateValues.push(id);
|
|
||||||
|
|
||||||
await pool.query(`
|
|
||||||
UPDATE users
|
|
||||||
SET ${updateFields.join(', ')}
|
|
||||||
WHERE id = ?
|
|
||||||
`, updateValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return updated study
|
|
||||||
return await this.findById(id);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error updating user:', error);
|
|
||||||
throw new Error('Failed to update user');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async findUserSessionById(id: number): Promise<UserSession | null> {
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
'SELECT * FROM user_sessions WHERE id = ?',
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!rows.length) return null;
|
|
||||||
return rows[0] as UserSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserSessionByIp(userId: number, ipAdress: string): Promise<UserSession | null> {
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
`SELECT *
|
|
||||||
FROM user_sessions
|
|
||||||
WHERE user_id = ?
|
|
||||||
AND ip_address = ?
|
|
||||||
ORDER BY expires_at DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
[userId, ipAdress]
|
|
||||||
);
|
|
||||||
return rows.length > 0 ? rows[0] as UserSession : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserSessionByUserAndAgent(userId: number, userAgent: string): Promise<UserSession | null> {
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
`SELECT *
|
|
||||||
FROM user_sessions
|
|
||||||
WHERE user_id = ?
|
|
||||||
AND user_agent = ?
|
|
||||||
ORDER BY expires_at DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
[userId, userAgent]
|
|
||||||
);
|
|
||||||
return rows.length > 0 ? rows[0] as UserSession : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserAndSessionByRefreshToken(refreshToken: string): Promise<User | null> {
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
|
||||||
`SELECT users.*
|
|
||||||
FROM user_sessions
|
|
||||||
JOIN users ON user_sessions.user_id = users.id
|
|
||||||
WHERE refresh_token = ? AND expires_at > NOW() AND users.status = "active"`,
|
|
||||||
[refreshToken]
|
|
||||||
);
|
|
||||||
|
|
||||||
return rows.length > 0 ? (rows[0] as User) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createUserSession(userSession: Partial<UserSession>): Promise<UserSession> {
|
|
||||||
try {
|
|
||||||
|
|
||||||
const [result] = await pool.query(
|
|
||||||
`INSERT INTO user_sessions (
|
|
||||||
user_id, refresh_token, ip_address,
|
|
||||||
user_agent, expires_at, created_at, last_activity
|
|
||||||
) VALUES (?, ?, ?, ?, ?, NOW(), NOW())`,
|
|
||||||
[
|
|
||||||
userSession.user_id,
|
|
||||||
userSession.refresh_token,
|
|
||||||
userSession.ip_address,
|
|
||||||
userSession.user_agent,
|
|
||||||
userSession.expires_at,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the created study with the correct router_id format
|
|
||||||
const insertId = (result as any).insertId;
|
|
||||||
return await this.findUserSessionById(insertId) as UserSession;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error creating User session:', error);
|
|
||||||
throw new Error('Failed to create User session');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateUserSession(refreshToken: string, userSessionData: Partial<UserSession>): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Build update query dynamically based on provided fields
|
|
||||||
const updateFields: string[] = [];
|
|
||||||
const updateValues: any[] = [];
|
|
||||||
|
|
||||||
Object.entries(userSessionData).forEach(([key, value]) => {
|
|
||||||
if (value !== undefined) {
|
|
||||||
updateFields.push(`${key} = ?`);
|
|
||||||
updateValues.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (updateFields.length > 0) {
|
|
||||||
// Add updated_at timestamp
|
|
||||||
updateFields.push('last_activity = CURRENT_TIMESTAMP');
|
|
||||||
|
|
||||||
// Add id for WHERE clause
|
|
||||||
updateValues.push(refreshToken);
|
|
||||||
|
|
||||||
const [result] = await pool.query<ResultSetHeader>(`
|
|
||||||
UPDATE user_sessions
|
|
||||||
SET ${updateFields.join(', ')}
|
|
||||||
WHERE refresh_token = ?
|
|
||||||
`, updateValues);
|
|
||||||
|
|
||||||
// Return true if at least one row was affected
|
|
||||||
return result.affectedRows > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return updated study
|
|
||||||
return false;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error updating user sessions:', error);
|
|
||||||
throw new Error('Failed to update user sessions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteUserSession(refreshToken: string): Promise<boolean> {
|
|
||||||
const [result] = await pool.query<ResultSetHeader>(
|
|
||||||
'DELETE FROM user_sessions WHERE refresh_token = ?',
|
|
||||||
[refreshToken]
|
|
||||||
);
|
|
||||||
return result.affectedRows > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
// src/routes/router.routes.ts
|
|
||||||
import { Router } from 'express';
|
|
||||||
import pool from '../config/db'; // If using default export
|
|
||||||
import { AuthController } from '../controllers/AuthController';
|
|
||||||
import { authMiddleware } from '../middleware/auth';
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
const authController = new AuthController(pool);
|
|
||||||
|
|
||||||
|
|
||||||
router.post('/login', authController.login);
|
|
||||||
router.post('/token', authController.getAuthToken);
|
|
||||||
router.post('/refresh-token', authController.refreshToken);
|
|
||||||
|
|
||||||
// Protected routes
|
|
||||||
router.use(authMiddleware(pool));
|
|
||||||
|
|
||||||
router.post('/logout', authController.logout);
|
|
||||||
|
|
||||||
// Export the router
|
|
||||||
export default router;
|
|
||||||
@ -3,11 +3,9 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { DicomStudyController } from '../controllers/DicomStudyController';
|
import { DicomStudyController } from '../controllers/DicomStudyController';
|
||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
import pool from '../config/db'; // If using default export
|
|
||||||
import { authMiddleware } from '../middleware/auth';
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const dicomStudyController = new DicomStudyController(pool);
|
const dicomStudyController = new DicomStudyController();
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
logger.info('Initializing DICOM routes');
|
logger.info('Initializing DICOM routes');
|
||||||
@ -23,9 +21,6 @@ router.get('/test', (req, res) => {
|
|||||||
res.json({ message: 'DICOM routes are working' });
|
res.json({ message: 'DICOM routes are working' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Protected routes
|
|
||||||
router.use(authMiddleware(pool));
|
|
||||||
|
|
||||||
router.post('/', (req, res, next) => {
|
router.post('/', (req, res, next) => {
|
||||||
logger.debug('POST / route hit with body:', req.body);
|
logger.debug('POST / route hit with body:', req.body);
|
||||||
dicomStudyController.createStudy(req, res, next);
|
dicomStudyController.createStudy(req, res, next);
|
||||||
|
|||||||
@ -3,8 +3,6 @@ import { Router } from 'express';
|
|||||||
import routerRoutes from './router.routes';
|
import routerRoutes from './router.routes';
|
||||||
import dicomRoutes from './dicom.routes';
|
import dicomRoutes from './dicom.routes';
|
||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
import authRoutes from './auth.routes';
|
|
||||||
import userRoutes from './user.routes';
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -12,13 +10,9 @@ const router = Router();
|
|||||||
logger.info('Registering routes:');
|
logger.info('Registering routes:');
|
||||||
logger.info('- /routers -> router routes');
|
logger.info('- /routers -> router routes');
|
||||||
logger.info('- /studies -> dicom routes');
|
logger.info('- /studies -> dicom routes');
|
||||||
logger.info('- /auth -> auth routes');
|
|
||||||
logger.info('- /users -> user routes');
|
|
||||||
|
|
||||||
router.use('/routers', routerRoutes);
|
router.use('/routers', routerRoutes);
|
||||||
router.use('/studies', dicomRoutes);
|
router.use('/studies', dicomRoutes);
|
||||||
router.use('/auth', authRoutes);
|
|
||||||
router.use('/users', userRoutes);
|
|
||||||
|
|
||||||
// Debug middleware to log incoming requests
|
// Debug middleware to log incoming requests
|
||||||
router.use((req, res, next) => {
|
router.use((req, res, next) => {
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import pool from '../config/db'; // If using default export
|
import pool from '../config/db'; // If using default export
|
||||||
import { RouterController } from '../controllers/RouterController';
|
import { RouterController } from '../controllers/RouterController';
|
||||||
import { authMiddleware } from '../middleware/auth';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -10,9 +9,6 @@ import { authMiddleware } from '../middleware/auth';
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
const controller = new RouterController(pool);
|
const controller = new RouterController(pool);
|
||||||
|
|
||||||
// Protected routes
|
|
||||||
router.use(authMiddleware(pool));
|
|
||||||
|
|
||||||
router.put('/vms', async (req, res, next) => {
|
router.put('/vms', async (req, res, next) => {
|
||||||
console.log('Route handler: /vms endpoint hit');
|
console.log('Route handler: /vms endpoint hit');
|
||||||
console.log('Query params:', req.query);
|
console.log('Query params:', req.query);
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import { Router } from 'express';
|
|
||||||
import pool from '../config/db'; // If using default export
|
|
||||||
import { SetupController } from '../controllers/SetupController';
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
const controller = new SetupController(pool);
|
|
||||||
|
|
||||||
router.post('/setup', controller.createInitialUser);
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
// src/routes/router.routes.ts
|
|
||||||
import { Router } from 'express';
|
|
||||||
import pool from '../config/db'; // If using default export
|
|
||||||
import { UserController } from '../controllers/UserController';
|
|
||||||
import { authMiddleware } from '../middleware/auth';
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
const controller = new UserController(pool);
|
|
||||||
|
|
||||||
// Protected routes
|
|
||||||
router.use(authMiddleware(pool));
|
|
||||||
|
|
||||||
router.get('/', controller.getAllUsers);
|
|
||||||
router.get('/:id', controller.getUserById);
|
|
||||||
router.post('/', controller.createUser);
|
|
||||||
router.put('/:id', controller.updateUser);
|
|
||||||
router.delete('/:id', controller.deleteUser);
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import bcrypt from 'bcryptjs';
|
|
||||||
import jwt from 'jsonwebtoken';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
|
|
||||||
import { UserService } from '../services';
|
|
||||||
import { User, UserSession, CreateUserSessionDTO, UpdateUser, UserWithSession } from '../types/user';
|
|
||||||
|
|
||||||
export class AuthService {
|
|
||||||
private userService: UserService;
|
|
||||||
|
|
||||||
constructor(pool: Pool) {
|
|
||||||
this.userService = new UserService(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate JWT token
|
|
||||||
generateAccessToken(user: Partial<User>) {
|
|
||||||
return jwt.sign(
|
|
||||||
{ userId: user.id, username: user.username, role: user.role },
|
|
||||||
process.env.JWT_SECRET as string,
|
|
||||||
{ expiresIn: '30m' }
|
|
||||||
//{ expiresIn: '1m' }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate JWT tokens
|
|
||||||
generateTokens(user: Partial<User>) {
|
|
||||||
const accessToken = jwt.sign(
|
|
||||||
{ userId: user.id, username: user.username, role: user.role },
|
|
||||||
process.env.JWT_SECRET as string,
|
|
||||||
{ expiresIn: '30m' }
|
|
||||||
//{ expiresIn: '1m' }
|
|
||||||
);
|
|
||||||
|
|
||||||
const refreshToken = jwt.sign(
|
|
||||||
{ userId: user.id, username: user.username, role: user.role, type: 'refresh' }, // Include a claim to distinguish token types
|
|
||||||
process.env.JWT_SECRET as string,
|
|
||||||
{ expiresIn: '7d' } // Longer expiry for refresh token
|
|
||||||
//{ expiresIn: '1m' }
|
|
||||||
);
|
|
||||||
|
|
||||||
return { accessToken, refreshToken };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the user by username and password
|
|
||||||
async validateUser(username: string, password: string): Promise<User> {
|
|
||||||
const user = await this.userService.getUserByUsername(username);
|
|
||||||
if (!user) {
|
|
||||||
throw new Error('Invalid credentials'); // Throw custom error
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = await bcrypt.compare(password, user.password_hash);
|
|
||||||
if (!isValid) {
|
|
||||||
throw new Error('Invalid credentials'); // Throw custom error
|
|
||||||
}
|
|
||||||
|
|
||||||
return user; // Return the valid user
|
|
||||||
};
|
|
||||||
|
|
||||||
async getUserById (id: number): Promise<User | null> {
|
|
||||||
return await this.userService.getUserById(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
async createUserSession (userSessionData: Partial<UserSession>) {
|
|
||||||
const requiredFields = [
|
|
||||||
'user_id',
|
|
||||||
'refresh_token',
|
|
||||||
'ip_address',
|
|
||||||
'user_agent',
|
|
||||||
'expires_at'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
// Check for undefined or null only (allow empty strings)
|
|
||||||
if (userSessionData[field as keyof UserSession] == null) {
|
|
||||||
throw new Error(`Missing required field: ${field}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Creating new user session', { userSessionData });
|
|
||||||
const userSession = await this.userService.createUserSession(userSessionData);
|
|
||||||
};
|
|
||||||
|
|
||||||
async updateUser (userId: number, user: UpdateUser) {
|
|
||||||
this.userService.updateUser(userId, user);
|
|
||||||
};
|
|
||||||
|
|
||||||
async getUserSessionByIp (userId: number, ipAdress: string): Promise<UserSession | null> {
|
|
||||||
return await this.userService.getUserSessionByIp(userId, ipAdress);
|
|
||||||
};
|
|
||||||
async getUserSessionByUserAndAgent (userId: number, userAgent: string): Promise<UserSession | null> {
|
|
||||||
return await this.userService.getUserSessionByUserAndAgent(userId, userAgent);
|
|
||||||
};
|
|
||||||
|
|
||||||
async getUserAndSessionByRefreshToken (refreshToken: string): Promise<User | null> {
|
|
||||||
return this.userService.getUserAndSessionByRefreshToken(refreshToken);
|
|
||||||
};
|
|
||||||
|
|
||||||
async deleteUserSession (refreshToken: string) {
|
|
||||||
this.userService.deleteUserSession(refreshToken);
|
|
||||||
};
|
|
||||||
|
|
||||||
async updateUserSession (refreshToken:string, userSessionData: Partial<UserSession>) {
|
|
||||||
const requiredFields = [
|
|
||||||
'refresh_token',
|
|
||||||
'expires_at'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
// Check for undefined or null only (allow empty strings)
|
|
||||||
if (userSessionData[field as keyof UserSession] == null) {
|
|
||||||
throw new Error(`Missing required field: ${field}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Updating user session', { userSessionData });
|
|
||||||
const userSession = await this.userService.updateUserSession(refreshToken, userSessionData);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import logger from '../utils/logger';
|
|
||||||
import { ApiError } from '@/types/error';
|
|
||||||
|
|
||||||
export class CommonService {
|
|
||||||
|
|
||||||
handleError(error: unknown, message: string): ApiError {
|
|
||||||
const apiError: ApiError = {
|
|
||||||
message: message
|
|
||||||
};
|
|
||||||
|
|
||||||
if (error instanceof Error) {
|
|
||||||
logger.error(`${message}: ${error.message}`);
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
apiError.message = error.message;
|
|
||||||
apiError.stack = error.stack;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.error(`${message}: Unknown error type`, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiError;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,14 +1,13 @@
|
|||||||
import { DicomStudy, CreateDicomStudyDTO, UpdateDicomStudyDTO, DicomStudySearchParams} from '../types/dicom';
|
import { DicomStudy, CreateDicomStudyDTO, UpdateDicomStudyDTO, DicomStudySearchParams } from '../types/dicom';
|
||||||
import { DicomStudyRepository } from '../repositories/DicomStudyRepository';
|
import { DicomStudyRepository } from '../repositories/DicomStudyRepository';
|
||||||
import pool from '../config/db';
|
import pool from '../config/db';
|
||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
|
|
||||||
export class DicomStudyService {
|
export class DicomStudyService {
|
||||||
private repository: DicomStudyRepository;
|
private repository: DicomStudyRepository;
|
||||||
|
|
||||||
constructor(pool: Pool) {
|
constructor() {
|
||||||
this.repository = new DicomStudyRepository(pool);
|
this.repository = new DicomStudyRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async isValidStatusCode(statusCode: string): Promise<boolean> {
|
private async isValidStatusCode(statusCode: string): Promise<boolean> {
|
||||||
@ -40,18 +39,21 @@ export class DicomStudyService {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const field of requiredFields) {
|
for (const field of requiredFields) {
|
||||||
// Check for undefined or null only (allow empty strings)
|
if (!studyData[field as keyof CreateDicomStudyDTO]) {
|
||||||
if (studyData[field as keyof CreateDicomStudyDTO] == null) {
|
|
||||||
throw new Error(`Missing required field: ${field}`);
|
throw new Error(`Missing required field: ${field}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commented below, currently this field is inserted with active/idle
|
|
||||||
// Validate status code
|
// Validate status code
|
||||||
//const isValidStatus = await this.isValidStatusCode(studyData.study_status_code);
|
const isValidStatus = await this.isValidStatusCode(studyData.study_status_code);
|
||||||
//if (!isValidStatus) {
|
if (!isValidStatus) {
|
||||||
//throw new Error(`Invalid study status code: ${studyData.study_status_code}. Must be one of: NEW, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, ON_HOLD`);
|
throw new Error(`Invalid study status code: ${studyData.study_status_code}. Must be one of: NEW, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, ON_HOLD`);
|
||||||
//}
|
}
|
||||||
|
|
||||||
|
// Validate date format
|
||||||
|
if (!this.isValidDate(studyData.study_date)) {
|
||||||
|
throw new Error('Invalid study date format. Use YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('Creating new study', { studyData });
|
logger.info('Creating new study', { studyData });
|
||||||
return await this.repository.create(studyData);
|
return await this.repository.create(studyData);
|
||||||
@ -149,19 +151,4 @@ export class DicomStudyService {
|
|||||||
throw new Error('Failed to search studies');
|
throw new Error('Failed to search studies');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async processStudies(routerId: number, studies: DicomStudy[]): Promise<void> {
|
|
||||||
for (const study of studies) {
|
|
||||||
const existingStudy = await this.repository.findByStudyInstanceUid(study.study_instance_uid);
|
|
||||||
if (existingStudy) {
|
|
||||||
study.router_id = existingStudy.router_id;
|
|
||||||
await this.updateStudy(existingStudy.id, study);
|
|
||||||
} else {
|
|
||||||
study.router_id = routerId.toString();
|
|
||||||
logger.info(`Inserting study for router: ${routerId}`);
|
|
||||||
await this.createStudy(study);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,8 +1,7 @@
|
|||||||
// src/services/RouterService.ts
|
// src/services/RouterService.ts
|
||||||
import { RouterRepository } from '../repositories/RouterRepository';
|
import { RouterRepository } from '../repositories/RouterRepository';
|
||||||
import { Container, RouterData,VMUpdate} from '../types';
|
import { RouterData,VMUpdate} from '../types';
|
||||||
import { Pool } from 'mysql2/promise';
|
import { Pool } from 'mysql2/promise';
|
||||||
import logger from '../utils/logger';
|
|
||||||
|
|
||||||
|
|
||||||
export class RouterService {
|
export class RouterService {
|
||||||
@ -31,10 +30,6 @@ async updateRouterVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
return this.repository.findById(id);
|
return this.repository.findById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRouterByRouterId(routerId: string): Promise<RouterData | null> {
|
|
||||||
return this.repository.findByRouterId(routerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRoutersByFacility(facility: string): Promise<RouterData[]> {
|
async getRoutersByFacility(facility: string): Promise<RouterData[]> {
|
||||||
return this.repository.findByFacility(facility);
|
return this.repository.findByFacility(facility);
|
||||||
}
|
}
|
||||||
@ -50,10 +45,5 @@ async updateRouterVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
async deleteRouter(id: number): Promise<boolean> {
|
async deleteRouter(id: number): Promise<boolean> {
|
||||||
return this.repository.delete(id);
|
return this.repository.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async processContainers(routerId: number, containers: Container[]): Promise<any> {
|
|
||||||
return this.repository.upsertContainerStatus(routerId.toString(), containers);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
import { UserRepository } from '../repositories/UserRepository';
|
|
||||||
import { User, UserRole } from '../types/user';
|
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
import bcrypt from 'bcryptjs';
|
|
||||||
|
|
||||||
export class SetupService {
|
|
||||||
|
|
||||||
private repository: UserRepository;
|
|
||||||
|
|
||||||
constructor(pool: Pool) {
|
|
||||||
this.repository = new UserRepository(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
createDefaultUsers = async () => {
|
|
||||||
const defaultUsers = [
|
|
||||||
{ name: 'API User', username: 'api_user', email: 'apiuser@ve.com', password: 'api_user@@124', role: 'api' },
|
|
||||||
{ 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: 'Guest', username: 'guest', email: 'guest@ve.com', password: 'guest@@012', role: 'viewer' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const createdUsers = [];
|
|
||||||
|
|
||||||
for (const user of defaultUsers) {
|
|
||||||
// Check if the user already exists
|
|
||||||
const existingUser = await this.repository.findUserByUsernameOrEmail(user.username, user.email);
|
|
||||||
if (!existingUser) {
|
|
||||||
const hashedPassword = await bcrypt.hash(user.password, 10);
|
|
||||||
const newUser = await this.repository.create({
|
|
||||||
name: user.name,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
password_hash: hashedPassword,
|
|
||||||
role: user.role as UserRole
|
|
||||||
});
|
|
||||||
createdUsers.push(newUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return createdUsers;
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
// src/services/UserService.ts
|
|
||||||
import { UserRepository } from '../repositories/UserRepository';
|
|
||||||
import { User, UpdateUser, UserSession, CreateUserSessionDTO, UserWithSession} from '../types/user';
|
|
||||||
import { Pool } from 'mysql2/promise';
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
|
|
||||||
export class UserService {
|
|
||||||
private repository: UserRepository;
|
|
||||||
|
|
||||||
constructor(pool: Pool) {
|
|
||||||
this.repository = new UserRepository(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserByUsername(username: string): Promise<User | null> {
|
|
||||||
return this.repository.findByUsername(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateUser(id: number, user: UpdateUser): Promise<User | null> {
|
|
||||||
return this.repository.update(id, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserById(id: number): Promise<User | null> {
|
|
||||||
return await this.repository.findById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserSessionByIp (userId: number, ipAdress: string): Promise<UserSession | null> {
|
|
||||||
return await this.repository.getUserSessionByIp(userId, ipAdress);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserSessionByUserAndAgent (userId: number, userAgent: string): Promise<UserSession | null> {
|
|
||||||
return await this.repository.getUserSessionByUserAndAgent(userId, userAgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createUserSession(userSessionDTO: Partial<UserSession>): Promise<UserSession> {
|
|
||||||
return this.repository.createUserSession(userSessionDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserAndSessionByRefreshToken (refreshToken: string): Promise<User | null> {
|
|
||||||
return this.repository.getUserAndSessionByRefreshToken(refreshToken);
|
|
||||||
};
|
|
||||||
|
|
||||||
async updateUserSession (refreshToken:string, userSessionData: Partial<UserSession>) {
|
|
||||||
return this.repository.updateUserSession(refreshToken, userSessionData);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteUserSession(refreshToken: string): Promise<boolean> {
|
|
||||||
return this.repository.deleteUserSession(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import logger from '../utils/logger';
|
|
||||||
|
|
||||||
export class UtilityService {
|
|
||||||
|
|
||||||
// Get disk status based on disk usage (handles float values like 10.25)
|
|
||||||
getDiskStatus(diskUsage: number): string {
|
|
||||||
if (diskUsage >= 90) {
|
|
||||||
return 'DISK_CRITICAL'; // Critical disk status
|
|
||||||
} else if (diskUsage >= 70) {
|
|
||||||
return 'DISK_WARNING'; // Warning disk status
|
|
||||||
} else {
|
|
||||||
return 'DISK_NORMAL'; // Normal disk status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,8 +1,3 @@
|
|||||||
export * from './RouterService';
|
export * from './RouterService';
|
||||||
export * from './DicomStudyService';
|
export * from './DicomStudyService';
|
||||||
export * from './UtilityService';
|
|
||||||
export * from './AuthService';
|
|
||||||
export * from './UserService';
|
|
||||||
export * from './CommonService';
|
|
||||||
export * from './SetupService';
|
|
||||||
// Add more service exports as needed
|
// Add more service exports as needed
|
||||||
|
|||||||
@ -72,18 +72,3 @@ export interface DicomStudySearchParams {
|
|||||||
modality?: string;
|
modality?: string;
|
||||||
patientName?: string;
|
patientName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Study {
|
|
||||||
patientId: string;
|
|
||||||
patientName: string;
|
|
||||||
siuid: string;
|
|
||||||
accessionNumber: string;
|
|
||||||
studyDate: string;
|
|
||||||
modality: string;
|
|
||||||
studyDescription: string;
|
|
||||||
seriesInstanceUid: string;
|
|
||||||
procedureCode: string;
|
|
||||||
referringPhysicianName: string;
|
|
||||||
associationId: string;
|
|
||||||
studyStatusCode: string;
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
export interface ApiError {
|
|
||||||
message: string;
|
|
||||||
code?: string;
|
|
||||||
stack?: string;
|
|
||||||
}
|
|
||||||
@ -5,9 +5,6 @@ export interface RouterData {
|
|||||||
routerId: string; // maps to backend 'router_id'
|
routerId: string; // maps to backend 'router_id'
|
||||||
facility: string; // maps to backend 'facility'
|
facility: string; // maps to backend 'facility'
|
||||||
routerAlias: string; // maps to backend 'router_alias'
|
routerAlias: string; // maps to backend 'router_alias'
|
||||||
facilityAET: string; // maps to backend 'facility_aet'
|
|
||||||
openvpnIp: string; // maps to backend 'openvpn_ip'
|
|
||||||
routerVmPrimaryIp: string; // maps to backend 'router_vm_primary_ip'
|
|
||||||
lastSeen: string; // maps to backend 'last_seen'
|
lastSeen: string; // maps to backend 'last_seen'
|
||||||
diskStatus: string; // maps to backend 'disk_status_code'
|
diskStatus: string; // maps to backend 'disk_status_code'
|
||||||
diskUsage: number; // maps to backend 'disk_usage'
|
diskUsage: number; // maps to backend 'disk_usage'
|
||||||
@ -18,27 +15,19 @@ export interface RouterData {
|
|||||||
};
|
};
|
||||||
systemStatus: {
|
systemStatus: {
|
||||||
vpnStatus: string; // maps to backend 'vpn_status_code'
|
vpnStatus: string; // maps to backend 'vpn_status_code'
|
||||||
appStatus: string; // maps to backend 'app_status_code'
|
appStatus: string; // maps to backend 'disk_status_code'
|
||||||
vmStatus: string; // router machine status
|
|
||||||
routerStatus: string; // overall operational status of the router
|
|
||||||
vms: VM[];
|
vms: VM[];
|
||||||
containers: Container[];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Study {
|
export interface Study {
|
||||||
patientId: string;
|
|
||||||
patientName: string;
|
|
||||||
siuid: string;
|
siuid: string;
|
||||||
|
patientId: string;
|
||||||
accessionNumber: string;
|
accessionNumber: string;
|
||||||
|
patientName: string;
|
||||||
studyDate: string;
|
studyDate: string;
|
||||||
modality: string;
|
modality: string;
|
||||||
studyDescription: string;
|
studyDescription: string;
|
||||||
seriesInstanceUid: string;
|
|
||||||
procedureCode: string;
|
|
||||||
referringPhysicianName: string;
|
|
||||||
associationId: string;
|
|
||||||
studyStatusCode: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VM {
|
export interface VM {
|
||||||
@ -55,8 +44,3 @@ export interface VMUpdate {
|
|||||||
export interface VMUpdateRequest {
|
export interface VMUpdateRequest {
|
||||||
vms: VMUpdate[];
|
vms: VMUpdate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Container {
|
|
||||||
container_name: string;
|
|
||||||
status_code: string;
|
|
||||||
}
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
// User Role enum
|
|
||||||
export type UserRole = 'admin' | 'operator' | 'viewer' | 'api';
|
|
||||||
|
|
||||||
// User Status enum
|
|
||||||
export type UserStatus = 'active' | 'locked' | 'disabled';
|
|
||||||
|
|
||||||
// User Interface
|
|
||||||
export interface User {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
password_hash: string;
|
|
||||||
role: UserRole;
|
|
||||||
status: UserStatus;
|
|
||||||
failed_login_attempts: number;
|
|
||||||
last_login: Date | null;
|
|
||||||
password_changed_at: Date;
|
|
||||||
created_at: Date;
|
|
||||||
updated_at: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update User Interface
|
|
||||||
export interface UpdateUser {
|
|
||||||
name: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
password_hash: string;
|
|
||||||
role: UserRole;
|
|
||||||
status: UserStatus;
|
|
||||||
failed_login_attempts: number;
|
|
||||||
last_login: Date | null;
|
|
||||||
password_changed_at: Date;
|
|
||||||
created_at: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
// User Session Interface
|
|
||||||
export interface UserSession {
|
|
||||||
id: number;
|
|
||||||
user_id: number;
|
|
||||||
refresh_token: string;
|
|
||||||
ip_address: string;
|
|
||||||
user_agent: string | null;
|
|
||||||
expires_at: Date;
|
|
||||||
created_at: Date;
|
|
||||||
last_activity: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create User Session Interface
|
|
||||||
export interface CreateUserSessionDTO {
|
|
||||||
user_id: number;
|
|
||||||
refresh_token: string;
|
|
||||||
ip_address: string;
|
|
||||||
user_agent: string | null;
|
|
||||||
expires_at: Date;
|
|
||||||
created_at: Date;
|
|
||||||
last_activity: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
// User and Session interface
|
|
||||||
export interface UserWithSession {
|
|
||||||
user: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
role: UserRole;
|
|
||||||
status: UserStatus;
|
|
||||||
last_login: Date | null;
|
|
||||||
password_changed_at: Date;
|
|
||||||
created_at: Date;
|
|
||||||
updated_at: Date;
|
|
||||||
};
|
|
||||||
session: {
|
|
||||||
id: number;
|
|
||||||
refresh_token: string;
|
|
||||||
ip_address: string;
|
|
||||||
user_agent: string | null;
|
|
||||||
expires_at: Date;
|
|
||||||
created_at: Date;
|
|
||||||
last_activity: Date;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user