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