Skip to content

Commit fe80ec9

Browse files
committed
Add beatiful replica list
1 parent ac4d4c1 commit fe80ec9

File tree

6 files changed

+145
-29
lines changed

6 files changed

+145
-29
lines changed

BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rust_binary(
2929
"@crates//:futures",
3030
"@crates//:glob",
3131
"@crates//:home",
32+
"@crates//:itertools",
3233
"@crates//:rpassword",
3334
"@crates//:rustyline",
3435
"@crates//:sentry",

dependencies/typedb/repositories.bzl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
66

77
def typedb_dependencies():
8-
# TODO: Return ref after merge to master, currently points to 'raft-dependencies-addition'
8+
# TODO: Return ref after merge to master
99
git_repository(
1010
name = "typedb_dependencies",
1111
remote = "https://github.com/typedb/typedb-dependencies",
1212
commit = "19a70bcad19b9a28814016f183ac3e3a23c1ff0d", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_dependencies
1313
)
1414

1515
def typedb_driver():
16-
# TODO: Return typedb
16+
# TODO: Return ref after merge to master
17+
# native.local_repository(
18+
# name = "typedb_driver",
19+
# path = "../typedb-driver",
20+
# )
1721
git_repository(
1822
name = "typedb_driver",
1923
remote = "https://github.com/typedb/typedb-driver",

src/cli.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ pub struct Args {
4747
pub replication_disabled: bool,
4848

4949
// TODO: Add cluster-related retries/attempts flags from Driver Options?
50-
5150
/// Username for authentication
5251
#[arg(long, value_name = USERNAME_VALUE_NAME)]
5352
pub username: Option<String>,

src/main.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
use std::{
8+
collections::HashMap,
89
env,
910
env::temp_dir,
1011
error::Error,
@@ -17,7 +18,7 @@ use std::{
1718
rc::Rc,
1819
sync::Arc,
1920
};
20-
use std::collections::HashMap;
21+
2122
use clap::Parser;
2223
use home::home_dir;
2324
use rustyline::error::ReadlineError;
@@ -29,8 +30,9 @@ use crate::{
2930
completions::{database_name_completer_fn, file_completer},
3031
operations::{
3132
database_create, database_create_init, database_delete, database_export, database_import, database_list,
32-
database_schema, transaction_close, transaction_commit, transaction_query, transaction_read,
33-
transaction_rollback, transaction_schema, transaction_source, transaction_write, user_create, user_delete,
33+
database_schema, replica_deregister, replica_list, replica_primary, replica_register, server_version,
34+
transaction_close, transaction_commit, transaction_query, transaction_read, transaction_rollback,
35+
transaction_schema, transaction_source, transaction_write, user_create, user_delete,
3436
user_list, user_update_password,
3537
},
3638
repl::{
@@ -40,7 +42,6 @@ use crate::{
4042
},
4143
runtime::BackgroundRuntime,
4244
};
43-
use crate::operations::{replica_deregister, replica_list, replica_primary, replica_register, server_version};
4445

4546
mod cli;
4647
mod completions;
@@ -158,7 +159,11 @@ fn main() {
158159
}
159160
let tls_root_ca_path = args.tls_root_ca.as_ref().map(|value| Path::new(value));
160161
let runtime = BackgroundRuntime::new();
161-
let driver_options = DriverOptions::new().use_replication(!args.replication_disabled).tls_enabled(!args.tls_disabled).tls_root_ca(tls_root_ca_path).unwrap();
162+
let driver_options = DriverOptions::new()
163+
.use_replication(!args.replication_disabled)
164+
.tls_enabled(!args.tls_disabled)
165+
.tls_root_ca(tls_root_ca_path)
166+
.unwrap();
162167
let driver = match runtime.run(TypeDBDriver::new(
163168
address_info.addresses,
164169
Credentials::new(&username, args.password.as_ref().unwrap()),
@@ -327,8 +332,8 @@ fn execute_commands(context: &mut ConsoleContext, mut input: &str, must_log_comm
327332
}
328333

329334
fn entry_repl(driver: Arc<TypeDBDriver>, runtime: BackgroundRuntime) -> Repl<ConsoleContext> {
330-
let server_commands = Subcommand::new("server")
331-
.add(CommandLeaf::new("version", "Retrieve server version.", server_version));
335+
let server_commands =
336+
Subcommand::new("server").add(CommandLeaf::new("version", "Retrieve server version.", server_version));
332337

333338
let replica_commands = Subcommand::new("replica")
334339
.add(CommandLeaf::new("list", "List replicas.", replica_list))
@@ -534,11 +539,14 @@ struct AddressInfo {
534539

535540
fn parse_addresses(args: &Args) -> AddressInfo {
536541
if let Some(address) = &args.address {
537-
AddressInfo {only_https: is_https_address(address), addresses: Addresses::try_from_address_str(address).unwrap() }
542+
AddressInfo {
543+
only_https: is_https_address(address),
544+
addresses: Addresses::try_from_address_str(address).unwrap(),
545+
}
538546
} else if let Some(addresses) = &args.addresses {
539547
let split = addresses.split(',').map(str::to_string).collect::<Vec<_>>();
540548
let only_https = split.iter().all(|address| is_https_address(address));
541-
AddressInfo {only_https, addresses: Addresses::try_from_addresses_str(split).unwrap() }
549+
AddressInfo { only_https, addresses: Addresses::try_from_addresses_str(split).unwrap() }
542550
} else if let Some(translation) = &args.address_translation {
543551
let mut map = HashMap::new();
544552
let mut only_https = true;
@@ -550,7 +558,7 @@ fn parse_addresses(args: &Args) -> AddressInfo {
550558
map.insert(public_address.to_string(), private_address.to_string());
551559
}
552560
println!("Translation map:: {map:?}"); // TODO: Remove
553-
AddressInfo {only_https, addresses: Addresses::try_from_translation_str(map).unwrap() }
561+
AddressInfo { only_https, addresses: Addresses::try_from_translation_str(map).unwrap() }
554562
} else {
555563
panic!("At least one of --address, --addresses, or --address-translation must be provided.");
556564
}

src/operations.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use ureq;
1616

1717
use crate::{
1818
constants::DEFAULT_TRANSACTION_TIMEOUT,
19-
printer::{print_document, print_row},
19+
printer::{print_document, print_replicas_table, print_row},
2020
repl::command::{parse_one_query, CommandResult, ReplError},
2121
transaction_repl, ConsoleContext,
2222
};
@@ -218,17 +218,17 @@ pub(crate) fn replica_list(context: &mut ConsoleContext, _input: &[String]) -> C
218218
if replicas.is_empty() {
219219
println!("No replicas are present.");
220220
} else {
221-
for replica in replicas {
222-
println!("{}", replica.address());
223-
}
221+
print_replicas_table(replicas);
224222
}
225223
Ok(())
226224
})
227225
}
228226

229227
pub(crate) fn replica_primary(context: &mut ConsoleContext, _input: &[String]) -> CommandResult {
230228
let driver = context.driver.clone();
231-
let primary_replica = driver.primary_replica();
229+
let primary_replica = context
230+
.background_runtime
231+
.run(async move { driver.primary_replica().await.map_err(|err| Box::new(err) as Box<dyn Error + Send>) })?;
232232
if let Some(primary_replica) = primary_replica {
233233
println!("{}", primary_replica.address());
234234
} else {
@@ -239,8 +239,9 @@ pub(crate) fn replica_primary(context: &mut ConsoleContext, _input: &[String]) -
239239

240240
pub(crate) fn replica_register(context: &mut ConsoleContext, input: &[String]) -> CommandResult {
241241
let driver = context.driver.clone();
242-
let replica_id: u64 = input[0].parse().map_err(|_| Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) })
243-
as Box<dyn Error + Send>)?;
242+
let replica_id: u64 = input[0].parse().map_err(|_| {
243+
Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) }) as Box<dyn Error + Send>
244+
})?;
244245
let address = input[1].clone();
245246
context
246247
.background_runtime
@@ -252,8 +253,9 @@ pub(crate) fn replica_register(context: &mut ConsoleContext, input: &[String]) -
252253

253254
pub(crate) fn replica_deregister(context: &mut ConsoleContext, input: &[String]) -> CommandResult {
254255
let driver = context.driver.clone();
255-
let replica_id: u64 = input[0].parse().map_err(|_| Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) })
256-
as Box<dyn Error + Send>)?;
256+
let replica_id: u64 = input[0].parse().map_err(|_| {
257+
Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) }) as Box<dyn Error + Send>
258+
})?;
257259
context
258260
.background_runtime
259261
.run(async move { driver.deregister_replica(replica_id).await })

