feat(ui): add colored output and tables for project info

This commit is contained in:
hdbg
2025-12-04 19:07:57 +01:00
parent bf0ae1b3ee
commit bc37b58d80
6 changed files with 222 additions and 34 deletions

151
Cargo.lock generated
View File

@@ -255,6 +255,26 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "comfy-table"
version = "7.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b"
dependencies = [
"crossterm",
"unicode-segmentation",
"unicode-width 0.2.2",
]
[[package]]
name = "console"
version = "0.16.1"
@@ -274,6 +294,29 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossterm"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [
"bitflags",
"crossterm_winapi",
"document-features",
"parking_lot",
"rustix",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "darling"
version = "0.21.3"
@@ -330,6 +373,15 @@ dependencies = [
"syn",
]
[[package]]
name = "document-features"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
dependencies = [
"litrs",
]
[[package]]
name = "dyn-clone"
version = "1.0.20"
@@ -849,6 +901,12 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "litrs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -1006,6 +1064,8 @@ version = "0.0.1"
dependencies = [
"bollard",
"clap",
"colored",
"comfy-table",
"futures",
"indicatif",
"miette",
@@ -1876,13 +1936,22 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
"windows-targets 0.53.5",
]
[[package]]
@@ -1894,6 +1963,22 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
@@ -1901,58 +1986,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"

View File

@@ -9,6 +9,8 @@ license = "MIT"
[dependencies]
bollard = "0.19.4"
clap = { version = "4.5.53", features = ["derive"] }
colored = "3.0.0"
comfy-table = "7.2.1"
futures = "0.3.31"
indicatif = { version = "0.18.3", features = ["improved_unicode"] }
miette = { version = "7.6.0", features = ["fancy"] }

View File

@@ -1,3 +1,3 @@
version = "18.1"
password = "GypnQVF1uV23DWvp"
password = "a7BASi7P3gCgc0Xx"
port = 5433

View File

@@ -1,3 +0,0 @@
version = "18.1"
password = "P8t42UQ53iIza2Ic"
port = 5432

View File

