Compare commits

...

2 Commits

Author SHA1 Message Date
8630a3e2c5 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.
2024-11-22 13:44:03 +05:30
8fe130f918 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.
2024-11-18 10:16:05 +05:30
30 changed files with 656 additions and 426 deletions

6
.env Normal file
View File

@ -0,0 +1,6 @@
VITE_API_URL=http://localhost:3000/api/v1
NODE_ENV=development
#Database Configuration
DB_NAME=ve_router_db

View File

@ -4,35 +4,40 @@ services:
frontend:
build:
context: ./router-dashboard
dockerfile: Dockerfile
dockerfile: dockerfile
ports:
- "5173:5173"
environment:
- VITE_API_URL=http://localhost:3001/api/v1
- VITE_API_URL=${VITE_API_URL}
restart: always
depends_on:
- backend
volumes:
- ./router-dashboard:/app
- /app/node_modules
backend:
condition: service_healthy
container_name: router_dashboard_frontend
backend:
build:
context: ./ve-router-backend
dockerfile: Dockerfile
dockerfile: dockerfile
ports:
- "3001:3000"
- "3000:3000"
environment:
- NODE_ENV=development
- NODE_ENV=${NODE_ENV}
- DB_HOST=host.docker.internal
- DB_PORT=3307
- DB_PORT=3306
- DB_USER=ve_router_user
- DB_PASSWORD=ve_router_password
- DB_NAME=ve_router_db
- DB_NAME=${DB_NAME}
restart: always
depends_on:
- mysql
volumes:
- ./ve-router-backend:/app
- /app/node_modules
mysql:
condition: service_healthy
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "3000"] # Netcat check to see if port 3000 is open
interval: 30s # Check every 30 seconds
retries: 3 # Retry 3 times before marking unhealthy
start_period: 30s # Wait 30 seconds before starting health checks
timeout: 10s # Wait for 10 seconds for each health check to respond
mysql:
image: mysql:8.0
@ -44,11 +49,11 @@ services:
volumes:
- mysql_data:/var/lib/mysql
# Correct paths for init scripts
- ./router-dashboard/sql/init.sql:/docker-entrypoint-initdb.d/01-init.sql
- ./router-dashboard/sql/seed_data.sql:/docker-entrypoint-initdb.d/02-seed_data.sql
- ./sql:/docker-entrypoint-initdb.d
ports:
- "3307:3306"
- "3306:3306"
command: --default-authentication-plugin=mysql_native_password
restart: always
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "ve_router_user", "-pve_router_password"]
interval: 10s
@ -57,4 +62,4 @@ services:
start_period: 30s
volumes:
mysql_data:
mysql_data:

18
readme.txt Normal file
View File

@ -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

View File

@ -1,2 +1,2 @@
VITE_API_URL=http://localhost:3001/api/v1
VITE_API_URL=http://localhost:3000/api/v1
VITE_NODE_ENV=development

View File

@ -8,7 +8,7 @@ RUN npm install
COPY . .
ENV VITE_API_URL=http://localhost:3001/api/v1
ENV VITE_API_URL=http://localhost:3000/api/v1
EXPOSE 5173

View File

