Add printing, commands work
This commit is contained in:
147
Cargo.lock
generated
147
Cargo.lock
generated
@@ -17,6 +17,25 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "ansi-str"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "060de1453b69f46304b28274f382132f4e72c55637cf362920926a70d090890d"
|
||||
dependencies = [
|
||||
"ansitok",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansitok"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0a8acea8c2f1c60f0a92a8cd26bf96ca97db56f10bbcab238bbe0cceba659ee"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
@@ -73,6 +92,12 @@ version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -85,6 +110,12 @@ version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.0"
|
||||
@@ -230,7 +261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -239,6 +270,12 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
@@ -300,6 +337,12 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.31"
|
||||
@@ -392,6 +435,12 @@ dependencies = [
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.1"
|
||||
@@ -403,6 +452,16 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
@@ -415,6 +474,19 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "papergrid"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0984e668274d34691bc2b262ef0d115de5fa9973bcdee7ae32213f93099153e"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"ansitok",
|
||||
"bytecount",
|
||||
"fnv",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -626,6 +698,19 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
@@ -690,6 +775,42 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabled"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5dc662e6da844ad6e428ad16b57967c9d33c82e16bb1c258326c0c078605dff"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"ansitok",
|
||||
"papergrid",
|
||||
"tabled_derive",
|
||||
"testing_table",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabled_derive"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea5d1b13ca6cff1f9231ffd62f15eefd72543dab5e468735f1a456728a02846"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "testing_table"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc"
|
||||
dependencies = [
|
||||
"ansitok",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
@@ -793,6 +914,12 @@ version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -814,6 +941,16 @@ dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wara"
|
||||
version = "0.1.0"
|
||||
@@ -827,6 +964,8 @@ dependencies = [
|
||||
"mikrotik-rs",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tabled",
|
||||
"tokio",
|
||||
"toml",
|
||||
]
|
||||
@@ -936,3 +1075,9 @@ name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
@@ -13,5 +13,7 @@ log = "0.4.33"
|
||||
mikrotik-rs = { version = "0.8.0", features = ["tokio-tls"] }
|
||||
regex = "1.12.4"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.150"
|
||||
tabled = { version = "0.21.0", features = ["ansi"] }
|
||||
tokio = { version = "1.52.3", features = ["full"] }
|
||||
toml = "1.1.2"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[default_logins]
|
||||
username="monitor"
|
||||
password="HewoWorld"
|
||||
port=8782
|
||||
|
||||
[[host]]
|
||||
name="Example1"
|
||||
@@ -9,5 +10,6 @@ ip="127.0.0.1"
|
||||
[[host]]
|
||||
name="Example2"
|
||||
ip="127.0.0.2"
|
||||
port=8728
|
||||
username="admin"
|
||||
password="password123"
|
||||
|
||||
5
out.csv
Normal file
5
out.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
Name,IP,.id,ap,bridge,bytes,distance,frame-bytes,frames,interface,last-activity,last-ip,mac-address,packets,radio-name,routeros-version,rx-ccq,rx-rate,signal-strength,signal-strength-ch0,signal-strength-ch1,signal-to-noise,strength-at-rates,tdma-retx,tdma-rx-size,tdma-timing-offset,tdma-tx-size,tdma-winfull,tx-ccq,tx-rate,tx-rate-set,tx-signal-strength,tx-signal-strength-ch0,tx-signal-strength-ch1,uptime,wds
|
||||
KLI_SPURGA,10.60.17.23,*1,false,true,"2557633336,128708034",1,"2557852248,129123775","2188267,441115",wlan1,0ms,10.80.17.11,64:D1:54:91:EC:47,"2236186,552855",Sedos 34,6.49.18,66,104Mbps-20MHz/2S,-63,-66,-65,40,"-63@6Mbps 0ms,-61@9Mbps 15h29m13s760ms,-62@12Mbps 15h29m10s460ms,-61@18Mbps 15h29m10s450ms,-64@24Mbps 3m4s740ms,-65@36Mbps 3m2s60ms,-65@48Mbps 17s490ms,-67@54Mbps 16s980ms",5,1520,7,496,0,90,144.4Mbps-20MHz/2S/SGI,"OFDM:6-54 BW:1x SGI:1x HT:0-15 VHTMCS:SS1=0-9,SS2=0-9",-57,-59,-63,15h38m10s,false
|
||||
KLI_SPURGA,10.60.17.23,*2,false,true,"950673764,218649231",1,"951713687,220908444","4234949,1016954",wlan1,0ms,10.80.17.21,64:D1:54:43:F6:35,"4414597,1456217",sedos 42,6.46.2,95,780Mbps-80MHz/2S/SGI,-46,-50,-48,57,"-46@6Mbps 0ms,-46@9Mbps 2h30m27s860ms,-46@12Mbps 2h30m27s850ms,-47@18Mbps 2h30m39s760ms,-50@54Mbps 8s530ms",1,496,1,4000,0,95,780Mbps-80MHz/2S/SGI,"OFDM:6-54 BW:1x-4x SGI:1x-4x HT:0-15 VHTMCS:SS1=0-9,SS2=0-9",-44,-46,-46,15h38m9s,false
|
||||
KLI_VENTOS_KRANTO,10.60.17.19,*27,false,true,"79571155703,2550226838",6,"79698231153,2427715753","63248407,20461337",wlan2,10ms,10.80.17.25,64:D1:54:20:C5:C1,"63248407,20459064",Kapu 15,6.49.19,75,6Mbps,-72@6Mbps,-73,-79,33,"-72@6Mbps 10ms,-73@9Mbps 9h58m8s680ms,-74@12Mbps 9h58m2s760ms,-75@18Mbps 3h55m21s430ms,-75@24Mbps 3h55m21s760ms,-75@36Mbps 2h33m49s190ms,-75@48Mbps 2h11m54s820ms,-77@54Mbps 2h11m54s930ms,-74@HT20-0 9h59m22s650ms,-74@HT20-1 9h59m22s910ms,-74@HT20-2 2h46m17s960ms,-74@HT20-3 12s300ms,-71@HT20-4 12s430ms,-74@HT20-5 11s950ms,-77@HT20-6 1m2s750ms,-78@HT20-7 2d7h31m37s420ms,-77@HT40-0 2h32m55s340ms,-77@HT40-1 2h31m53s970ms,-76@HT40-2 3m5s190ms,-76@HT40-3 12s330ms,-75@HT40-4 3s90ms,-79@HT40-5 11s960ms,-79@HT40-6 5d15h8m28s70ms",,,,,,74,180Mbps-40MHz/2S/SGI,OFDM:6-54 BW:1x-2x SGI:1x-2x HT:0-15,-59,-61,-63,4d16h34m,false
|
||||
KLI_VENTOS_KRANTO,10.60.17.19,*2B,false,true,"12498473399,900601701",10,"12519902471,878865267","10455470,3627677",wlan2,20ms,10.60.17.23,64:D1:54:5F:3C:E5,"10455470,3627415",aerodromas,6.45.1,82,6Mbps,-67@6Mbps,-68,-75,38,"-67@6Mbps 20ms,-66@9Mbps 9h59m1s350ms,-67@12Mbps 9h57m56s490ms,-66@18Mbps 9h57m40s200ms,-64@24Mbps 57m14s230ms,-65@36Mbps 57m14s570ms,-66@48Mbps 57m15s540ms,-67@54Mbps 57m17s140ms",,,,,,87,195Mbps-80MHz/1S/SGI,"OFDM:6-54 BW:1x-4x SGI:1x-4x HT:0-15 VHTMCS:SS1=0-9,SS2=0-9",-66,-74,-67,21h44m30s,false
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use clap::ArgGroup;
|
||||
use serde::de;
|
||||
|
||||
#[derive(Debug, Clone, clap::Parser, Default)]
|
||||
pub struct CliArgs {
|
||||
@@ -34,6 +35,9 @@ pub enum CliCommand {
|
||||
/// Command to run
|
||||
#[arg(long, short = 'c')]
|
||||
command: String,
|
||||
/// Output format
|
||||
#[arg(long, short = 'f')]
|
||||
format: OutputFormat,
|
||||
},
|
||||
/// Run command continuosly
|
||||
#[command(
|
||||
@@ -53,8 +57,11 @@ pub enum CliCommand {
|
||||
/// Command to run
|
||||
#[arg(long, short = 'c')]
|
||||
command: String,
|
||||
#[arg(long, short = 'i')]
|
||||
/// Output format
|
||||
#[arg(long, short = 'f')]
|
||||
format: OutputFormat,
|
||||
/// Interval to run the commands at
|
||||
#[arg(long, short = 'i')]
|
||||
interval: f32,
|
||||
},
|
||||
/// Manage devices
|
||||
@@ -77,3 +84,11 @@ pub enum CliDeviceCommand {
|
||||
#[clap(skip)]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, clap::ValueEnum)]
|
||||
pub enum OutputFormat {
|
||||
#[default]
|
||||
Csv,
|
||||
Table,
|
||||
Json,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use clap::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tabled::Tabled;
|
||||
|
||||
use crate::config::cli::{CliArgs, CliCommand};
|
||||
|
||||
@@ -23,16 +24,32 @@ pub struct Config {
|
||||
pub struct ConfigDefaultLogins {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct ConfigHost {
|
||||
pub name: String,
|
||||
pub ip: IpAddr,
|
||||
pub port: Option<u16>,
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Tabled)]
|
||||
pub struct HostDisplay {
|
||||
#[tabled(rename = "Name")]
|
||||
pub name: String,
|
||||
#[tabled(rename = "IP")]
|
||||
pub ip: IpAddr,
|
||||
#[tabled(rename = "Port")]
|
||||
pub port: u16,
|
||||
#[tabled(rename = "Username")]
|
||||
pub username: String,
|
||||
#[tabled(rename = "Pasword")]
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn parse() -> anyhow::Result<Self> {
|
||||
let cli = cli::CliArgs::parse();
|
||||
@@ -63,6 +80,22 @@ impl ConfigHost {
|
||||
&cfg.default_logins.password
|
||||
}
|
||||
}
|
||||
pub fn port(&self, cfg: &Config) -> u16 {
|
||||
if let Some(port) = self.port {
|
||||
port
|
||||
} else {
|
||||
cfg.default_logins.port
|
||||
}
|
||||
}
|
||||
pub fn display_tabled(&self, cfg: &Config) -> HostDisplay {
|
||||
HostDisplay {
|
||||
name: self.name.clone(),
|
||||
ip: self.ip.clone(),
|
||||
port: self.port(&cfg),
|
||||
username: self.username(cfg).clone(),
|
||||
password: self.password(cfg).clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigHost {
|
||||
@@ -70,6 +103,7 @@ impl Default for ConfigHost {
|
||||
Self {
|
||||
name: String::default(),
|
||||
ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
port: None,
|
||||
username: None,
|
||||
password: None,
|
||||
}
|
||||
|
||||
190
src/main.rs
190
src/main.rs
@@ -1,6 +1,19 @@
|
||||
use std::{net::IpAddr, str::FromStr};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
net::IpAddr,
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::config::cli::CliCommand;
|
||||
use clap::builder;
|
||||
use log::LevelFilter;
|
||||
use serde_json::json;
|
||||
use tabled::Table;
|
||||
|
||||
use crate::config::{
|
||||
Config, ConfigHost,
|
||||
cli::{CliCommand, CliDeviceCommand, OutputFormat},
|
||||
};
|
||||
|
||||
mod config;
|
||||
mod mt_commander;
|
||||
@@ -8,7 +21,19 @@ mod mt_commander;
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let cfg = config::Config::parse()?;
|
||||
dbg!(&cfg);
|
||||
//dbg!(&cfg);
|
||||
|
||||
env_logger::builder()
|
||||
.parse_default_env()
|
||||
.filter_module(
|
||||
"wara",
|
||||
if cfg.cli.verbose {
|
||||
LevelFilter::Debug
|
||||
} else {
|
||||
LevelFilter::Info
|
||||
},
|
||||
)
|
||||
.init();
|
||||
|
||||
mt_commander::MtCommander::init(cfg.clone())?;
|
||||
|
||||
@@ -17,40 +42,43 @@ async fn main() -> anyhow::Result<()> {
|
||||
all,
|
||||
devices,
|
||||
command,
|
||||
format,
|
||||
} => {
|
||||
let mut hosts = Vec::new();
|
||||
if *all {
|
||||
hosts.clone_from(&cfg.hosts);
|
||||
} else {
|
||||
let mut errored = false;
|
||||
for dev in devices {
|
||||
let Ok(ip) = IpAddr::from_str(dev) else {
|
||||
log::error!("Unable to parse {dev} as an IP");
|
||||
errored = true;
|
||||
continue;
|
||||
};
|
||||
let found_hosts = cfg.hosts.iter().filter(|h| h.ip == ip).collect::<Vec<_>>();
|
||||
let Some(host) = found_hosts.first() else {
|
||||
log::error!("Could not find host with IP {dev}");
|
||||
errored = true;
|
||||
continue;
|
||||
};
|
||||
hosts.push((**host).clone());
|
||||
}
|
||||
if errored {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
mt_commander::MtCommander::run_command_on_hosts(&hosts, command).await?;
|
||||
let hosts = get_devices(devices, *all, &cfg)?;
|
||||
let out = mt_commander::MtCommander::run_command_on_hosts(&hosts, command).await?;
|
||||
print_output(&cfg, &out, format)?;
|
||||
}
|
||||
CliCommand::Monitor {
|
||||
all,
|
||||
devices,
|
||||
command,
|
||||
interval,
|
||||
format,
|
||||
} => {
|
||||
let hosts = get_devices(devices, *all, &cfg)?;
|
||||
loop {
|
||||
let out = mt_commander::MtCommander::run_command_on_hosts(&hosts, command).await?;
|
||||
print_output(&cfg, &out, format)?;
|
||||
tokio::time::sleep(Duration::from_secs_f32(*interval)).await;
|
||||
}
|
||||
}
|
||||
CliCommand::Device { command } => match command {
|
||||
CliDeviceCommand::List => {
|
||||
let devices = cfg.hosts.iter().map(|h| h.display_tabled(&cfg));
|
||||
|
||||
let table = tabled::Table::new(devices);
|
||||
println!("{}", table);
|
||||
}
|
||||
CliDeviceCommand::None => unreachable!(),
|
||||
},
|
||||
CliCommand::None => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_devices(devices: &Vec<String>, all: bool, cfg: &Config) -> anyhow::Result<Vec<ConfigHost>> {
|
||||
let mut hosts = Vec::new();
|
||||
if *all {
|
||||
if all {
|
||||
hosts.clone_from(&cfg.hosts);
|
||||
} else {
|
||||
let mut errored = false;
|
||||
@@ -69,13 +97,111 @@ async fn main() -> anyhow::Result<()> {
|
||||
hosts.push((**host).clone());
|
||||
}
|
||||
if errored {
|
||||
return Ok(());
|
||||
anyhow::bail!("");
|
||||
}
|
||||
}
|
||||
mt_commander::MtCommander::run_command_on_hosts(&hosts, command).await?;
|
||||
Ok(hosts)
|
||||
}
|
||||
|
||||
fn print_output(
|
||||
cfg: &Config,
|
||||
output: &HashMap<ConfigHost, Vec<HashMap<String, Option<String>>>>,
|
||||
format: &OutputFormat,
|
||||
) -> anyhow::Result<()> {
|
||||
match *format {
|
||||
OutputFormat::Csv => {
|
||||
let mut table = Vec::new();
|
||||
let mut wanted_cols = Vec::new();
|
||||
let mut top_row_added = false;
|
||||
for (host, out) in output {
|
||||
for out in out {
|
||||
let out: BTreeMap<_, _> = out.clone().into_iter().collect();
|
||||
if !top_row_added {
|
||||
let mut row = vec!["Name".to_string(), "IP".to_string()];
|
||||
let mut data_cols = out.keys().cloned().collect::<Vec<String>>();
|
||||
wanted_cols = data_cols.clone();
|
||||
row.append(&mut data_cols);
|
||||
|
||||
table.push(row);
|
||||
top_row_added = true;
|
||||
}
|
||||
let mut row = vec![host.name.clone(), host.ip.clone().to_string()];
|
||||
|
||||
for wanted_col in &wanted_cols {
|
||||
let mut val = String::new();
|
||||
if let Some(v) = out.get(wanted_col) {
|
||||
if let Some(v) = v {
|
||||
val = v.clone();
|
||||
}
|
||||
}
|
||||
row.push(val.clone());
|
||||
}
|
||||
table.push(row);
|
||||
}
|
||||
}
|
||||
let mut table_escaped = Vec::new();
|
||||
for line in table {
|
||||
let mut row = Vec::new();
|
||||
for mut item in line {
|
||||
if item.contains(',') {
|
||||
if item.contains('"') {
|
||||
item = item.replace('"', "\"\"");
|
||||
}
|
||||
item = format!("\"{item}\"");
|
||||
}
|
||||
row.push(item);
|
||||
}
|
||||
table_escaped.push(row.join(","));
|
||||
}
|
||||
for row in table_escaped {
|
||||
println!("{row}");
|
||||
}
|
||||
}
|
||||
OutputFormat::Table => {
|
||||
let mut builder = tabled::builder::Builder::default();
|
||||
let mut wanted_cols = Vec::new();
|
||||
let mut top_row_added = false;
|
||||
for (host, out) in output {
|
||||
for out in out {
|
||||
let out: BTreeMap<_, _> = out.clone().into_iter().collect();
|
||||
if !top_row_added {
|
||||
let mut row = vec!["Name".to_string(), "IP".to_string()];
|
||||
let mut data_cols = out.keys().cloned().collect::<Vec<String>>();
|
||||
wanted_cols = data_cols.clone();
|
||||
row.append(&mut data_cols);
|
||||
|
||||
builder.push_record(row);
|
||||
top_row_added = true;
|
||||
}
|
||||
let mut row = vec![host.name.clone(), host.ip.clone().to_string()];
|
||||
for wanted_col in &wanted_cols {
|
||||
let mut val = String::new();
|
||||
if let Some(v) = out.get(wanted_col) {
|
||||
if let Some(v) = v {
|
||||
val = v.clone();
|
||||
}
|
||||
}
|
||||
row.push(val.clone());
|
||||
}
|
||||
builder.push_record(row);
|
||||
}
|
||||
}
|
||||
let table = builder.build();
|
||||
println!("{}", table);
|
||||
}
|
||||
OutputFormat::Json => {
|
||||
let mut res = Vec::new();
|
||||
|
||||
for (host, out) in output {
|
||||
let val = json!({
|
||||
"host": host.display_tabled(&cfg),
|
||||
"response": out,
|
||||
});
|
||||
res.push(val);
|
||||
}
|
||||
let out_text = serde_json::to_string_pretty(&res)?;
|
||||
println!("{out_text}");
|
||||
}
|
||||
CliCommand::Device { command } => {}
|
||||
CliCommand::None => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ impl MtCommander {
|
||||
pub async fn run_command_on_hosts(
|
||||
hosts: &[ConfigHost],
|
||||
command: &str,
|
||||
) -> anyhow::Result<HashMap<ConfigHost, String>> {
|
||||
) -> anyhow::Result<HashMap<ConfigHost, Vec<HashMap<String, Option<String>>>>> {
|
||||
let mut res_map = HashMap::new();
|
||||
for host in hosts {
|
||||
let res = Self::run_command_on_host(host, command).await?;
|
||||
@@ -37,12 +37,15 @@ impl MtCommander {
|
||||
}
|
||||
Ok(res_map)
|
||||
}
|
||||
pub async fn run_command_on_host(host: &ConfigHost, command: &str) -> anyhow::Result<String> {
|
||||
pub async fn run_command_on_host(
|
||||
host: &ConfigHost,
|
||||
command: &str,
|
||||
) -> anyhow::Result<Vec<HashMap<String, Option<String>>>> {
|
||||
let mtc = MTC.lock().map_err(|e| anyhow::anyhow!(e.to_string()))?;
|
||||
mtc.run_command(host, &command).await
|
||||
}
|
||||
|
||||
pub async fn run_command_on_hosts_and_parse_re(
|
||||
/*pub async fn run_command_on_hosts_and_parse_re(
|
||||
hosts: &[ConfigHost],
|
||||
command: &str,
|
||||
parse_re: &Regex,
|
||||
@@ -53,9 +56,9 @@ impl MtCommander {
|
||||
res_map.insert(host.clone(), res);
|
||||
}
|
||||
Ok(res_map)
|
||||
}
|
||||
}*/
|
||||
|
||||
pub async fn run_command_on_host_and_parse_re(
|
||||
/*pub async fn run_command_on_host_and_parse_re(
|
||||
host: &ConfigHost,
|
||||
command: &str,
|
||||
parse_re: &Regex,
|
||||
@@ -67,7 +70,7 @@ impl MtCommander {
|
||||
.map(|s| s.as_str().to_string())
|
||||
.collect();
|
||||
Ok(out)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
impl MtCommanderInternal {
|
||||
@@ -95,9 +98,13 @@ impl MtCommanderInternal {
|
||||
tokio::spawn(watcher());
|
||||
Ok(())
|
||||
}
|
||||
async fn run_command(&self, host: &ConfigHost, cmd: &str) -> anyhow::Result<String> {
|
||||
async fn run_command(
|
||||
&self,
|
||||
host: &ConfigHost,
|
||||
cmd: &str,
|
||||
) -> anyhow::Result<Vec<HashMap<String, Option<String>>>> {
|
||||
let dev = MikrotikDevice::connect(
|
||||
format!("{}", &host.ip),
|
||||
format!("{}:{}", &host.ip, host.port(&self.cfg())),
|
||||
&host.username(self.cfg()),
|
||||
Some(&host.password(self.cfg())),
|
||||
)
|
||||
@@ -107,14 +114,17 @@ impl MtCommanderInternal {
|
||||
.send_command(CommandBuilder::new().command(cmd).build())
|
||||
.await?;
|
||||
|
||||
let mut res = String::new();
|
||||
let mut res = Vec::new();
|
||||
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
Event::Reply { response, .. } => {
|
||||
log::debug!("({}) {:?}", &host.ip, response.attributes);
|
||||
res.push(response.attributes);
|
||||
}
|
||||
Event::Done { .. } => {
|
||||
break;
|
||||
}
|
||||
Event::Done { .. } => break,
|
||||
Event::Trap { response, .. } => {
|
||||
log::error!("({}) {:?}", &host.ip, response.message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user