dev-manager-tauri/src-tauri/src/commands/cloud_products.rs
lanrtop 9660bcbc48 feat: 服务器管理体系 + 云产品扩展 + 源码库 + API密钥管理
新增多个核心功能模块(后端 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 个新模块
2026-04-17 22:25:06 +09:00

150 lines
5.4 KiB
Rust

use rusqlite::{params, Connection};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CloudProduct {
pub id: String,
pub vendor: String,
pub product_type: String,
pub name: String,
pub region: Option<String>,
pub instance_id: Option<String>,
pub endpoint: Option<String>,
pub expire_at: Option<String>,
pub status: String,
pub config_data: String,
pub notes: Option<String>,
pub sort_order: i32,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct SaveCloudProductInput {
pub vendor: String,
pub product_type: String,
pub name: String,
pub region: Option<String>,
pub instance_id: Option<String>,
pub endpoint: Option<String>,
pub expire_at: Option<String>,
pub status: String,
pub config_data: Option<String>,
pub notes: Option<String>,
pub sort_order: Option<i32>,
}
fn row_to_cloud_product(row: &rusqlite::Row<'_>) -> rusqlite::Result<CloudProduct> {
Ok(CloudProduct {
id: row.get(0)?,
vendor: row.get(1)?,
product_type: row.get(2)?,
name: row.get(3)?,
region: row.get(4)?,
instance_id: row.get(5)?,
endpoint: row.get(6)?,
expire_at: row.get(7)?,
status: row.get(8)?,
config_data: row.get(9)?,
notes: row.get(10)?,
sort_order: row.get(11)?,
created_at: row.get(12)?,
updated_at: row.get(13)?,
})
}
const SELECT_COLS: &str =
"id, vendor, product_type, name, region, instance_id, endpoint, expire_at, \
status, config_data, notes, sort_order, created_at, updated_at";
pub fn list_cloud_products(conn: &Connection) -> Vec<CloudProduct> {
let sql = format!(
"SELECT {SELECT_COLS} FROM cloud_products ORDER BY vendor, sort_order, name"
);
let mut stmt = match conn.prepare(&sql) {
Ok(s) => s,
Err(_) => return vec![],
};
stmt.query_map([], row_to_cloud_product)
.map(|iter| iter.filter_map(|r| r.ok()).collect())
.unwrap_or_default()
}
fn get_cloud_product_by_id(conn: &Connection, id: &str) -> Option<CloudProduct> {
let sql = format!("SELECT {SELECT_COLS} FROM cloud_products WHERE id = ?1");
conn.query_row(&sql, params![id], row_to_cloud_product).ok()
}
pub fn create_cloud_product(conn: &Connection, input: &SaveCloudProductInput) -> Result<CloudProduct, String> {
let id = format!("cp-{}", uuid::Uuid::new_v4());
let config_data = input.config_data.as_deref().unwrap_or("{}");
conn.execute(
"INSERT INTO cloud_products
(id, vendor, product_type, name, region, instance_id, endpoint, expire_at,
status, config_data, notes, sort_order)
VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12)",
params![
id, input.vendor, input.product_type, input.name,
input.region, input.instance_id, input.endpoint, input.expire_at,
input.status, config_data, input.notes,
input.sort_order.unwrap_or(0)
],
)
.map_err(|e| e.to_string())?;
get_cloud_product_by_id(conn, &id).ok_or_else(|| "创建后查询失败".into())
}
pub fn update_cloud_product(conn: &Connection, id: &str, input: &SaveCloudProductInput) -> Result<CloudProduct, String> {
let config_data = input.config_data.as_deref().unwrap_or("{}");
let rows = conn.execute(
"UPDATE cloud_products
SET vendor=?1, product_type=?2, name=?3, region=?4, instance_id=?5,
endpoint=?6, expire_at=?7, status=?8, config_data=?9, notes=?10,
sort_order=?11, updated_at=datetime('now')
WHERE id=?12",
params![
input.vendor, input.product_type, input.name,
input.region, input.instance_id, input.endpoint, input.expire_at,
input.status, config_data, input.notes,
input.sort_order.unwrap_or(0), id
],
)
.map_err(|e| e.to_string())?;
if rows == 0 {
return Err(format!("云产品 {} 不存在", id));
}
get_cloud_product_by_id(conn, id).ok_or_else(|| "更新后查询失败".into())
}
pub fn delete_cloud_product_fn(conn: &Connection, id: &str) -> Result<(), String> {
conn.execute("DELETE FROM cloud_products WHERE id=?1", params![id])
.map_err(|e| e.to_string())?;
Ok(())
}
// ── Tauri commands ───────────────────────────────────────────────────────────
#[tauri::command]
pub fn get_cloud_products() -> Result<Vec<CloudProduct>, String> {
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
Ok(list_cloud_products(&conn))
}
#[tauri::command]
pub fn add_cloud_product(input: SaveCloudProductInput) -> Result<CloudProduct, String> {
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
create_cloud_product(&conn, &input)
}
#[tauri::command]
pub fn edit_cloud_product(id: String, input: SaveCloudProductInput) -> Result<CloudProduct, String> {
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
update_cloud_product(&conn, &id, &input)
}
#[tauri::command]
pub fn delete_cloud_product(id: String) -> Result<(), String> {
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
delete_cloud_product_fn(&conn, &id)
}