From 8630a3e2c5091625b45991476f4f110c52c74a20 Mon Sep 17 00:00:00 2001 From: shankar Date: Fri, 22 Nov 2024 13:44:03 +0530 Subject: [PATCH] Fixed React backend startup issue. Updated SQL script to correct procedure and column sizes. Moved SQL scripts to the project root folder (outside React frontend). Resolved backend container dependency issue to ensure MySQL is up before starting React backend. Moved common values to .env file in the project root. Updated React backend and MySQL ports to use default values. Added code to get last study received, containers status and updated into DB. --- docker-compose.yml | 3 +- readme.txt | 18 ++ router-dashboard/index.html | 7 +- router-dashboard/public/ve.png | Bin 0 -> 905 bytes .../src/assets/images/react-logo-tm.svg | 1 + .../src/assets/images/ve-logo-tm.svg | 100 ++++++++++- .../src/components/dashboard/Dashboard.tsx | 2 +- .../components/dashboard/RouterTableRow.tsx | 24 ++- router-dashboard/src/services/api.service.ts | 6 + router-dashboard/src/types/index.ts | 6 + router-dashboard/src/utils/statusHelpers.ts | 62 ++++++- sql/01-init.sql | 33 +--- sql/02-seed_data.sql | 40 ++--- .../src/controllers/DicomStudyController.ts | 5 +- .../src/controllers/RouterController.ts | 156 ++++++++++++------ .../src/repositories/DicomStudyRepository.ts | 37 ++++- .../src/repositories/RouterRepository.ts | 88 +++++++++- ve-router-backend/src/routes/dicom.routes.ts | 3 +- .../src/services/DicomStudyService.ts | 39 +++-- .../src/services/RouterService.ts | 12 +- .../src/services/UtilityService.ts | 17 ++ ve-router-backend/src/services/index.ts | 1 + ve-router-backend/src/types/dicom.ts | 15 ++ ve-router-backend/src/types/index.ts | 17 +- 24 files changed, 529 insertions(+), 163 deletions(-) create mode 100644 readme.txt create mode 100644 router-dashboard/public/ve.png create mode 100644 router-dashboard/src/assets/images/react-logo-tm.svg create mode 100644 ve-router-backend/src/services/UtilityService.ts diff --git a/docker-compose.yml b/docker-compose.yml index ac8588a..5bbda1e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,8 @@ services: depends_on: backend: condition: service_healthy - + container_name: router_dashboard_frontend + backend: build: context: ./ve-router-backend diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..4854c08 --- /dev/null +++ b/readme.txt @@ -0,0 +1,18 @@ +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 \ No newline at end of file diff --git a/router-dashboard/index.html b/router-dashboard/index.html index e4b78ea..89bb70a 100644 --- a/router-dashboard/index.html +++ b/router-dashboard/index.html @@ -2,9 +2,12 @@ - + + - Vite + React + TS + + Router Management +
diff --git a/router-dashboard/public/ve.png b/router-dashboard/public/ve.png new file mode 100644 index 0000000000000000000000000000000000000000..402cb80cadc8774980b0a6de61272d92b47b0859 GIT binary patch literal 905 zcmV;419tq0P)ResPgwet5nw7bN>)OG_oG`bcOc+%WQ9lSGiYS7D3gQ=E z5QT~$sI{m?QLFextJV5OsfB7yY;9xGHf_yoliS9dNE@rIg7gm$_dMsE`#aC`oWmo; zWigFPW12PXK;_ur=RG%=k*C2{XiWMVrGlNsg=m!%&+H3(+1%G~GJbQ0+bHDZPX zqqsd4;o0VwIQxQhwE0lQh2%n58sTzJ-v<#ibkx%y>gA?KBYOfGRR*zGE79Sm6!7Ss z7mz*!DNV_`LM!UgGzNzxPPcjJ>JN;=2Ex7EdU+YGN=4tW24}#4J0#E@9Hre`lL9U% zwxKX*v8w88=9+Z;nw`U=mLLzCdfB+Mb{yE)eG#i6D`{q(k@`**=bRGfT@pRR-=%M672ES!_>UWJs5|NZU9_Cn(QtCCkj^*UkgLQQryn7alF6UjuL}1yR zOujELqL5R+*Y3spD#~WZPsueYCa~1<3!QRFPBmOZ=o0v2_5!NrI;I9Dw4pSYXuFf) z=bh|6aE0|HWr*o&3XCEv=9TgPgOm6xE%+B&*jiEj{_m+8j;XlF%<@IwQgh}WkGowo zG`F$IQI1KQ!S$Y2T#*6DP_pc+QfyhK4}ijC-R5;5Liw@-1kHYCih3??Tf?X_PHF8f zUikavx(Yn`cLxRf>`zCSkU^(r890AEtzT fG{Vo-v^Ur{*gh$BI@Y9&00000NkvXXu0mjfIOeZr literal 0 HcmV?d00001 diff --git a/router-dashboard/src/assets/images/react-logo-tm.svg b/router-dashboard/src/assets/images/react-logo-tm.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/router-dashboard/src/assets/images/react-logo-tm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/router-dashboard/src/assets/images/ve-logo-tm.svg b/router-dashboard/src/assets/images/ve-logo-tm.svg index 6c87de9..5cf4778 100644 --- a/router-dashboard/src/assets/images/ve-logo-tm.svg +++ b/router-dashboard/src/assets/images/ve-logo-tm.svg @@ -1 +1,99 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/router-dashboard/src/components/dashboard/Dashboard.tsx b/router-dashboard/src/components/dashboard/Dashboard.tsx index c7fab2d..7ba5bf1 100644 --- a/router-dashboard/src/components/dashboard/Dashboard.tsx +++ b/router-dashboard/src/components/dashboard/Dashboard.tsx @@ -40,7 +40,7 @@ const Dashboard = () => { router.diskStatus === 'DISK_CRITICAL' ).length, diskWarnings: data.filter(router => - router.diskUsage >= 80 + router.diskUsage >= 70 ).length }; diff --git a/router-dashboard/src/components/dashboard/RouterTableRow.tsx b/router-dashboard/src/components/dashboard/RouterTableRow.tsx index a5dada0..a534524 100644 --- a/router-dashboard/src/components/dashboard/RouterTableRow.tsx +++ b/router-dashboard/src/components/dashboard/RouterTableRow.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { ChevronRight, ChevronDown } from 'lucide-react'; import { RouterData } from '../../types'; -import { STATUS_COLORS, formatStatus, getStatusColor } from '../../utils/statusHelpers'; +import { STATUS_COLORS, formatStatus, getStatusColor, getVMStatus, getSystemStatus } from '../../utils/statusHelpers'; interface RouterTableRowProps { router: RouterData; @@ -88,6 +88,12 @@ export const RouterTableRow: React.FC = ({ />
+
+

