Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a1c6df0
Add candidate failure details to usage payload
RWDai May 28, 2026
6959f9a
Wire usage detail candidates through gateway
RWDai May 28, 2026
b2c70d5
Type request failure candidate payloads
RWDai May 28, 2026
0e81cd7
Prioritize upstream failures in usage notices
RWDai May 28, 2026
cd3f8a2
Show upstream candidate errors in request timeline
RWDai May 28, 2026
d2d28c3
Use bearer auth for OpenAI embedding passthrough
RWDai May 28, 2026
bca4746
Normalize usage failure messages
RWDai May 28, 2026
4ba29d4
Mark active usage errors as failed
RWDai May 28, 2026
3f3f4e0
Reduce noisy timeline failure details
RWDai May 28, 2026
195c326
Localize usage failure type labels
RWDai May 28, 2026
dff8ef7
Remove timeline error context noise
RWDai May 28, 2026
6d28541
Preserve dashboard daily breakdown rows
RWDai May 28, 2026
f81e668
Summarize usage failure reasons
RWDai May 28, 2026
5a1c39c
Hide noisy timeline error fields
RWDai May 28, 2026
07e0f15
Merge remote-tracking branch 'origin/fix-dashboard-daily-stats' into …
RWDai May 29, 2026
e196dc6
Merge remote-tracking branch 'origin/fix/openai-embedding-bearer-auth…
RWDai May 29, 2026
07dba77
Merge remote-tracking branch 'origin/plan/request-failure-display' in…
RWDai May 29, 2026
fbd7872
Merge branch 'main' of https://github.com/fawney19/Aether into build-…
RWDai May 29, 2026
e328d49
feat: 创建用户时自动生成符合密码策略的随机密码
RWDai May 29, 2026
7223801
Merge branch 'main' of https://github.com/fawney19/Aether into build-…
RWDai May 29, 2026
205c51f
Merge remote-tracking branch 'origin/feature/admin-user-password-defa…
RWDai May 29, 2026
cf4ac9d
Merge branch 'main' of https://github.com/fawney19/Aether into build-…
RWDai May 30, 2026
4a14722
refactor: 移除密码框点击清空功能,保留自动生成的密码
RWDai May 30, 2026
5c7b4e1
fix: 移除 usage 状态码冗余转换
RWDai May 30, 2026
c40f76c
feat: 为用户表增加备注字段
RWDai May 30, 2026
d11cf17
feat: 接入用户备注读写接口
RWDai May 30, 2026
537d4f4
feat: 用户管理支持备注编辑展示
RWDai May 30, 2026
8ef0c40
fix: 移除历史基线中的备注列
RWDai May 30, 2026
f395ada
fix: 同步驱动基线备注列
RWDai May 30, 2026
f08f8ce
fix: 更新备注迁移快照版本
RWDai May 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,27 @@ pub(super) fn should_try_same_format_provider_oauth_auth(
behavior: &SameFormatProviderRequestBehavior,
transport: &GatewayProviderTransportSnapshot,
family: LocalSameFormatProviderFamily,
provider_api_format: &str,
) -> bool {
should_try_same_format_provider_oauth_auth_impl(
behavior,
transport,
same_format_provider_family(family),
provider_api_format,
)
}