src/printer.rs

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,44 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7+
use std::collections::HashSet;
8+
79
use clap::builder::styling::{AnsiColor, Color, Style};
10+
use itertools::Itertools;
811
use typedb_driver::{
912
answer::{ConceptDocument, ConceptRow},
1013
concept::{Concept, Value},
11-
IID,
14+
Replica, ReplicaRole, ServerReplica, IID,
1215
};
1316

1417
const TABLE_INDENT: &'static str = " ";
1518
const CONTENT_INDENT: &'static str = " ";
1619
const TABLE_DASHES: usize = 7;
1720

18-
pub const ERROR_STYLE: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red))).bold();
19-
pub const WARNING_STYLE: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow))).bold();
20-
pub const ARGUMENT_STYLE: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
21+
pub const STYLE_RED: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red)));
22+
pub const STYLE_GREEN: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green)));
23+
pub const STYLE_ERROR: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red))).bold();
24+
pub const STYLE_WARNING: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow))).bold();
25+
pub const STYLE_ARGUMENT: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
2126

2227
#[macro_export]
2328
macro_rules! format_error {
2429
($($arg:tt)*) => {
25-
$crate::format_colored!($crate::printer::ERROR_STYLE, $($arg)*)
30+
$crate::format_colored!($crate::printer::STYLE_ERROR, $($arg)*)
2631
};
2732
}
2833