@ -2,9 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<link rel="icon" type="image/png" href="ve.png" >
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<!-- <title>Vite + React + TS </title> -->
<title>Router Management</title>
</head>
<body>
<div id="root"></div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1 +1,99 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 225.9 45.8" style="enable-background:new 0 0 225.9 45.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#231F52;}
.st1{fill:#05887A;}
.st2{fill:#87C35F;}
.st3{fill:#68C7D7;}
.st4{fill:#24767B;}
.st5{fill:#2377B5;}
.st6{fill:#069666;}
.st7{fill:#60B046;}
.st8{fill:#ECE843;}
.st9{fill:#D8DF25;}
.st10{fill:#204780;}
.st11{fill:#81BC42;}
.st12{fill:#244087;}
.st13{fill:#224480;}
.st14{fill:#148745;}
.st15{fill:#298DC9;}
.st16{fill:#17464A;}
.st17{fill:#2B67A8;}
.st18{fill:#25AC6D;}
.st19{fill:#276780;}
.st20{fill:#2783C1;}
.st21{fill:#2894CE;}
</style>
<g id="VitalEngine_Horizontal_4c" transform="translate(-135.213 -220.46)">
<g id="Group_3" transform="translate(188.083 239.605)">
<g id="Group_2" transform="translate(67.686)">
<g id="Group_1">
<path id="Path_1" class="st0" d="M14.4,22H0V0.9h14.4v2.9h-11v5.8h9.1v2.9H3.4v6.6h11V22z"/>
<path id="Path_2" class="st0" d="M18.2,22V5.7h3.1v2c2-1.7,2.7-2,4.1-2h2.6c1-0.1,2,0.2,2.8,0.9c0.6,0.6,1,1.4,1,3.7V22h-3.1
V10.5c0.1-0.6-0.1-1.1-0.4-1.6c-0.3-0.3-0.6-0.5-1.7-0.5h-2.1c-1.1,0-2.1,0.4-3,0.9V22L18.2,22L18.2,22z"/>
<path id="Path_3" class="st0" d="M39.8,26.5c-1,0.1-1.9-0.2-2.7-0.8c-0.6-0.6-0.9-1.4-0.9-3.2h3.1c0,0.7,0.1,1,0.3,1.2
c0.2,0.2,0.4,0.2,1.2,0.2h3.9c0.9,0,1.2-0.1,1.4-0.3c0.2-0.2,0.3-0.5,0.3-2v-3.4c-2.1,1.7-2.7,2-4.1,2h-2.2
c-1.7,0-2.6-0.3-3.3-1c-0.9-0.9-1.3-1.8-1.3-6.3s0.4-5.4,1.3-6.3c0.7-0.7,1.6-1,3.3-1h2.2c1.3,0,2,0.3,4.1,2v-2h3.1v15.5
c0,2.9-0.3,3.7-1,4.4c-0.5,0.5-1.3,0.9-3.2,0.9L39.8,26.5L39.8,26.5z M46.6,16.5V9.4c-1-0.6-2-0.9-3.2-1h-2.3
c-1,0-1.4,0.1-1.7,0.4C39,9.3,38.9,9.7,38.9,13s0.1,3.6,0.6,4.1c0.3,0.3,0.7,0.4,1.7,0.4h2.4C44.7,17.4,45.7,17.1,46.6,16.5
L46.6,16.5z"/>
<path id="Path_4" class="st0" d="M57.7,3.4h-3.6V0h3.6V3.4z M57.5,22h-3.1V5.7h3.1V22z"/>
<path id="Path_5" class="st0" d="M62.1,22V5.7h3.1v2c2-1.7,2.7-2,4.1-2h2.6c1-0.1,2,0.2,2.8,0.9c0.6,0.6,1,1.4,1,3.7V22h-3.1
V10.5c0.1-0.6-0.1-1.1-0.4-1.6c-0.3-0.3-0.6-0.5-1.7-0.5h-2.1c-1.1,0-2.1,0.4-3,0.9V22L62.1,22L62.1,22z"/>
<path id="Path_6" class="st0" d="M82.7,14.9c0,3.2,0.2,3.8,0.5,4.1s0.5,0.3,1.4,0.3h4.2c0.7,0,0.9-0.1,1.1-0.3
c0.2-0.2,0.3-0.6,0.3-2h3c-0.1,2.5-0.3,3.4-1.1,4.2c-0.7,0.6-1.7,0.9-2.7,0.8h-5.3c-1.2,0.1-2.3-0.2-3.2-0.9
c-1.1-1.1-1.4-2.3-1.4-7.2s0.3-6.1,1.4-7.2c0.7-0.7,1.6-0.9,3.2-0.9h4.5c1.2-0.1,2.3,0.2,3.2,0.9c1.1,1.1,1.4,2.3,1.4,7.1v0.8
c0,0.2-0.1,0.4-0.4,0.4L82.7,14.9L82.7,14.9z M82.7,12.4h7.4c0-2.7-0.2-3.3-0.5-3.6c-0.2-0.2-0.5-0.3-1.4-0.3h-3.7
c-0.9,0-1.2,0.1-1.4,0.3C82.9,9.1,82.8,9.6,82.7,12.4z"/>
</g>
</g>
<path id="Path_7" class="st0" d="M20.8,3.4h3.6V0h-3.6V3.4z M21,22h3.1V5.7H21L21,22z M9.4,18.7H9L3.6,0.9H0l6.4,20.4
c0.2,0.7,0.5,0.8,1.2,0.8h2.9c0.8,0,1-0.1,1.2-0.8l6.4-20.4h-3.4L9.4,18.7z M49.1,5.7H44c-1-0.1-2,0.2-2.7,0.9
c-0.6,0.6-0.9,1.4-1,4h3.1c0.1-1.2,0.1-1.8,0.4-2c0.2-0.2,0.5-0.3,1.3-0.3h3.2c0.9,0,1.2,0.1,1.4,0.3c0.2,0.2,0.4,0.7,0.4,2.6v1.8
c-1.3-0.3-2.7-0.5-4-0.5c-3.9,0-4.7,0.5-5.4,1.2c-0.6,0.6-1,1.9-1,3.8c0,2,0.4,3,1,3.6c0.7,0.7,1.4,0.8,2.9,0.8h2.5
c1.4,0,2-0.4,4-2v2h3.1V11c0-2.9-0.2-3.7-0.9-4.4C51.7,6.1,50.9,5.7,49.1,5.7z M50.1,18.3c-1,0.7-2.2,1-3.4,1h-2.3
c-0.7,0-1.1,0-1.3-0.2c-0.2-0.2-0.3-0.8-0.3-2c0-1.1,0.1-1.4,0.4-1.7c0.3-0.3,0.7-0.4,2.7-0.4h4.2L50.1,18.3L50.1,18.3z M33,1.7
h-3.2v4h-2.8v2.7h2.8v10.4c-0.1,0.9,0.1,1.8,0.7,2.5c0.7,0.6,1.6,0.9,2.4,0.8c0.7,0,1.4-0.1,2-0.2l1.8-0.5v-2.1h-2.5
c-0.8,0-1.1,0-1.2-0.1C33.1,18.9,33,18.7,33,18V8.5h3.9V5.7H33L33,1.7z M62.2,19.2c-0.8,0-1.1,0-1.2-0.1c-0.1-0.1-0.2-0.4-0.2-1.1
V8.5l0,0V0.3h-3.1v8.2h0v10.4c-0.1,0.9,0.1,1.8,0.7,2.5c0.7,0.6,1.6,0.9,2.4,0.8c0.7,0,1.4-0.1,2-0.2l1.8-0.5v-2.1H62.2L62.2,19.2
z"/>
</g>
<g id="Group_4" transform="translate(135.213 220.46)">
<path id="Path_8" class="st1" d="M29.6,25.9l-4.1,7.6L17,18.4h8.4L29.6,25.9z"/>
<path id="Path_9" class="st2" d="M25.4,18.4l5-9.2h10.1l-4.2,7.6c0,0-1,0-1.5,0c-0.2,0-0.4,0.1-0.4,0.2c-0.2,0.4-0.8,1.3-0.8,1.3
L25.4,18.4z"/>
<path id="Path_10" class="st3" d="M11.9,9.3l-1.8-3.2L9.2,7.5L5.1,0l9.8,0c0.2,0,0.4,0.1,0.5,0.3c1.3,2.3,5,8.9,5,8.9L11.9,9.3z"
/>
<path id="Path_11" class="st4" d="M23.6,36.6l1.8,3.2l0.8-1.4l4.2,7.4c0,0,0,0-0.2,0c-3.2,0-6.4,0-9.7,0c-0.1,0-0.3-0.1-0.3-0.2
l-5-9L23.6,36.6z"/>
<path id="Path_12" class="st5" d="M5.1,0l4.2,7.5l-1,1.8c0,0-7.7,0-8.3,0c0-0.1,0-0.1,0-0.2c0.3-0.5,4.1-7.5,4.9-8.9
C4.9,0.2,4.9,0.1,5.1,0C5,0,5,0,5.1,0z"/>
<path id="Path_13" class="st6" d="M40.6,27.5l-5.1,9.1h-8.3l5-9.1L40.6,27.5z"/>
<path id="Path_14" class="st7" d="M30.4,9.2c0,0,4.4-8,4.8-8.8c0.1-0.1,0.2-0.2,0.3-0.3l5.1,9.1L30.4,9.2z"/>
<path id="Path_15" class="st8" d="M40.6,9.2c0,0,5-9,5.1-9.1c1.5,2.8,5,9.1,5,9.1c0,0,0,0,0,0C50.6,9.2,43.9,9.2,40.6,9.2z"/>
<path id="Path_16" class="st9" d="M45.7,0.1l-5.1,9.1l-5.1-9.1c0,0,0-0.1,0.3-0.1c3.2,0,6.3,0,9.5,0C45.5,0,45.6,0,45.7,0.1z"/>
<path id="Path_17" class="st10" d="M27.2,36.7h8.3c0,0-4.6,8.4-5,9c0,0-0.1,0.1-0.1,0.1l-4.2-7.4L27.2,36.7z"/>
<path id="Path_18" class="st11" d="M50.7,9.2c0,0-2.8,5.2-4,7.4c-0.1,0.2-0.2,0.3-0.4,0.2c-0.5,0-1.5,0-1.5,0l-4.2-7.6L50.7,9.2z"
/>
<path id="Path_19" class="st12" d="M19.5,29.2l-4.2,7.4l-5.1-9.1l8.3,0L19.5,29.2z"/>
<path id="Path_20" class="st13" d="M25.4,18.4H17l-0.9-1.6l4.2-7.6L25.4,18.4z"/>
<path id="Path_21" class="st14" d="M40.6,27.5L36.4,20c0.1-0.1,0.2-0.1,0.3-0.1h8.1L40.6,27.5z"/>
<path id="Path_22" class="st7" d="M44.8,16.8h-8.4l4.2-7.6L44.8,16.8z"/>
<path id="Path_23" class="st15" d="M16.1,16.8l-4.2-7.5l8.4-0.1L16.1,16.8z"/>
<path id="Path_24" class="st16" d="M36.4,20l4.2,7.5l-8.4,0c0,0,2.9-5.3,4-7.3C36.3,20.1,36.3,20.1,36.4,20z"/>
<path id="Path_25" class="st17" d="M14.4,20c0.2,0.4,4.2,7.5,4.2,7.5l-8.3,0C10.2,27.5,14.3,20.1,14.4,20z"/>
<path id="Path_26" class="st18" d="M25.4,18.4l8.3,0l-4.1,7.5L25.4,18.4z"/>
<path id="Path_27" class="st19" d="M19.5,29.2l4.1,7.4l-8.3,0L19.5,29.2z"/>
<path id="Path_28" class="st20" d="M6,20l4.2,7.5l4.2-7.5L6,20z"/>
<path id="Path_29" class="st21" d="M0,9.2l8.3,0l-4.1,7.5L0,9.2z"/>
<path id="Path_30" class="st3" d="M14.4,20L6,20l4.1-7.6L14.4,20z"/>
</g>
</g>
<g>
<path class="st0" d="M218.2,16.4h-2v5.5h-1.4v-5.5h-2v-1.1h5.4V16.4z"/>
<path class="st0" d="M220.7,15.3l1.7,4.8l1.7-4.8h1.8v6.6h-1.4v-1.8l0.1-3.1l-1.8,4.9h-0.9l-1.8-4.9l0.1,3.1v1.8h-1.4v-6.6H220.7z"
/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -40,7 +40,7 @@ const Dashboard = () => {
router.diskStatus === 'DISK_CRITICAL'
).length,
diskWarnings: data.filter(router =>
router.diskUsage >= 80
router.diskUsage >= 70
).length
};

View File

@ -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<RouterTableRowProps> = ({
/>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<h4 className="font-semibold mb-2">VM Status</h4>
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(getVMStatus(router.lastSeen))}`}>
{formatStatus(getVMStatus(router.lastSeen))}
</span>
</div>
<div>
<h4 className="font-semibold mb-2">VPN Status</h4>
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.vpnStatus)}`}>
@ -96,22 +102,22 @@ export const RouterTableRow: React.FC<RouterTableRowProps> = ({
</div>
<div>
<h4 className="font-semibold mb-2">App Status</h4>
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.diskStatus)}`}>
{formatStatus(router.diskStatus)}
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(router.systemStatus.appStatus)}`}>
{formatStatus(router.systemStatus.appStatus)}
</span>
</div>
<div>
<h4 className="font-semibold mb-2">VM Status</h4>
{router.systemStatus.vms.length > 0 ? (
router.systemStatus.vms.map((vm, idx) => (
<h4 className="font-semibold mb-2">Container Status</h4>
{router.systemStatus.containers.length > 0 ? (
router.systemStatus.containers.map((container, idx) => (
<div key={idx} className="mb-1">
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(vm.status)}`}>
VM {vm.id}: {formatStatus(vm.status)}
<span className={`px-2 py-1 rounded-full text-sm ${getStatusColor(container.status)}`}>
{container.container_name}: {formatStatus(container.status)}
</span>
</div>
))
) : (
<span className="text-gray-500">No VMs configured</span>
<span className="text-gray-500">No Containers configured</span>
)}
</div>
</div>

View File

@ -5,7 +5,7 @@ interface Config {
}
const config: Config = {
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3001/api/v1',
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
environment: import.meta.env.VITE_NODE_ENV || 'development',
};

View File

@ -1,7 +1,7 @@
// router-dashboard/src/services/api.service.ts
import { RouterData, FilterType, BackendRouter } from '../types';
const API_BASE_URL = 'http://localhost:3001/api/v1';
const API_BASE_URL = 'http://localhost:3000/api/v1';
// Default request options for all API calls
const DEFAULT_OPTIONS = {
@ -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
}))
: []
}
};

View File

@ -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[];
};
}

View File

@ -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<StatusType | string, string> = {
'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<StatusType | string, string> = {
'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';
};

View File

@ -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
@ -118,7 +101,7 @@ CREATE TABLE IF NOT EXISTS status_type (
category_id VARCHAR(50),
name VARCHAR(100),
code VARCHAR(100),
description VARCHAR(20),
description VARCHAR(150),
severity INT
);

105
sql/02-seed_data.sql Normal file
View File

@ -0,0 +1,105 @@
DELIMITER //
CREATE PROCEDURE seed_complete_router_system()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE table_name VARCHAR(64);
DECLARE table_cursor CURSOR FOR
SELECT table_name
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name IN (
'auth_log', 'user_sessions', 'user_router_access', 'users',
'container_status_history', 'router_status_history',
'container_status', 'dicom_study_overview',
'router_settings_history', 'router_settings',
'routers', 'status_type', 'status_category'
);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- Disable foreign key checks
SET FOREIGN_KEY_CHECKS=0;
-- Truncate all tables dynamically
OPEN table_cursor;
truncate_loop: LOOP
FETCH table_cursor INTO table_name;
IF done THEN
LEAVE truncate_loop;
END IF;
SET @query = CONCAT('TRUNCATE TABLE ', table_name);
PREPARE stmt FROM @query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE table_cursor;
-- Re-enable foreign key checks
SET FOREIGN_KEY_CHECKS=1;
-- Insert Status Categories
INSERT INTO status_category (name, description)
VALUES
('Network', 'Network related statuses'),
('Disk', 'Disk related statuses'),
('VPN', 'VPN connection statuses'),
('License', 'License statuses'),
('Container', 'Container related statuses')
ON DUPLICATE KEY UPDATE id = id;
-- Insert Status Types
INSERT INTO status_type (category_id, name, code, description, severity)
VALUES
(1, 'Online', 'NET_ONLINE', 'System is online', 1),
(1, 'Offline', 'NET_OFFLINE', 'System is offline', 5),
(2, 'Normal', 'DISK_NORMAL', 'Disk usage is normal', 1),
(2, 'Warning', 'DISK_WARNING', 'Disk usage is high', 3),
(2, 'Critical', 'DISK_CRITICAL', 'Disk usage is critical', 5),
(3, 'Connected', 'VPN_CONNECTED', 'VPN is connected', 1),
(3, 'Disconnected', 'VPN_DISCONNECTED', 'VPN is disconnected', 5),
(5, 'Running', 'CONTAINER_RUNNING', 'Container is running', 1),
(5, 'Stopped', 'CONTAINER_STOPPED', 'Container is stopped', 5)
ON DUPLICATE KEY UPDATE id = id;
-- Insert Routers
INSERT INTO routers (router_id, facility, router_alias, last_seen, vpn_status_code, disk_status_code, app_status_code, license_status, free_disk, total_disk, disk_usage)
VALUES
('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
SET @router1_id = (SELECT id FROM routers WHERE router_id = 'RTR001');
SET @router2_id = (SELECT id FROM routers WHERE router_id = 'RTR002');
SET @router3_id = (SELECT id FROM routers WHERE router_id = 'RTR003');
-- Insert Container Status
INSERT INTO container_status (router_id, container_name, status_code, created_at, updated_at)
VALUES
(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
INSERT INTO dicom_study_overview (
router_id, study_instance_uid, patient_id, patient_name,
accession_number, study_date, modality, study_description,
series_instance_uid, procedure_code, referring_physician_name
)
VALUES
(@router1_id, '1.2.840.113619.2.55.3.283116435.276.1543707218.134', 'P1', 'John Doe', 'ACC1234', '2024-03-15', 'CT', 'Chest CT', '1.2.840.113619.2.55.3.283116435.276.1543707219.135', 'CT001', 'Dr. Smith'),
(@router2_id, '1.2.840.113619.2.55.3.283116435.276.1543707218.136', 'P2', 'Jane Doe', 'ACC1235', '2024-03-15', 'MR', 'Brain MRI', '1.2.840.113619.2.55.3.283116435.276.1543707219.137', 'MR001', 'Dr. Johnson')
ON DUPLICATE KEY UPDATE id = id;
END //
DELIMITER ;
-- Automatically call the procedure after creation
CALL seed_complete_router_system();

View File

@ -1,267 +0,0 @@
DELIMITER //
CREATE PROCEDURE seed_complete_router_system()
BEGIN
-- Disable foreign key checks and start fresh
SET FOREIGN_KEY_CHECKS=0;
-- Conditionally clear existing data, only if the table exists
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'auth_log') THEN
TRUNCATE TABLE auth_log;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_sessions') THEN
TRUNCATE TABLE user_sessions;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_router_access') THEN
TRUNCATE TABLE user_router_access;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'users') THEN
TRUNCATE TABLE users;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'container_status_history') THEN
TRUNCATE TABLE container_status_history;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'router_status_history') THEN
TRUNCATE TABLE router_status_history;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'container_status') THEN
TRUNCATE TABLE container_status;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'vm_details') THEN
TRUNCATE TABLE vm_details;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'dicom_study_overview') THEN
TRUNCATE TABLE dicom_study_overview;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'system_status') THEN
TRUNCATE TABLE system_status;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'router_settings_history') THEN
TRUNCATE TABLE router_settings_history;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'router_settings') THEN
TRUNCATE TABLE router_settings;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'routers') THEN
TRUNCATE TABLE routers;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'status_type') THEN
TRUNCATE TABLE status_type;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'status_category') THEN
TRUNCATE TABLE status_category;
END IF;
-- Re-enable foreign key checks
SET FOREIGN_KEY_CHECKS=1;
-- Insert status categories
INSERT INTO status_category (name, description)
VALUES
('Network', 'Network related statuses'),
('Disk', 'Disk related statuses'),
('VPN', 'VPN connection statuses'),
('License', 'License statuses'),
('Container', 'Container related statuses')
ON DUPLICATE KEY UPDATE id = id;
-- Insert status types
INSERT INTO status_type (category_id, name, code, description, severity)
VALUES
(1, 'Online', 'NET_ONLINE', 'System is online', 1),
(1, 'Offline', 'NET_OFFLINE', 'System is offline', 5),
(2, 'Normal', 'DISK_NORMAL', 'Disk usage is normal', 1),
(2, 'Warning', 'DISK_WARNING', 'Disk usage is high', 3),
(2, 'Critical', 'DISK_CRITICAL', 'Disk usage is critical', 5),
(3, 'Connected', 'VPN_CONNECTED', 'VPN is connected', 1),
(3, 'Disconnected', 'VPN_DISCONNECTED', 'VPN is disconnected', 5),
(5, 'Running', 'CONTAINER_RUNNING', 'Container is running', 1),
(5, 'Stopped', 'CONTAINER_STOPPED', 'Container is stopped', 5)
ON DUPLICATE KEY UPDATE id = id;
-- Insert routers
INSERT INTO routers (router_id, facility, router_alias, last_seen, vpn_status_code, disk_status_code, license_status, free_disk, total_disk, disk_usage)
VALUES
('RTR001', 'Main Hospital', 'MAIN_RAD', NOW(), 'VPN_CONNECTED', 'DISK_NORMAL', 'active', 500000000000, 1000000000000, 50.00),
('RTR002', 'Emergency Center', 'ER_RAD', NOW(), 'VPN_CONNECTED', 'DISK_WARNING', 'active', 400000000000, 1000000000000, 60.00),
('RTR003', 'Imaging Center', 'IMG_CENTER', NOW(), 'VPN_CONNECTED', 'DISK_NORMAL', 'active', 600000000000, 1000000000000, 40.00)
ON DUPLICATE KEY UPDATE id = id;
-- Store router IDs for later use
SET @router1_id = (SELECT id FROM routers WHERE router_id = 'RTR001');
SET @router2_id = (SELECT id FROM routers WHERE router_id = 'RTR002');
SET @router3_id = (SELECT id FROM routers WHERE router_id = 'RTR003');
-- Insert system status
INSERT INTO system_status (router_id)
VALUES
(@router1_id),
(@router2_id),
(@router3_id)
ON DUPLICATE KEY UPDATE id = id;
-- Insert container status
INSERT INTO container_status (system_status_id, container_number, status_code)
VALUES
(1, 1, 'CONTAINER_RUNNING'),
(1, 2, 'CONTAINER_RUNNING'),
(2, 1, 'CONTAINER_RUNNING'),
(2, 2, 'CONTAINER_STOPPED'),
(3, 1, 'CONTAINER_RUNNING')
ON DUPLICATE KEY UPDATE id = id;
-- Insert VM details
INSERT INTO vm_details (router_id, vm_number, status_code)
VALUES
(@router1_id, 1, 'NET_ONLINE'),
(@router2_id, 1, 'NET_ONLINE'),
(@router3_id, 1, 'NET_ONLINE')
ON DUPLICATE KEY UPDATE id = id;
-- Insert DICOM studies
INSERT INTO dicom_study_overview (
router_id,
study_instance_uid,
patient_id,
patient_name,
accession_number,
study_date,
modality,
study_description,
series_instance_uid,
procedure_code,
referring_physician_name
)
VALUES
(@router1_id, '1.2.840.113619.2.55.3.283116435.276.1543707218.134', 'P1', 'John Doe', 'ACC1234', '2024-03-15', 'CT', 'Chest CT', '1.2.840.113619.2.55.3.283116435.276.1543707219.135', 'CT001', 'Dr. Smith'),
(@router2_id, '1.2.840.113619.2.55.3.283116435.276.1543707218.136', 'P2', 'Jane Doe', 'ACC1235', '2024-03-15', 'MR', 'Brain MRI', '1.2.840.113619.2.55.3.283116435.276.1543707219.137', 'MR001', 'Dr. Johnson')
ON DUPLICATE KEY UPDATE id = id;
-- Insert router settings for each router (calls to upsert_router_settings are disabled)
-- Main Hospital Router
-- CALL upsert_router_settings(
-- @router1_id,
-- 'client',
-- '{
-- "dicom": {
-- "local": {
-- "aet": "MAIN_RAD",
-- "port": 104,
-- "file_directory": "/dicom_images",
-- "wait_time": 2,
-- "receiver_wait_time": 5000
-- },
-- "association": {
-- "acse_timeout": 5,
-- "dimse_timeout": 1000,
-- "network_timeout": 1000,
-- "retry": {
-- "attempts": 3,
-- "interval": 10
-- }
-- }
-- },
-- "rabbitmq": {
-- "local": {
-- "hostname": "router-rabbitmq",
-- "port": 5672,
-- "credentials": {
-- "username": "vitalengine",
-- "password": "vitalengine"
-- },
-- "settings": {
-- "durable": true,
-- "auto_delete": false,
-- "exchange_type": "direct",
-- "heartbeat": 50
-- }
-- }
-- },
-- "scp_connections": {
-- "pacs_nodes": [
-- {
-- "host": "pacsmain.example.com",
-- "port": 104
-- },
-- {
-- "host": "pacsbackup.example.com",
-- "port": 104
-- }
-- ]
-- }
-- }',
-- 'system',
-- 'Initial client configuration for Main Hospital'
-- );
-- Emergency Center Router
-- CALL upsert_router_settings(
-- @router2_id,
-- 'client',
-- '{
-- "dicom": {
-- "local": {
-- "aet": "ER_RAD",
-- "port": 104,
-- "file_directory": "/dicom_images",
-- "wait_time": 2,
-- "receiver_wait_time": 5000
-- },
-- "association": {
-- "acse_timeout": 5,
-- "dimse_timeout": 1000,
-- "network_timeout": 1000,
-- "retry": {
-- "attempts": 3,
-- "interval": 10
-- }
-- }
-- },
-- "rabbitmq": {
-- "local": {
-- "hostname": "router-rabbitmq",
-- "port": 5672,
-- "credentials": {
-- "username": "vitalengine",
-- "password": "vitalengine"
-- },
-- "settings": {
-- "durable": true,
-- "auto_delete": false,
-- "exchange_type": "direct",
-- "heartbeat": 50
-- }
-- }
-- },
-- "scp_connections": {
-- "pacs_nodes": [
-- {
-- "host": "pacsemergency.example.com",
-- "port": 104
-- }
-- ]
-- }
-- }',
-- 'system',
-- 'Initial client configuration for Emergency Center'
-- );
-- Insert settings for other routers as needed...
END //
DELIMITER ;

View File

@ -7,7 +7,7 @@ CORS_ORIGIN=http://localhost:5173,http://localhost:3000
# Database Configuration
DB_HOST=localhost
DB_PORT=3307
DB_PORT=3306
DB_USER=root
DB_PASSWORD=rootpassword
DB_NAME=ve_router_db

View File

@ -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 {

View File

@ -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' });
}
};

View File

@ -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<string> {
try {
const [result] = await pool.query(
@ -40,10 +44,9 @@ export class DicomStudyRepository {
}
private async mapDBStudyToDicomStudy(dbStudy: DBDicomStudy): Promise<DicomStudy> {
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<DicomStudy> {
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<DicomStudy | null> {
try {
// Use RowDataPacket[] to align with mysql2/promise type expectations
const [rows] = await pool.query<DBDicomStudy[] & RowDataPacket[]>(`
SELECT * FROM dicom_study_overview WHERE study_instance_uid = ?`,
[studyInstanceUid]
);
// Check if rows is empty
if (rows.length === 0) {
return null;
}
// Map the first result to your DicomStudy object
return await this.mapDBStudyToDicomStudy(rows[0]);
} catch (error) {
logger.error('Error fetching DICOM study by Study Instance UID:', error);
throw new Error('Failed to fetch DICOM study');
}
}
}

View File

@ -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<any> {
}
}
private async getRouterContainers(routerId: number): Promise<Container[]> {
try {
// Then use this to query vm_details
const [rows] = await pool.query<RowDataPacket[]>(
`SELECT
container_name,
status_code
FROM container_status
WHERE router_id = ?`,
[routerId]
);
logger.info(`Containers for router ${routerId}:`, rows);
return rows as Container[];
} catch (error) {
logger.error(`Error fetching Containers for router ${routerId}:`, error);
return [];
}
}
private async transformDatabaseRouter(dbRouter: any, index: number): Promise<RouterData> {
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<any> {
},
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<any> {
return this.transformDatabaseRouter(rows[0], 0);
}
async findByRouterId(routerId: string): Promise<RouterData | null> {
const [rows] = await pool.query<RowDataPacket[]>(
'SELECT * FROM routers WHERE router_id = ?',
[routerId]
);
if (!rows.length) return null;
return this.transformDatabaseRouter(rows[0], 0);
}
async findByFacility(facility: string): Promise<RouterData[]> {
const [rows] = await pool.query<RowDataPacket[]>(
'SELECT * FROM routers WHERE facility = ? ORDER BY created_at DESC',
@ -198,18 +231,19 @@ async updateVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
}
async create(router: Partial<RouterData>): Promise<RouterData> {
const [result] = await pool.query<ResultSetHeader>(
const [result] = await pool.query<ResultSetHeader>(
`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<any> {
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<any> {
.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<any> {
);
return result.affectedRows > 0;
}
async upsertContainerStatus(routerId: string, containers: Container[]): Promise<ResultSetHeader> {
const values = containers.map((container) => [
routerId,
container.container_name,
container.status_code,
new Date(), // created_at
new Date(), // updated_at
]);
const query = `
INSERT INTO container_status (router_id, container_name, status_code, created_at, updated_at)
VALUES ${values.map(() => "(?, ?, ?, ?, ?)").join(", ")}
ON DUPLICATE KEY UPDATE
status_code = VALUES(status_code),
updated_at = VALUES(updated_at);
`;
// Flatten the values array manually (compatible with older TypeScript versions)
const flattenedValues = values.reduce((acc, val) => acc.concat(val), []);
try {
const [result] = await pool.query<ResultSetHeader>(query, flattenedValues);
return result;
} catch (error) {
logger.error("Error inserting or updating container status:");
logger.error(`Query: ${query}`);
logger.error(`Flattened Values: ${JSON.stringify(flattenedValues)}`);
logger.error("Error Details:", error);
throw new Error("Error inserting or updating container status");
}
}
}

View File

@ -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');

View File

@ -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<boolean> {
@ -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<void> {
for (const study of studies) {
const existingStudy = await this.repository.findByStudyInstanceUid(study.study_instance_uid);
if (existingStudy) {
study.router_id = existingStudy.router_id;
await this.updateStudy(existingStudy.id, study);
} else {
study.router_id = routerId.toString();
logger.info(`Inserting study for router: ${routerId}`);
await this.createStudy(study);
}
}
}
}

View File

@ -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<any> {
return this.repository.findById(id);
}
async getRouterByRouterId(routerId: string): Promise<RouterData | null> {
return this.repository.findByRouterId(routerId);
}
async getRoutersByFacility(facility: string): Promise<RouterData[]> {
return this.repository.findByFacility(facility);
}
@ -45,5 +50,10 @@ async updateRouterVMs(routerId: string, vms: VMUpdate[]): Promise<any> {
async deleteRouter(id: number): Promise<boolean> {
return this.repository.delete(id);
}
async processContainers(routerId: number, containers: Container[]): Promise<any> {
return this.repository.upsertContainerStatus(routerId.toString(), containers);
}
}

View File

@ -0,0 +1,17 @@
import logger from '../utils/logger';
export class UtilityService {
// Get disk status based on disk usage (handles float values like 10.25)
getDiskStatus(diskUsage: number): string {
if (diskUsage >= 90) {
return 'DISK_CRITICAL'; // Critical disk status
} else if (diskUsage >= 70) {
return 'DISK_WARNING'; // Warning disk status
} else {
return 'DISK_NORMAL'; // Normal disk status
}
}
}

View File

@ -1,3 +1,4 @@
export * from './RouterService';
export * from './DicomStudyService';
export * from './UtilityService';
// Add more service exports as needed

View File

@ -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;
}

View File

@ -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;
}