/
mcmare
/
RustAPI
Обзор
Документация
Войти
/
mcmare
/
RustAPI
Код
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
engine/src/webui/settings.rs
157 строк
5 KB
mcmare
Add WebUI: zero-config bootstrap, admin login, browser-based settings/endpoints/scripts management
18 июл 2026, 18:34
18 июл 2026, 18:34
e401c22
Код
Авторство
О чём код?
use axum::{ extract::{Path as AxumPath, State}, http::StatusCode, Json, }; use rustapi_core::ApiError; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, path::Path}; use super::{write_validated, WebuiState}; // ---- connections.yaml ---- #[derive(Serialize, Deserialize, Clone)] struct RawConn { driver: String, url: String, } #[derive(Serialize, Deserialize, Default)] struct ConnectionsFile { #[serde(default)] connections: BTreeMap<String, RawConn>, } fn connections_path(config_dir: &Path) -> std::path::PathBuf { config_dir.join("connections.yaml") } fn read_connections(config_dir: &Path) -> Result<ConnectionsFile, ApiError> { let path = connections_path(config_dir); if !path.exists() { return Ok(ConnectionsFile::default()); } let text = std::fs::read_to_string(&path).map_err(|e| ApiError::Internal(e.into()))?; serde_yaml::from_str(&text).map_err(|e| ApiError::Internal(e.into())) } #[derive(Serialize)] pub struct ConnectionOut { name: String, driver: String, url: String, } pub async fn list_connections(State(state): State<WebuiState>) -> Result<Json<Vec<ConnectionOut>>, ApiError> { let file = read_connections(&state.config_dir)?; Ok(Json( file.connections .into_iter() .map(|(name, c)| ConnectionOut { name, driver: c.driver, url: c.url }) .collect(), )) } #[derive(Deserialize)] pub struct UpsertConnection { name: String, driver: String, url: String, } /// Note: connection changes only take effect after a restart (DB pools are created once /// at startup) — this endpoint validates and saves, the frontend is responsible for /// telling the operator to restart. pub async fn upsert_connection( State(state): State<WebuiState>, Json(body): Json<UpsertConnection>, ) -> Result<StatusCode, ApiError> { if body.name.trim().is_empty() { return Err(ApiError::Validation("connection name must not be empty".to_string())); } if !matches!(body.driver.as_str(), "postgres" | "mysql" | "sqlite") { return Err(ApiError::Validation(format!( "unknown driver '{}': expected postgres, mysql or sqlite", body.driver ))); } if body.url.trim().is_empty() { return Err(ApiError::Validation("url must not be empty".to_string())); } let mut file = read_connections(&state.config_dir)?; file.connections.insert(body.name, RawConn { driver: body.driver, url: body.url }); let text = serde_yaml::to_string(&file).map_err(|e| ApiError::Internal(e.into()))?; write_validated(&state.config_dir, &connections_path(&state.config_dir), Some(text))?; Ok(StatusCode::NO_CONTENT) } pub async fn delete_connection( State(state): State<WebuiState>, AxumPath(name): AxumPath<String>, ) -> Result<StatusCode, ApiError> { let mut file = read_connections(&state.config_dir)?; if file.connections.remove(&name).is_none() { return Err(ApiError::NotFound); } let text = serde_yaml::to_string(&file).map_err(|e| ApiError::Internal(e.into()))?; write_validated(&state.config_dir, &connections_path(&state.config_dir), Some(text))?; Ok(StatusCode::NO_CONTENT) } // ---- auth.yaml ---- #[derive(Serialize, Deserialize, Default)] struct AuthFile { #[serde(default)] tokens: Vec<String>, } fn auth_path(config_dir: &Path) -> std::path::PathBuf { config_dir.join("auth.yaml") } fn read_auth(config_dir: &Path) -> Result<AuthFile, ApiError> { let path = auth_path(config_dir); if !path.exists() { return Ok(AuthFile::default()); } let text = std::fs::read_to_string(&path).map_err(|e| ApiError::Internal(e.into()))?; serde_yaml::from_str(&text).map_err(|e| ApiError::Internal(e.into())) } pub async fn list_tokens(State(state): State<WebuiState>) -> Result<Json<Vec<String>>, ApiError> { Ok(Json(read_auth(&state.config_dir)?.tokens)) } #[derive(Deserialize)] pub struct AddToken { token: String, } /// Same caveat as connections: applied on next restart, not live. pub async fn add_token(State(state): State<WebuiState>, Json(body): Json<AddToken>) -> Result<StatusCode, ApiError> { if body.token.trim().is_empty() { return Err(ApiError::Validation("token must not be empty".to_string())); } let mut file = read_auth(&state.config_dir)?; file.tokens.push(body.token); let text = serde_yaml::to_string(&file).map_err(|e| ApiError::Internal(e.into()))?; write_validated(&state.config_dir, &auth_path(&state.config_dir), Some(text))?; Ok(StatusCode::NO_CONTENT) } pub async fn delete_token( State(state): State<WebuiState>, AxumPath(index): AxumPath<usize>, ) -> Result<StatusCode, ApiError> { let mut file = read_auth(&state.config_dir)?; if index >= file.tokens.len() { return Err(ApiError::NotFound); } file.tokens.remove(index); let text = serde_yaml::to_string(&file).map_err(|e| ApiError::Internal(e.into()))?; write_validated(&state.config_dir, &auth_path(&state.config_dir), Some(text))?; Ok(StatusCode::NO_CONTENT) }