feat(pixelle): 连接凭证 admin 端点(GET/PUT 掩码 + 重启)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ mod compat;
|
||||
mod downstream_keys;
|
||||
mod model_candidates;
|
||||
pub(crate) mod models_marketplace;
|
||||
mod pixelle_connection;
|
||||
mod sites;
|
||||
mod tokens;
|
||||
mod users;
|
||||
@@ -191,6 +192,10 @@ pub fn routes(state: AppState) -> Router<AppState> {
|
||||
"/api/admin/user-balances",
|
||||
get(users::list_user_balances),
|
||||
)
|
||||
.route(
|
||||
"/api/admin/pixelle/connection",
|
||||
get(pixelle_connection::get_connection).put(pixelle_connection::put_connection),
|
||||
)
|
||||
.route(
|
||||
"/api/admin/users/{id}/balance/adjust",
|
||||
post(users::adjust_user_balance),
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
use crate::error::AppResult;
|
||||
use crate::services::pixelle_connection::{self, PixelleConnection};
|
||||
use crate::AppState;
|
||||
use axum::{extract::State, Json};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
const MASK_SENTINEL: &str = "••••";
|
||||
|
||||
fn mask(v: &Option<String>) -> Value {
|
||||
match v {
|
||||
Some(s) if s.chars().count() > 4 => {
|
||||
let last4: String = s.chars().rev().take(4).collect::<Vec<_>>().into_iter().rev().collect();
|
||||
json!(format!("{}{}", MASK_SENTINEL, last4))
|
||||
}
|
||||
Some(s) if !s.is_empty() => json!(MASK_SENTINEL),
|
||||
_ => Value::Null,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_connection(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
||||
let c = pixelle_connection::read_connection(&state.db).await?;
|
||||
Ok(Json(json!({
|
||||
"success": true,
|
||||
"connection": {
|
||||
"comfyuiUrl": c.comfyui_url,
|
||||
"runninghubApiKey": mask(&c.runninghub_api_key),
|
||||
"comfyuiApiKey": mask(&c.comfyui_api_key),
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdatePayload {
|
||||
runninghub_api_key: Option<String>,
|
||||
comfyui_url: Option<String>,
|
||||
comfyui_api_key: Option<String>,
|
||||
}
|
||||
|
||||
fn keep_if_not_mask(incoming: Option<String>) -> Option<String> {
|
||||
match incoming {
|
||||
Some(s) if s.starts_with(MASK_SENTINEL) => None,
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn put_connection(
|
||||
State(state): State<AppState>,
|
||||
Json(p): Json<UpdatePayload>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let conn = PixelleConnection {
|
||||
runninghub_api_key: keep_if_not_mask(p.runninghub_api_key),
|
||||
comfyui_url: p.comfyui_url,
|
||||
comfyui_api_key: keep_if_not_mask(p.comfyui_api_key),
|
||||
};
|
||||
pixelle_connection::save_connection(&state.db, &conn).await?;
|
||||
let backup = pixelle_connection::sync_to_pixelle(&state.config.pixelle_video.config_path, &conn)?;
|
||||
let restart = pixelle_connection::restart_pixelle(&state.config.pixelle_video.compose_dir).await;
|
||||
match restart {
|
||||
Ok(()) => Ok(Json(json!({ "success": true, "backup": backup, "restarted": true }))),
|
||||
Err(e) => Ok(Json(json!({
|
||||
"success": true, "backup": backup, "restarted": false,
|
||||
"warning": format!("配置已写入,但 Pixelle 重启失败,请手动重启: {e}")
|
||||
}))),
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,22 @@ pub fn sync_to_pixelle(config_path: &str, conn: &PixelleConnection) -> AppResult
|
||||
Ok(backup)
|
||||
}
|
||||
|
||||
pub async fn restart_pixelle(compose_dir: &str) -> AppResult<()> {
|
||||
let output = tokio::process::Command::new("docker")
|
||||
.args(["compose", "restart", "api"])
|
||||
.current_dir(compose_dir)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| AppError::ServiceUnavailable(format!("docker compose restart 启动失败: {e}")))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(AppError::ServiceUnavailable(format!(
|
||||
"Pixelle 重启失败: {}", stderr.trim()
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user