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 0000000..402cb80 Binary files /dev/null and b/router-dashboard/public/ve.png differ 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