Add app update detection and multi-folder project support
Feature 1 - Update Detection: Query Gitea releases API on startup (3s
delay) and every 24h, compare patch versions by platform, show pulsing
"Update" button in TopBar with dialog for release notes/downloads.
Settings: auto-check toggle, manual check, dismiss per-version.
Feature 2 - Multi-Folder Projects: Replace single `path` with
`paths: Vec<ProjectPath>` (host_path + mount_name). Each folder mounts
to `/workspace/{mount_name}`. Auto-migrate old single-path JSON on load.
Container recreation via paths-fingerprint label. AddProjectDialog and
ProjectCard support add/remove/edit of multiple folders.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ImageSource {
|
||||
@@ -52,6 +56,10 @@ pub struct AppSettings {
|
||||
pub global_aws: GlobalAwsSettings,
|
||||
#[serde(default)]
|
||||
pub global_claude_instructions: Option<String>,
|
||||
#[serde(default = "default_true")]
|
||||
pub auto_check_updates: bool,
|
||||
#[serde(default)]
|
||||
pub dismissed_update_version: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for AppSettings {
|
||||
@@ -65,6 +73,8 @@ impl Default for AppSettings {
|
||||
custom_image_name: None,
|
||||
global_aws: GlobalAwsSettings::default(),
|
||||
global_claude_instructions: None,
|
||||
auto_check_updates: true,
|
||||
dismissed_update_version: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
pub mod project;
|
||||
pub mod container_config;
|
||||
pub mod app_settings;
|
||||
pub mod update_info;
|
||||
|
||||
pub use project::*;
|
||||
pub use container_config::*;
|
||||
pub use app_settings::*;
|
||||
pub use update_info::*;
|
||||
|
||||
@@ -6,11 +6,17 @@ pub struct EnvVar {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ProjectPath {
|
||||
pub host_path: String,
|
||||
pub mount_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Project {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub paths: Vec<ProjectPath>,
|
||||
pub container_id: Option<String>,
|
||||
pub status: ProjectStatus,
|
||||
pub auth_mode: AuthMode,
|
||||
@@ -91,12 +97,12 @@ pub struct BedrockConfig {
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn new(name: String, path: String) -> Self {
|
||||
pub fn new(name: String, paths: Vec<ProjectPath>) -> Self {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
Self {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
name,
|
||||
path,
|
||||
paths,
|
||||
container_id: None,
|
||||
status: ProjectStatus::Stopped,
|
||||
auth_mode: AuthMode::default(),
|
||||
@@ -116,4 +122,29 @@ impl Project {
|
||||
pub fn container_name(&self) -> String {
|
||||
format!("triple-c-{}", self.id)
|
||||
}
|
||||
|
||||
/// Migrate a project JSON value from old single-`path` format to new `paths` format.
|
||||
/// If the value already has `paths`, it is returned unchanged.
|
||||
pub fn migrate_from_value(mut val: serde_json::Value) -> serde_json::Value {
|
||||
if let Some(obj) = val.as_object_mut() {
|
||||
if obj.contains_key("paths") {
|
||||
return val;
|
||||
}
|
||||
if let Some(path_val) = obj.remove("path") {
|
||||
let path_str = path_val.as_str().unwrap_or("").to_string();
|
||||
let mount_name = path_str
|
||||
.trim_end_matches(['/', '\\'])
|
||||
.rsplit(['/', '\\'])
|
||||
.next()
|
||||
.unwrap_or("workspace")
|
||||
.to_string();
|
||||
let project_path = serde_json::json!([{
|
||||
"host_path": path_str,
|
||||
"mount_name": if mount_name.is_empty() { "workspace".to_string() } else { mount_name },
|
||||
}]);
|
||||
obj.insert("paths".to_string(), project_path);
|
||||
}
|
||||
}
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
37
app/src-tauri/src/models/update_info.rs
Normal file
37
app/src-tauri/src/models/update_info.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Info returned to the frontend about an available update.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UpdateInfo {
|
||||
pub version: String,
|
||||
pub tag_name: String,
|
||||
pub release_url: String,
|
||||
pub body: String,
|
||||
pub assets: Vec<ReleaseAsset>,
|
||||
pub published_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReleaseAsset {
|
||||
pub name: String,
|
||||
pub browser_download_url: String,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
/// Gitea API release response (internal).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct GiteaRelease {
|
||||
pub tag_name: String,
|
||||
pub html_url: String,
|
||||
pub body: String,
|
||||
pub assets: Vec<GiteaAsset>,
|
||||
pub published_at: String,
|
||||
}
|
||||
|
||||
/// Gitea API asset response (internal).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct GiteaAsset {
|
||||
pub name: String,
|
||||
pub browser_download_url: String,
|
||||
pub size: u64,
|
||||
}
|
||||
Reference in New Issue
Block a user