feat: sync upstream analytics schema support
This commit is contained in:
+214
-4
@@ -25,6 +25,15 @@ pub struct SiteDistributionQuery {
|
||||
_refresh: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ProxyDebugTraceQuery {
|
||||
limit: Option<i64>,
|
||||
offset: Option<i64>,
|
||||
session_id: Option<String>,
|
||||
model: Option<String>,
|
||||
status: Option<String>,
|
||||
}
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/api/stats/dashboard", get(dashboard))
|
||||
@@ -523,14 +532,215 @@ async fn proxy_log_detail(
|
||||
Ok(Json(json!({ "item": item, "data": item })))
|
||||
}
|
||||
|
||||
async fn proxy_debug_traces() -> Json<Value> {
|
||||
Json(json!({ "items": [], "data": [], "total": 0 }))
|
||||
async fn proxy_debug_traces(
|
||||
State(state): State<AppState>,
|
||||
Query(q): Query<ProxyDebugTraceQuery>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let limit = q.limit.unwrap_or(50).clamp(1, 200);
|
||||
let offset = q.offset.unwrap_or(0).max(0);
|
||||
let mut where_sql = Vec::new();
|
||||
let mut bind_values = Vec::new();
|
||||
|
||||
if let Some(session_id) = q
|
||||
.session_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
{
|
||||
where_sql.push("session_id = ?");
|
||||
bind_values.push(session_id.to_string());
|
||||
}
|
||||
if let Some(model) = q.model.as_deref().map(str::trim).filter(|v| !v.is_empty()) {
|
||||
where_sql.push("requested_model LIKE ?");
|
||||
bind_values.push(format!("%{model}%"));
|
||||
}
|
||||
if let Some(status) = q.status.as_deref().map(str::trim).filter(|v| !v.is_empty()) {
|
||||
where_sql.push("final_status = ?");
|
||||
bind_values.push(status.to_string());
|
||||
}
|
||||
|
||||
let filter = if where_sql.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" WHERE {}", where_sql.join(" AND "))
|
||||
};
|
||||
|
||||
let list_sql = format!(
|
||||
r#"
|
||||
SELECT
|
||||
id, downstream_path, client_kind, session_id, trace_hint,
|
||||
requested_model, downstream_api_key_id, sticky_session_key,
|
||||
sticky_hit_channel_id, selected_channel_id, selected_route_id,
|
||||
selected_account_id, selected_site_id, selected_site_platform,
|
||||
final_status, final_http_status, final_upstream_path,
|
||||
created_at, updated_at
|
||||
FROM proxy_debug_traces
|
||||
{filter}
|
||||
ORDER BY created_at DESC, id DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"#
|
||||
);
|
||||
let mut list_query = sqlx::query_as::<_, ProxyDebugTraceRow>(&list_sql);
|
||||
for value in &bind_values {
|
||||
list_query = list_query.bind(value);
|
||||
}
|
||||
let items: Vec<Value> = list_query
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&*state.db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|row| serde_json::to_value(row).unwrap_or_else(|_| json!({})))
|
||||
.collect();
|
||||
|
||||
let count_sql = format!("SELECT COUNT(*) FROM proxy_debug_traces{filter}");
|
||||
let mut count_query = sqlx::query_scalar::<_, i64>(&count_sql);
|
||||
for value in &bind_values {
|
||||
count_query = count_query.bind(value);
|
||||
}
|
||||
let total = count_query.fetch_one(&*state.db).await?;
|
||||
|
||||
Ok(Json(json!({
|
||||
"items": items,
|
||||
"data": items,
|
||||
"total": total,
|
||||
"page": (offset / limit) + 1,
|
||||
"pageSize": limit,
|
||||
})))
|
||||
}
|
||||
|
||||
async fn proxy_debug_trace_detail(
|
||||
State(state): State<AppState>,
|
||||
axum::extract::Path(id): axum::extract::Path<i64>,
|
||||
) -> Json<Value> {
|
||||
Json(json!({ "id": id, "events": [], "data": null }))
|
||||
) -> AppResult<Json<Value>> {
|
||||
let trace: Option<ProxyDebugTraceDetailRow> = sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
id, downstream_path, client_kind, session_id, trace_hint,
|
||||
requested_model, downstream_api_key_id, request_headers_json,
|
||||
request_body_json, sticky_session_key, sticky_hit_channel_id,
|
||||
selected_channel_id, selected_route_id, selected_account_id,
|
||||
selected_site_id, selected_site_platform, endpoint_candidates_json,
|
||||
endpoint_runtime_state_json, decision_summary_json, final_status,
|
||||
final_http_status, final_upstream_path, final_response_headers_json,
|
||||
final_response_body_json, created_at, updated_at
|
||||
FROM proxy_debug_traces
|
||||
WHERE id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(&*state.db)
|
||||
.await?;
|
||||
|
||||
let Some(trace) = trace else {
|
||||
return Err(crate::error::AppError::NotFound(
|
||||
"proxy debug trace not found".into(),
|
||||
));
|
||||
};
|
||||
|
||||
let attempts: Vec<Value> = sqlx::query_as::<_, ProxyDebugAttemptRow>(
|
||||
r#"
|
||||
SELECT
|
||||
id, trace_id, attempt_index, endpoint, request_path, target_url,
|
||||
runtime_executor, request_headers_json, request_body_json,
|
||||
response_status, response_headers_json, response_body_json,
|
||||
raw_error_text, recover_applied, downgrade_decision,
|
||||
downgrade_reason, memory_write_json, created_at
|
||||
FROM proxy_debug_attempts
|
||||
WHERE trace_id = ?
|
||||
ORDER BY attempt_index ASC, id ASC
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_all(&*state.db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|row| serde_json::to_value(row).unwrap_or_else(|_| json!({})))
|
||||
.collect();
|
||||
|
||||
let data = serde_json::to_value(trace).unwrap_or_else(|_| json!({}));
|
||||
Ok(Json(json!({
|
||||
"id": id,
|
||||
"item": data,
|
||||
"data": data,
|
||||
"attempts": attempts,
|
||||
"events": attempts,
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
|
||||
pub struct ProxyDebugTraceRow {
|
||||
id: i64,
|
||||
downstream_path: String,
|
||||
client_kind: Option<String>,
|
||||
session_id: Option<String>,
|
||||
trace_hint: Option<String>,
|
||||
requested_model: Option<String>,
|
||||
downstream_api_key_id: Option<i64>,
|
||||
sticky_session_key: Option<String>,
|
||||
sticky_hit_channel_id: Option<i64>,
|
||||
selected_channel_id: Option<i64>,
|
||||
selected_route_id: Option<i64>,
|
||||
selected_account_id: Option<i64>,
|
||||
selected_site_id: Option<i64>,
|
||||
selected_site_platform: Option<String>,
|
||||
final_status: Option<String>,
|
||||
final_http_status: Option<i64>,
|
||||
final_upstream_path: Option<String>,
|
||||
created_at: Option<String>,
|
||||
updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
|
||||
pub struct ProxyDebugTraceDetailRow {
|
||||
id: i64,
|
||||
downstream_path: String,
|
||||
client_kind: Option<String>,
|
||||
session_id: Option<String>,
|
||||
trace_hint: Option<String>,
|
||||
requested_model: Option<String>,
|
||||
downstream_api_key_id: Option<i64>,
|
||||
request_headers_json: Option<String>,
|
||||
request_body_json: Option<String>,
|
||||
sticky_session_key: Option<String>,
|
||||
sticky_hit_channel_id: Option<i64>,
|
||||
selected_channel_id: Option<i64>,
|
||||
selected_route_id: Option<i64>,
|
||||
selected_account_id: Option<i64>,
|
||||
selected_site_id: Option<i64>,
|
||||
selected_site_platform: Option<String>,
|
||||
endpoint_candidates_json: Option<String>,
|
||||
endpoint_runtime_state_json: Option<String>,
|
||||
decision_summary_json: Option<String>,
|
||||
final_status: Option<String>,
|
||||
final_http_status: Option<i64>,
|
||||
final_upstream_path: Option<String>,
|
||||
final_response_headers_json: Option<String>,
|
||||
final_response_body_json: Option<String>,
|
||||
created_at: Option<String>,
|
||||
updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
|
||||
pub struct ProxyDebugAttemptRow {
|
||||
id: i64,
|
||||
trace_id: i64,
|
||||
attempt_index: i64,
|
||||
endpoint: String,
|
||||
request_path: String,
|
||||
target_url: String,
|
||||
runtime_executor: Option<String>,
|
||||
request_headers_json: Option<String>,
|
||||
request_body_json: Option<String>,
|
||||
response_status: Option<i64>,
|
||||
response_headers_json: Option<String>,
|
||||
response_body_json: Option<String>,
|
||||
raw_error_text: Option<String>,
|
||||
recover_applied: Option<bool>,
|
||||
downgrade_decision: Option<bool>,
|
||||
downgrade_reason: Option<String>,
|
||||
memory_write_json: Option<String>,
|
||||
created_at: Option<String>,
|
||||
}
|
||||
|
||||
/// GET /api/stats/model-by-site?siteId=...&days=...
|
||||
|
||||
Reference in New Issue
Block a user