feat,refactor(core,backend,frontend): add simple frontend and a separate router for it
This commit is contained in:
@@ -1 +1,6 @@
|
|||||||
"""Web backend module for disk-checker API."""
|
"""Web backend module for disk-checker API."""
|
||||||
|
|
||||||
|
from .web_routes import web_router
|
||||||
|
from .routes import api_router
|
||||||
|
|
||||||
|
__all__ = ["web_router", "api_router"]
|
||||||
@@ -1,56 +1,12 @@
|
|||||||
"""API routes for disk-checker web backend."""
|
"""API routes for disk-checker web backend."""
|
||||||
|
|
||||||
import asyncio
|
from fastapi import APIRouter, Request
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List, Optional, Dict
|
|
||||||
|
|
||||||
from fastapi import APIRouter, UploadFile, File, HTTPException, Depends, Request
|
|
||||||
from fastapi.responses import JSONResponse, FileResponse
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from disk_checker.core.extractor import load_latency_csv
|
|
||||||
from disk_checker.core.helpers import percentile_table
|
|
||||||
from disk_checker.core.tool_scanner import ToolInfo
|
from disk_checker.core.tool_scanner import ToolInfo
|
||||||
|
|
||||||
|
api_router = APIRouter()
|
||||||
|
|
||||||
from disk_checker.plotting.plots import generate_plot
|
@api_router.get("/tools")
|
||||||
from disk_checker.web.models import (
|
|
||||||
JobStatusResponse,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/jobs/{job_id}", response_model=JobStatusResponse)
|
|
||||||
async def get_job_status(job_id: str, request: Request):
|
|
||||||
"""
|
|
||||||
Check the status of a benchmark job.
|
|
||||||
"""
|
|
||||||
jobs = request.app.state.jobs
|
|
||||||
if job_id not in jobs:
|
|
||||||
raise HTTPException(status_code=404, detail="Job not found")
|
|
||||||
|
|
||||||
job = jobs[job_id]
|
|
||||||
task = job["task"]
|
|
||||||
|
|
||||||
if task.done():
|
|
||||||
result = task.result()
|
|
||||||
return JobStatusResponse(**result)
|
|
||||||
|
|
||||||
return JobStatusResponse(job_id=job_id, status="running")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/jobs", response_model=List[str])
|
|
||||||
async def list_jobs(request: Request):
|
|
||||||
"""
|
|
||||||
List all benchmark jobs.
|
|
||||||
"""
|
|
||||||
return list(request.app.state.jobs.keys())
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/tools")
|
|
||||||
async def get_available_tools(request: Request):
|
async def get_available_tools(request: Request):
|
||||||
"""
|
"""
|
||||||
Get list of available benchmarking tools.
|
Get list of available benchmarking tools.
|
||||||
|
|||||||
@@ -2,14 +2,31 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from disk_checker.core.orchestrator import Orchestrator
|
from disk_checker.core.orchestrator import Orchestrator
|
||||||
from disk_checker.core.tool_scanner import scan_tools
|
from disk_checker.core.tool_scanner import scan_tools
|
||||||
|
# Import and include routers
|
||||||
|
from disk_checker.web.routes import api_router
|
||||||
|
from disk_checker.web.web_routes import web_router
|
||||||
|
|
||||||
|
|
||||||
|
# Get application root
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent
|
||||||
|
TEMPLATES_DIR = BASE_DIR / "templates"
|
||||||
|
STATIC_DIR = BASE_DIR / "static"
|
||||||
|
|
||||||
|
# Setup templates
|
||||||
|
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
|
||||||
|
|
||||||
|
# Setup logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +41,10 @@ async def lifespan(app: FastAPI):
|
|||||||
app.state.available_tools = await asyncio.to_thread(scan_tools)
|
app.state.available_tools = await asyncio.to_thread(scan_tools)
|
||||||
logger.info(f"Available tools: {list(app.state.available_tools.keys())}")
|
logger.info(f"Available tools: {list(app.state.available_tools.keys())}")
|
||||||
|
|
||||||
|
# Store templates and static_dir path in app.state
|
||||||
|
app.state.templates = templates
|
||||||
|
app.state.static_dir = STATIC_DIR
|
||||||
|
|
||||||
logger.info("Disk Checker API started")
|
logger.info("Disk Checker API started")
|
||||||
|
|
||||||
yield
|
yield
|
||||||
@@ -37,7 +58,13 @@ app = FastAPI(
|
|||||||
version="0.1.0",
|
version="0.1.0",
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
)
|
)
|
||||||
|
# Setup static files
|
||||||
|
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
||||||
|
# Include routers
|
||||||
|
app.include_router(api_router, prefix="/api/v1")
|
||||||
|
app.include_router(web_router)
|
||||||
|
|
||||||
|
# Add CORS (permissive by default)
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
@@ -46,13 +73,8 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Simple health endpoint
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health_check():
|
async def health_check():
|
||||||
"""Health check endpoint."""
|
"""Health check endpoint."""
|
||||||
return {"status": "ok", "version": "0.1.0"}
|
return {"status": "ok", "version": "0.1.0"}
|
||||||
|
|
||||||
|
|
||||||
# Import and include routers
|
|
||||||
from disk_checker.web.routes import router as api_router
|
|
||||||
app.include_router(api_router, prefix="/api/v1")
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
web_router = APIRouter()
|
||||||
|
|
||||||
|
@web_router.get("/")
|
||||||
|
async def render_home(request: Request):
|
||||||
|
"""
|
||||||
|
Render the main page
|
||||||
|
"""
|
||||||
|
tools = request.app.state.available_tools
|
||||||
|
return request.app.state.templates.TemplateResponse(
|
||||||
|
name="index.html",
|
||||||
|
request=request,
|
||||||
|
context={
|
||||||
|
"tools":tools
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* Basic styling for Disk Checker */
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Disk Checker</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet">
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||||
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="/">Disk Checker</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!--Available tools block, tools are loaded automatically-->
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2>Available Benchmarking Tools</h2>
|
||||||
|
|
||||||
|
<!-- Server-side rendered tools -->
|
||||||
|
{% if tools %}
|
||||||
|
<div class="row">
|
||||||
|
{% for tool_name, tool in tools.items() %}
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card {% if tool.available %}border-success{% else %}border-secondary{% endif %}">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ tool.name }}</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
{% if tool.available %}
|
||||||
|
<span class="badge bg-success">Available</span>
|
||||||
|
{% if tool.version %}
|
||||||
|
<small class="text-muted">{{ tool.version }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">Not Installed</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="tools-content"></div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user