新增多个核心功能模块(后端 Rust 命令 + 前端 Panel 全部接线完成): - 服务器注册表:servers.rs + ServerManagerPanel、ProjectServerPanel(项目关联服务器) - SSH Config 自动同步:ssh_config.rs - 服务器软件管理:server_software.rs + ServerSoftwareBlock - 宿主机应用管理:host_apps.rs + HostAppsPanel - 服务配置管理:service_config.rs + ServiceConfigBlock - 项目部署信息:deploy_info.rs + DeployInfoPanel - API密钥管理:api_keys.rs + ApiKeysPanel - 云产品管理:cloud_products.rs + CloudProductsPanel(含 Docker应用、云数据库实例子模块) - 源码库:source_library.rs + SourceLibraryPage(分类管理 + 项目详情) 导航栏新增「源码库」入口;蓝图 manifest 同步补录 7 个新模块
195 lines
6.6 KiB
Rust
195 lines
6.6 KiB
Rust
use rusqlite::{params, Connection};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct DeployInfo {
|
|
pub id: String,
|
|
pub project_id: String,
|
|
pub server_host: Option<String>,
|
|
pub server_port: Option<String>,
|
|
pub username: Option<String>,
|
|
pub password: Option<String>,
|
|
pub ssh_key_path: Option<String>,
|
|
pub quick_links: Option<String>, // JSON: [{label, url}]
|
|
pub notes: Option<String>,
|
|
pub updated_at: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct SaveDeployInfoInput {
|
|
pub server_host: Option<String>,
|
|
pub server_port: Option<String>,
|
|
pub username: Option<String>,
|
|
pub password: Option<String>,
|
|
pub ssh_key_path: Option<String>,
|
|
pub quick_links: Option<String>,
|
|
pub notes: Option<String>,
|
|
}
|
|
|
|
// ── 业务函数 ──────────────────────────────────────────────────────────────────
|
|
|
|
pub fn get_info(conn: &Connection, project_id: &str) -> Option<DeployInfo> {
|
|
conn.query_row(
|
|
"SELECT id, project_id, server_host, server_port, username, password,
|
|
ssh_key_path, quick_links, notes, updated_at
|
|
FROM project_deploy_info WHERE project_id = ?1",
|
|
params![project_id],
|
|
|row| {
|
|
Ok(DeployInfo {
|
|
id: row.get(0)?,
|
|
project_id: row.get(1)?,
|
|
server_host: row.get(2)?,
|
|
server_port: row.get(3)?,
|
|
username: row.get(4)?,
|
|
password: row.get(5)?,
|
|
ssh_key_path: row.get(6)?,
|
|
quick_links: row.get(7)?,
|
|
notes: row.get(8)?,
|
|
updated_at: row.get(9)?,
|
|
})
|
|
},
|
|
)
|
|
.ok()
|
|
}
|
|
|
|
pub fn save_info(
|
|
conn: &Connection,
|
|
project_id: &str,
|
|
input: &SaveDeployInfoInput,
|
|
) -> Result<DeployInfo, String> {
|
|
let existing = get_info(conn, project_id);
|
|
let id = existing
|
|
.as_ref()
|
|
.map(|e| e.id.clone())
|
|
.unwrap_or_else(|| format!("deploy-{}", uuid::Uuid::new_v4()));
|
|
|
|
conn.execute(
|
|
"INSERT INTO project_deploy_info
|
|
(id, project_id, server_host, server_port, username, password,
|
|
ssh_key_path, quick_links, notes, updated_at)
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, datetime('now'))
|
|
ON CONFLICT(project_id) DO UPDATE SET
|
|
server_host = ?3,
|
|
server_port = ?4,
|
|
username = ?5,
|
|
password = ?6,
|
|
ssh_key_path = ?7,
|
|
quick_links = ?8,
|
|
notes = ?9,
|
|
updated_at = datetime('now')",
|
|
params![
|
|
id,
|
|
project_id,
|
|
input.server_host,
|
|
input.server_port,
|
|
input.username,
|
|
input.password,
|
|
input.ssh_key_path,
|
|
input.quick_links,
|
|
input.notes,
|
|
],
|
|
)
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
get_info(conn, project_id).ok_or_else(|| "保存后查询失败".to_string())
|
|
}
|
|
|
|
// ── Tauri command 薄包装层 ────────────────────────────────────────────────────
|
|
|
|
#[tauri::command]
|
|
pub fn get_deploy_info(project_id: String) -> Result<Option<DeployInfo>, String> {
|
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
|
Ok(get_info(&conn, &project_id))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn save_deploy_info(
|
|
project_id: String,
|
|
input: SaveDeployInfoInput,
|
|
) -> Result<DeployInfo, String> {
|
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
|
save_info(&conn, &project_id, &input)
|
|
}
|
|
|
|
// ── 单元测试 ──────────────────────────────────────────────────────────────────
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use rusqlite::Connection;
|
|
|
|
fn setup() -> Connection {
|
|
let conn = Connection::open_in_memory().unwrap();
|
|
conn.execute_batch(
|
|
"CREATE TABLE project_deploy_info (
|
|
id TEXT PRIMARY KEY,
|
|
project_id TEXT NOT NULL UNIQUE,
|
|
server_host TEXT,
|
|
server_port TEXT DEFAULT '22',
|
|
username TEXT,
|
|
password TEXT,
|
|
ssh_key_path TEXT,
|
|
quick_links TEXT,
|
|
notes TEXT,
|
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
);",
|
|
)
|
|
.unwrap();
|
|
conn
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_empty() {
|
|
let conn = setup();
|
|
assert!(get_info(&conn, "proj-1").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_and_get() {
|
|
let conn = setup();
|
|
let input = SaveDeployInfoInput {
|
|
server_host: Some("192.168.1.100".to_string()),
|
|
server_port: Some("22".to_string()),
|
|
username: Some("root".to_string()),
|
|
password: Some("secret".to_string()),
|
|
ssh_key_path: None,
|
|
quick_links: Some(r#"[{"label":"控制台","url":"https://example.com"}]"#.to_string()),
|
|
notes: Some("测试服务器".to_string()),
|
|
};
|
|
let info = save_info(&conn, "proj-1", &input).unwrap();
|
|
assert_eq!(info.server_host.as_deref(), Some("192.168.1.100"));
|
|
assert_eq!(info.username.as_deref(), Some("root"));
|
|
|
|
let fetched = get_info(&conn, "proj-1").unwrap();
|
|
assert_eq!(fetched.password.as_deref(), Some("secret"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_upsert_overwrites() {
|
|
let conn = setup();
|
|
let input1 = SaveDeployInfoInput {
|
|
server_host: Some("1.1.1.1".to_string()),
|
|
server_port: Some("22".to_string()),
|
|
username: Some("admin".to_string()),
|
|
password: None,
|
|
ssh_key_path: None,
|
|
quick_links: None,
|
|
notes: None,
|
|
};
|
|
save_info(&conn, "proj-1", &input1).unwrap();
|
|
|
|
let input2 = SaveDeployInfoInput {
|
|
server_host: Some("2.2.2.2".to_string()),
|
|
server_port: Some("2222".to_string()),
|
|
username: Some("deploy".to_string()),
|
|
password: Some("new-pass".to_string()),
|
|
ssh_key_path: None,
|
|
quick_links: None,
|
|
notes: None,
|
|
};
|
|
let info = save_info(&conn, "proj-1", &input2).unwrap();
|
|
assert_eq!(info.server_host.as_deref(), Some("2.2.2.2"));
|
|
assert_eq!(info.username.as_deref(), Some("deploy"));
|
|
}
|
|
}
|