mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-05 00:47:48 +08:00
feat(health): add model and provider health monitoring
- Rename the original health monitor to endpoint health monitor - Add tab navigation for endpoint, model, and provider health views - Add model health monitor cards with availability, latency, first-byte latency, and 60-point history - Add admin-only provider health monitor with collapsible active-provider sections - Show per-provider model health cards after expanding a provider - Add backend model health and provider health monitor payload builders - Add admin endpoint for provider health monitoring - Add provider-scoped usage breakdown filtering for per-provider model statistics - Add frontend API types and request helpers for model/provider health data - Add demo mock data for model and provider health monitoring - Fix model health timeline time-unit handling so request history segments render correctly Verification: - cargo fmt - npm run type-check - npm run build - cargo test -p aether-gateway health_models - cargo test -p aether-gateway health_providers - cargo test -p aether-gateway gateway_exposes_frontdoor_manifest_without_proxying_upstream
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use super::extractors::{admin_health_key_id, admin_recover_key_id};
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::query_param_value;
|
||||
use crate::handlers::public::ApiFormatHealthMonitorOptions;
|
||||
use crate::handlers::public::{ApiFormatHealthMonitorOptions, ModelHealthMonitorOptions};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::Body,
|
||||
@@ -157,6 +157,80 @@ pub(super) async fn maybe_build_local_admin_endpoints_health_response(
|
||||
return Ok(Some(Json(payload).into_response()));
|
||||
}
|
||||
|
||||
if decision.route_family.as_deref() == Some("endpoints_health")
|
||||
&& decision.route_kind.as_deref() == Some("health_models")
|
||||
&& request_context.path() == "/api/admin/endpoints/health/models"
|
||||
{
|
||||
if !state.has_usage_data_reader() {
|
||||
return Ok(Some(build_admin_endpoint_health_data_unavailable_response()));
|
||||
}
|
||||
let lookback_hours = query_param_value(request_context.query_string(), "lookback_hours")
|
||||
.and_then(|value| value.parse::<u64>().ok())
|
||||
.filter(|value| (1..=72).contains(value))
|
||||
.unwrap_or(6);
|
||||
let model_limit = query_param_value(request_context.query_string(), "model_limit")
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.filter(|value| (1..=50).contains(value))
|
||||
.unwrap_or(12);
|
||||
let per_model_limit = query_param_value(request_context.query_string(), "per_model_limit")
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.filter(|value| (10..=200).contains(value))
|
||||
.unwrap_or(60);
|
||||
let Some(payload) = state
|
||||
.build_model_health_monitor_payload(
|
||||
lookback_hours,
|
||||
model_limit,
|
||||
per_model_limit,
|
||||
ModelHealthMonitorOptions {
|
||||
include_provider_count: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
else {
|
||||
return Ok(Some(build_admin_endpoint_health_data_unavailable_response()));
|
||||
};
|
||||
return Ok(Some(Json(payload).into_response()));
|
||||
}
|
||||
|
||||
if decision.route_family.as_deref() == Some("endpoints_health")
|
||||
&& decision.route_kind.as_deref() == Some("health_providers")
|
||||
&& request_context.path() == "/api/admin/endpoints/health/providers"
|
||||
{
|
||||
if !state.has_provider_catalog_data_reader() || !state.has_usage_data_reader() {
|
||||
return Ok(Some(build_admin_endpoint_health_data_unavailable_response()));
|
||||
}
|
||||
let lookback_hours = query_param_value(request_context.query_string(), "lookback_hours")
|
||||
.and_then(|value| value.parse::<u64>().ok())
|
||||
.filter(|value| (1..=72).contains(value))
|
||||
.unwrap_or(6);
|
||||
let provider_limit = query_param_value(request_context.query_string(), "provider_limit")
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.filter(|value| (1..=100).contains(value))
|
||||
.unwrap_or(50);
|
||||
let per_provider_model_limit =
|
||||
query_param_value(request_context.query_string(), "per_provider_model_limit")
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.filter(|value| (1..=50).contains(value))
|
||||
.unwrap_or(12);
|
||||
let per_model_event_limit =
|
||||
query_param_value(request_context.query_string(), "per_model_limit")
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.filter(|value| (10..=200).contains(value))
|
||||
.unwrap_or(100);
|
||||
let Some(payload) = state
|
||||
.build_provider_health_monitor_payload(
|
||||
lookback_hours,
|
||||
provider_limit,
|
||||
per_provider_model_limit,
|
||||
per_model_event_limit,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
return Ok(Some(build_admin_endpoint_health_data_unavailable_response()));
|
||||
};
|
||||
return Ok(Some(Json(payload).into_response()));
|
||||
}
|
||||
|
||||
if decision.route_family.as_deref() == Some("endpoints_health")
|
||||
&& decision.route_kind.as_deref() == Some("health_status")
|
||||
&& request_context.path() == "/api/admin/endpoints/health/status"
|
||||
|
||||
Reference in New Issue
Block a user