新增多个核心功能模块(后端 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 个新模块
373 lines
12 KiB
Rust
373 lines
12 KiB
Rust
use rusqlite::Row;
|
||
use serde::{Deserialize, Serialize};
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct Project {
|
||
/// workspace.id(也是旧 projects.id,向后兼容所有关联表)
|
||
pub id: String,
|
||
/// profile.id,用于跨空间共享更新
|
||
pub profile_id: Option<String>,
|
||
/// 等于 id,前端可用于区分语义
|
||
pub workspace_id: Option<String>,
|
||
/// 关联的 git_repos.id(仓库锚点)
|
||
pub repo_id: Option<String>,
|
||
pub name: String,
|
||
pub description: Option<String>,
|
||
#[serde(rename = "type")]
|
||
pub type_: Option<String>,
|
||
// ── profile 级(项目档案,跨空间共享)────────────────────────
|
||
pub status: Option<String>,
|
||
pub priority: Option<String>,
|
||
pub tech_stack: Option<String>,
|
||
pub tags: Option<String>,
|
||
pub repo_url: Option<String>,
|
||
pub upstream_url: Option<String>,
|
||
pub default_branch: Option<String>,
|
||
pub staging_url: Option<String>,
|
||
pub prod_url: Option<String>,
|
||
pub server_note: Option<String>,
|
||
// ── workspace 级(每空间独立)───────────────────────────────
|
||
pub space_id: Option<String>,
|
||
pub wsl_path: Option<String>,
|
||
pub win_path: Option<String>,
|
||
pub platform: Option<String>,
|
||
pub recent_focus: Option<String>,
|
||
pub startup_commands: Option<String>,
|
||
pub last_push_at: Option<String>,
|
||
pub updated_at: Option<String>,
|
||
}
|
||
|
||
impl Project {
|
||
/// 从 project_workspaces JOIN project_profiles 的查询结果构造
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
profile_id: row.get("profile_id").ok(),
|
||
workspace_id: row.get("workspace_id").ok(),
|
||
repo_id: row.get("repo_id").ok(),
|
||
name: row.get("name")?,
|
||
description: row.get("description").ok(),
|
||
type_: row.get("type").ok(),
|
||
status: row.get("status").ok(),
|
||
priority: row.get("priority").ok(),
|
||
tech_stack: row.get("tech_stack").ok(),
|
||
tags: row.get("tags").ok(),
|
||
repo_url: row.get("repo_url").ok(),
|
||
upstream_url: row.get("upstream_url").ok(),
|
||
default_branch: row.get("default_branch").ok(),
|
||
staging_url: row.get("staging_url").ok(),
|
||
prod_url: row.get("prod_url").ok(),
|
||
server_note: row.get("server_note").ok(),
|
||
space_id: row.get("space_id").ok(),
|
||
wsl_path: row.get("wsl_path").ok(),
|
||
win_path: row.get("win_path").ok(),
|
||
platform: row.get("platform").ok(),
|
||
recent_focus: row.get("recent_focus").ok(),
|
||
startup_commands: row.get("startup_commands").ok(),
|
||
last_push_at: row.get("last_push_at").ok(),
|
||
updated_at: row.get("updated_at").ok(),
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct ActivityLog {
|
||
pub id: i64,
|
||
pub project_id: String,
|
||
pub summary: String,
|
||
pub created_at: Option<String>,
|
||
}
|
||
|
||
impl ActivityLog {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
project_id: row.get("project_id")?,
|
||
summary: row.get("summary")?,
|
||
created_at: row.get("created_at")?,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct ProductGroup {
|
||
pub id: String,
|
||
pub name: String,
|
||
pub description: Option<String>,
|
||
pub space_id: Option<String>,
|
||
pub sort_order: i32,
|
||
}
|
||
|
||
impl ProductGroup {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
name: row.get("name")?,
|
||
description: row.get("description")?,
|
||
space_id: row.get("space_id").ok(),
|
||
sort_order: row.get("sort_order").unwrap_or(0),
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct ProjectGroupMap {
|
||
pub project_id: String,
|
||
pub group_id: String,
|
||
pub role: Option<String>,
|
||
}
|
||
|
||
impl ProjectGroupMap {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
project_id: row.get("project_id")?,
|
||
group_id: row.get("group_id")?,
|
||
role: row.get("role")?,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct Space {
|
||
pub id: String,
|
||
pub name: String,
|
||
pub is_current: bool,
|
||
pub created_at: Option<String>,
|
||
}
|
||
|
||
impl Space {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
name: row.get("name")?,
|
||
is_current: row.get::<_, i64>("is_current")? != 0,
|
||
created_at: row.get("created_at")?,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct GitAccount {
|
||
pub id: String,
|
||
pub username: String,
|
||
pub avatar_url: Option<String>,
|
||
pub provider: String,
|
||
pub created_at: Option<String>,
|
||
}
|
||
|
||
impl GitAccount {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
username: row.get("username")?,
|
||
avatar_url: row.get("avatar_url")?,
|
||
provider: row.get("provider")?,
|
||
created_at: row.get("created_at")?,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct DeployCommand {
|
||
pub id: String,
|
||
pub project_id: String,
|
||
pub name: String,
|
||
pub command: String,
|
||
pub run_in: String,
|
||
}
|
||
|
||
impl DeployCommand {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
project_id: row.get("project_id")?,
|
||
name: row.get("name")?,
|
||
command: row.get("command")?,
|
||
run_in: row.get("run_in")?,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct CicdConfig {
|
||
pub id: String,
|
||
pub project_id: String,
|
||
pub config_type: String,
|
||
pub trigger_branch: String,
|
||
pub build_command: Option<String>,
|
||
pub node_version: Option<String>,
|
||
pub server_host: Option<String>,
|
||
pub server_user: Option<String>,
|
||
pub deploy_path: Option<String>,
|
||
pub start_command: Option<String>,
|
||
pub oss_endpoint: Option<String>,
|
||
pub created_at: Option<String>,
|
||
}
|
||
|
||
impl CicdConfig {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
project_id: row.get("project_id")?,
|
||
config_type: row.get("config_type")?,
|
||
trigger_branch: row.get("trigger_branch")?,
|
||
build_command: row.get("build_command").ok(),
|
||
node_version: row.get("node_version").ok(),
|
||
server_host: row.get("server_host").ok(),
|
||
server_user: row.get("server_user").ok(),
|
||
deploy_path: row.get("deploy_path").ok(),
|
||
start_command: row.get("start_command").ok(),
|
||
oss_endpoint: row.get("oss_endpoint").ok(),
|
||
created_at: row.get("created_at").ok(),
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct WorkflowResult {
|
||
pub content: String,
|
||
pub path: String,
|
||
pub secrets: Vec<String>,
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct GitRepo {
|
||
pub id: String,
|
||
pub repo_url: String,
|
||
pub name: String,
|
||
pub description: Option<String>,
|
||
pub default_branch: Option<String>,
|
||
pub upstream_url: Option<String>,
|
||
pub staging_url: Option<String>,
|
||
pub prod_url: Option<String>,
|
||
pub server_note: Option<String>,
|
||
pub status: Option<String>,
|
||
pub priority: Option<String>,
|
||
pub tech_stack: Option<String>,
|
||
pub last_synced_at: Option<String>,
|
||
pub created_at: Option<String>,
|
||
}
|
||
|
||
impl GitRepo {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
repo_url: row.get("repo_url")?,
|
||
name: row.get("name")?,
|
||
description: row.get("description").ok(),
|
||
default_branch: row.get("default_branch").ok(),
|
||
upstream_url: row.get("upstream_url").ok(),
|
||
staging_url: row.get("staging_url").ok(),
|
||
prod_url: row.get("prod_url").ok(),
|
||
server_note: row.get("server_note").ok(),
|
||
status: row.get("status").ok(),
|
||
priority: row.get("priority").ok(),
|
||
tech_stack: row.get("tech_stack").ok(),
|
||
last_synced_at: row.get("last_synced_at").ok(),
|
||
created_at: row.get("created_at").ok(),
|
||
})
|
||
}
|
||
}
|
||
|
||
// ── 源码库 ─────────────────────────────────────────────────────────────────
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct SourceCategory {
|
||
pub id: String,
|
||
pub name: String,
|
||
pub icon: String,
|
||
pub color: String,
|
||
pub sort_order: i32,
|
||
}
|
||
|
||
impl SourceCategory {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
name: row.get("name")?,
|
||
icon: row.get::<_, String>("icon").unwrap_or_else(|_| "📁".into()),
|
||
color: row.get::<_, String>("color").unwrap_or_else(|_| "#6B7280".into()),
|
||
sort_order: row.get("sort_order").unwrap_or(0),
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct SourceProject {
|
||
pub id: String,
|
||
pub name: String,
|
||
pub description: Option<String>,
|
||
pub category_id: Option<String>,
|
||
pub language: Option<String>,
|
||
pub tags: Option<String>,
|
||
pub repo_url: Option<String>,
|
||
pub introduction: Option<String>,
|
||
pub readme: Option<String>,
|
||
pub notes: Option<String>,
|
||
pub created_at: Option<String>,
|
||
pub updated_at: Option<String>,
|
||
// join 字段
|
||
pub category_name: Option<String>,
|
||
pub version_count: Option<i32>,
|
||
}
|
||
|
||
impl SourceProject {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
name: row.get("name")?,
|
||
description: row.get("description").ok(),
|
||
category_id: row.get("category_id").ok(),
|
||
language: row.get("language").ok(),
|
||
tags: row.get("tags").ok(),
|
||
repo_url: row.get("repo_url").ok(),
|
||
introduction: row.get("introduction").ok(),
|
||
readme: row.get("readme").ok(),
|
||
notes: row.get("notes").ok(),
|
||
created_at: row.get("created_at").ok(),
|
||
updated_at: row.get("updated_at").ok(),
|
||
category_name: row.get("category_name").ok(),
|
||
version_count: row.get("version_count").ok(),
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct SourceVersion {
|
||
pub id: String,
|
||
pub project_id: String,
|
||
pub version: String,
|
||
pub file_path: String,
|
||
pub archive_path: Option<String>,
|
||
pub file_size: i64,
|
||
pub is_primary: bool,
|
||
pub dir_tree: Option<String>,
|
||
pub notes: Option<String>,
|
||
pub imported_at: Option<String>,
|
||
}
|
||
|
||
impl SourceVersion {
|
||
pub fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||
Ok(Self {
|
||
id: row.get("id")?,
|
||
project_id: row.get("project_id")?,
|
||
version: row.get("version")?,
|
||
file_path: row.get("file_path")?,
|
||
archive_path: row.get("archive_path").ok(),
|
||
file_size: row.get("file_size").unwrap_or(0),
|
||
is_primary: row.get::<_, i64>("is_primary").unwrap_or(0) != 0,
|
||
dir_tree: row.get("dir_tree").ok(),
|
||
notes: row.get("notes").ok(),
|
||
imported_at: row.get("imported_at").ok(),
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct DirEntry {
|
||
pub name: String,
|
||
#[serde(rename = "type")]
|
||
pub type_: String,
|
||
pub children_count: Option<i32>,
|
||
}
|