pub(super) fn resolve_same_format_provider_direct_auth(
behavior: &SameFormatProviderRequestBehavior,
transport: &GatewayProviderTransportSnapshot,
family: LocalSameFormatProviderFamily,
provider_api_format: &str,
) -> Option<(String, String)> {
resolve_same_format_provider_direct_auth_impl(
behavior,
transport,
same_format_provider_family(family),
provider_api_format,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,12 @@ pub(super) async fn prepare_local_same_format_provider_candidate(
} else {
None
};
let should_try_oauth_auth =
should_try_same_format_provider_oauth_auth(&behavior, &transport, spec.family);
let should_try_oauth_auth = should_try_same_format_provider_oauth_auth(
&behavior,
&transport,
spec.family,
provider_api_format,
);
let oauth_auth = if should_try_oauth_auth {
resolve_candidate_oauth_auth(
planner_state,
Expand All @@ -118,7 +122,12 @@ pub(super) async fn prepare_local_same_format_provider_candidate(
{
Some((name.clone(), value.clone()))
} else {
resolve_same_format_provider_direct_auth(&behavior, &transport, spec.family)
resolve_same_format_provider_direct_auth(
&behavior,
&transport,
spec.family,
provider_api_format,
)
};
let (auth_header, auth_value) = match auth {
Some((name, value)) => (Some(name), Some(value)),
Expand Down
11 changes: 11 additions & 0 deletions apps/aether-gateway/src/data/state/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,17 @@ impl GatewayDataState {
.await
}

pub(crate) async fn update_user_remark(
&self,
user_id: &str,
remark: Option<String>,
) -> Result<Option<Option<String>>, DataLayerError> {
let Some(repository) = self.user_reader.as_ref() else {
return Ok(None);
};
repository.update_user_remark(user_id, remark).await
}

pub(crate) async fn update_local_auth_user_profile(
&self,
user_id: &str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use super::replay::{
admin_usage_id_from_action_path, admin_usage_id_from_detail_path,
admin_usage_resolve_body_value, admin_usage_resolve_request_capture_body,
admin_usage_resolve_request_capture_body_for_item, build_admin_usage_curl_response,
build_admin_usage_detail_payload, build_admin_usage_replay_response,
build_admin_usage_detail_payload_with_candidates, build_admin_usage_replay_response,
};
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
use crate::handlers::admin::shared::{attach_admin_audit_response, query_param_bool};
Expand Down Expand Up @@ -172,6 +172,13 @@ pub(super) async fn maybe_build_local_admin_usage_detail_response(
admin_usage_api_key_names(state, std::slice::from_ref(&item)),
)?;
let provider_key_name = admin_usage_provider_key_name(&item, &provider_key_names);
let candidates = if state.has_request_candidate_data_reader() {
state
.read_request_candidates_by_request_id(&item.request_id)
.await?
} else {
Vec::new()
};

let mut detail_item = item.clone();
let request_body = if include_bodies {
Expand Down Expand Up @@ -207,7 +214,7 @@ pub(super) async fn maybe_build_local_admin_usage_detail_response(
// request_body 已通过 request capture 解析;其余 detached body 在上方并行加载。
}
let default_headers = admin_usage_curl_headers();
let payload = build_admin_usage_detail_payload(
let payload = build_admin_usage_detail_payload_with_candidates(
&detail_item,
&users_by_id,
&api_key_names,
Expand All @@ -217,6 +224,7 @@ pub(super) async fn maybe_build_local_admin_usage_detail_response(
include_bodies,
request_body,
&default_headers,
&candidates,
);

return Ok(Some(attach_admin_audit_response(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use aether_admin::observability::usage::{
admin_usage_bad_request_response, admin_usage_data_unavailable_response,
ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
};
use aether_data_contracts::repository::candidates::StoredRequestCandidate;
use aether_data_contracts::repository::{
provider_catalog::StoredProviderCatalogEndpoint,
usage::{StoredRequestUsageAudit, UsageBodyCaptureState, UsageBodyField},
Expand Down Expand Up @@ -140,6 +141,33 @@ pub(super) fn build_admin_usage_detail_payload(
)
}

#[allow(clippy::too_many_arguments)]
pub(super) fn build_admin_usage_detail_payload_with_candidates(
item: &StoredRequestUsageAudit,
users_by_id: &BTreeMap<String, aether_data::repository::users::StoredUserSummary>,
api_key_names: &BTreeMap<String, String>,
auth_user_reader_available: bool,
auth_api_key_reader_available: bool,
provider_key_name: Option<&str>,
include_bodies: bool,
request_body: Option<Value>,
default_headers: &BTreeMap<String, String>,
candidates: &[StoredRequestCandidate],
) -> Value {
aether_admin::observability::usage::build_admin_usage_detail_payload_with_candidates(
item,
users_by_id,
api_key_names,
auth_user_reader_available,
auth_api_key_reader_available,
provider_key_name,
include_bodies,
request_body,
default_headers,
candidates,
)
}

pub(super) fn build_admin_usage_replay_plan_response(
item: &StoredRequestUsageAudit,
target_provider: &aether_data_contracts::repository::provider_catalog::StoredProviderCatalogProvider,
Expand Down
12 changes: 12 additions & 0 deletions apps/aether-gateway/src/handlers/admin/request/observability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ impl<'a> AdminAppState<'a> {
self.app.read_recent_request_candidates(limit).await
}

pub(crate) async fn read_request_candidates_by_request_id(
&self,
request_id: &str,
) -> Result<
Vec<aether_data_contracts::repository::candidates::StoredRequestCandidate>,
GatewayError,
> {
self.app
.read_request_candidates_by_request_id(request_id)
.await
}

pub(crate) async fn list_usage_audits(
&self,
query: &aether_data_contracts::repository::usage::UsageAuditListQuery,
Expand Down
8 changes: 8 additions & 0 deletions apps/aether-gateway/src/handlers/admin/request/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,14 @@ impl<'a> AdminAppState<'a> {
.await
}

pub(crate) async fn update_user_remark(
&self,
user_id: &str,
remark: Option<String>,
) -> Result<Option<Option<String>>, GatewayError> {
self.app.update_user_remark(user_id, remark).await
}

pub(crate) async fn count_user_pending_refunds(
&self,
user_id: &str,
Expand Down
19 changes: 17 additions & 2 deletions apps/aether-gateway/src/handlers/admin/users/lifecycle/create.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use super::super::{
admin_default_user_initial_gift, build_admin_users_read_only_response,
disabled_user_policy_detail, disabled_user_policy_field, normalize_admin_feature_settings,
normalize_admin_optional_user_email, normalize_admin_user_group_ids, normalize_admin_user_role,
normalize_admin_username, validate_admin_user_password, AdminCreateUserRequest,
normalize_admin_optional_user_email, normalize_admin_optional_user_remark,
normalize_admin_user_group_ids, normalize_admin_user_role, normalize_admin_username,
validate_admin_user_password, AdminCreateUserRequest,
};
use super::support::{admin_user_password_policy, build_admin_user_payload_with_groups};
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
Expand Down Expand Up @@ -87,6 +88,16 @@ pub(in super::super) async fn build_admin_create_user_response(
.into_response())
}
};
let remark = match normalize_admin_optional_user_remark(payload.remark.as_deref()) {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
};
let username = match normalize_admin_username(&payload.username) {
Ok(value) => value,
Err(detail) => {
Expand Down Expand Up @@ -220,6 +231,9 @@ pub(in super::super) async fn build_admin_create_user_response(
.replace_user_groups_for_user(&user.id, &group_ids)
.await?;
}
if remark.is_some() {
state.update_user_remark(&user.id, remark.clone()).await?;
}
let feature_settings = if feature_settings.is_some() {
state
.update_user_feature_settings(&user.id, feature_settings.clone())
Expand All @@ -231,6 +245,7 @@ pub(in super::super) async fn build_admin_create_user_response(

let mut payload =
build_admin_user_payload_with_groups(&user, None, None, payload.unlimited, &groups);
payload["remark"] = remark.map(Value::String).unwrap_or(Value::Null);
payload["feature_settings"] = feature_settings.unwrap_or(Value::Null);

Ok(attach_admin_audit_response(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,10 @@ pub(in super::super) async fn build_admin_get_user_response(
.as_ref()
.and_then(|row| row.feature_settings.clone())
.unwrap_or(serde_json::Value::Null);
payload["remark"] = export_row
.as_ref()
.and_then(|row| row.remark.clone())
.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null);
Ok(Json(payload).into_response())
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub(super) fn build_admin_user_export_payload(
json!({
"id": row.id,
"email": row.email,
"remark": row.remark,
"username": row.username,
"role": row.role,
"allowed_providers": row.allowed_providers,
Expand Down
30 changes: 27 additions & 3 deletions apps/aether-gateway/src/handlers/admin/users/lifecycle/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use super::super::{
build_admin_users_bad_request_response, build_admin_users_data_unavailable_response,
build_admin_users_read_only_response, disabled_user_policy_detail, disabled_user_policy_field,
normalize_admin_feature_settings, normalize_admin_optional_user_email,
normalize_admin_user_group_ids, normalize_admin_user_role, normalize_admin_username,
validate_admin_user_password, AdminUpdateUserPatch,
normalize_admin_optional_user_remark, normalize_admin_user_group_ids,
normalize_admin_user_role, normalize_admin_username, validate_admin_user_password,
AdminUpdateUserPatch,
};
use super::support::{
admin_user_id_from_detail_path, admin_user_password_policy,
Expand Down Expand Up @@ -98,6 +99,20 @@ pub(in super::super) async fn build_admin_update_user_response(
},
None => None,
};
let remark = if field_presence.contains("remark") {
match normalize_admin_optional_user_remark(payload.remark.as_deref()) {
Ok(value) => Some(value),
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
}
} else {
None
};
if let Some(email) = email.as_deref() {
if state
.is_other_user_auth_email_taken(email, &user_id)
Expand Down Expand Up @@ -186,7 +201,8 @@ pub(in super::super) async fn build_admin_update_user_response(
|| role.is_some()
|| payload.is_active.is_some()
|| group_ids.is_some()
|| feature_settings.is_some();
|| feature_settings.is_some()
|| remark.is_some();
if needs_auth_user_write && !state.has_auth_user_write_capability() {
return Ok(build_admin_users_read_only_response(
"当前为只读模式,无法更新用户",
Expand Down Expand Up @@ -309,6 +325,9 @@ pub(in super::super) async fn build_admin_update_user_response(
.update_user_feature_settings(&user_id, feature_settings)
.await?;
}
if let Some(remark) = remark {
state.update_user_remark(&user_id, remark).await?;
}

let Some(user) = state.find_user_auth_by_id(&user_id).await? else {
return Ok((
Expand Down Expand Up @@ -340,6 +359,11 @@ pub(in super::super) async fn build_admin_update_user_response(
.as_ref()
.and_then(|row| row.feature_settings.clone())
.unwrap_or(Value::Null);
payload["remark"] = export_row
.as_ref()
.and_then(|row| row.remark.clone())
.map(Value::String)
.unwrap_or(Value::Null);

Ok(attach_admin_audit_response(
Json(payload).into_response(),
Expand Down
7 changes: 4 additions & 3 deletions apps/aether-gateway/src/handlers/admin/users/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ use self::shared::{
build_admin_users_data_unavailable_response, build_admin_users_read_only_response,
disabled_user_policy_detail, disabled_user_policy_field, format_optional_datetime_iso8601,
legacy_admin_list_policy_mode, legacy_admin_rate_limit_policy_mode,
normalize_admin_optional_user_email, normalize_admin_user_group_ids, normalize_admin_user_role,
normalize_admin_username, validate_admin_user_password, AdminCreateUserApiKeyRequest,
AdminCreateUserRequest, AdminToggleUserApiKeyLockRequest, AdminUpdateUserApiKeyRequest,
normalize_admin_optional_user_email, normalize_admin_optional_user_remark,
normalize_admin_user_group_ids, normalize_admin_user_role, normalize_admin_username,
validate_admin_user_password, AdminCreateUserApiKeyRequest, AdminCreateUserRequest,
AdminToggleUserApiKeyLockRequest, AdminUpdateUserApiKeyRequest,
};
pub(crate) use self::shared::{
normalize_admin_list_policy_mode, normalize_admin_rate_limit_policy_mode,
Expand Down
20 changes: 20 additions & 0 deletions apps/aether-gateway/src/handlers/admin/users/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ pub(super) struct AdminCreateUserRequest {
#[serde(default)]
pub(super) email: Option<String>,
#[serde(default)]
pub(super) remark: Option<String>,
#[serde(default)]
pub(super) role: Option<String>,
#[serde(default)]
pub(super) initial_gift_usd: Option<f64>,
Expand All @@ -89,6 +91,8 @@ pub(super) struct AdminUpdateUserRequest {
#[serde(default)]
pub(super) email: Option<String>,
#[serde(default)]
pub(super) remark: Option<String>,
#[serde(default)]
pub(super) username: Option<String>,
#[serde(default)]
pub(super) password: Option<String>,
Expand Down Expand Up @@ -176,6 +180,22 @@ pub(super) fn normalize_admin_optional_user_email(
Ok(Some(normalized))
}

pub(super) fn normalize_admin_optional_user_remark(
value: Option<&str>,
) -> Result<Option<String>, String> {
let Some(value) = value else {
return Ok(None);
};
let value = value.trim();
if value.is_empty() {
return Ok(None);
}
if value.chars().count() > 500 {
return Err("备注不能超过500个字符".to_string());
}
Ok(Some(value.to_string()))
}

pub(super) fn normalize_admin_username(value: &str) -> Result<String, String> {
let value = value.trim();
if value.is_empty() {
Expand Down
11 changes: 11 additions & 0 deletions apps/aether-gateway/src/state/runtime/auth/user_provisioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ impl AppState {
Ok(updated)
}

pub(crate) async fn update_user_remark(
&self,
user_id: &str,
remark: Option<String>,
) -> Result<Option<Option<String>>, GatewayError> {
self.data
.update_user_remark(user_id, remark)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}

pub(crate) async fn find_active_provider_name(
&self,
provider_id: &str,
Expand Down
Loading
Loading