Add printing, commands work

This commit is contained in:
2026-07-03 18:45:33 +03:00
parent 16615d32c1
commit 6537c41972
8 changed files with 402 additions and 63 deletions

147
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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
View 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 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
2 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
3 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
4 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
5 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

View File

@@ -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,
}

View File

@@ -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,
}

View File

@@ -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,65 +42,166 @@ 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 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(());
}
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;
}
mt_commander::MtCommander::run_command_on_hosts(&hosts, command).await?;
}
CliCommand::Device { command } => {}
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 {
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 {
anyhow::bail!("");
}
}
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}");
}
}
Ok(())
}

View File

@@ -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);
}