mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-27 19:30:15 +08:00
- 删除 MCP 配置、Agent 配置、部署 3 个页面,保留 6 个核心页面 - 重写模型配置页:Provider/模型 CRUD + 一键应用默认模型(自动生成 fallback) - 增强服务管理页:版本检测 + 配置备份管理(创建/恢复/删除) - 增强记忆文件页:单个文件下载 + 分类打包 zip 下载 - Rust 后端新增 5 个命令(4 个备份 + export_memory_zip) - 更新路由和侧边栏,同步清理
203 lines
6.2 KiB
Rust
203 lines
6.2 KiB
Rust
/// 配置读写命令
|
|
use serde_json::Value;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
use crate::models::types::VersionInfo;
|
|
|
|
fn openclaw_dir() -> PathBuf {
|
|
dirs::home_dir()
|
|
.unwrap_or_default()
|
|
.join(".openclaw")
|
|
}
|
|
|
|
fn backups_dir() -> PathBuf {
|
|
openclaw_dir().join("backups")
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn read_openclaw_config() -> Result<Value, String> {
|
|
let path = openclaw_dir().join("openclaw.json");
|
|
let content = fs::read_to_string(&path)
|
|
.map_err(|e| format!("读取配置失败: {e}"))?;
|
|
serde_json::from_str(&content)
|
|
.map_err(|e| format!("解析 JSON 失败: {e}"))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn write_openclaw_config(config: Value) -> Result<(), String> {
|
|
let path = openclaw_dir().join("openclaw.json");
|
|
// 备份
|
|
let bak = openclaw_dir().join("openclaw.json.bak");
|
|
let _ = fs::copy(&path, &bak);
|
|
// 写入
|
|
let json = serde_json::to_string_pretty(&config)
|
|
.map_err(|e| format!("序列化失败: {e}"))?;
|
|
fs::write(&path, json)
|
|
.map_err(|e| format!("写入失败: {e}"))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn read_mcp_config() -> Result<Value, String> {
|
|
let path = openclaw_dir().join("mcp.json");
|
|
if !path.exists() {
|
|
return Ok(Value::Object(Default::default()));
|
|
}
|
|
let content = fs::read_to_string(&path)
|
|
.map_err(|e| format!("读取 MCP 配置失败: {e}"))?;
|
|
serde_json::from_str(&content)
|
|
.map_err(|e| format!("解析 JSON 失败: {e}"))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn write_mcp_config(config: Value) -> Result<(), String> {
|
|
let path = openclaw_dir().join("mcp.json");
|
|
let json = serde_json::to_string_pretty(&config)
|
|
.map_err(|e| format!("序列化失败: {e}"))?;
|
|
fs::write(&path, json)
|
|
.map_err(|e| format!("写入失败: {e}"))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn get_version_info() -> Result<VersionInfo, String> {
|
|
// 从 openclaw.json 的 meta.lastTouchedVersion 读取
|
|
let config = read_openclaw_config()?;
|
|
let current = config
|
|
.get("meta")
|
|
.and_then(|m| m.get("lastTouchedVersion"))
|
|
.and_then(|v| v.as_str())
|
|
.map(String::from);
|
|
|
|
Ok(VersionInfo {
|
|
current,
|
|
latest: None,
|
|
update_available: false,
|
|
})
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn check_installation() -> Result<Value, String> {
|
|
let openclaw_dir = openclaw_dir();
|
|
let installed = openclaw_dir.join("openclaw.json").exists();
|
|
let mut result = serde_json::Map::new();
|
|
result.insert("installed".into(), Value::Bool(installed));
|
|
result.insert("path".into(), Value::String(openclaw_dir.to_string_lossy().to_string()));
|
|
Ok(Value::Object(result))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn write_env_file(path: String, config: String) -> Result<(), String> {
|
|
let expanded = if path.starts_with("~/") {
|
|
dirs::home_dir()
|
|
.unwrap_or_default()
|
|
.join(&path[2..])
|
|
} else {
|
|
PathBuf::from(&path)
|
|
};
|
|
if let Some(parent) = expanded.parent() {
|
|
let _ = fs::create_dir_all(parent);
|
|
}
|
|
fs::write(&expanded, &config)
|
|
.map_err(|e| format!("写入 .env 失败: {e}"))
|
|
}
|
|
|
|
// ===== 备份管理 =====
|
|
|
|
#[tauri::command]
|
|
pub fn list_backups() -> Result<Value, String> {
|
|
let dir = backups_dir();
|
|
if !dir.exists() {
|
|
return Ok(Value::Array(vec![]));
|
|
}
|
|
let mut backups: Vec<Value> = vec![];
|
|
let entries = fs::read_dir(&dir)
|
|
.map_err(|e| format!("读取备份目录失败: {e}"))?;
|
|
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
if path.extension().and_then(|e| e.to_str()) != Some("json") {
|
|
continue;
|
|
}
|
|
let name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
|
|
let meta = fs::metadata(&path).ok();
|
|
let size = meta.as_ref().map(|m| m.len()).unwrap_or(0);
|
|
let created = meta
|
|
.and_then(|m| m.modified().ok())
|
|
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
|
|
.map(|d| d.as_secs())
|
|
.unwrap_or(0);
|
|
|
|
let mut obj = serde_json::Map::new();
|
|
obj.insert("name".into(), Value::String(name));
|
|
obj.insert("size".into(), Value::Number(size.into()));
|
|
obj.insert("created_at".into(), Value::Number(created.into()));
|
|
backups.push(Value::Object(obj));
|
|
}
|
|
// 按时间倒序
|
|
backups.sort_by(|a, b| {
|
|
let ta = a.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0);
|
|
let tb = b.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0);
|
|
tb.cmp(&ta)
|
|
});
|
|
Ok(Value::Array(backups))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn create_backup() -> Result<Value, String> {
|
|
let dir = backups_dir();
|
|
fs::create_dir_all(&dir)
|
|
.map_err(|e| format!("创建备份目录失败: {e}"))?;
|
|
|
|
let src = openclaw_dir().join("openclaw.json");
|
|
if !src.exists() {
|
|
return Err("openclaw.json 不存在".into());
|
|
}
|
|
|
|
let now = chrono::Local::now();
|
|
let name = format!("openclaw-{}.json", now.format("%Y%m%d-%H%M%S"));
|
|
let dest = dir.join(&name);
|
|
fs::copy(&src, &dest)
|
|
.map_err(|e| format!("备份失败: {e}"))?;
|
|
|
|
let size = fs::metadata(&dest).map(|m| m.len()).unwrap_or(0);
|
|
let mut obj = serde_json::Map::new();
|
|
obj.insert("name".into(), Value::String(name));
|
|
obj.insert("size".into(), Value::Number(size.into()));
|
|
Ok(Value::Object(obj))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn restore_backup(name: String) -> Result<(), String> {
|
|
// 安全检查
|
|
if name.contains("..") || name.contains('/') {
|
|
return Err("非法文件名".into());
|
|
}
|
|
let backup_path = backups_dir().join(&name);
|
|
if !backup_path.exists() {
|
|
return Err(format!("备份文件不存在: {name}"));
|
|
}
|
|
let target = openclaw_dir().join("openclaw.json");
|
|
|
|
// 恢复前先自动备份当前配置
|
|
if target.exists() {
|
|
let _ = create_backup();
|
|
}
|
|
|
|
fs::copy(&backup_path, &target)
|
|
.map_err(|e| format!("恢复失败: {e}"))?;
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn delete_backup(name: String) -> Result<(), String> {
|
|
if name.contains("..") || name.contains('/') {
|
|
return Err("非法文件名".into());
|
|
}
|
|
let path = backups_dir().join(&name);
|
|
if !path.exists() {
|
|
return Err(format!("备份文件不存在: {name}"));
|
|
}
|
|
fs::remove_file(&path)
|
|
.map_err(|e| format!("删除失败: {e}"))
|
|
}
|