@@ -1,5 +1,9 @@
use std::time::Duration;
use miette::{bail, miette};
use colored::Colorize;
use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table, presets::UTF8_FULL};
use miette::Result;
use crate::{
@@ -12,7 +16,7 @@ mod docker;
mod utils;
const MAX_RETRIES: u32 = 10;
const VERIFY_DURATION_SECS: u64 = 10;
const VERIFY_DURATION_SECS: u64 = 5;
pub struct Controller {
docker: DockerController,
@@ -35,7 +39,7 @@ impl Controller {
return self.reconcile(project).await;
}
println!("Initializing new pgd project...");
println!("{}", "Initializing new pgd project...".cyan());
let mut versions = self.docker.available_versions().await?;
versions.sort();
@@ -50,15 +54,48 @@ impl Controller {
};
let project = Project::new(config)?;
println!("Created pgd.toml in {}", project.path.display());
println!(" Project: {}", project.name);
println!(" PostgreSQL version: {}", project.config.version);
println!(" Port: {}", project.config.port);
println!(" Password: {}", "*".repeat(project.config.password.len()));
println!(
"\n{} {}\n",
"Created pgd.toml in",
project.path.display().to_string().bright_white().bold()
);
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_style(comfy_table::TableComponent::MiddleIntersections, ' ')
.set_header(vec![
Cell::new("Instance Configuration").add_attribute(Attribute::Bold),
]);
use comfy_table::TableComponent::*;
table.set_style(TopLeftCorner, '╭');
table.set_style(TopRightCorner, '╮');
table.set_style(BottomLeftCorner, '╰');
table.set_style(BottomRightCorner, '╯');
table.add_row(vec![
Cell::new("Project").fg(Color::White),
Cell::new(&project.name).add_attribute(Attribute::Bold),
]);
table.add_row(vec![
Cell::new("PostgreSQL Version").fg(Color::White),
Cell::new(project.config.version.to_string()).add_attribute(Attribute::Bold),
]);
table.add_row(vec![
Cell::new("Port").fg(Color::White),
Cell::new(project.config.port.to_string()).add_attribute(Attribute::Bold),
]);
table.add_row(vec![
Cell::new("Password").fg(Color::White),
Cell::new("*".repeat(project.config.password.len())).fg(Color::DarkGrey),
]);
println!("{table}");
self.reconcile(&project).await?;
println!("\nProject initialized successfully!");
println!("\n{}", "Project initialized successfully!".green().bold());
Ok(())
}
@@ -98,32 +135,45 @@ impl Controller {
.is_container_running_by_id(&container_id)
.await?
{
println!("Container is already running");
println!("{}", "Container is already running".white());
return Ok(());
}
use indicatif::{ProgressBar, ProgressStyle};
let spinner = ProgressBar::new_spinner();
spinner.enable_steady_tick(Duration::from_millis(100));
spinner.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {msg}")
.template("{spinner:.cyan} {msg}")
.unwrap(),
);
spinner.set_message("Starting container...");
for attempt in 1..=MAX_RETRIES {
spinner.set_message(format!("Starting container (attempt {}/{})", attempt, MAX_RETRIES));
spinner.set_message(format!(
"Starting container (attempt {}/{})",
attempt, MAX_RETRIES
));
let result = self.try_starting_container(&container_id, &spinner).await;
match result {
Ok(_) => {
spinner.finish_with_message("Container started successfully");
spinner.finish_with_message(format!(
"{}",
"Container started successfully".green().bold()
));
return Ok(());
}
Err(err) => {
spinner.set_message(format!("Attempt {}/{} failed: {}", attempt, MAX_RETRIES, err));
spinner.set_message(format!(
"{} {}/{} failed: {}",
"Attempt".yellow(),
attempt,
MAX_RETRIES,
err
));
}
}
@@ -132,7 +182,7 @@ impl Controller {
}
}
spinner.finish_with_message("Failed to start container");
spinner.finish_with_message(format!("{}", "Failed to start container".red()));
miette::bail!("Failed to start container after {} attempts", MAX_RETRIES)
}
@@ -144,18 +194,19 @@ impl Controller {
match self.docker.start_container_by_id(container_id).await {
Ok(_) => {
spinner.set_message(format!(
"Verifying container is running ({}s)...",
"{} ({}s)...",
"Verifying container is running".cyan(),
VERIFY_DURATION_SECS
));
for i in 0..VERIFY_DURATION_SECS {
spinner.set_message(format!(
"Verifying container stability ({}/{}s)",
"{} ({}/{}s)",
"Verifying container stability".cyan(),
i + 1,
VERIFY_DURATION_SECS
));
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
spinner.tick();
}
if self.docker.is_container_running_by_id(container_id).await? {
@@ -175,7 +226,11 @@ impl Controller {
project: &Project,
state: &mut StateManager,
) -> Result<String, miette::Error> {
println!("Creating container {}...", project.container_name());
println!(
"{} {}",
"Creating container".cyan(),
project.container_name().yellow()
);
let id = self
.docker
.create_postgres_container(
@@ -185,7 +240,7 @@ impl Controller {
project.config.port,
)
.await?;
println!("Container created successfully");
println!("{}", "Container created successfully".green());
state.set(
project.name.clone(),
crate::state::InstanceState::new(

View File

@@ -9,6 +9,7 @@ use bollard::{
},
secret::ContainerCreateBody,
};
use colored::Colorize;
use indicatif::MultiProgress;
use miette::{Context, IntoDiagnostic, Result};
use tracing::info;
@@ -58,11 +59,11 @@ impl DockerController {
let multi = MultiProgress::new();
println!("Downloading {image}");
println!("{} {}", "Downloading".cyan(), image.yellow());
download::perform_download(multi, download_progress).await?;
println!("Download complete!");
println!("{}", "Download complete!".green().bold());
Ok(())
}