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 downstream_keys;
|
||||||
mod model_candidates;
|
mod model_candidates;
|
||||||
pub(crate) mod models_marketplace;
|
pub(crate) mod models_marketplace;
|
||||||
|
mod pixelle_connection;
|
||||||
mod sites;
|
mod sites;
|
||||||
mod tokens;
|
mod tokens;
|
||||||
mod users;
|
mod users;
|
||||||
@@ -191,6 +192,10 @@ pub fn routes(state: AppState) -> Router<AppState> {
|
|||||||
"/api/admin/user-balances",
|
"/api/admin/user-balances",
|
||||||
get(users::list_user_balances),
|
get(users::list_user_balances),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/api/admin/pixelle/connection",
|
||||||
|
get(pixelle_connection::get_connection).put(pixelle_connection::put_connection),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/admin/users/{id}/balance/adjust",
|
"/api/admin/users/{id}/balance/adjust",
|
||||||
post(users::adjust_user_balance),
|
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)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user