VM Status

+ + {formatStatus(getVMStatus(router.lastSeen))} + +

VPN Status

@@ -96,22 +102,22 @@ export const RouterTableRow: React.FC = ({

App Status

- - {formatStatus(router.diskStatus)} + + {formatStatus(router.systemStatus.appStatus)}
-

VM Status

- {router.systemStatus.vms.length > 0 ? ( - router.systemStatus.vms.map((vm, idx) => ( +

Container Status

+ {router.systemStatus.containers.length > 0 ? ( + router.systemStatus.containers.map((container, idx) => (
- - VM {vm.id}: {formatStatus(vm.status)} + + {container.container_name}: {formatStatus(container.status)}
)) ) : ( - No VMs configured + No Containers configured )}
diff --git a/router-dashboard/src/services/api.service.ts b/router-dashboard/src/services/api.service.ts index 65d77b6..4de683d 100644 --- a/router-dashboard/src/services/api.service.ts +++ b/router-dashboard/src/services/api.service.ts @@ -84,6 +84,12 @@ class ApiService { id: vm.id, status: vm.status })) + : [], + containers: Array.isArray(router.systemStatus?.containers) + ? router.systemStatus.containers.map((container: any) => ({ + container_name: container.container_name, + status: container.status_code + })) : [] } }; diff --git a/router-dashboard/src/types/index.ts b/router-dashboard/src/types/index.ts index 8957d60..adb7cd7 100644 --- a/router-dashboard/src/types/index.ts +++ b/router-dashboard/src/types/index.ts @@ -14,6 +14,11 @@ export interface VM { status: string; } +export interface Container { + container_name: string; + status_code: string; +} + export type FilterType = 'all' | 'active' | 'critical' | 'diskAlert'; export interface RouterData { @@ -34,6 +39,7 @@ export interface RouterData { vpnStatus: string; appStatus: string; vms: VM[]; + containers: Container[]; }; } diff --git a/router-dashboard/src/utils/statusHelpers.ts b/router-dashboard/src/utils/statusHelpers.ts index e31cd5b..c4fda2f 100644 --- a/router-dashboard/src/utils/statusHelpers.ts +++ b/router-dashboard/src/utils/statusHelpers.ts @@ -1,6 +1,7 @@ // src/utils/statusHelpers.ts -// Define all possible status values +// Below's are for demo purpose + export type StatusType = | 'RUNNING' | 'STOPPED' @@ -8,11 +9,16 @@ export type StatusType = | 'CONNECTED' | 'DISCONNECTED' | 'ERROR' - | 'UNKNOWN'; + | 'UNKNOWN' + | 'ONLINE' + | 'OFFLINE'; + export const STATUS_COLORS: Record = { 'RUNNING': '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', 'DISCONNECTED': 'bg-red-100 text-red-700', 'WARNING': 'bg-yellow-100 text-yellow-700', @@ -20,11 +26,55 @@ export const STATUS_COLORS: Record = { 'UNKNOWN': 'bg-gray-100 text-gray-700' }; -export const formatStatus = (status: string): string => { - return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase(); +// Add this helper function + +function getStatusAfterUnderscore(status: string): string { + if (status.includes('_')) { + const parts = status.split('_'); + return parts[1] || ''; // Get the part after the underscore or return an empty string + } + return status; // Return the original string if no underscore is present +} + +export const getStatus = (status: string): string => { + const keywords = ['CONNECTED', 'ONLINE', 'RUNNING']; + return keywords.some((keyword) => status === keyword) ? 'Up' : 'Down'; }; -// Add this helper function 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)); +}; + +export const getVMStatus = (lastSeen: string | number | Date) => { + 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_ONLINE' : 'NET_ONLINE'; //demo purpose returning only online +}; + +export const getSystemStatus = (lastSeen: string | number | Date, vpnStatus: string, appStatus:string) => { + const vmStatus = getVMStatus(lastSeen); + + const expectedStatuses = { + VPN_CONNECTED: 'VPN_CONNECTED', + CONTAINER_RUNNING: 'CONTAINER_RUNNING', + NET_ONLINE: 'NET_ONLINE', + }; + + if ( + vpnStatus === expectedStatuses.VPN_CONNECTED && + appStatus === expectedStatuses.CONTAINER_RUNNING && + vmStatus === expectedStatuses.NET_ONLINE + ) { + return 'CONNECTED'; + } + return 'DISCONNECTED'; + }; \ No newline at end of file diff --git a/sql/01-init.sql b/sql/01-init.sql index dcbd0cf..86e092c 100644 --- a/sql/01-init.sql +++ b/sql/01-init.sql @@ -11,6 +11,7 @@ CREATE TABLE IF NOT EXISTS routers ( last_seen TIMESTAMP NOT NULL, vpn_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', free_disk BIGINT NOT NULL CHECK (free_disk >= 0), total_disk BIGINT NOT NULL CHECK (total_disk > 0), @@ -63,33 +64,15 @@ CREATE TABLE IF NOT EXISTS user_sessions ( 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 CREATE TABLE IF NOT EXISTS container_status ( - id INT AUTO_INCREMENT PRIMARY KEY, - system_status_id INT NOT NULL, - container_number INT NOT NULL CHECK (container_number BETWEEN 1 AND 10), - status_code VARCHAR(50) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - --- 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) + id int NOT NULL AUTO_INCREMENT PRIMARY KEY, + router_id varchar(50) NOT NULL, + container_name varchar(50) NOT NULL, + status_code varchar(50) NOT NULL, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE(router_id, container_name) ); -- DICOM study overview table with router_id as a string reference diff --git a/sql/02-seed_data.sql b/sql/02-seed_data.sql index 1c940cf..9a8ffa7 100644 --- a/sql/02-seed_data.sql +++ b/sql/02-seed_data.sql @@ -11,8 +11,8 @@ BEGIN AND table_name IN ( 'auth_log', 'user_sessions', 'user_router_access', 'users', 'container_status_history', 'router_status_history', - 'container_status', 'vm_details', 'dicom_study_overview', - 'system_status', 'router_settings_history', 'router_settings', + 'container_status', 'dicom_study_overview', + 'router_settings_history', 'router_settings', 'routers', 'status_type', 'status_category' ); @@ -64,11 +64,11 @@ BEGIN 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) + INSERT INTO routers (router_id, facility, router_alias, 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', 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) + ('RTR001', 'Main Hospital', 'MAIN_RAD', NOW(), 'VPN_CONNECTED', 'DISK_NORMAL', 'CONTAINER_RUNNING', 'active', 500000000000, 1000000000000, 50.00), + ('RTR002', 'Emergency Center', 'ER_RAD', NOW(), 'VPN_CONNECTED', 'DISK_WARNING', 'CONTAINER_RUNNING', 'active', 400000000000, 1000000000000, 60.00), + ('RTR003', 'Imaging Center', 'IMG_CENTER', NOW(), 'VPN_CONNECTED', 'DISK_NORMAL', 'CONTAINER_RUNNING', 'active', 600000000000, 1000000000000, 40.00) ON DUPLICATE KEY UPDATE id = id; -- Store Router IDs @@ -76,30 +76,14 @@ BEGIN 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) + INSERT INTO container_status (router_id, container_name, status_code, created_at, updated_at) 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') + (1, 'router-cstore-scp', 'CONTAINER_RUNNING', NOW(), NOW()), + (1, 'router-cstore-scu', 'CONTAINER_RUNNING', NOW(), NOW()), + (2, 'router-cstore-scp', 'CONTAINER_RUNNING', NOW(), NOW()), + (2, 'router-cstore-scu', 'CONTAINER_RUNNING', NOW(), NOW()), + (3, 'router-cstore-scp', 'CONTAINER_RUNNING', NOW(), NOW()) ON DUPLICATE KEY UPDATE id = id; -- Insert DICOM Study Overview diff --git a/ve-router-backend/src/controllers/DicomStudyController.ts b/ve-router-backend/src/controllers/DicomStudyController.ts index 53cf406..7eada7a 100644 --- a/ve-router-backend/src/controllers/DicomStudyController.ts +++ b/ve-router-backend/src/controllers/DicomStudyController.ts @@ -3,6 +3,7 @@ import { Request, Response, NextFunction } from 'express'; import { DicomStudyService } from '../services/DicomStudyService'; import logger from '../utils/logger'; +import { Pool } from 'mysql2/promise'; interface ApiError { message: string; @@ -13,8 +14,8 @@ interface ApiError { export class DicomStudyController { private service: DicomStudyService; - constructor() { - this.service = new DicomStudyService(); + constructor(pool:Pool) { + this.service = new DicomStudyService(pool); } private handleError(error: unknown, message: string): ApiError { diff --git a/ve-router-backend/src/controllers/RouterController.ts b/ve-router-backend/src/controllers/RouterController.ts index 43b51a0..63ffc25 100644 --- a/ve-router-backend/src/controllers/RouterController.ts +++ b/ve-router-backend/src/controllers/RouterController.ts @@ -1,77 +1,80 @@ // src/controllers/RouterController.ts import { Request, Response } from 'express'; -import { RouterService } from '../services/RouterService'; +import { RouterService, DicomStudyService, UtilityService } from '../services'; import { Pool } from 'mysql2/promise'; import { RouterData, VMUpdate, VMUpdateRequest } from '../types'; +import logger from '../utils/logger'; + export class RouterController { private service: RouterService; + private dicomStudyService: DicomStudyService; + private utilityService: UtilityService constructor(pool: Pool) { this.service = new RouterService(pool); -} + this.dicomStudyService = new DicomStudyService(pool); + this.utilityService = new UtilityService(); + } // src/controllers/RouterController.ts -// src/controllers/RouterController.ts -updateRouterVMs = async (req: Request, res: Response) => { - try { - const routerId = req.query.router_id as string; - const { vms } = req.body; - - // Add debugging logs - console.log('Received request:'); - console.log('Router ID:', routerId); - console.log('VMs data:', vms); + // src/controllers/RouterController.ts + updateRouterVMs = async (req: Request, res: Response) => { + try { + const routerId = req.query.router_id as string; + const { vms } = req.body; + + // Add debugging logs + console.log('Received request:'); + console.log('Router ID:', routerId); + console.log('VMs data:', vms); - if (!routerId) { - return res.status(400).json({ error: 'router_id is required' }); - } + if (!routerId) { + return res.status(400).json({ error: 'router_id is required' }); + } - if (!Array.isArray(vms)) { - return res.status(400).json({ error: 'VMs must be an array' }); - } + if (!Array.isArray(vms)) { + return res.status(400).json({ error: 'VMs must be an array' }); + } - const updatedVMs = await this.service.updateRouterVMs(routerId, vms); - res.json(updatedVMs); - } catch (err) { - // Type cast the error - const error = err as Error; - - // Enhanced error logging - console.error('Error in updateRouterVMs:', { - message: error?.message || 'Unknown error', - stack: error?.stack, - type: error?.constructor.name - }); + const updatedVMs = await this.service.updateRouterVMs(routerId, vms); + res.json(updatedVMs); + } catch (err) { + // Type cast the error + const error = err as Error; + + // Enhanced error logging + console.error('Error in updateRouterVMs:', { + message: error?.message || 'Unknown error', + stack: error?.stack, + type: error?.constructor.name + }); - res.status(500).json({ - error: 'Failed to update VMs', - 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' }); + res.status(500).json({ + error: 'Failed to update VMs', + 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' }); + } + } + }; getRouterById = async (req: Request, res: Response) => { try { - const id = parseInt(req.params.id); + const id = parseInt(req.params.routerId); const router = await this.service.getRouterById(id); if (!router) { @@ -86,9 +89,54 @@ getAllRouters = async (req: Request, res: Response) => { createRouter = async (req: Request, res: Response) => { try { - const router = await this.service.createRouter(req.body); - res.status(201).json(router); + const routerMetrics = req.body; + 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) { + logger.error('Error creating router:', error); // Log error for debugging res.status(500).json({ error: 'Failed to create router' }); } }; diff --git a/ve-router-backend/src/repositories/DicomStudyRepository.ts b/ve-router-backend/src/repositories/DicomStudyRepository.ts index 6d1c6cb..247f048 100644 --- a/ve-router-backend/src/repositories/DicomStudyRepository.ts +++ b/ve-router-backend/src/repositories/DicomStudyRepository.ts @@ -3,8 +3,12 @@ import { DicomStudy, CreateDicomStudyDTO, UpdateDicomStudyDTO, DBDicomStudy, DicomStudySearchParams } from '../types/dicom'; import pool from '../config/db'; import logger from '../utils/logger'; +import { Pool } from 'mysql2/promise'; +import { RowDataPacket, ResultSetHeader } from 'mysql2'; export class DicomStudyRepository { + constructor(private pool: Pool) {} // Modified constructor + private async getRouterStringId(numericId: number): Promise { try { const [result] = await pool.query( @@ -40,10 +44,9 @@ export class DicomStudyRepository { } private async mapDBStudyToDicomStudy(dbStudy: DBDicomStudy): Promise { - const routerStringId = await this.getRouterStringId(dbStudy.router_id); return { id: dbStudy.id, - router_id: routerStringId, + router_id: dbStudy.router_id.toString(), study_instance_uid: dbStudy.study_instance_uid, patient_id: dbStudy.patient_id, patient_name: dbStudy.patient_name, @@ -63,18 +66,16 @@ export class DicomStudyRepository { async create(studyData: CreateDicomStudyDTO): Promise { try { - // Convert string router_id to numeric id for database - const numericRouterId = await this.getRouterNumericId(studyData.router_id); const [result] = await pool.query( `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, association_id - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + study_status_code, association_id, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`, [ - numericRouterId, + studyData.router_id, studyData.study_instance_uid, studyData.patient_id, studyData.patient_name, @@ -246,4 +247,26 @@ export class DicomStudyRepository { throw new Error('Failed to search DICOM studies'); } } + + async findByStudyInstanceUid(studyInstanceUid: string): Promise { + try { + // Use RowDataPacket[] to align with mysql2/promise type expectations + const [rows] = await pool.query(` + 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'); + } + } + } \ No newline at end of file diff --git a/ve-router-backend/src/repositories/RouterRepository.ts b/ve-router-backend/src/repositories/RouterRepository.ts index daf3a34..0cefefd 100644 --- a/ve-router-backend/src/repositories/RouterRepository.ts +++ b/ve-router-backend/src/repositories/RouterRepository.ts @@ -1,5 +1,5 @@ // 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 { RowDataPacket, ResultSetHeader } from 'mysql2'; import logger from '../utils/logger'; @@ -126,10 +126,32 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise { } } +private async getRouterContainers(routerId: number): Promise { + try { + + // Then use this to query vm_details + const [rows] = await pool.query( + `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 transformDatabaseRouter(dbRouter: any, index: number): Promise { try { const studies = await this.getRouterStudies(dbRouter.id); const vms = await this.getRouterVMs(dbRouter.id); + const containers = await this.getRouterContainers(dbRouter.id); return { id: dbRouter.id, @@ -147,8 +169,9 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise { }, systemStatus: { vpnStatus: dbRouter.vpn_status_code, - appStatus: dbRouter.disk_status_code, - vms + appStatus: dbRouter.app_status_code, + vms, + containers } }; } catch (error) { @@ -184,6 +207,16 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise { return this.transformDatabaseRouter(rows[0], 0); } + async findByRouterId(routerId: string): Promise { + const [rows] = await pool.query( + 'SELECT * FROM routers WHERE router_id = ?', + [routerId] + ); + + if (!rows.length) return null; + return this.transformDatabaseRouter(rows[0], 0); + } + async findByFacility(facility: string): Promise { const [rows] = await pool.query( 'SELECT * FROM routers WHERE facility = ? ORDER BY created_at DESC', @@ -198,18 +231,19 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise { } async create(router: Partial): Promise { - const [result] = await pool.query( + const [result] = await pool.query( `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 (?, ?, ?, NOW(), ?, ?, 'inactive', ?, ?, ?)`, + vpn_status_code, disk_status_code, app_status_code, + license_status, free_disk, total_disk, disk_usage + ) VALUES (?, ?, ?, NOW(), ?, ?, ?, 'inactive', ?, ?, ?)`, [ router.routerId, router.facility, router.routerAlias, - 'unknown', + router.systemStatus?.vpnStatus || 'unknown', router.diskStatus || 'unknown', + router.systemStatus?.appStatus || 'unknown', router.freeDisk, router.totalDisk, router.diskUsage || 0 @@ -226,6 +260,9 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise { if (router.facility) updates.facility = router.facility; if (router.routerAlias) updates.router_alias = router.routerAlias; 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) { const existingRouter = await this.findById(id); @@ -242,7 +279,7 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise { .join(', '); 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] ); } @@ -257,4 +294,37 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise { ); return result.affectedRows > 0; } + + async upsertContainerStatus(routerId: string, containers: Container[]): Promise { + 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(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"); + } + } + } \ No newline at end of file diff --git a/ve-router-backend/src/routes/dicom.routes.ts b/ve-router-backend/src/routes/dicom.routes.ts index e4eb81a..a2bb312 100644 --- a/ve-router-backend/src/routes/dicom.routes.ts +++ b/ve-router-backend/src/routes/dicom.routes.ts @@ -3,9 +3,10 @@ import express from 'express'; import { DicomStudyController } from '../controllers/DicomStudyController'; import logger from '../utils/logger'; +import pool from '../config/db'; // If using default export const router = express.Router(); -const dicomStudyController = new DicomStudyController(); +const dicomStudyController = new DicomStudyController(pool); // Debug logging logger.info('Initializing DICOM routes'); diff --git a/ve-router-backend/src/services/DicomStudyService.ts b/ve-router-backend/src/services/DicomStudyService.ts index 775f654..b5127fc 100644 --- a/ve-router-backend/src/services/DicomStudyService.ts +++ b/ve-router-backend/src/services/DicomStudyService.ts @@ -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 pool from '../config/db'; import logger from '../utils/logger'; +import { Pool } from 'mysql2/promise'; export class DicomStudyService { private repository: DicomStudyRepository; - constructor() { - this.repository = new DicomStudyRepository(); + constructor(pool: Pool) { + this.repository = new DicomStudyRepository(pool); } private async isValidStatusCode(statusCode: string): Promise { @@ -39,21 +40,18 @@ export class DicomStudyService { ]; 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}`); } } + // Commented, currently this field is inserted with active/idle // Validate status code - const isValidStatus = await this.isValidStatusCode(studyData.study_status_code); - 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`); - } - - // Validate date format - if (!this.isValidDate(studyData.study_date)) { - throw new Error('Invalid study date format. Use YYYY-MM-DD'); - } + //const isValidStatus = await this.isValidStatusCode(studyData.study_status_code); + //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`); + //} logger.info('Creating new study', { studyData }); return await this.repository.create(studyData); @@ -151,4 +149,19 @@ export class DicomStudyService { throw new Error('Failed to search studies'); } } + + async processStudies(routerId: number, studies: DicomStudy[]): Promise { + 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); + } + } + } + } \ No newline at end of file diff --git a/ve-router-backend/src/services/RouterService.ts b/ve-router-backend/src/services/RouterService.ts index e2cf976..150f8c1 100644 --- a/ve-router-backend/src/services/RouterService.ts +++ b/ve-router-backend/src/services/RouterService.ts @@ -1,7 +1,8 @@ // src/services/RouterService.ts import { RouterRepository } from '../repositories/RouterRepository'; -import { RouterData,VMUpdate} from '../types'; +import { Container, RouterData,VMUpdate} from '../types'; import { Pool } from 'mysql2/promise'; +import logger from '../utils/logger'; export class RouterService { @@ -30,6 +31,10 @@ async updateRouterVMs(routerId: string, vms: VMUpdate[]): Promise { return this.repository.findById(id); } + async getRouterByRouterId(routerId: string): Promise { + return this.repository.findByRouterId(routerId); + } + async getRoutersByFacility(facility: string): Promise { return this.repository.findByFacility(facility); } @@ -45,5 +50,10 @@ async updateRouterVMs(routerId: string, vms: VMUpdate[]): Promise { async deleteRouter(id: number): Promise { return this.repository.delete(id); } + + async processContainers(routerId: number, containers: Container[]): Promise { + return this.repository.upsertContainerStatus(routerId.toString(), containers); + } + } diff --git a/ve-router-backend/src/services/UtilityService.ts b/ve-router-backend/src/services/UtilityService.ts new file mode 100644 index 0000000..c20ca91 --- /dev/null +++ b/ve-router-backend/src/services/UtilityService.ts @@ -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 + } + } + + } + \ No newline at end of file diff --git a/ve-router-backend/src/services/index.ts b/ve-router-backend/src/services/index.ts index f14bd6e..1a7c373 100644 --- a/ve-router-backend/src/services/index.ts +++ b/ve-router-backend/src/services/index.ts @@ -1,3 +1,4 @@ export * from './RouterService'; export * from './DicomStudyService'; +export * from './UtilityService'; // Add more service exports as needed diff --git a/ve-router-backend/src/types/dicom.ts b/ve-router-backend/src/types/dicom.ts index b15f103..79ce48c 100644 --- a/ve-router-backend/src/types/dicom.ts +++ b/ve-router-backend/src/types/dicom.ts @@ -71,4 +71,19 @@ export interface DicomStudySearchParams { endDate?: string; modality?: 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; } \ No newline at end of file diff --git a/ve-router-backend/src/types/index.ts b/ve-router-backend/src/types/index.ts index 139f3f4..668bfc9 100644 --- a/ve-router-backend/src/types/index.ts +++ b/ve-router-backend/src/types/index.ts @@ -15,19 +15,25 @@ export interface RouterData { }; systemStatus: { vpnStatus: string; // maps to backend 'vpn_status_code' - appStatus: string; // maps to backend 'disk_status_code' + appStatus: string; // maps to backend 'app_status_code' vms: VM[]; + containers: Container[]; }; } export interface Study { - siuid: string; patientId: string; - accessionNumber: string; patientName: string; + siuid: string; + accessionNumber: string; studyDate: string; modality: string; studyDescription: string; + seriesInstanceUid: string; + procedureCode: string; + referringPhysicianName: string; + associationId: string; + studyStatusCode: string; } export interface VM { @@ -43,4 +49,9 @@ export interface VMUpdate { export interface VMUpdateRequest { vms: VMUpdate[]; +} + +export interface Container { + container_name: string; + status_code: string; } \ No newline at end of file