feat: add read-only admin settings

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
zyycn
2026-06-12 00:52:52 +08:00
co-authored by Codex
parent 73cf235ee8
commit cfec8bac00
12 changed files with 583 additions and 27 deletions
+95 -16
View File
@@ -14,6 +14,7 @@ use uuid::Uuid;
use crate::{
auth::admin_session::verify_admin_password,
config::{AppConfig, QuotaWarningThresholds},
http::{auth::admin_session_id, middleware::RequestId},
pagination::{clamp_limit, Page},
state::AppState,
@@ -129,6 +130,54 @@ pub struct LoginData {
pub expires_at: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AdminSettingsData {
pub default_model: String,
pub default_reasoning_effort: Option<String>,
pub service_tier: Option<String>,
pub model_aliases: std::collections::BTreeMap<String, String>,
pub refresh_enabled: bool,
pub refresh_margin_seconds: u64,
pub refresh_concurrency: u32,
pub max_concurrent_per_account: usize,
pub request_interval_ms: u64,
pub rotation_strategy: String,
pub tier_priority: Vec<String>,
pub quota_refresh_interval_minutes: u64,
pub quota_warning_thresholds: QuotaWarningThresholds,
pub quota_skip_exhausted: bool,
pub logs_enabled: bool,
pub logs_capacity: u32,
pub logs_capture_body: bool,
pub usage_history_retention_days: Option<u64>,
}
impl AdminSettingsData {
fn from_config(config: &AppConfig) -> Self {
Self {
default_model: config.model.default_model.clone(),
default_reasoning_effort: config.model.default_reasoning_effort.clone(),
service_tier: config.model.service_tier.clone(),
model_aliases: config.model.aliases.clone(),
refresh_enabled: config.auth.refresh_enabled,
refresh_margin_seconds: config.auth.refresh_margin_seconds,
refresh_concurrency: config.auth.refresh_concurrency,
max_concurrent_per_account: config.auth.max_concurrent_per_account,
request_interval_ms: config.auth.request_interval_ms,
rotation_strategy: config.auth.rotation_strategy.clone(),
tier_priority: config.auth.tier_priority.clone(),
quota_refresh_interval_minutes: config.quota.refresh_interval_minutes,
quota_warning_thresholds: config.quota.warning_thresholds.clone(),
quota_skip_exhausted: config.quota.skip_exhausted,
logs_enabled: config.logging.enabled,
logs_capacity: config.logging.capacity,
logs_capture_body: config.logging.capture_body,
usage_history_retention_days: config.usage_stats.history_retention_days,
}
}
}
pub async fn login(
State(state): State<AppState>,
Extension(request_id): Extension<RequestId>,
@@ -241,22 +290,8 @@ pub async fn logs(
)
.into_response();
};
match validate_admin_session(pool, &headers).await {
Ok(true) => {}
Ok(false) => {
return AdminResponse::new(
StatusCode::UNAUTHORIZED,
AdminEnvelope::new(40101, "Admin session required", (), request_id),
)
.into_response();
}
Err(_) => {
return AdminResponse::new(
StatusCode::INTERNAL_SERVER_ERROR,
AdminEnvelope::new(50001, "Failed to validate admin session", (), request_id),
)
.into_response();
}
if let Err(response) = require_admin_session(pool, &headers, &request_id).await {
return response;
}
let limit = clamp_limit(query.limit.unwrap_or(50));
@@ -286,6 +321,30 @@ pub async fn logs(
}
}
pub async fn settings(
State(state): State<AppState>,
Extension(request_id): Extension<RequestId>,
headers: HeaderMap,
) -> Response {
let request_id = request_id.as_str().to_string();
let Some(pool) = state.db() else {
return AdminResponse::new(
StatusCode::INTERNAL_SERVER_ERROR,
AdminEnvelope::new(50001, "Database is not initialized", (), request_id),
)
.into_response();
};
if let Err(response) = require_admin_session(pool, &headers, &request_id).await {
return response;
}
AdminResponse::new(
StatusCode::OK,
AdminEnvelope::ok(AdminSettingsData::from_config(state.config()), request_id),
)
.into_response()
}
#[derive(Debug)]
struct AdminUserRow {
id: String,
@@ -350,3 +409,23 @@ async fn validate_admin_session(
fn admin_session_cookie(headers: &HeaderMap) -> Option<&str> {
admin_session_id(headers)
}
async fn require_admin_session(
pool: &sqlx::SqlitePool,
headers: &HeaderMap,
request_id: &str,
) -> Result<(), Response> {
match validate_admin_session(pool, headers).await {
Ok(true) => Ok(()),
Ok(false) => Err(AdminResponse::new(
StatusCode::UNAUTHORIZED,
AdminEnvelope::new(40101, "Admin session required", (), request_id),
)
.into_response()),
Err(_) => Err(AdminResponse::new(
StatusCode::INTERNAL_SERVER_ERROR,
AdminEnvelope::new(50001, "Failed to validate admin session", (), request_id),
)
.into_response()),
}
}