5 files changed
+183
-37
crates/cli/src/project/cli.rs
+6
-1
@@ -26,5 +26,10 @@ pub enum Commands {
26
Use {
27
#[clap(short, long, required = true)]
28
id: String,
29
- },
29
+ },
30
+ #[clap(about = "Manage project deployment. Pass --id to get detail deployment.")]
31
+ Deployment {
32
+ #[clap(short, long)]
33
+ id: Option<String>,
34
+ }
35
}
crates/cli/src/project/deployment.rs
new
+84
@@ -0,0 +1,84 @@
1
+use smbcloud_networking::environment::Environment;
2
+use smbcloud_networking_project::crud_project_deployment_read::{get_deployment_detail, list_deployments};
3
+use spinners::Spinner;
4
+use crate::{cli::CommandResult, deploy::config::check_config, ui::{succeed_message, succeed_symbol}};
5
+use anyhow::{anyhow, Result};
6
+use smbcloud_model::project::Config;
7
+use std::fs;
8
+use dirs;
9
+use tabled::{Table, Tabled};
10
+use smbcloud_model::project::Deployment;
11
+
12
+pub(crate) async fn process_deployment(env: Environment, id: Option<String>) -> Result<CommandResult> {
13
+
14
+ let mut spinner: Spinner = Spinner::new(
15
+ spinners::Spinners::Hamburger,
16
+ succeed_message("Loading"),
17
+ );
18
+ // Load project id from .smb/config.toml
19
+ let config = check_config().await?;
20
+
21
+ if let Some(deployment_id) = id {
22
+ // Show detail for a specific deployment
23
+ let deployment_id: i32 = deployment_id.parse()?;
24
+ let deployment = get_deployment_detail(env, config.repository.id, deployment_id).await?;
25
+ spinner.stop_and_persist(&succeed_symbol(), succeed_message("Loaded"));
26
+ show_deployment_detail(&deployment);
27
+ } else {
28
+ // List all deployments for the project
29
+ let deployments = list_deployments(env, config.repository.id).await?;
30
+ spinner.stop_and_persist(&succeed_symbol(), succeed_message("Load all deployments"));
31
+ show_project_deployments(&deployments);
32
+ };
33
+
34
+ Ok(CommandResult { spinner: Spinner::new(
35
+ spinners::Spinners::Hamburger,
36
+ succeed_message("Loading."),
37
+ ), symbol: succeed_symbol(), msg: succeed_message("Loaded") })
38
+}
39
+
40
+// Helper struct for table display
41
+#[derive(Tabled)]
42
+struct DeploymentRow {
43
+ id: i32,
44
+ commit_hash: String,
45
+ status: String,
46
+ created_at: String,
47
+ updated_at: String,
48
+}
49
+
50
+pub fn show_project_deployments(deployments: &[Deployment]) {
51
+ let rows: Vec<DeploymentRow> = deployments
52
+ .iter()
53
+ .map(|d| DeploymentRow {
54
+ id: d.id,
55
+ commit_hash: d.commit_hash.clone(),
56
+ status: format!("{:?}", d.status),
57
+ created_at: d.created_at.format("%Y-%m-%d %H:%M:%S").to_string(),
58
+ updated_at: d.updated_at.format("%Y-%m-%d %H:%M:%S").to_string(),
59
+ })
60
+ .collect();
61
+
62
+ let table = Table::new(rows);
63
+ println!("{table}");
64
+}
65
+
66
+#[derive(Tabled)]
67
+struct DeploymentDetailRow {
68
+ field: &'static str,
69
+ value: String,
70
+}
71
+
72
+pub fn show_deployment_detail(deployment: &Deployment) {
73
+ let rows = vec![
74
+ DeploymentDetailRow { field: "ID", value: deployment.id.to_string() },
75
+ DeploymentDetailRow { field: "Project ID", value: deployment.project_id.to_string() },
76
+ DeploymentDetailRow { field: "Commit Hash", value: deployment.commit_hash.clone() },
77
+ DeploymentDetailRow { field: "Status", value: format!("{:?}", deployment.status) },
78
+ DeploymentDetailRow { field: "Created At", value: deployment.created_at.format("%Y-%m-%d %H:%M:%S").to_string() },
79
+ DeploymentDetailRow { field: "Updated At", value: deployment.updated_at.format("%Y-%m-%d %H:%M:%S").to_string() },
80
+ ];
81
+
82
+ let table = Table::new(rows);
83
+ println!("{table}");
84
+}
\ No newline at end of file
crates/cli/src/project/mod.rs
+36
-36
@@ -2,13 +2,12 @@ pub mod cli;
2
pub mod crud_create;
3
pub mod crud_delete;
4
pub mod crud_read;
5
+mod deployment;
6
7
use self::cli::Commands;
8
use crate::{
9
cli::CommandResult,
9
- project::crud_create::process_project_init,
10
- project::crud_delete::process_project_delete,
11
- project::crud_read::{process_project_list, process_project_show},
10
+ project::{crud_create::process_project_init, crud_delete::process_project_delete, crud_read::{process_project_list, process_project_show}, deployment::process_deployment},
11
ui::{succeed_message, succeed_symbol},
12
};
13
use anyhow::{anyhow, Result};
@@ -26,39 +25,40 @@ pub async fn process_project(env: Environment, commands: Commands) -> Result<Com
25
Commands::Show { id } => process_project_show(env, id).await,
26
Commands::Delete { id } => process_project_delete(env, id).await,
27
Commands::Use { id } => {
29
- let project = get_project(env, id).await?;
30
-
31
- let config = Config {
32
- current_project: Some(project),
33
- current_auth_app: None,
34
- };
35
-
36
- let spinner = Spinner::new(
37
- spinners::Spinners::SimpleDotsScrolling,
38
- succeed_message("Loading"),
39
- );
40
- match home::home_dir() {
41
- Some(path) => {
42
- debug!("{}", path.to_str().unwrap());
43
- let mut file = OpenOptions::new()
44
- .create(true)
45
- .truncate(true)
46
- .write(true)
47
- .open([path.to_str().unwrap(), "/.smb/config.json"].join(""))?;
48
- let json = serde_json::to_string(&config)?;
49
- file.write_all(json.as_bytes())?;
50
-
51
- Ok(CommandResult {
52
- spinner,
53
- symbol: succeed_symbol(),
54
- msg: succeed_message("Use project successful."),
55
- })
56
- }
57
- None => {
58
- let error = anyhow!("Failed to get home directory.");
59
- Err(error)
60
- }
28
+ let project = get_project(env, id).await?;
29
+
30
+ let config = Config {
31
+ current_project: Some(project),
32
+ current_auth_app: None,
33
+ };
34
+
35
+ let spinner = Spinner::new(
36
+ spinners::Spinners::SimpleDotsScrolling,
37
+ succeed_message("Loading"),
38
+ );
39
+ match home::home_dir() {
40
+ Some(path) => {
41
+ debug!("{}", path.to_str().unwrap());
42
+ let mut file = OpenOptions::new()
43
+ .create(true)
44
+ .truncate(true)
45
+ .write(true)
46
+ .open([path.to_str().unwrap(), "/.smb/config.json"].join(""))?;
47
+ let json = serde_json::to_string(&config)?;
48
+ file.write_all(json.as_bytes())?;
49
+
50
+ Ok(CommandResult {
51
+ spinner,
52
+ symbol: succeed_symbol(),
53
+ msg: succeed_message("Use project successful."),
54
+ })
55
+ }
56
+ None => {
57
+ let error = anyhow!("Failed to get home directory.");
58
+ Err(error)
59
+ }
60
+ }
61
}
62
- }
62
+ Commands::Deployment { id } => process_deployment(env, id).await,
63
}
64
}
crates/smbcloud-networking-project/src/crud_project_deployment_read.rs
new
+56
@@ -0,0 +1,56 @@
1
+use crate::build_project_deployment_index;
2
+use anyhow::{anyhow, Result};
3
+use reqwest::Client;
4
+use smbcloud_model::project::Deployment;
5
+use smbcloud_networking::{environment::Environment, get_smb_token};
6
+
7
+pub async fn list_deployments(
8
+ env: Environment,
9
+ project_id: i32,
10
+) -> Result<Vec<Deployment>> {
11
+ let token = get_smb_token(env).await?;
12
+
13
+ let response = Client::new()
14
+ .get(build_project_deployment_index(env, project_id.to_string()))
15
+ .header("Authorization", token)
16
+ .header("User-agent", "smbcloud-cli")
17
+ .send()
18
+ .await?;
19
+
20
+ match response.status() {
21
+ reqwest::StatusCode::OK => {
22
+ let deployments: Vec<Deployment> = response.json().await?;
23
+ Ok(deployments)
24
+ }
25
+ _ => Err(anyhow!("Failed to list deployments.")),
26
+ }
27
+}
28
+
29
+pub async fn get_deployment_detail(
30
+ env: Environment,
31
+ project_id: i32,
32
+ deployment_id: i32,
33
+) -> Result<Deployment> {
34
+ let token = get_smb_token(env).await?;
35
+
36
+ let url = format!(
37
+ "{}/{}",
38
+ build_project_deployment_index(env, project_id.to_string()),
39
+ deployment_id
40
+ );
41
+
42
+ let response = Client::new()
43
+ .get(url)
44
+ .header("Authorization", token)
45
+ .header("User-agent", "smbcloud-cli")
46
+ .send()
47
+ .await?;
48
+
49
+ match response.status() {
50
+ reqwest::StatusCode::OK => {
51
+ let deployment: Deployment = response.json().await?;
52
+ Ok(deployment)
53
+ }
54
+ _ => Err(anyhow!("Failed to get deployment detail.")),
55
+ }
56
+}
\ No newline at end of file
crates/smbcloud-networking-project/src/lib.rs
+1
@@ -1,4 +1,5 @@
1
pub mod crud_project_deployment_create;
2
+pub mod crud_project_deployment_read;
3
4
use anyhow::{anyhow, Result};
5
use log::debug;