2934
#[macro_export]
3035
macro_rules! format_warning {
3136
($($arg:tt)*) => {
32-
$crate::format_colored!($crate::printer::WARNING_STYLE, $($arg)*)
37+
$crate::format_colored!($crate::printer::STYLE_WARNING, $($arg)*)
3338
};
3439
}
3540

3641
#[macro_export]
3742
macro_rules! format_argument {
3843
($($arg:tt)*) => {
39-
$crate::format_colored!($crate::printer::ARGUMENT_STYLE, $($arg)*)
44+
$crate::format_colored!($crate::printer::STYLE_ARGUMENT, $($arg)*)
4045
};
4146
}
4247

@@ -67,6 +72,103 @@ fn println(string: &str) {
6772
println!("{}", string)
6873
}
6974

75+
pub(crate) fn print_replicas_table(replicas: HashSet<ServerReplica>) {
76+
const COLUMN_NUM: usize = 5;
77+
#[derive(Debug)]
78+
struct Row {
79+
id: String,
80+
address: String,
81+
role: String,
82+
term: String,
83+
status: (String, Style),
84+
}
85+
86+
let mut rows = Vec::new();
87+
rows.push(Row {
88+
id: "id".to_string(),
89+
address: "address".to_string(),
90+
role: "role".to_string(),
91+
term: "term".to_string(),
92+
status: ("status".to_string(), Style::new()),
93+
});
94+
95+
for replica in replicas.into_iter().sorted_by_key(|replica| replica.id()) {
96+
let role = match replica.role() {
97+
Some(ReplicaRole::Primary) => "primary",
98+
Some(ReplicaRole::Candidate) => "candidate",
99+
Some(ReplicaRole::Secondary) => "secondary",
100+
None => "",
101+
}
102+
.to_string();
103+
104+
let term = replica.term().map(|t| t.to_string()).unwrap_or_default();
105+
106+
let status = match &replica {
107+
ServerReplica::Available(_) => ("available".to_string(), STYLE_GREEN),
108+
ServerReplica::Unavailable { .. } => ("unavailable".to_string(), STYLE_RED),
109+
};
110+
111+
rows.push(Row {
112+
id: replica.id().to_string(),
113+
address: replica.address().map(|address| address.to_string()).unwrap_or_default(),
114+
role,
115+
term,
116+
status,
117+
});
118+
}
119+
120+
// Compute max content length per column (without padding)
121+
let mut width_id = 0usize;
122+
let mut width_address = 0usize;
123+
let mut width_role = 0usize;
124+
let mut width_term = 0usize;
125+
let mut width_status = 0usize;
126+
127+
for r in &rows {
128+
width_id = width_id.max(r.id.len());
129+
width_address = width_address.max(r.address.len());
130+
width_role = width_role.max(r.role.len());
131+
width_term = width_term.max(r.term.len());
132+
width_status = width_status.max(r.status.0.len());
133+
}
134+
135+
// Add 2 spaces per column (one at the beginning and one at the end)
136+
width_id += 2;
137+
width_address += 2;
138+
width_role += 2;
139+
width_term += 2;
140+
width_status += 2;
141+
142+
fn print_cell(content: &str, width: usize, style: Option<Style>) {
143+
// One space on the left, one at the right, rest is extra right padding
144+
let content_len = content.len();
145+
let base_len = content_len + 2; // left + right space
146+
let extra_padding = width.saturating_sub(base_len);
147+
let styled_content = format_colored!(style.unwrap_or_default(), "{content}");
148+
print!(" {}{} ", styled_content, " ".repeat(extra_padding));
149+
}
150+
151+
const PIPES_NUM: usize = COLUMN_NUM - 1;
152+
let total_width = width_id + width_address + width_role + width_term + width_status + PIPES_NUM;
153+
154+
for (row_index, row) in rows.iter().enumerate() {
155+
print_cell(&row.id, width_id, None);
156+
print!("|");
157+
print_cell(&row.address, width_address, None);
158+
print!("|");
159+
print_cell(&row.role, width_role, None);
160+
print!("|");
161+
print_cell(&row.term, width_term, None);
162+
print!("|");
163+
print_cell(&row.status.0, width_status, Some(row.status.1));
164+
println!();
165+
166+
if row_index == 0 {
167+
println!("{}", "-".repeat(total_width));
168+
}
169+
}
170+
}
171+
70172
pub(crate) fn print_document(document: ConceptDocument) {
71173
// Note: inefficient, but easy...
72174
match serde_json::from_str::<serde_json::Value>(&document.into_json().to_string()) {

0 commit comments

Comments
 (0)