diff --git a/db-scripts/00-init-db.sh b/db-scripts/00-init-db.sh new file mode 100644 index 0000000..582fed0 --- /dev/null +++ b/db-scripts/00-init-db.sh @@ -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 diff --git a/sql/01-init.sql b/db-scripts/01-init.sql similarity index 80% rename from sql/01-init.sql rename to db-scripts/01-init.sql index 86e092c..7fdc1f2 100644 --- a/sql/01-init.sql +++ b/db-scripts/01-init.sql @@ -8,6 +8,9 @@ CREATE TABLE IF NOT EXISTS routers ( router_id VARCHAR(10) UNIQUE NOT NULL, -- Unique router identifier facility 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, vpn_status_code VARCHAR(50) NOT NULL, disk_status_code VARCHAR(50) NOT NULL, @@ -76,24 +79,24 @@ CREATE TABLE IF NOT EXISTS container_status ( ); -- DICOM study overview table with router_id as a string reference -CREATE TABLE IF NOT EXISTS dicom_study_overview ( - id INT AUTO_INCREMENT PRIMARY KEY, - router_id VARCHAR(10) NOT NULL, -- Matching VARCHAR(10) with the routers table - study_instance_uid VARCHAR(100) UNIQUE NOT NULL, - patient_id VARCHAR(50) NOT NULL, - patient_name VARCHAR(100) NOT NULL, - accession_number VARCHAR(50) NOT NULL, - study_date DATE NOT NULL, - modality VARCHAR(20) NOT NULL, - study_description VARCHAR(255), - series_instance_uid VARCHAR(100) NOT NULL, - procedure_code VARCHAR(50), - referring_physician_name VARCHAR(100), - 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', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); + CREATE TABLE IF NOT EXISTS dicom_study_overview ( + id INT AUTO_INCREMENT PRIMARY KEY, + router_id VARCHAR(10) NOT NULL, -- Matching VARCHAR(10) with the routers table + study_instance_uid VARCHAR(100) UNIQUE NOT NULL, + patient_id VARCHAR(50) NOT NULL, + patient_name VARCHAR(100) NOT NULL, + accession_number VARCHAR(50) NOT NULL, + study_date DATE NOT NULL, + modality VARCHAR(20) NOT NULL, + study_description VARCHAR(255), + series_instance_uid VARCHAR(100) NOT NULL, + procedure_code VARCHAR(50), + referring_physician_name VARCHAR(100), + 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', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ); -- Create tables if they don't exist CREATE TABLE IF NOT EXISTS status_type ( diff --git a/db-scripts/02-seed_common_data.sql b/db-scripts/02-seed_common_data.sql new file mode 100644 index 0000000..c7e79bb --- /dev/null +++ b/db-scripts/02-seed_common_data.sql @@ -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 ; diff --git a/db-scripts/03-seed_router_data_qa.sql b/db-scripts/03-seed_router_data_qa.sql new file mode 100644 index 0000000..a0f961a --- /dev/null +++ b/db-scripts/03-seed_router_data_qa.sql @@ -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(); diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..f537041 --- /dev/null +++ b/deploy.sh @@ -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!" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 5bbda1e..64957f8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,61 +5,86 @@ services: build: context: ./router-dashboard dockerfile: dockerfile + args: + - SERVER_IP=${SERVER_IP:-localhost} ports: - - "5173:5173" + - "${FRONTEND_PORT:-5173}:5173" environment: - - VITE_API_URL=${VITE_API_URL} + - 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: backend: condition: service_healthy container_name: router_dashboard_frontend + networks: + - app_network + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://${SERVER_IP:-localhost}:5173"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s backend: build: context: ./ve-router-backend dockerfile: dockerfile ports: - - "3000:3000" + - "${BACKEND_PORT:-3000}:3000" environment: - - NODE_ENV=${NODE_ENV} - - DB_HOST=host.docker.internal - - DB_PORT=3306 - - DB_USER=ve_router_user - - DB_PASSWORD=ve_router_password - - DB_NAME=${DB_NAME} + - NODE_ENV=${NODE_ENV:-development} + - DB_HOST=${DB_HOST:-mysql} + - DB_PORT=${DB_PORT:-3306} + - DB_USER=${DB_USER:-ve_router_user} + - DB_PASSWORD=${DB_PASSWORD:-ve_router_password} + - 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: mysql: - condition: service_healthy + condition: service_healthy healthcheck: - test: ["CMD", "nc", "-z", "localhost", "3000"] # Netcat check to see if port 3000 is open - interval: 30s # Check every 30 seconds - retries: 3 # Retry 3 times before marking unhealthy - start_period: 30s # Wait 30 seconds before starting health checks - timeout: 10s # Wait for 10 seconds for each health check to respond + 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: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_DATABASE: ve_router_db - MYSQL_USER: ve_router_user - MYSQL_PASSWORD: ve_router_password + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} + MYSQL_DATABASE: ${DB_NAME:-ve_router_db} + MYSQL_USER: ${DB_USER:-ve_router_user} + MYSQL_PASSWORD: ${DB_PASSWORD:-ve_router_password} + ENVIRONMENT: ${NODE_ENV:-development} volumes: - mysql_data:/var/lib/mysql - # Correct paths for init scripts - - ./sql:/docker-entrypoint-initdb.d + - ./db-scripts:/docker-entrypoint-initdb.d:ro ports: - - "3306:3306" + - "${DB_PORT:-3306}:3306" command: --default-authentication-plugin=mysql_native_password restart: always 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 timeout: 5s retries: 5 start_period: 30s + networks: + - app_network + container_name: router_dashboard_mysql + +networks: + app_network: + driver: bridge volumes: - mysql_data: \ No newline at end of file + mysql_data: + name: router_dashboard_mysql_data \ No newline at end of file diff --git a/readme.txt b/readme.txt index 4854c08..a757553 100644 --- a/readme.txt +++ b/readme.txt @@ -15,4 +15,24 @@ user/password: ve_router_user/ve_router_password 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 \ No newline at end of file +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 \ No newline at end of file diff --git a/router-dashboard/.env b/router-dashboard/.env index 983a024..5132f4a 100644 --- a/router-dashboard/.env +++ b/router-dashboard/.env @@ -1,2 +1,2 @@ -VITE_API_URL=http://localhost:3000/api/v1 +VITE_API_URL=http://${SERVER_IP:-localhost}:3000/api/v1 VITE_NODE_ENV=development \ No newline at end of file diff --git a/router-dashboard/.env.production b/router-dashboard/.env.production new file mode 100644 index 0000000..f04b834 --- /dev/null +++ b/router-dashboard/.env.production @@ -0,0 +1,2 @@ +VITE_API_URL=http://${SERVER_IP}:3000/api/v1 +VITE_NODE_ENV=production \ No newline at end of file diff --git a/router-dashboard/src/components/dashboard/DashboardLayout.tsx b/router-dashboard/src/components/dashboard/DashboardLayout.tsx index 28815ca..0d4d707 100644 --- a/router-dashboard/src/components/dashboard/DashboardLayout.tsx +++ b/router-dashboard/src/components/dashboard/DashboardLayout.tsx @@ -36,11 +36,10 @@ const DashboardLayout: React.FC = () => { data = data.filter(router => { switch (activeFilter) { case 'active': - return router.routerActivity?.studies && - router.routerActivity.studies.length > 0; + return router.systemStatus.routerStatus === 'CONNECTED' && + router.routerActivity?.studies?.some(study => study.studyStatusCode === 'Active'); case 'critical': - return router.systemStatus.vpnStatus === 'VPN_DISCONNECTED' || - router.systemStatus.appStatus === 'DISK_CRITICAL' || + return router.systemStatus.routerStatus === 'DISCONNECTED' || router.diskStatus === 'DISK_CRITICAL'; case 'diskAlert': return router.diskUsage > 80; @@ -74,17 +73,18 @@ const DashboardLayout: React.FC = () => { return { total: routerData.length, active: routerData.filter(r => - r.routerActivity?.studies && - r.routerActivity.studies.length > 0 + r.systemStatus.routerStatus === 'CONNECTED' && // Router (VM, app, VPN) is up + r.routerActivity?.studies?.some(study => study.studyStatusCode === 'Active') // At least one study is active ).length, critical: routerData.filter(r => - r.systemStatus.vpnStatus === 'VPN_DISCONNECTED' || - r.systemStatus.appStatus === 'DISK_CRITICAL' || - r.diskStatus === 'DISK_CRITICAL' + r.systemStatus.routerStatus === 'DISCONNECTED' || // Router (VM, app, VPN) is down + r.diskStatus === 'DISK_CRITICAL' // Disk is critical ).length, - diskAlert: routerData.filter(r => r.diskUsage > 80).length + diskAlert: routerData.filter(r => r.diskUsage > 80).length // Disk usage alert }; }; + + const renderContent = () => { if (loading) { @@ -128,6 +128,7 @@ const DashboardLayout: React.FC = () => { activeFilter === 'active' ? 'ring-2 ring-blue-500' : '' }`} onClick={() => setActiveFilter('active')} + title="Study in transmit currently" >
{summary.active}
diff --git a/router-dashboard/src/components/dashboard/Navbar.tsx b/router-dashboard/src/components/dashboard/Navbar.tsx index e38a22b..b3928db 100644 --- a/router-dashboard/src/components/dashboard/Navbar.tsx +++ b/router-dashboard/src/components/dashboard/Navbar.tsx @@ -22,8 +22,8 @@ interface Tab { } const Navbar: React.FC