diff --git a/.gitignore b/.gitignore index 63e3afe..13e92a9 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ temp/ # Node.js package-lock.json **/package-lock.json + +# Built static files (generated during Docker build from Angular) +static/ diff --git a/README.md b/README.md index b894398..2c37805 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,6 @@ A lightweight, cloud-native API for storing and querying test artifacts includin .\quickstart.ps1 ``` -**Windows (Command Prompt):** -```batch -quickstart.bat -``` - ### Air-Gapped/Restricted Environment Deployment **For environments with restricted npm access:** @@ -65,7 +60,7 @@ This script: 2. Packages pre-built files into Docker 3. Starts all services -See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed instructions. +See [DEPLOYMENT.md](docs/DEPLOYMENT.md) for detailed instructions. ### Manual Setup with Docker Compose diff --git a/app/main.py b/app/main.py index e6cd10a..a6c252d 100644 --- a/app/main.py +++ b/app/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI +from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse @@ -39,10 +39,8 @@ app.add_middleware( app.include_router(artifacts_router) app.include_router(seed_router) -# Mount static files +# Static directory setup static_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static") -if os.path.exists(static_dir): - app.mount("/static", StaticFiles(directory=static_dir), name="static") @app.on_event("startup") @@ -87,15 +85,13 @@ async def ui_root(): # Catch-all route for Angular SPA routing - must be last @app.get("/{full_path:path}") async def serve_spa(full_path: str): - """Serve Angular SPA for all non-API routes""" - # If it's an API route, it should have been handled by routers above - # If it's a static file request, try to serve it - if full_path.startswith("static/"): - file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), full_path) - if os.path.exists(file_path): - return FileResponse(file_path) + """Serve Angular SPA static files and handle client-side routing""" + # Try to serve static file first (JS, CSS, images, etc.) + file_path = os.path.join(static_dir, full_path) + if os.path.exists(file_path) and os.path.isfile(file_path): + return FileResponse(file_path) - # For all other routes (Angular routes), serve index.html + # For all other routes (Angular client-side routes), serve index.html index_path = os.path.join(static_dir, "index.html") if os.path.exists(index_path): return FileResponse(index_path) diff --git a/docker-compose.yml b/docker-compose.yml index d9bdac4..0fdcc5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,8 @@ services: timeout: 5s retries: 5 - api: + app: + container_name: warehouse13-app build: . ports: - "8000:8000" diff --git a/API.md b/docs/API.md similarity index 100% rename from API.md rename to docs/API.md diff --git a/ARCHITECTURE.md b/docs/ARCHITECTURE.md similarity index 100% rename from ARCHITECTURE.md rename to docs/ARCHITECTURE.md diff --git a/DEPLOYMENT.md b/docs/DEPLOYMENT.md similarity index 100% rename from DEPLOYMENT.md rename to docs/DEPLOYMENT.md diff --git a/FEATURES.md b/docs/FEATURES.md similarity index 100% rename from FEATURES.md rename to docs/FEATURES.md diff --git a/FRONTEND_SETUP.md b/docs/FRONTEND_SETUP.md similarity index 100% rename from FRONTEND_SETUP.md rename to docs/FRONTEND_SETUP.md diff --git a/SUMMARY.md b/docs/SUMMARY.md similarity index 100% rename from SUMMARY.md rename to docs/SUMMARY.md diff --git a/frontend/angular.json b/frontend/angular.json index 5784175..919a41c 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -93,5 +93,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/quickstart.bat b/quickstart.bat deleted file mode 100644 index d0f8808..0000000 --- a/quickstart.bat +++ /dev/null @@ -1,106 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -echo ========================================= -echo Warehouse13 - Quick Start -echo ========================================= -echo. - -REM Check if Docker is installed -where docker >nul 2>nul -if %errorlevel% neq 0 ( - echo Error: Docker is not installed. Please install Docker Desktop first. - echo Visit: https://www.docker.com/products/docker-desktop - pause - exit /b 1 -) - -REM Check if Docker Compose is available -where docker-compose >nul 2>nul -if %errorlevel% neq 0 ( - REM Try docker compose (new version) - docker compose version >nul 2>nul - if %errorlevel% neq 0 ( - echo Error: Docker Compose is not available. - echo Please ensure Docker Desktop is running. - pause - exit /b 1 - ) - set COMPOSE_CMD=docker compose -) else ( - set COMPOSE_CMD=docker-compose -) - -REM Create .env file if it doesn't exist -if not exist .env ( - echo Creating .env file from .env.example... - copy .env.example .env >nul - echo [OK] .env file created -) else ( - echo [OK] .env file already exists -) - -echo. -echo Building and starting services with Docker Compose... -%COMPOSE_CMD% up -d --build - -if %errorlevel% neq 0 ( - echo. - echo Error: Failed to start services. - echo Make sure Docker Desktop is running. - pause - exit /b 1 -) - -echo. -echo Waiting for services to be ready... -timeout /t 15 /nobreak >nul - -echo. -echo ========================================= -echo Services are running! -echo ========================================= -echo. -echo Web UI: http://localhost:8000 -echo API Docs: http://localhost:8000/docs -echo MinIO Console: http://localhost:9001 -echo Username: minioadmin -echo Password: minioadmin -echo. -echo To view logs: %COMPOSE_CMD% logs -f -echo To stop: %COMPOSE_CMD% down -echo. -echo ========================================= -echo Testing the API... -echo ========================================= -echo. - -REM Wait a bit more for API to be fully ready -timeout /t 5 /nobreak >nul - -REM Test health endpoint -curl -s http://localhost:8000/health | findstr "healthy" >nul 2>nul -if %errorlevel% equ 0 ( - echo [OK] API is healthy! - echo. - echo ========================================= - echo Open your browser to get started: - echo http://localhost:8000 - echo ========================================= -) else ( - echo [WARNING] API is not responding yet. - echo Please wait a moment and check http://localhost:8000 -) - -echo. -echo ========================================= -echo Setup complete! -echo ========================================= -echo. -echo Press any key to open the UI in your browser... -pause >nul - -REM Open browser -start http://localhost:8000 - -exit /b 0 diff --git a/quickstart.ps1 b/quickstart.ps1 index 920c584..03804f0 100644 --- a/quickstart.ps1 +++ b/quickstart.ps1 @@ -121,7 +121,7 @@ Write-Host "Useful Commands:" -ForegroundColor Cyan Write-Host " Generate seed data: " -NoNewline Write-Host "Use the 'Generate Seed Data' button in the UI" -ForegroundColor Yellow Write-Host " View logs: " -NoNewline -Write-Host "$composeCmd logs -f api" -ForegroundColor Yellow +Write-Host "$composeCmd logs -f app" -ForegroundColor Yellow Write-Host " Restart services: " -NoNewline Write-Host "$composeCmd restart" -ForegroundColor Yellow Write-Host " Stop all: " -NoNewline diff --git a/static/css/styles.css b/static/css/styles.css deleted file mode 100644 index edee773..0000000 --- a/static/css/styles.css +++ /dev/null @@ -1,564 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - background: #0f172a; - min-height: 100vh; - padding: 20px; - color: #e2e8f0; -} - -.container { - max-width: 1400px; - margin: 0 auto; - background: #1e293b; - border-radius: 12px; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); - overflow: hidden; -} - -header { - background: linear-gradient(135deg, #1e3a8a 0%, #4338ca 100%); - color: white; - padding: 30px; - display: flex; - justify-content: space-between; - align-items: center; -} - -header h1 { - font-size: 28px; - font-weight: 600; - display: flex; - align-items: center; - gap: 12px; -} - -.logo { - font-family: 'Courier New', monospace; - font-weight: 700; - font-size: 24px; - color: #60a5fa; - letter-spacing: -1px; - padding: 2px 4px; - border: 2px solid #60a5fa; - border-radius: 4px; - background: rgba(96, 165, 250, 0.1); -} - -.header-info { - display: flex; - gap: 10px; -} - -.badge { - background: rgba(255, 255, 255, 0.2); - padding: 6px 12px; - border-radius: 20px; - font-size: 12px; - font-weight: 500; - text-transform: uppercase; - backdrop-filter: blur(10px); -} - -.tabs { - display: flex; - background: #0f172a; - border-bottom: 2px solid #334155; -} - -.tab-button { - flex: 1; - padding: 16px 24px; - background: none; - border: none; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.3s; - color: #94a3b8; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; -} - -.tab-button:hover { - background: #1e293b; - color: #e2e8f0; -} - -.tab-button.active { - background: #1e293b; - color: #60a5fa; - border-bottom: 3px solid #60a5fa; -} - -.tab-content { - display: none; - padding: 30px; -} - -.tab-content.active { - display: block; -} - -.toolbar { - display: flex; - gap: 10px; - margin-bottom: 20px; - align-items: center; -} - -.filter-inline { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - background: #0f172a; - border-radius: 6px; - border: 1px solid #334155; - min-width: 250px; -} - -.filter-inline input { - flex: 1; - padding: 4px 8px; - background: transparent; - border: none; - color: #e2e8f0; - font-size: 14px; -} - -.filter-inline input:focus { - outline: none; -} - -.filter-inline input::placeholder { - color: #64748b; -} - -.btn-clear { - background: none; - border: none; - cursor: pointer; - padding: 4px; - border-radius: 4px; - color: #64748b; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.3s; -} - -.btn-clear:hover { - background: #334155; - color: #e2e8f0; -} - -.btn { - padding: 10px 20px; - border: none; - border-radius: 6px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.3s; - display: inline-flex; - align-items: center; - gap: 8px; -} - -.btn-primary { - background: #3b82f6; - color: white; -} - -.btn-primary:hover { - background: #2563eb; - transform: translateY(-1px); -} - -.btn-secondary { - background: #334155; - color: #e2e8f0; -} - -.btn-secondary:hover { - background: #475569; -} - -.btn-danger { - background: #ef4444; - color: white; -} - -.btn-danger:hover { - background: #dc2626; -} - -.btn-success { - background: #10b981; - color: white; -} - -.btn-large { - padding: 14px 28px; - font-size: 16px; -} - -.count-badge { - background: #1e3a8a; - color: #93c5fd; - padding: 8px 16px; - border-radius: 20px; - font-size: 13px; - font-weight: 600; - margin-left: auto; -} - -.table-container { - overflow-x: auto; - border: 1px solid #334155; - border-radius: 8px; - background: #0f172a; -} - -table { - width: 100%; - border-collapse: collapse; - font-size: 14px; -} - -thead { - background: #1e293b; -} - -th { - padding: 14px 12px; - text-align: left; - font-weight: 600; - color: #94a3b8; - border-bottom: 2px solid #334155; - white-space: nowrap; - text-transform: uppercase; - font-size: 12px; - letter-spacing: 0.5px; -} - -th.sortable { - cursor: pointer; - user-select: none; - transition: color 0.3s; -} - -th.sortable:hover { - color: #60a5fa; -} - -.sort-indicator { - display: inline-block; - margin-left: 5px; - font-size: 10px; - color: #64748b; -} - -th.sort-asc .sort-indicator::after { - content: '▲'; - color: #60a5fa; -} - -th.sort-desc .sort-indicator::after { - content: '▼'; - color: #60a5fa; -} - -td { - padding: 16px 12px; - border-bottom: 1px solid #1e293b; - color: #cbd5e1; -} - -tbody tr:hover { - background: #1e293b; -} - -.loading { - text-align: center; - color: #64748b; - padding: 40px !important; -} - -.result-badge { - padding: 4px 10px; - border-radius: 12px; - font-size: 12px; - font-weight: 600; - text-transform: uppercase; -} - -.result-pass { - background: #064e3b; - color: #6ee7b7; -} - -.result-fail { - background: #7f1d1d; - color: #fca5a5; -} - -.result-skip { - background: #78350f; - color: #fcd34d; -} - -.result-error { - background: #7f1d1d; - color: #fca5a5; -} - -.tag { - display: inline-block; - background: #1e3a8a; - color: #93c5fd; - padding: 3px 8px; - border-radius: 10px; - font-size: 11px; - margin: 2px; -} - -.file-type-badge { - background: #1e3a8a; - color: #93c5fd; - padding: 4px 8px; - border-radius: 6px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; -} - -.pagination { - display: flex; - justify-content: center; - align-items: center; - gap: 20px; - margin-top: 20px; - padding: 20px; -} - -#page-info { - font-weight: 500; - color: #94a3b8; -} - -.upload-section, .query-section { - max-width: 800px; - margin: 0 auto; -} - -.form-group { - margin-bottom: 20px; -} - -.form-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 20px; -} - -label { - display: block; - font-weight: 500; - color: #cbd5e1; - margin-bottom: 6px; - font-size: 14px; -} - -input[type="text"], -input[type="file"], -input[type="datetime-local"], -select, -textarea { - width: 100%; - padding: 10px 14px; - border: 1px solid #334155; - border-radius: 6px; - font-size: 14px; - font-family: inherit; - transition: border-color 0.3s; - background: #0f172a; - color: #e2e8f0; -} - -input:focus, -select:focus, -textarea:focus { - outline: none; - border-color: #3b82f6; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); -} - -small { - color: #64748b; - font-size: 12px; - display: block; - margin-top: 4px; -} - -#upload-status { - margin-top: 20px; - padding: 14px; - border-radius: 6px; - display: none; -} - -#upload-status.success { - background: #064e3b; - color: #6ee7b7; - display: block; -} - -#upload-status.error { - background: #7f1d1d; - color: #fca5a5; - display: block; -} - -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(4px); -} - -.modal.active { - display: flex; - align-items: center; - justify-content: center; -} - -.modal-content { - background: #1e293b; - padding: 30px; - border-radius: 12px; - max-width: 700px; - max-height: 80vh; - overflow-y: auto; - position: relative; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); - border: 1px solid #334155; -} - -.close { - position: absolute; - right: 20px; - top: 20px; - font-size: 28px; - font-weight: bold; - color: #64748b; - cursor: pointer; - transition: color 0.3s; -} - -.close:hover { - color: #e2e8f0; -} - -.detail-row { - margin-bottom: 16px; - padding-bottom: 16px; - border-bottom: 1px solid #334155; -} - -.detail-row:last-child { - border-bottom: none; -} - -.detail-label { - font-weight: 600; - color: #94a3b8; - margin-bottom: 4px; -} - -.detail-value { - color: #cbd5e1; -} - -pre { - background: #0f172a; - padding: 12px; - border-radius: 6px; - overflow-x: auto; - font-size: 12px; - border: 1px solid #334155; -} - -code { - background: #0f172a; - padding: 2px 6px; - border-radius: 4px; - font-size: 12px; - color: #93c5fd; -} - -.action-buttons { - display: flex; - gap: 8px; -} - -.icon-btn { - background: none; - border: none; - cursor: pointer; - padding: 8px; - border-radius: 4px; - transition: all 0.3s; - color: #94a3b8; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.icon-btn:hover { - background: #334155; - color: #e2e8f0; - transform: scale(1.1); -} - -/* Ensure SVG icons inherit color */ -.icon-btn svg { - stroke: currentColor; -} - -@media (max-width: 768px) { - .form-row { - grid-template-columns: 1fr; - } - - header { - flex-direction: column; - gap: 15px; - text-align: center; - } - - .table-container { - font-size: 12px; - } - - th, td { - padding: 8px 6px; - } - - .toolbar { - flex-wrap: wrap; - } -} diff --git a/static/index.html b/static/index.html deleted file mode 100644 index a41a8ad..0000000 --- a/static/index.html +++ /dev/null @@ -1,251 +0,0 @@ - - -
- - -| - Sim Source - | -- Artifacts - | -- Date - | -- Uploaded By - | -Actions | -
|---|---|---|---|---|
| Loading artifacts... | -||||
${artifact.storage_path}${JSON.stringify(artifact.test_config, null, 2)}${JSON.stringify(artifact.custom_metadata, null, 2)}