Compare commits
3 Commits
main
...
feature/ro
| Author | SHA1 | Date | |
|---|---|---|---|
| dfa974fe6b | |||
| 8630a3e2c5 | |||
| 8fe130f918 |
6
.env
Normal file
6
.env
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
VITE_API_URL=http://localhost:3000/api/v1
|
||||||
|
|
||||||
|
NODE_ENV=development
|
||||||
|
|
||||||
|
#Database Configuration
|
||||||
|
DB_NAME=ve_router_db
|
||||||
25
db-scripts/00-init-db.sh
Normal file
25
db-scripts/00-init-db.sh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#!/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
|
||||||
@ -8,9 +8,13 @@ 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),
|
||||||
@ -63,54 +67,36 @@ CREATE TABLE IF NOT EXISTS user_sessions (
|
|||||||
CONSTRAINT unique_refresh_token UNIQUE(refresh_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 AUTO_INCREMENT PRIMARY KEY,
|
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
system_status_id INT NOT NULL,
|
router_id varchar(50) NOT NULL,
|
||||||
container_number INT NOT NULL CHECK (container_number BETWEEN 1 AND 10),
|
container_name varchar(50) NOT NULL,
|
||||||
status_code VARCHAR(50) NOT NULL,
|
status_code varchar(50) NOT NULL,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
updated_at timestamp NULL 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,
|
||||||
patient_id VARCHAR(50) NOT NULL,
|
patient_id VARCHAR(50) NOT NULL,
|
||||||
patient_name VARCHAR(100) NOT NULL,
|
patient_name VARCHAR(100) NOT NULL,
|
||||||
accession_number VARCHAR(50) NOT NULL,
|
accession_number VARCHAR(50) NOT NULL,
|
||||||
study_date DATE NOT NULL,
|
study_date DATE NOT NULL,
|
||||||
modality VARCHAR(20) NOT NULL,
|
modality VARCHAR(20) NOT NULL,
|
||||||
study_description VARCHAR(255),
|
study_description VARCHAR(255),
|
||||||
series_instance_uid VARCHAR(100) NOT NULL,
|
series_instance_uid VARCHAR(100) NOT NULL,
|
||||||
procedure_code VARCHAR(50),
|
procedure_code VARCHAR(50),
|
||||||
referring_physician_name VARCHAR(100),
|
referring_physician_name VARCHAR(100),
|
||||||
study_status_code VARCHAR(50) NOT NULL DEFAULT 'NEW', -- Default value, ensure 'NEW' exists in status_type
|
study_status_code VARCHAR(50) NOT NULL DEFAULT 'NEW', -- Default value, ensure 'NEW' exists in status_type
|
||||||
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 (
|
||||||
@ -118,7 +104,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(20),
|
description VARCHAR(150),
|
||||||
severity INT
|
severity INT
|
||||||
);
|
);
|
||||||
|
|
||||||
34
db-scripts/02-seed_common_data.sql
Normal file
34
db-scripts/02-seed_common_data.sql
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
-- 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 ;
|
||||||
49
db-scripts/03-seed_router_data_qa.sql
Normal file
49
db-scripts/03-seed_router_data_qa.sql
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
-- 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
Normal file
142
deploy.sh
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#!/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,57 +4,87 @@ services:
|
|||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./router-dashboard
|
context: ./router-dashboard
|
||||||
dockerfile: Dockerfile
|
dockerfile: dockerfile
|
||||||
|
args:
|
||||||
|
- SERVER_IP=${SERVER_IP:-localhost}
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
- "${FRONTEND_PORT:-5173}:5173"
|
||||||
environment:
|
environment:
|
||||||
- VITE_API_URL=http://localhost:3001/api/v1
|
- SERVER_IP=${SERVER_IP:-localhost}
|
||||||
|
- 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:
|
||||||
volumes:
|
condition: service_healthy
|
||||||
- ./router-dashboard:/app
|
container_name: router_dashboard_frontend
|
||||||
- /app/node_modules
|
networks:
|
||||||
|
- 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:
|
||||||
- "3001:3000"
|
- "${BACKEND_PORT:-3000}:3000"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=${NODE_ENV:-development}
|
||||||
- DB_HOST=host.docker.internal
|
- DB_HOST=${DB_HOST:-mysql}
|
||||||
- DB_PORT=3307
|
- DB_PORT=${DB_PORT:-3306}
|
||||||
- DB_USER=ve_router_user
|
- DB_USER=${DB_USER:-ve_router_user}
|
||||||
- DB_PASSWORD=ve_router_password
|
- DB_PASSWORD=${DB_PASSWORD:-ve_router_password}
|
||||||
- DB_NAME=ve_router_db
|
- DB_NAME=${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:
|
||||||
volumes:
|
condition: service_healthy
|
||||||
- ./ve-router-backend:/app
|
healthcheck:
|
||||||
- /app/node_modules
|
test: ["CMD", "nc", "-z", "${SERVER_IP:-localhost}", "${BACKEND_PORT:-3000}"]
|
||||||
|
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: rootpassword
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
|
||||||
MYSQL_DATABASE: ve_router_db
|
MYSQL_DATABASE: ${DB_NAME:-ve_router_db}
|
||||||
MYSQL_USER: ve_router_user
|
MYSQL_USER: ${DB_USER:-ve_router_user}
|
||||||
MYSQL_PASSWORD: ve_router_password
|
MYSQL_PASSWORD: ${DB_PASSWORD:-ve_router_password}
|
||||||
|
ENVIRONMENT: ${NODE_ENV:-development}
|
||||||
volumes:
|
volumes:
|
||||||
- mysql_data:/var/lib/mysql
|
- mysql_data:/var/lib/mysql
|
||||||
# Correct paths for init scripts
|
- ./db-scripts:/docker-entrypoint-initdb.d:ro
|
||||||
- ./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:
|
||||||
- "3307:3306"
|
- "${DB_PORT:-3306}:3306"
|
||||||
command: --default-authentication-plugin=mysql_native_password
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
|
restart: always
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "ve_router_user", "-pve_router_password"]
|
test: ["CMD", "mysqladmin", "ping", "-h", "${DB_HOST:-mysql}", "-u${DB_USER:-ve_router_user}", "-p${DB_PASSWORD:-ve_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
Normal file
38
readme.txt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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,2 +1,2 @@
|
|||||||
VITE_API_URL=http://localhost:3001/api/v1
|
VITE_API_URL=http://${SERVER_IP:-localhost}:3000/api/v1
|
||||||
VITE_NODE_ENV=development
|
VITE_NODE_ENV=development
|
||||||
2
router-dashboard/.env.production
Normal file
2
router-dashboard/.env.production
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
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:3001/api/v1
|
ENV VITE_API_URL=http://localhost:3000/api/v1
|
||||||
|
|
||||||
EXPOSE 5173
|
EXPOSE 5173
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,12 @@
|
|||||||
<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>
|
||||||
|
|||||||
BIN
router-dashboard/public/ve.png
Normal file
BIN
router-dashboard/public/ve.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 905 B |
1
router-dashboard/src/assets/images/react-logo-tm.svg
Normal file
1
router-dashboard/src/assets/images/react-logo-tm.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@ -1 +1,99 @@
|
|||||||
<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>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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: 4.0 KiB After Width: | Height: | Size: 6.5 KiB |
@ -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 >= 80
|
router.diskUsage >= 70
|
||||||
).length
|
).length
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -36,11 +36,10 @@ const DashboardLayout: React.FC = () => {
|
|||||||
data = data.filter(router => {
|
data = data.filter(router => {
|
||||||
switch (activeFilter) {
|
switch (activeFilter) {
|
||||||
case 'active':
|
case 'active':
|
||||||
return router.routerActivity?.studies &&
|
return router.systemStatus.routerStatus === 'CONNECTED' &&
|
||||||
router.routerActivity.studies.length > 0;
|
router.routerActivity?.studies?.some(study => study.studyStatusCode === 'Active');
|
||||||
case 'critical':
|
case 'critical':
|
||||||
return router.systemStatus.vpnStatus === 'VPN_DISCONNECTED' ||
|
return router.systemStatus.routerStatus === '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;
|
||||||
@ -74,18 +73,19 @@ const DashboardLayout: React.FC = () => {
|
|||||||
return {
|
return {
|
||||||
total: routerData.length,
|
total: routerData.length,
|
||||||
active: routerData.filter(r =>
|
active: routerData.filter(r =>
|
||||||
r.routerActivity?.studies &&
|
r.systemStatus.routerStatus === 'CONNECTED' && // Router (VM, app, VPN) is up
|
||||||
r.routerActivity.studies.length > 0
|
r.routerActivity?.studies?.some(study => study.studyStatusCode === 'Active') // At least one study is active
|
||||||
).length,
|
).length,
|
||||||
critical: routerData.filter(r =>
|
critical: routerData.filter(r =>
|
||||||
r.systemStatus.vpnStatus === 'VPN_DISCONNECTED' ||
|
r.systemStatus.routerStatus === 'DISCONNECTED' || // Router (VM, app, VPN) is down
|
||||||
r.systemStatus.appStatus === 'DISK_CRITICAL' ||
|
r.diskStatus === 'DISK_CRITICAL' // Disk is critical
|
||||||
r.diskStatus === 'DISK_CRITICAL'
|
|
||||||
).length,
|
).length,
|
||||||
diskAlert: routerData.filter(r => r.diskUsage > 80).length
|
diskAlert: routerData.filter(r => r.diskUsage > 80).length // Disk usage alert
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@ -128,6 +128,7 @@ 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>
|
||||||
|
|||||||
@ -22,8 +22,8 @@ interface Tab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||||
const [isPinned, setIsPinned] = useState(true);
|
const [isPinned, setIsPinned] = useState(false);
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
const tabs: Tab[] = [
|
const tabs: Tab[] = [
|
||||||
|
|||||||
@ -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: 'activity' | 'status' | 'disk') => void;
|
onToggleExpansion: (id: number, section: 'router_details' |'activity' | 'status' | 'disk') => void;
|
||||||
onExpandedContentHover: (id: number, section: 'activity' | 'status' | 'disk') => void;
|
onExpandedContentHover: (id: number, section: 'activity' | 'status' | 'disk') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +41,32 @@ 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">OpenVPN IP:</span> {router.openvpnIp}</div>
|
||||||
|
<div><span className="font-medium">Router VM Primary 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"
|
||||||
@ -88,6 +114,12 @@ 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)}`}>
|
||||||
@ -96,22 +128,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.diskStatus)}`}>
|
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.appStatus)}`}>
|
||||||
{formatStatus(router.diskStatus)}
|
{formatStatus(router.systemStatus.appStatus)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold mb-2">VM Status</h4>
|
<h4 className="font-semibold mb-2">Container Status</h4>
|
||||||
{router.systemStatus.vms.length > 0 ? (
|
{router.systemStatus.containers.length > 0 ? (
|
||||||
router.systemStatus.vms.map((vm, idx) => (
|
router.systemStatus.containers.map((container, idx) => (
|
||||||
<div key={idx} className="mb-1">
|
<div key={idx} className="mb-1">
|
||||||
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(vm.status)}`}>
|
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(container.status)}`}>
|
||||||
VM {vm.id}: {formatStatus(vm.status)}
|
{container.container_name}: {formatStatus(container.status)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<span className="text-gray-500">No VMs configured</span>
|
<span className="text-gray-500">No Containers configured</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -150,7 +182,19 @@ 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">{router.routerId}</td>
|
<td className="px-4 py-2">
|
||||||
|
<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">
|
||||||
@ -163,7 +207,8 @@ 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">
|
||||||
@ -176,8 +221,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.vpnStatus)}`}>
|
<span className={`ml-2 px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.routerStatus)}`}>
|
||||||
{formatStatus(router.systemStatus.vpnStatus)}
|
{formatStatus(router.systemStatus.routerStatus)}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@ -206,6 +251,13 @@ 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}>
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
// src/config/env.ts
|
// src/config/env.ts
|
||||||
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 || 'http://localhost:3001/api/v1',
|
apiUrl: import.meta.env.VITE_API_URL,
|
||||||
environment: import.meta.env.VITE_NODE_ENV || 'development',
|
environment: import.meta.env.VITE_NODE_ENV || 'development',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
@ -21,6 +21,9 @@ 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,
|
||||||
@ -64,6 +67,9 @@ 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,
|
||||||
@ -97,6 +103,9 @@ 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,7 +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 = 'http://localhost:3001/api/v1';
|
const API_BASE_URL = 'http://localhost:3000/api/v1';
|
||||||
|
|
||||||
// Default request options for all API calls
|
// Default request options for all API calls
|
||||||
const DEFAULT_OPTIONS = {
|
const DEFAULT_OPTIONS = {
|
||||||
@ -58,6 +58,9 @@ 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
|
||||||
@ -72,18 +75,28 @@ 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
|
||||||
|
}))
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ 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 {
|
||||||
@ -21,6 +22,9 @@ 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,6 +7,7 @@ export interface Study {
|
|||||||
studyDate: string;
|
studyDate: string;
|
||||||
modality: string;
|
modality: string;
|
||||||
studyDescription: string;
|
studyDescription: string;
|
||||||
|
studyStatusCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VM {
|
export interface VM {
|
||||||
@ -14,6 +15,11 @@ 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 {
|
||||||
@ -22,6 +28,9 @@ 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;
|
||||||
@ -33,7 +42,10 @@ export interface RouterData {
|
|||||||
systemStatus: {
|
systemStatus: {
|
||||||
vpnStatus: string;
|
vpnStatus: string;
|
||||||
appStatus: string;
|
appStatus: string;
|
||||||
|
vmStatus: string;
|
||||||
|
routerStatus: string;
|
||||||
vms: VM[];
|
vms: VM[];
|
||||||
|
containers: Container[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// src/utils/statusHelpers.ts
|
// src/utils/statusHelpers.ts
|
||||||
|
|
||||||
// Define all possible status values
|
// Below's are for demo purpose
|
||||||
|
|
||||||
export type StatusType =
|
export type StatusType =
|
||||||
| 'RUNNING'
|
| 'RUNNING'
|
||||||
| 'STOPPED'
|
| 'STOPPED'
|
||||||
@ -8,11 +9,16 @@ 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',
|
||||||
@ -20,11 +26,26 @@ export const STATUS_COLORS: Record<StatusType | string, string> = {
|
|||||||
'UNKNOWN': 'bg-gray-100 text-gray-700'
|
'UNKNOWN': 'bg-gray-100 text-gray-700'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatStatus = (status: string): string => {
|
// Add this helper function
|
||||||
return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
|
|
||||||
|
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';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add this helper function
|
|
||||||
export const getStatusColor = (status: string): string => {
|
export const getStatusColor = (status: string): string => {
|
||||||
return STATUS_COLORS[status] || STATUS_COLORS['UNKNOWN'];
|
return STATUS_COLORS[getStatusAfterUnderscore(status)] || STATUS_COLORS['UNKNOWN'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatStatus = (status: string): string => {
|
||||||
|
return getStatus(getStatusAfterUnderscore(status));
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,267 +0,0 @@
|
|||||||
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,15 +1,13 @@
|
|||||||
|
|
||||||
# Server Configuration
|
# Server Configuration
|
||||||
NODE_ENV=development
|
NODE_ENV=production
|
||||||
PORT=3000
|
PORT=3000
|
||||||
CORS_ORIGIN=http://localhost:5173,http://localhost:3000
|
CORS_ORIGIN=http://${FRONTEND_IP}:5173,http://${SERVER_IP}:3000
|
||||||
|
|
||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
DB_HOST=localhost
|
DB_HOST=${DB_HOST}
|
||||||
DB_PORT=3307
|
DB_PORT=3306
|
||||||
DB_USER=root
|
DB_USER=ve_router_user
|
||||||
DB_PASSWORD=rootpassword
|
DB_PASSWORD=ve_router_password
|
||||||
DB_NAME=ve_router_db
|
DB_NAME=ve_router_db
|
||||||
DB_CONNECTION_LIMIT=10
|
DB_CONNECTION_LIMIT=10
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,20 @@
|
|||||||
// 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),
|
||||||
// Update this to include your frontend URL
|
corsOrigin: process.env.CORS_ORIGIN?.split(',') || ['*'], // In production, replace with actual domains
|
||||||
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 || 'root',
|
user: process.env.DB_USER || 've_router_user',
|
||||||
password: process.env.DB_PASSWORD || '', // Make sure this is getting the password
|
password: process.env.DB_PASSWORD || 've_router_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)
|
||||||
},
|
},
|
||||||
@ -26,13 +24,4 @@ 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;
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { DicomStudyService } from '../services/DicomStudyService';
|
import { DicomStudyService } from '../services/DicomStudyService';
|
||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
|
import { Pool } from 'mysql2/promise';
|
||||||
|
|
||||||
interface ApiError {
|
interface ApiError {
|
||||||
message: string;
|
message: string;
|
||||||
@ -13,8 +14,8 @@ interface ApiError {
|
|||||||
export class DicomStudyController {
|
export class DicomStudyController {
|
||||||
private service: DicomStudyService;
|
private service: DicomStudyService;
|
||||||
|
|
||||||
constructor() {
|
constructor(pool:Pool) {
|
||||||
this.service = new DicomStudyService();
|
this.service = new DicomStudyService(pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleError(error: unknown, message: string): ApiError {
|
private handleError(error: unknown, message: string): ApiError {
|
||||||
|
|||||||
@ -1,77 +1,80 @@
|
|||||||
// src/controllers/RouterController.ts
|
// src/controllers/RouterController.ts
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { RouterService } from '../services/RouterService';
|
import { RouterService, DicomStudyService, UtilityService } from '../services';
|
||||||
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;
|
||||||
|
|
||||||
// Add debugging logs
|
// Add debugging logs
|
||||||
console.log('Received request:');
|
console.log('Received request:');
|
||||||
console.log('Router ID:', routerId);
|
console.log('Router ID:', routerId);
|
||||||
console.log('VMs data:', vms);
|
console.log('VMs data:', vms);
|
||||||
|
|
||||||
if (!routerId) {
|
if (!routerId) {
|
||||||
return res.status(400).json({ error: 'router_id is required' });
|
return res.status(400).json({ error: 'router_id is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(vms)) {
|
if (!Array.isArray(vms)) {
|
||||||
return res.status(400).json({ error: 'VMs must be an array' });
|
return res.status(400).json({ error: 'VMs must be an array' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedVMs = await this.service.updateRouterVMs(routerId, vms);
|
const updatedVMs = await this.service.updateRouterVMs(routerId, vms);
|
||||||
res.json(updatedVMs);
|
res.json(updatedVMs);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Type cast the error
|
// Type cast the error
|
||||||
const error = err as Error;
|
const error = err as Error;
|
||||||
|
|
||||||
// Enhanced error logging
|
// Enhanced error logging
|
||||||
console.error('Error in updateRouterVMs:', {
|
console.error('Error in updateRouterVMs:', {
|
||||||
message: error?.message || 'Unknown error',
|
message: error?.message || 'Unknown error',
|
||||||
stack: error?.stack,
|
stack: error?.stack,
|
||||||
type: error?.constructor.name
|
type: error?.constructor.name
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Failed to update VMs',
|
error: 'Failed to update VMs',
|
||||||
details: error?.message || 'Unknown error'
|
details: error?.message || 'Unknown error'
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
getAllRouters = async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const routers = await this.service.getAllRouters();
|
|
||||||
res.json(routers);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (error && typeof error === 'object' && 'message' in error) {
|
|
||||||
const e = error as { message: string }; // Type assertion here
|
|
||||||
res.status(500).json({ error: e.message });
|
|
||||||
} else {
|
|
||||||
res.status(500).json({ error: 'An unexpected error occurred' });
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
getAllRouters = async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const routers = await this.service.getAllRouters();
|
||||||
|
res.json(routers);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error && typeof error === 'object' && 'message' in error) {
|
||||||
|
const e = error as { message: string }; // Type assertion here
|
||||||
|
res.status(500).json({ error: e.message });
|
||||||
|
} else {
|
||||||
|
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.id);
|
const id = parseInt(req.params.routerId);
|
||||||
const router = await this.service.getRouterById(id);
|
const router = await this.service.getRouterById(id);
|
||||||
|
|
||||||
if (!router) {
|
if (!router) {
|
||||||
@ -86,9 +89,54 @@ getAllRouters = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
createRouter = async (req: Request, res: Response) => {
|
createRouter = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const router = await this.service.createRouter(req.body);
|
const routerMetrics = req.body;
|
||||||
res.status(201).json(router);
|
let routerId: number;
|
||||||
|
|
||||||
|
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' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,8 +3,12 @@
|
|||||||
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(
|
||||||
@ -40,10 +44,9 @@ 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: routerStringId,
|
router_id: dbStudy.router_id.toString(),
|
||||||
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,
|
||||||
@ -63,18 +66,16 @@ 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
|
study_status_code, association_id, created_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`,
|
||||||
[
|
[
|
||||||
numericRouterId,
|
studyData.router_id,
|
||||||
studyData.study_instance_uid,
|
studyData.study_instance_uid,
|
||||||
studyData.patient_id,
|
studyData.patient_id,
|
||||||
studyData.patient_name,
|
studyData.patient_name,
|
||||||
@ -246,4 +247,26 @@ 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 } from '../types';
|
import { RouterData, Study, VM, VMUpdate, Container } 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,65 +11,65 @@ 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();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await connection.beginTransaction();
|
await connection.beginTransaction();
|
||||||
console.log('Started transaction');
|
console.log('Started transaction');
|
||||||
|
|
||||||
// First verify router exists
|
// First verify router exists
|
||||||
const [routers] = await connection.query(
|
const [routers] = await connection.query(
|
||||||
'SELECT id FROM routers WHERE router_id = ?',
|
'SELECT id FROM routers WHERE router_id = ?',
|
||||||
[routerId]
|
[routerId]
|
||||||
);
|
);
|
||||||
console.log('Router query result:', routers);
|
console.log('Router query result:', routers);
|
||||||
|
|
||||||
if (!Array.isArray(routers) || routers.length === 0) {
|
if (!Array.isArray(routers) || routers.length === 0) {
|
||||||
throw new Error(`Router ${routerId} not found`);
|
throw new Error(`Router ${routerId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log each VM update
|
// Log each VM update
|
||||||
for (const vm of vms) {
|
for (const vm of vms) {
|
||||||
console.log(`Processing VM ${vm.vm_number}`);
|
console.log(`Processing VM ${vm.vm_number}`);
|
||||||
const [result]: any = await connection.query(
|
const [result]: any = await connection.query(
|
||||||
`UPDATE vm_details
|
`UPDATE vm_details
|
||||||
SET status_code = ?,
|
SET status_code = ?,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE router_id = ? AND vm_number = ?`,
|
WHERE router_id = ? AND vm_number = ?`,
|
||||||
[vm.status_code, routerId, vm.vm_number]
|
[vm.status_code, routerId, vm.vm_number]
|
||||||
);
|
);
|
||||||
console.log('Update result:', result);
|
console.log('Update result:', result);
|
||||||
|
|
||||||
if (!result.affectedRows) {
|
if (!result.affectedRows) {
|
||||||
console.log('No rows affected, attempting insert');
|
console.log('No rows affected, attempting insert');
|
||||||
await connection.query(
|
await connection.query(
|
||||||
`INSERT INTO vm_details (router_id, vm_number, status_code)
|
`INSERT INTO vm_details (router_id, vm_number, status_code)
|
||||||
VALUES (?, ?, ?)`,
|
VALUES (?, ?, ?)`,
|
||||||
[routerId, vm.vm_number, vm.status_code]
|
[routerId, vm.vm_number, vm.status_code]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await connection.commit();
|
await connection.commit();
|
||||||
console.log('Transaction committed');
|
console.log('Transaction committed');
|
||||||
|
|
||||||
const [updatedVMs] = await connection.query(
|
const [updatedVMs] = await connection.query(
|
||||||
`SELECT * FROM vm_details WHERE router_id = ? ORDER BY vm_number`,
|
`SELECT * FROM vm_details WHERE router_id = ? ORDER BY vm_number`,
|
||||||
[routerId]
|
[routerId]
|
||||||
);
|
);
|
||||||
return updatedVMs;
|
return updatedVMs;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Repository error:', err);
|
console.error('Repository error:', err);
|
||||||
await connection.rollback();
|
await connection.rollback();
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
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,10 +81,12 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
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 study_date DESC`,
|
ORDER BY updated_at DESC
|
||||||
|
LIMIT 1`,
|
||||||
[routerId]
|
[routerId]
|
||||||
);
|
);
|
||||||
return rows as Study[];
|
return rows as Study[];
|
||||||
@ -126,10 +128,52 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(`Containers for router ${routerId}:`, rows);
|
||||||
|
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';
|
||||||
|
};
|
||||||
|
|
||||||
private async transformDatabaseRouter(dbRouter: any, index: number): Promise<RouterData> {
|
private async transformDatabaseRouter(dbRouter: any, index: number): Promise<RouterData> {
|
||||||
try {
|
try {
|
||||||
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);
|
||||||
|
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);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: dbRouter.id,
|
id: dbRouter.id,
|
||||||
@ -137,7 +181,10 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
routerId: dbRouter.router_id,
|
routerId: dbRouter.router_id,
|
||||||
facility: dbRouter.facility,
|
facility: dbRouter.facility,
|
||||||
routerAlias: dbRouter.router_alias,
|
routerAlias: dbRouter.router_alias,
|
||||||
lastSeen: new Date(dbRouter.last_seen).toISOString(),
|
facilityAET: dbRouter.facility_aet,
|
||||||
|
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),
|
||||||
@ -146,9 +193,12 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
studies
|
studies
|
||||||
},
|
},
|
||||||
systemStatus: {
|
systemStatus: {
|
||||||
vpnStatus: dbRouter.vpn_status_code,
|
vpnStatus: vpnStatus,
|
||||||
appStatus: dbRouter.disk_status_code,
|
appStatus: appStatus,
|
||||||
vms
|
vmStatus: vmStatus,
|
||||||
|
routerStatus: routerStatus,
|
||||||
|
vms,
|
||||||
|
containers
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -184,6 +234,16 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
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',
|
||||||
@ -198,18 +258,22 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, last_seen,
|
router_id, facility, router_alias, facility_aet, openvpn_ip, router_vm_primary_ip,
|
||||||
vpn_status_code, disk_status_code, license_status,
|
last_seen, vpn_status_code, disk_status_code, app_status_code,
|
||||||
free_disk, total_disk, disk_usage
|
license_status, free_disk, total_disk, disk_usage
|
||||||
) VALUES (?, ?, ?, NOW(), ?, ?, 'inactive', ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?, NOW(), ?, ?, ?, 'inactive', ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
router.routerId,
|
router.routerId,
|
||||||
router.facility,
|
router.facility,
|
||||||
router.routerAlias,
|
router.routerAlias,
|
||||||
'unknown',
|
router.facilityAET,
|
||||||
|
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
|
||||||
@ -225,8 +289,14 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
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;
|
||||||
@ -242,7 +312,7 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`UPDATE routers SET ${setClauses}, updated_at = NOW() WHERE id = ?`,
|
`UPDATE routers SET ${setClauses}, last_seen = NOW(), updated_at = NOW() WHERE id = ?`,
|
||||||
[...Object.values(updates), id]
|
[...Object.values(updates), id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -257,4 +327,37 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
|
|||||||
);
|
);
|
||||||
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -3,9 +3,10 @@
|
|||||||
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
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const dicomStudyController = new DicomStudyController();
|
const dicomStudyController = new DicomStudyController(pool);
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
logger.info('Initializing DICOM routes');
|
logger.info('Initializing DICOM routes');
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
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() {
|
constructor(pool: Pool) {
|
||||||
this.repository = new DicomStudyRepository();
|
this.repository = new DicomStudyRepository(pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async isValidStatusCode(statusCode: string): Promise<boolean> {
|
private async isValidStatusCode(statusCode: string): Promise<boolean> {
|
||||||
@ -39,21 +40,18 @@ export class DicomStudyService {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const field of requiredFields) {
|
for (const field of requiredFields) {
|
||||||
if (!studyData[field as keyof CreateDicomStudyDTO]) {
|
// Check for undefined or null only (allow empty strings)
|
||||||
|
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);
|
||||||
@ -151,4 +149,19 @@ 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,7 +1,8 @@
|
|||||||
// src/services/RouterService.ts
|
// src/services/RouterService.ts
|
||||||
import { RouterRepository } from '../repositories/RouterRepository';
|
import { RouterRepository } from '../repositories/RouterRepository';
|
||||||
import { RouterData,VMUpdate} from '../types';
|
import { Container, 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 {
|
||||||
@ -30,6 +31,10 @@ 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);
|
||||||
}
|
}
|
||||||
@ -45,5 +50,10 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
ve-router-backend/src/services/UtilityService.ts
Normal file
17
ve-router-backend/src/services/UtilityService.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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,3 +1,4 @@
|
|||||||
export * from './RouterService';
|
export * from './RouterService';
|
||||||
export * from './DicomStudyService';
|
export * from './DicomStudyService';
|
||||||
|
export * from './UtilityService';
|
||||||
// Add more service exports as needed
|
// Add more service exports as needed
|
||||||
|
|||||||
@ -72,3 +72,18 @@ 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,33 +1,44 @@
|
|||||||
// src/types/index.ts
|
// src/types/index.ts
|
||||||
export interface RouterData {
|
export interface RouterData {
|
||||||
id: number; // maps to backend 'id'
|
id: number; // maps to backend 'id'
|
||||||
slNo: number; // maps to backend 'slNo'
|
slNo: number; // maps to backend 'slNo'
|
||||||
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'
|
||||||
lastSeen: string; // maps to backend 'last_seen'
|
facilityAET: string; // maps to backend 'facility_aet'
|
||||||
diskStatus: string; // maps to backend 'disk_status_code'
|
openvpnIp: string; // maps to backend 'openvpn_ip'
|
||||||
diskUsage: number; // maps to backend 'disk_usage'
|
routerVmPrimaryIp: string; // maps to backend 'router_vm_primary_ip'
|
||||||
freeDisk: number; // maps to backend 'free_disk'
|
lastSeen: string; // maps to backend 'last_seen'
|
||||||
totalDisk: number; // maps to backend 'total_disk'
|
diskStatus: string; // maps to backend 'disk_status_code'
|
||||||
|
diskUsage: number; // maps to backend 'disk_usage'
|
||||||
|
freeDisk: number; // maps to backend 'free_disk'
|
||||||
|
totalDisk: number; // maps to backend 'total_disk'
|
||||||
routerActivity: {
|
routerActivity: {
|
||||||
studies: Study[];
|
studies: Study[];
|
||||||
};
|
};
|
||||||
systemStatus: {
|
systemStatus: {
|
||||||
vpnStatus: string; // maps to backend 'vpn_status_code'
|
vpnStatus: string; // maps to backend 'vpn_status_code'
|
||||||
appStatus: string; // maps to backend 'disk_status_code'
|
appStatus: string; // maps to backend 'app_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 {
|
||||||
siuid: string;
|
|
||||||
patientId: string;
|
patientId: string;
|
||||||
accessionNumber: string;
|
|
||||||
patientName: string;
|
patientName: string;
|
||||||
|
siuid: string;
|
||||||
|
accessionNumber: 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 {
|
||||||
@ -44,3 +55,8 @@ export interface VMUpdate {
|
|||||||
export interface VMUpdateRequest {
|
export interface VMUpdateRequest {
|
||||||
vms: VMUpdate[];
|
vms: VMUpdate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Container {
|
||||||
|
container_name: string;
|
||||||
|
status_code: string;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user