1
+//! Plain-text renderers for the headless interface.
2
+//!
3
+//! The default (headless) interface prints line-based plain text instead of
4
+//! taking over the terminal with a full-screen `ratatui` view. These renderers
5
+//! are the headless counterparts to the `show_*_tui` entry points; a handler
6
+//! chooses between them with [`crate::interface::is_tui`].
7
+
8
+use {
9
+ crate::ui::highlight,
10
+ smbcloud_model::{
11
+ account::User,
12
+ project::{Deployment, Project},
13
+ },
14
+ tabled::{builder::Builder, settings::Style},
15
+};
16
+
17
+/// Render the authenticated user's account details as a plain key/value block.
18
+pub fn render_user(user: &User) {
19
+ println!();
20
+ println!(" {}", highlight(&user.email));
21
+ println!(" {:<14}{}", "ID", user.id);
22
+ println!(
23
+ " {:<14}{}",
24
+ "Member since",
25
+ user.created_at.format("%Y-%m-%d")
26
+ );
27
+ println!(
28
+ " {:<14}{}",
29
+ "Last updated",
30
+ user.updated_at.format("%Y-%m-%d")
31
+ );
32
+ println!();
33
+}
34
+
35
+/// Render the project list as a plain table.
36
+pub fn render_projects(projects: &[Project]) {
37
+ if projects.is_empty() {
38
+ println!("No projects found.");
39
+ return;
40
+ }
41
+ let mut builder = Builder::default();
42
+ builder.push_record(["ID", "Name", "Runner", "Repository"]);
43
+ for project in projects {
44
+ builder.push_record([
45
+ project.id.to_string(),
46
+ project.name.clone(),
47
+ project.runner.to_string(),
48
+ project.repository.clone().unwrap_or_default(),
49
+ ]);
50
+ }
51
+ let mut table = builder.build();
52
+ table.with(Style::rounded());
53
+ println!("{table}");
54
+}
55
+
56
+/// Render a single project's details as a plain key/value block.
57
+pub fn render_project_detail(project: &Project) {
58
+ println!();
59
+ println!(" {}", highlight(&project.name));
60
+ println!(" {:<16}{}", "ID", project.id);
61
+ println!(" {:<16}{}", "Runner", project.runner);
62
+ println!(" {:<16}{}", "Deployment", project.deployment_method);
63
+ field_opt("Repository", project.repository.as_deref());
64
+ field_opt("Description", project.description.as_deref());
65
+ field_opt("Path", project.path.as_deref());
66
+ println!();
67
+}
68
+
69
+/// Render the deployment list as a plain table.
70
+pub fn render_deployments(deployments: &[Deployment]) {
71
+ if deployments.is_empty() {
72
+ println!("No deployments found.");
73
+ return;
74
+ }
75
+ let mut builder = Builder::default();
76
+ builder.push_record(["ID", "Commit", "Status", "Created"]);
77
+ for deployment in deployments {
78
+ builder.push_record([
79
+ deployment.id.to_string(),
80
+ deployment.commit_hash.chars().take(8).collect::<String>(),
81
+ deployment.status.to_string(),
82
+ deployment.created_at.format("%Y-%m-%d %H:%M").to_string(),
83
+ ]);
84
+ }
85
+ let mut table = builder.build();
86
+ table.with(Style::rounded());
87
+ println!("{table}");
88
+}
89
+
90
+/// Render a single deployment's details as a plain key/value block.
91
+pub fn render_deployment_detail(deployment: &Deployment) {
92
+ println!();
93
+ println!(" {}", highlight(&format!("Deployment #{}", deployment.id)));
94
+ println!(" {:<16}{}", "Project", deployment.project_id);
95
+ println!(" {:<16}{}", "Commit", deployment.commit_hash);
96
+ println!(" {:<16}{}", "Status", deployment.status);
97
+ field_opt("App", deployment.frontend_app_name.as_deref());
98
+ println!(
99
+ " {:<16}{}",
100
+ "Created",
101
+ deployment.created_at.format("%Y-%m-%d %H:%M")
102
+ );
103
+ println!();
104
+}
105
+
106
+/// Print a `label: value` line only when the optional value is present.
107
+fn field_opt(label: &str, value: Option<&str>) {
108
+ if let Some(value) = value {
109
+ if !value.is_empty() {
110
+ println!(" {:<16}{}", label, value);
111
+ }
112
+ }
113
+}