mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 23:34:00 -05:00
Add unified health and version reporting
This commit is contained in:
@@ -36,6 +36,11 @@ class Settings(BaseSettings):
|
|||||||
def allowed_origins_list(self) -> List[str]:
|
def allowed_origins_list(self) -> List[str]:
|
||||||
return [origin.strip() for origin in self.ALLOWED_ORIGINS.split(",")]
|
return [origin.strip() for origin in self.ALLOWED_ORIGINS.split(",")]
|
||||||
|
|
||||||
|
# Prism / Status page targets
|
||||||
|
PRISM_CORE_API_URL: str = ""
|
||||||
|
PRISM_PUBLIC_API_URL: str = ""
|
||||||
|
PRISM_CONSOLE_URL: str = ""
|
||||||
|
|
||||||
# AWS S3
|
# AWS S3
|
||||||
AWS_ACCESS_KEY_ID: str = ""
|
AWS_ACCESS_KEY_ID: str = ""
|
||||||
AWS_SECRET_ACCESS_KEY: str = ""
|
AWS_SECRET_ACCESS_KEY: str = ""
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from fastapi.responses import JSONResponse, FileResponse
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime, timezone
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
@@ -225,8 +226,22 @@ else:
|
|||||||
async def health_check():
|
async def health_check():
|
||||||
"""Health check endpoint"""
|
"""Health check endpoint"""
|
||||||
return {
|
return {
|
||||||
|
"service": "core-api",
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"timestamp": time.time()
|
"environment": settings.ENVIRONMENT,
|
||||||
|
"version": settings.APP_VERSION,
|
||||||
|
"timestamp": datetime.now(timezone.utc).isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/version")
|
||||||
|
async def version():
|
||||||
|
"""Service version endpoint"""
|
||||||
|
return {
|
||||||
|
"service": "core-api",
|
||||||
|
"version": settings.APP_VERSION,
|
||||||
|
"environment": settings.ENVIRONMENT,
|
||||||
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,15 @@ async def serve_prism_console():
|
|||||||
return FileResponse(prism_index)
|
return FileResponse(prism_index)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/prism/health")
|
||||||
|
async def prism_health():
|
||||||
|
"""Health endpoint for Prism Console assets."""
|
||||||
|
return {
|
||||||
|
"service": "prism-console",
|
||||||
|
"status": "healthy",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/prism/{file_path:path}")
|
@router.get("/prism/{file_path:path}")
|
||||||
async def serve_prism_static_files(file_path: str):
|
async def serve_prism_static_files(file_path: str):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""System endpoints for core OS operations"""
|
"""System endpoints for core OS operations"""
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends, Request
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
@@ -22,7 +22,7 @@ async def get_version():
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"version": settings.APP_VERSION,
|
"version": settings.APP_VERSION,
|
||||||
"build_time": datetime.utcnow().isoformat(),
|
"build_time": datetime.now(timezone.utc).isoformat(),
|
||||||
"env": settings.ENVIRONMENT,
|
"env": settings.ENVIRONMENT,
|
||||||
"git_sha": git_sha[:8] if len(git_sha) > 8 else git_sha,
|
"git_sha": git_sha[:8] if len(git_sha) > 8 else git_sha,
|
||||||
"app_name": settings.APP_NAME,
|
"app_name": settings.APP_NAME,
|
||||||
@@ -81,3 +81,39 @@ async def get_os_state(db: AsyncSession = Depends(get_db)):
|
|||||||
},
|
},
|
||||||
"note": "This is a stub endpoint. Full OS state tracking coming in Phase 2.",
|
"note": "This is a stub endpoint. Full OS state tracking coming in Phase 2.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/prism/config")
|
||||||
|
async def prism_config(request: Request):
|
||||||
|
"""Return Prism Console service configuration for health/status checks."""
|
||||||
|
|
||||||
|
def resolve_url(env_url: str, fallback: str) -> str:
|
||||||
|
return env_url.rstrip("/") if env_url else fallback.rstrip("/")
|
||||||
|
|
||||||
|
base_url = str(request.base_url).rstrip("/")
|
||||||
|
|
||||||
|
services = [
|
||||||
|
{
|
||||||
|
"name": "core-api",
|
||||||
|
"url": resolve_url(settings.PRISM_CORE_API_URL, base_url),
|
||||||
|
"health_path": "/health",
|
||||||
|
"version_path": "/version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "public-api",
|
||||||
|
"url": resolve_url(settings.PRISM_PUBLIC_API_URL, base_url),
|
||||||
|
"health_path": "/health",
|
||||||
|
"version_path": "/version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "prism-console",
|
||||||
|
"url": resolve_url(settings.PRISM_CONSOLE_URL, base_url),
|
||||||
|
"health_path": "/prism/health",
|
||||||
|
"version_path": "/version",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"environment": settings.ENVIRONMENT,
|
||||||
|
"services": services,
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h3>Service Health</h3>
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Service</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Version</th>
|
||||||
|
<th>Endpoint</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="service-health-body">
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="empty-state">Waiting for health checks...</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>Quick Actions</h3>
|
<h3>Quick Actions</h3>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
|
|||||||
@@ -216,6 +216,28 @@ body {
|
|||||||
border-bottom: 1px solid var(--prism-border);
|
border-bottom: 1px solid var(--prism-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
padding: 0.35rem 0.6rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge.healthy {
|
||||||
|
background: rgba(16, 185, 129, 0.12);
|
||||||
|
color: #10b981;
|
||||||
|
border: 1px solid rgba(16, 185, 129, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge.unhealthy {
|
||||||
|
background: rgba(239, 68, 68, 0.12);
|
||||||
|
color: #ef4444;
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--prism-text-muted);
|
color: var(--prism-text-muted);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
class PrismConsole {
|
class PrismConsole {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.apiBase = window.location.origin;
|
this.apiBase = window.location.origin;
|
||||||
|
this.services = [];
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ class PrismConsole {
|
|||||||
// Setup tab navigation
|
// Setup tab navigation
|
||||||
this.setupTabs();
|
this.setupTabs();
|
||||||
|
|
||||||
// Load initial data
|
// Load configuration and initial data
|
||||||
|
await this.loadConfig();
|
||||||
await this.loadDashboard();
|
await this.loadDashboard();
|
||||||
|
|
||||||
// Setup auto-refresh
|
// Setup auto-refresh
|
||||||
@@ -46,6 +48,20 @@ class PrismConsole {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadConfig() {
|
||||||
|
try {
|
||||||
|
const config = await this.fetchAPI('/api/system/prism/config');
|
||||||
|
this.services = config.services || [];
|
||||||
|
|
||||||
|
if (config.environment) {
|
||||||
|
document.getElementById('environment-badge').textContent = config.environment;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load Prism configuration', error);
|
||||||
|
this.services = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async loadDashboard() {
|
async loadDashboard() {
|
||||||
try {
|
try {
|
||||||
// Get system version
|
// Get system version
|
||||||
@@ -54,8 +70,7 @@ class PrismConsole {
|
|||||||
document.getElementById('environment-badge').textContent = version.env;
|
document.getElementById('environment-badge').textContent = version.env;
|
||||||
|
|
||||||
// Get system status
|
// Get system status
|
||||||
document.getElementById('system-status').textContent = 'Healthy ✓';
|
await this.loadServiceHealth();
|
||||||
document.getElementById('health-status').style.color = '#10b981';
|
|
||||||
|
|
||||||
// Update last updated time
|
// Update last updated time
|
||||||
const now = new Date().toLocaleTimeString();
|
const now = new Date().toLocaleTimeString();
|
||||||
@@ -120,6 +135,61 @@ class PrismConsole {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadServiceHealth() {
|
||||||
|
const tbody = document.getElementById('service-health-body');
|
||||||
|
|
||||||
|
if (!tbody || !this.services.length) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="4" class="empty-state">No services configured</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await Promise.all(
|
||||||
|
this.services.map(async (service) => {
|
||||||
|
const healthUrl = `${service.url}${service.health_path || '/health'}`;
|
||||||
|
const versionUrl = `${service.url}${service.version_path || '/version'}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [health, version] = await Promise.all([
|
||||||
|
this.fetchExternal(healthUrl),
|
||||||
|
this.fetchExternal(versionUrl),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: service.name,
|
||||||
|
status: (health.status || 'unknown').toLowerCase(),
|
||||||
|
version: version.version || 'unknown',
|
||||||
|
endpoint: healthUrl,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Health check failed for ${service.name}:`, error);
|
||||||
|
return {
|
||||||
|
name: service.name,
|
||||||
|
status: 'error',
|
||||||
|
version: 'n/a',
|
||||||
|
endpoint: healthUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const unhealthy = results.some((result) => result.status !== 'healthy');
|
||||||
|
document.getElementById('system-status').textContent = unhealthy ? 'Issues Detected' : 'Healthy ✓';
|
||||||
|
document.getElementById('health-status').style.color = unhealthy ? '#ef4444' : '#10b981';
|
||||||
|
|
||||||
|
tbody.innerHTML = results
|
||||||
|
.map(
|
||||||
|
(result) => `
|
||||||
|
<tr>
|
||||||
|
<td>${result.name}</td>
|
||||||
|
<td><span class="status-badge ${result.status === 'healthy' ? 'healthy' : 'unhealthy'}">${result.status}</span></td>
|
||||||
|
<td>${result.version}</td>
|
||||||
|
<td><a href="${result.endpoint}" target="_blank" rel="noopener noreferrer">${result.endpoint}</a></td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
async fetchAPI(endpoint) {
|
async fetchAPI(endpoint) {
|
||||||
const response = await fetch(`${this.apiBase}${endpoint}`);
|
const response = await fetch(`${this.apiBase}${endpoint}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -127,6 +197,14 @@ class PrismConsole {
|
|||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchExternal(url) {
|
||||||
|
const response = await fetch(url, { headers: { Accept: 'application/json' } });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global functions for HTML onclick
|
// Global functions for HTML onclick
|
||||||
|
|||||||
Reference in New Issue
Block a user