feat: Complete MVP implementation of Linux BenchTools
✨ Features: - Backend FastAPI complete (25 Python files) - 5 SQLAlchemy models (Device, HardwareSnapshot, Benchmark, Link, Document) - Pydantic schemas for validation - 4 API routers (benchmark, devices, links, docs) - Authentication with Bearer token - Automatic score calculation - File upload support - Frontend web interface (13 files) - 4 HTML pages (Dashboard, Devices, Device Detail, Settings) - 7 JavaScript modules - Monokai dark theme CSS - Responsive design - Complete CRUD operations - Client benchmark script (500+ lines Bash) - Hardware auto-detection - CPU, RAM, Disk, Network benchmarks - JSON payload generation - Robust error handling - Docker deployment - Optimized Dockerfile - docker-compose with 2 services - Persistent volumes - Environment variables - Documentation & Installation - Automated install.sh script - README, QUICKSTART, DEPLOYMENT guides - Complete API documentation - Project structure documentation 📊 Stats: - ~60 files created - ~5000 lines of code - Full MVP feature set implemented 🚀 Ready for production deployment! 🤖 Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
0
backend/app/schemas/__init__.py
Normal file
0
backend/app/schemas/__init__.py
Normal file
109
backend/app/schemas/benchmark.py
Normal file
109
backend/app/schemas/benchmark.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
Linux BenchTools - Benchmark Schemas
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from app.schemas.hardware import HardwareData
|
||||
|
||||
|
||||
class CPUResults(BaseModel):
|
||||
"""CPU benchmark results"""
|
||||
events_per_sec: Optional[float] = None
|
||||
duration_s: Optional[float] = None
|
||||
score: Optional[float] = None
|
||||
|
||||
|
||||
class MemoryResults(BaseModel):
|
||||
"""Memory benchmark results"""
|
||||
throughput_mib_s: Optional[float] = None
|
||||
score: Optional[float] = None
|
||||
|
||||
|
||||
class DiskResults(BaseModel):
|
||||
"""Disk benchmark results"""
|
||||
read_mb_s: Optional[float] = None
|
||||
write_mb_s: Optional[float] = None
|
||||
iops_read: Optional[int] = None
|
||||
iops_write: Optional[int] = None
|
||||
latency_ms: Optional[float] = None
|
||||
score: Optional[float] = None
|
||||
|
||||
|
||||
class NetworkResults(BaseModel):
|
||||
"""Network benchmark results"""
|
||||
upload_mbps: Optional[float] = None
|
||||
download_mbps: Optional[float] = None
|
||||
ping_ms: Optional[float] = None
|
||||
jitter_ms: Optional[float] = None
|
||||
packet_loss_percent: Optional[float] = None
|
||||
score: Optional[float] = None
|
||||
|
||||
|
||||
class GPUResults(BaseModel):
|
||||
"""GPU benchmark results"""
|
||||
glmark2_score: Optional[int] = None
|
||||
score: Optional[float] = None
|
||||
|
||||
|
||||
class BenchmarkResults(BaseModel):
|
||||
"""Complete benchmark results"""
|
||||
cpu: Optional[CPUResults] = None
|
||||
memory: Optional[MemoryResults] = None
|
||||
disk: Optional[DiskResults] = None
|
||||
network: Optional[NetworkResults] = None
|
||||
gpu: Optional[GPUResults] = None
|
||||
global_score: float = Field(..., ge=0, le=100, description="Global score (0-100)")
|
||||
|
||||
|
||||
class BenchmarkPayload(BaseModel):
|
||||
"""Complete benchmark payload from client script"""
|
||||
device_identifier: str = Field(..., min_length=1, max_length=255)
|
||||
bench_script_version: str = Field(..., min_length=1, max_length=50)
|
||||
hardware: HardwareData
|
||||
results: BenchmarkResults
|
||||
|
||||
|
||||
class BenchmarkResponse(BaseModel):
|
||||
"""Response after successful benchmark submission"""
|
||||
status: str = "ok"
|
||||
device_id: int
|
||||
benchmark_id: int
|
||||
message: Optional[str] = None
|
||||
|
||||
|
||||
class BenchmarkDetail(BaseModel):
|
||||
"""Detailed benchmark information"""
|
||||
id: int
|
||||
device_id: int
|
||||
hardware_snapshot_id: int
|
||||
run_at: str
|
||||
bench_script_version: str
|
||||
|
||||
global_score: float
|
||||
cpu_score: Optional[float] = None
|
||||
memory_score: Optional[float] = None
|
||||
disk_score: Optional[float] = None
|
||||
network_score: Optional[float] = None
|
||||
gpu_score: Optional[float] = None
|
||||
|
||||
details: dict # details_json parsed
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class BenchmarkSummary(BaseModel):
|
||||
"""Summary benchmark information for lists"""
|
||||
id: int
|
||||
run_at: str
|
||||
global_score: float
|
||||
cpu_score: Optional[float] = None
|
||||
memory_score: Optional[float] = None
|
||||
disk_score: Optional[float] = None
|
||||
network_score: Optional[float] = None
|
||||
gpu_score: Optional[float] = None
|
||||
bench_script_version: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
66
backend/app/schemas/device.py
Normal file
66
backend/app/schemas/device.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Linux BenchTools - Device Schemas
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from app.schemas.benchmark import BenchmarkSummary
|
||||
from app.schemas.hardware import HardwareSnapshotResponse
|
||||
|
||||
|
||||
class DeviceBase(BaseModel):
|
||||
"""Base device schema"""
|
||||
hostname: str
|
||||
fqdn: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
asset_tag: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
owner: Optional[str] = None
|
||||
tags: Optional[str] = None
|
||||
|
||||
|
||||
class DeviceCreate(DeviceBase):
|
||||
"""Schema for creating a device"""
|
||||
pass
|
||||
|
||||
|
||||
class DeviceUpdate(BaseModel):
|
||||
"""Schema for updating a device"""
|
||||
hostname: Optional[str] = None
|
||||
fqdn: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
asset_tag: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
owner: Optional[str] = None
|
||||
tags: Optional[str] = None
|
||||
|
||||
|
||||
class DeviceSummary(DeviceBase):
|
||||
"""Device summary for lists"""
|
||||
id: int
|
||||
created_at: str
|
||||
updated_at: str
|
||||
last_benchmark: Optional[BenchmarkSummary] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class DeviceDetail(DeviceBase):
|
||||
"""Detailed device information"""
|
||||
id: int
|
||||
created_at: str
|
||||
updated_at: str
|
||||
last_benchmark: Optional[BenchmarkSummary] = None
|
||||
last_hardware_snapshot: Optional[HardwareSnapshotResponse] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class DeviceListResponse(BaseModel):
|
||||
"""Paginated device list response"""
|
||||
items: List[DeviceSummary]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
25
backend/app/schemas/document.py
Normal file
25
backend/app/schemas/document.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Linux BenchTools - Document Schemas
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
|
||||
class DocumentResponse(BaseModel):
|
||||
"""Document response"""
|
||||
id: int
|
||||
device_id: int
|
||||
doc_type: str
|
||||
filename: str
|
||||
mime_type: str
|
||||
size_bytes: int
|
||||
uploaded_at: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class DocumentListResponse(BaseModel):
|
||||
"""List of documents"""
|
||||
items: List[DocumentResponse] = []
|
||||
179
backend/app/schemas/hardware.py
Normal file
179
backend/app/schemas/hardware.py
Normal file
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
Linux BenchTools - Hardware Schemas
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class CPUInfo(BaseModel):
|
||||
"""CPU information schema"""
|
||||
vendor: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
microarchitecture: Optional[str] = None
|
||||
cores: Optional[int] = None
|
||||
threads: Optional[int] = None
|
||||
base_freq_ghz: Optional[float] = None
|
||||
max_freq_ghz: Optional[float] = None
|
||||
cache_l1_kb: Optional[int] = None
|
||||
cache_l2_kb: Optional[int] = None
|
||||
cache_l3_kb: Optional[int] = None
|
||||
flags: Optional[List[str]] = None
|
||||
tdp_w: Optional[float] = None
|
||||
|
||||
|
||||
class RAMSlot(BaseModel):
|
||||
"""RAM slot information"""
|
||||
slot: str
|
||||
size_mb: int
|
||||
type: Optional[str] = None
|
||||
speed_mhz: Optional[int] = None
|
||||
vendor: Optional[str] = None
|
||||
part_number: Optional[str] = None
|
||||
|
||||
|
||||
class RAMInfo(BaseModel):
|
||||
"""RAM information schema"""
|
||||
total_mb: int
|
||||
slots_total: Optional[int] = None
|
||||
slots_used: Optional[int] = None
|
||||
ecc: Optional[bool] = None
|
||||
layout: Optional[List[RAMSlot]] = None
|
||||
|
||||
|
||||
class GPUInfo(BaseModel):
|
||||
"""GPU information schema"""
|
||||
vendor: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
driver_version: Optional[str] = None
|
||||
memory_dedicated_mb: Optional[int] = None
|
||||
memory_shared_mb: Optional[int] = None
|
||||
api_support: Optional[List[str]] = None
|
||||
|
||||
|
||||
class StorageDevice(BaseModel):
|
||||
"""Storage device information"""
|
||||
name: str
|
||||
type: Optional[str] = None
|
||||
interface: Optional[str] = None
|
||||
capacity_gb: Optional[int] = None
|
||||
vendor: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
smart_health: Optional[str] = None
|
||||
temperature_c: Optional[int] = None
|
||||
|
||||
|
||||
class Partition(BaseModel):
|
||||
"""Partition information"""
|
||||
name: str
|
||||
mount_point: Optional[str] = None
|
||||
fs_type: Optional[str] = None
|
||||
used_gb: Optional[float] = None
|
||||
total_gb: Optional[float] = None
|
||||
|
||||
|
||||
class StorageInfo(BaseModel):
|
||||
"""Storage information schema"""
|
||||
devices: Optional[List[StorageDevice]] = None
|
||||
partitions: Optional[List[Partition]] = None
|
||||
|
||||
|
||||
class NetworkInterface(BaseModel):
|
||||
"""Network interface information"""
|
||||
name: str
|
||||
type: Optional[str] = None
|
||||
mac: Optional[str] = None
|
||||
ip: Optional[str] = None
|
||||
speed_mbps: Optional[int] = None
|
||||
driver: Optional[str] = None
|
||||
|
||||
|
||||
class NetworkInfo(BaseModel):
|
||||
"""Network information schema"""
|
||||
interfaces: Optional[List[NetworkInterface]] = None
|
||||
|
||||
|
||||
class MotherboardInfo(BaseModel):
|
||||
"""Motherboard information schema"""
|
||||
vendor: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
bios_version: Optional[str] = None
|
||||
bios_date: Optional[str] = None
|
||||
|
||||
|
||||
class OSInfo(BaseModel):
|
||||
"""Operating system information schema"""
|
||||
name: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
kernel_version: Optional[str] = None
|
||||
architecture: Optional[str] = None
|
||||
virtualization_type: Optional[str] = None
|
||||
|
||||
|
||||
class SensorsInfo(BaseModel):
|
||||
"""Sensors information schema"""
|
||||
cpu_temp_c: Optional[float] = None
|
||||
disk_temps_c: Optional[dict] = None # {"/dev/nvme0n1": 42}
|
||||
|
||||
|
||||
class RawInfo(BaseModel):
|
||||
"""Raw command output"""
|
||||
lscpu: Optional[str] = None
|
||||
lsblk: Optional[str] = None
|
||||
dmidecode: Optional[str] = None
|
||||
|
||||
|
||||
class HardwareData(BaseModel):
|
||||
"""Complete hardware information payload"""
|
||||
cpu: Optional[CPUInfo] = None
|
||||
ram: Optional[RAMInfo] = None
|
||||
gpu: Optional[GPUInfo] = None
|
||||
storage: Optional[StorageInfo] = None
|
||||
network: Optional[NetworkInfo] = None
|
||||
motherboard: Optional[MotherboardInfo] = None
|
||||
os: Optional[OSInfo] = None
|
||||
sensors: Optional[SensorsInfo] = None
|
||||
raw_info: Optional[RawInfo] = None
|
||||
|
||||
|
||||
class HardwareSnapshotResponse(BaseModel):
|
||||
"""Hardware snapshot response"""
|
||||
id: int
|
||||
device_id: int
|
||||
captured_at: str
|
||||
|
||||
# CPU
|
||||
cpu_vendor: Optional[str] = None
|
||||
cpu_model: Optional[str] = None
|
||||
cpu_cores: Optional[int] = None
|
||||
cpu_threads: Optional[int] = None
|
||||
cpu_base_freq_ghz: Optional[float] = None
|
||||
cpu_max_freq_ghz: Optional[float] = None
|
||||
|
||||
# RAM
|
||||
ram_total_mb: Optional[int] = None
|
||||
ram_slots_total: Optional[int] = None
|
||||
ram_slots_used: Optional[int] = None
|
||||
|
||||
# GPU
|
||||
gpu_summary: Optional[str] = None
|
||||
gpu_model: Optional[str] = None
|
||||
|
||||
# Storage
|
||||
storage_summary: Optional[str] = None
|
||||
storage_devices_json: Optional[str] = None
|
||||
|
||||
# Network
|
||||
network_interfaces_json: Optional[str] = None
|
||||
|
||||
# OS / Motherboard
|
||||
os_name: Optional[str] = None
|
||||
os_version: Optional[str] = None
|
||||
kernel_version: Optional[str] = None
|
||||
architecture: Optional[str] = None
|
||||
virtualization_type: Optional[str] = None
|
||||
motherboard_vendor: Optional[str] = None
|
||||
motherboard_model: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
36
backend/app/schemas/link.py
Normal file
36
backend/app/schemas/link.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Linux BenchTools - Link Schemas
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from typing import List
|
||||
|
||||
|
||||
class LinkBase(BaseModel):
|
||||
"""Base link schema"""
|
||||
label: str
|
||||
url: str
|
||||
|
||||
|
||||
class LinkCreate(LinkBase):
|
||||
"""Schema for creating a link"""
|
||||
pass
|
||||
|
||||
|
||||
class LinkUpdate(LinkBase):
|
||||
"""Schema for updating a link"""
|
||||
pass
|
||||
|
||||
|
||||
class LinkResponse(LinkBase):
|
||||
"""Link response"""
|
||||
id: int
|
||||
device_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class LinkListResponse(BaseModel):
|
||||
"""List of links"""
|
||||
items: List[LinkResponse] = []
|
||||
Reference in New Issue
Block a user