M3: extended plugins
- folders, amps, ports, raid, wifi, ip (drop-in compatible stats) - differential tests pass for all 22 implemented plugins - TUI left sidebar extended
This commit is contained in:
Generated
+600
-12
@@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.5"
|
||||
@@ -62,7 +68,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -73,7 +79,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -112,6 +118,12 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
@@ -175,6 +187,12 @@ version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.4"
|
||||
@@ -296,6 +314,35 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"document-features",
|
||||
"idna",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@@ -311,6 +358,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
@@ -372,7 +428,7 @@ checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
|
||||
dependencies = [
|
||||
"dispatch2",
|
||||
"nix 0.31.3",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -465,6 +521,17 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.12"
|
||||
@@ -493,7 +560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -544,6 +611,17 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -556,6 +634,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
@@ -590,6 +677,17 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
@@ -663,6 +761,7 @@ dependencies = [
|
||||
"sysinfo",
|
||||
"thiserror 2.0.21",
|
||||
"tracing",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -714,6 +813,22 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
@@ -738,12 +853,116 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
@@ -860,6 +1079,12 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "1.0.0"
|
||||
@@ -897,7 +1122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b367b50a9be9a5d3b5718c1da74e5d6b9c17647f89752ee2562a470cc3c4eb1a"
|
||||
dependencies = [
|
||||
"nix 0.30.1",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -936,6 +1161,16 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.4"
|
||||
@@ -945,7 +1180,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1010,7 +1245,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1182,6 +1417,12 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.9.3"
|
||||
@@ -1290,6 +1531,15 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.1"
|
||||
@@ -1489,6 +1739,20 @@ version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@@ -1508,7 +1772,42 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1636,6 +1935,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.4"
|
||||
@@ -1654,6 +1959,12 @@ version = "1.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.25"
|
||||
@@ -1664,7 +1975,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1700,6 +2011,12 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -1733,6 +2050,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.39.6"
|
||||
@@ -1758,7 +2086,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"signal-hook",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1768,7 +2096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1799,7 +2127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.13.2",
|
||||
"fancy-regex",
|
||||
"filedescriptor",
|
||||
@@ -1896,6 +2224,7 @@ dependencies = [
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1904,6 +2233,26 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
@@ -2006,6 +2355,68 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"cookie_store",
|
||||
"flate2",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq-proto",
|
||||
"utf8-zero",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-zero"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
@@ -2105,6 +2516,15 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wezterm-bidi"
|
||||
version = "0.2.3"
|
||||
@@ -2300,6 +2720,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@@ -2309,6 +2738,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",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
@@ -2318,12 +2763,155 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[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_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[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_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[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_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
|
||||
@@ -15,3 +15,4 @@ serde_json.workspace = true
|
||||
sysinfo = "0.39.6"
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
ureq = { version = "3.4.2", features = ["json"] }
|
||||
|
||||
@@ -559,6 +559,129 @@ pub fn is_admin() -> bool {
|
||||
unsafe { libc::getuid() == 0 }
|
||||
}
|
||||
|
||||
/// Return the current IP address and netmask.
|
||||
pub fn get_ip_address() -> (Option<String>, Option<String>) {
|
||||
// Ask the kernel which source address it would use for default-route
|
||||
// traffic (no packet is sent).
|
||||
let mut ip_address: Option<String> = None;
|
||||
unsafe {
|
||||
let sock = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0);
|
||||
if sock >= 0 {
|
||||
let mut addr: libc::sockaddr_in = std::mem::zeroed();
|
||||
addr.sin_family = libc::AF_INET as u16;
|
||||
addr.sin_port = 53u16.to_be();
|
||||
addr.sin_addr.s_addr = u32::from_be_bytes([192, 0, 2, 1]).to_be();
|
||||
if libc::connect(sock, &addr as *const _ as *const libc::sockaddr, std::mem::size_of::<libc::sockaddr_in>() as u32) == 0 {
|
||||
let mut local: libc::sockaddr_in = std::mem::zeroed();
|
||||
let mut len = std::mem::size_of::<libc::sockaddr_in>() as u32;
|
||||
if libc::getsockname(sock, &mut local as *mut _ as *mut libc::sockaddr, &mut len) == 0 {
|
||||
let bytes = local.sin_addr.s_addr.to_ne_bytes();
|
||||
let candidate = format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3]);
|
||||
if !candidate.starts_with("127.") && candidate != "0.0.0.0" {
|
||||
ip_address = Some(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
libc::close(sock);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ip) = &ip_address {
|
||||
// Find the matching netmask with getifaddrs.
|
||||
let mut netmask = None;
|
||||
unsafe {
|
||||
let mut ifap: *mut libc::ifaddrs = std::ptr::null_mut();
|
||||
if libc::getifaddrs(&mut ifap) == 0 {
|
||||
let mut current = ifap;
|
||||
while !current.is_null() {
|
||||
let addr = (*current).ifa_addr;
|
||||
let mask = (*current).ifa_netmask;
|
||||
if !addr.is_null() && (*addr).sa_family as i32 == libc::AF_INET {
|
||||
let sin = addr as *const libc::sockaddr_in;
|
||||
let bytes = (*sin).sin_addr.s_addr.to_ne_bytes();
|
||||
let candidate = format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3]);
|
||||
if &candidate == ip && !mask.is_null() {
|
||||
let sin_mask = mask as *const libc::sockaddr_in;
|
||||
let mbytes = (*sin_mask).sin_addr.s_addr.to_ne_bytes();
|
||||
netmask = Some(format!("{}.{}.{}.{}", mbytes[0], mbytes[1], mbytes[2], mbytes[3]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
current = (*current).ifa_next;
|
||||
}
|
||||
libc::freeifaddrs(ifap);
|
||||
}
|
||||
}
|
||||
return (ip_address, netmask);
|
||||
}
|
||||
|
||||
// Fallback: first up non-loopback interface with an address.
|
||||
let mut found = (None, None);
|
||||
unsafe {
|
||||
let mut ifap: *mut libc::ifaddrs = std::ptr::null_mut();
|
||||
if libc::getifaddrs(&mut ifap) == 0 {
|
||||
let mut current = ifap;
|
||||
while !current.is_null() {
|
||||
let addr = (*current).ifa_addr;
|
||||
if !addr.is_null() && (*addr).sa_family as i32 == libc::AF_INET {
|
||||
let name = std::ffi::CStr::from_ptr((*current).ifa_name).to_string_lossy().to_string();
|
||||
let flags = (*current).ifa_flags as u32;
|
||||
if name != "lo" && flags & libc::IFF_UP as u32 != 0 {
|
||||
let sin = addr as *const libc::sockaddr_in;
|
||||
let bytes = (*sin).sin_addr.s_addr.to_ne_bytes();
|
||||
let candidate = format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3]);
|
||||
let mask = (*current).ifa_netmask;
|
||||
let mut netmask = None;
|
||||
if !mask.is_null() {
|
||||
let sin_mask = mask as *const libc::sockaddr_in;
|
||||
let mbytes = (*sin_mask).sin_addr.s_addr.to_ne_bytes();
|
||||
netmask = Some(format!("{}.{}.{}.{}", mbytes[0], mbytes[1], mbytes[2], mbytes[3]));
|
||||
}
|
||||
found = (Some(candidate), netmask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
current = (*current).ifa_next;
|
||||
}
|
||||
libc::freeifaddrs(ifap);
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
/// Return the default gateway IP address (from `/proc/net/route`).
|
||||
pub fn get_default_gateway() -> Option<String> {
|
||||
let content = std::fs::read_to_string("/proc/net/route").ok()?;
|
||||
for line in content.lines().skip(1) {
|
||||
let fields: Vec<&str> = line.split_whitespace().collect();
|
||||
if fields.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
if fields[1] == "00000000" {
|
||||
let gateway = fields[2];
|
||||
if gateway.len() == 8 {
|
||||
let bytes: Vec<String> = (0..4)
|
||||
.map(|i| u8::from_str_radix(&gateway[i * 2..i * 2 + 2], 16).unwrap_or(0).to_string())
|
||||
.collect();
|
||||
return Some(bytes.join("."));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Convert a netmask to CIDR notation (port of `ip_to_cidr`).
|
||||
pub fn ip_to_cidr(ip: Option<&str>) -> u32 {
|
||||
match ip {
|
||||
None => 0,
|
||||
Some(ip) => ip
|
||||
.split('.')
|
||||
.filter_map(|part| part.parse::<u32>().ok())
|
||||
.map(|part| part.count_ones())
|
||||
.sum(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a sysfs hwmon input value.
|
||||
pub fn read_number_file(path: &str) -> Option<f64> {
|
||||
std::fs::read_to_string(path).ok().and_then(|v| v.trim().parse::<f64>().ok())
|
||||
|
||||
@@ -27,10 +27,13 @@ impl Engine {
|
||||
// Registration order mirrors the alphabetical plugin loading of the
|
||||
// Python implementation (in particular processcount runs before
|
||||
// processlist and programlist).
|
||||
plugins.insert("amps".to_string(), Box::new(AmpsPlugin::new(&config, &args)));
|
||||
plugins.insert("core".to_string(), Box::new(CorePlugin::new(&config, &args)));
|
||||
plugins.insert("cpu".to_string(), Box::new(CpuPlugin::new(&config, &args)));
|
||||
plugins.insert("diskio".to_string(), Box::new(DiskioPlugin::new(&config, &args)));
|
||||
plugins.insert("folders".to_string(), Box::new(FoldersPlugin::new(&config, &args)));
|
||||
plugins.insert("fs".to_string(), Box::new(FsPlugin::new(&config, &args)));
|
||||
plugins.insert("ip".to_string(), Box::new(IpPlugin::new(&config, &args)));
|
||||
plugins.insert("irq".to_string(), Box::new(IrqPlugin::new(&config, &args)));
|
||||
plugins.insert("load".to_string(), Box::new(LoadPlugin::new(&config, &args)));
|
||||
plugins.insert("mem".to_string(), Box::new(MemPlugin::new(&config, &args)));
|
||||
@@ -38,15 +41,18 @@ impl Engine {
|
||||
plugins.insert("network".to_string(), Box::new(NetworkPlugin::new(&config, &args)));
|
||||
plugins.insert("now".to_string(), Box::new(NowPlugin::new(&config, &args)));
|
||||
plugins.insert("percpu".to_string(), Box::new(PerCpuPlugin::new(&config, &args)));
|
||||
plugins.insert("ports".to_string(), Box::new(PortsPlugin::new(&config, &args)));
|
||||
plugins.insert("processcount".to_string(), Box::new(ProcessCountPlugin::new(&config, &args)));
|
||||
plugins.insert("processlist".to_string(), Box::new(ProcessListPlugin::new(&config, &args)));
|
||||
plugins.insert("programlist".to_string(), Box::new(ProgramListPlugin::new(&config, &args)));
|
||||
plugins.insert("psutilversion".to_string(), Box::new(PsutilVersionPlugin::new(&config, &args)));
|
||||
plugins.insert("quicklook".to_string(), Box::new(QuicklookPlugin::new(&config, &args)));
|
||||
plugins.insert("raid".to_string(), Box::new(RaidPlugin::new(&config, &args)));
|
||||
plugins.insert("sensors".to_string(), Box::new(SensorsPlugin::new(&config, &args)));
|
||||
plugins.insert("system".to_string(), Box::new(SystemPlugin::new(&config, &args)));
|
||||
plugins.insert("uptime".to_string(), Box::new(UptimePlugin::new(&config, &args)));
|
||||
plugins.insert("version".to_string(), Box::new(VersionPlugin::new(&config, &args)));
|
||||
plugins.insert("wifi".to_string(), Box::new(WifiPlugin::new(&config, &args)));
|
||||
// The refresh time is set on the model, which was built with the
|
||||
// command line value already.
|
||||
let _ = refresh_time;
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
//! AMPs plugin: run user-defined commands and report their result.
|
||||
|
||||
use crate::fields;
|
||||
use glances_core::plugin::{Field, Plugin, PluginModel, UpdateContext};
|
||||
use glances_core::processes::processes;
|
||||
use glances_core::secure::secure_popen;
|
||||
use glances_core::{Args, Config};
|
||||
use serde_json::{Value, json};
|
||||
use std::time::Instant;
|
||||
|
||||
pub struct AmpsPlugin {
|
||||
pub model: PluginModel,
|
||||
amps: Vec<AmpConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AmpConfig {
|
||||
name: String,
|
||||
command: Option<String>,
|
||||
regex: Option<regex::Regex>,
|
||||
refresh: f64,
|
||||
countmin: Option<f64>,
|
||||
countmax: Option<f64>,
|
||||
one_line: bool,
|
||||
enabled: bool,
|
||||
timer: Instant,
|
||||
result: Option<String>,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
impl AmpsPlugin {
|
||||
pub fn new(config: &Config, args: &Args) -> Self {
|
||||
let fields = fields! {
|
||||
"name" => Field::new("AMP name."),
|
||||
"result" => Field::new("AMP result (a string)."),
|
||||
"refresh" => Field::new("AMP refresh interval.").unit("second"),
|
||||
"timer" => Field::new("Time until next refresh.").unit("second"),
|
||||
"count" => Field::new("Number of matching processes.").unit("number"),
|
||||
"countmin" => Field::new("Minimum number of matching processes.").unit("number"),
|
||||
"countmax" => Field::new("Maximum number of matching processes.").unit("number"),
|
||||
};
|
||||
let mut model = PluginModel::new(
|
||||
"amps",
|
||||
fields,
|
||||
Vec::new(),
|
||||
Value::Array(Vec::new()),
|
||||
config,
|
||||
args.time.unwrap_or(2.0),
|
||||
args.disable_history,
|
||||
args.disable_config_exec,
|
||||
);
|
||||
model.set_item_key("name");
|
||||
|
||||
let mut amps = Vec::new();
|
||||
for section in config.sections() {
|
||||
if !section.starts_with("amp_") {
|
||||
continue;
|
||||
}
|
||||
let command = config.get_value(section, "command", None);
|
||||
let enabled = config.get_bool_value(section, "enable", false);
|
||||
let regex = config
|
||||
.get_value(section, "regex", None)
|
||||
.and_then(|pattern| regex::Regex::new(&pattern).ok());
|
||||
let refresh = config.get_float_value(section, "refresh", 3.0);
|
||||
let countmin = config.get_value(section, "countmin", None).and_then(|v| v.parse::<f64>().ok());
|
||||
let countmax = config.get_value(section, "countmax", None).and_then(|v| v.parse::<f64>().ok());
|
||||
let one_line = config.get_bool_value(section, "one_line", false);
|
||||
amps.push(AmpConfig {
|
||||
name: amp_display_name(section),
|
||||
enabled,
|
||||
command,
|
||||
regex,
|
||||
refresh,
|
||||
countmin,
|
||||
countmax,
|
||||
one_line,
|
||||
timer: Instant::now(),
|
||||
result: None,
|
||||
count: 0,
|
||||
});
|
||||
}
|
||||
Self { model, amps }
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the AMP display name (mirrors the Python AMP module names).
|
||||
fn amp_display_name(section: &str) -> String {
|
||||
let raw = section.trim_start_matches("amp_");
|
||||
match raw {
|
||||
"systemv" => "SystemV".to_string(),
|
||||
"systemd" => "Systemd".to_string(),
|
||||
"nginx" => "Nginx".to_string(),
|
||||
other => {
|
||||
let mut chars = other.chars();
|
||||
match chars.next() {
|
||||
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for AmpsPlugin {
|
||||
fn name(&self) -> &str {
|
||||
"amps"
|
||||
}
|
||||
|
||||
fn model(&self) -> &PluginModel {
|
||||
&self.model
|
||||
}
|
||||
|
||||
fn model_mut(&mut self) -> &mut PluginModel {
|
||||
&mut self.model
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Option<&'static str> {
|
||||
Some("name")
|
||||
}
|
||||
|
||||
fn collect(&mut self, ctx: &mut UpdateContext) -> anyhow::Result<Value> {
|
||||
let mut stats = Vec::new();
|
||||
for amp in self.amps.iter_mut() {
|
||||
if amp.timer.elapsed().as_secs_f64() >= amp.refresh && amp.enabled {
|
||||
let result = match &.command {
|
||||
Some(command) => secure_popen(command, !ctx.args.disable_config_exec, None),
|
||||
None => String::new(),
|
||||
};
|
||||
amp.result = Some(if amp.one_line {
|
||||
result.lines().next().unwrap_or("").to_string()
|
||||
} else {
|
||||
result.trim_end().to_string()
|
||||
});
|
||||
amp.timer = Instant::now();
|
||||
}
|
||||
if let Some(regex) = &.regex {
|
||||
let procs = processes().lock().unwrap();
|
||||
amp.count = procs
|
||||
.get_list(false)
|
||||
.iter()
|
||||
.filter(|process| {
|
||||
let name = process.get("name").and_then(Value::as_str).unwrap_or("");
|
||||
let cmdline = process
|
||||
.get("cmdline")
|
||||
.and_then(Value::as_array)
|
||||
.map(|a| a.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>().join(" "))
|
||||
.unwrap_or_default();
|
||||
regex.is_match(name) || regex.is_match(&cmdline)
|
||||
})
|
||||
.count() as i64;
|
||||
}
|
||||
let timer_left = (amp.refresh - amp.timer.elapsed().as_secs_f64()).max(0.0);
|
||||
stats.push(json!({
|
||||
"key": "name",
|
||||
"name": amp.name,
|
||||
"result": amp.result,
|
||||
"refresh": amp.refresh,
|
||||
"timer": timer_left,
|
||||
"count": amp.count,
|
||||
"countmin": amp.countmin,
|
||||
"countmax": amp.countmax,
|
||||
"regex": amp.regex.is_some(),
|
||||
}));
|
||||
}
|
||||
Ok(Value::Array(stats))
|
||||
}
|
||||
|
||||
fn update_views(&mut self) {
|
||||
let key = self.get_key();
|
||||
self.model.update_views(key);
|
||||
let items: Vec<(String, i64, Option<i64>, Option<i64>, bool)> = match &self.model.stats {
|
||||
Value::Array(list) => list
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let name = item.get("name")?.as_str()?.to_string();
|
||||
let count = item.get("count")?.as_i64()?;
|
||||
let countmin = item.get("countmin").and_then(Value::as_i64);
|
||||
let countmax = item.get("countmax").and_then(Value::as_i64);
|
||||
let regex = item.get("regex").and_then(Value::as_bool).unwrap_or(false);
|
||||
Some((name, count, countmin, countmax, regex))
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
for (name, count, countmin, countmax, regex) in items {
|
||||
let decoration = if !regex {
|
||||
glances_core::Decoration::Default
|
||||
} else if countmax.is_some_and(|max| count > max) {
|
||||
glances_core::Decoration::Critical
|
||||
} else if countmin.is_some_and(|min| count < min) {
|
||||
glances_core::Decoration::Warning
|
||||
} else {
|
||||
glances_core::Decoration::Ok
|
||||
};
|
||||
self.model.set_view_decoration(Some(&name), "count", decoration);
|
||||
}
|
||||
}
|
||||
|
||||
fn display_curse(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn render(&mut self, ctx: &glances_core::RenderContext) -> Vec<glances_core::StatLine> {
|
||||
let mut ret = Vec::new();
|
||||
if self.model.stats.as_array().map(|a| a.is_empty()).unwrap_or(true) || ctx.max_width == 0 {
|
||||
return ret;
|
||||
}
|
||||
let name_max_width = ctx.max_width.saturating_sub(12);
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", "AMPS", width = name_max_width),
|
||||
glances_core::Decoration::Title,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
ret.push(self.model.curse_add_line(format!("{:>9}", "Count"), glances_core::Decoration::Default, false, false, false));
|
||||
let items = self.model.sorted_stats();
|
||||
let Some(items) = items.as_array() else {
|
||||
return ret;
|
||||
};
|
||||
for i in items {
|
||||
let name = i.get("name").and_then(Value::as_str).unwrap_or("");
|
||||
let count = i.get("count").and_then(Value::as_i64).unwrap_or(0);
|
||||
let result = i.get("result").and_then(Value::as_str).unwrap_or("");
|
||||
let display_name = if result.is_empty() { name.to_string() } else { format!("{name}: {result}") };
|
||||
ret.push(self.model.curse_new_line());
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", display_name.chars().take(name_max_width).collect::<String>(), width = name_max_width),
|
||||
glances_core::Decoration::Default,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{count:>9}"),
|
||||
self.model.view_decoration(Some(name), "count"),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
//! Folders plugin: monitor the size of configured directories.
|
||||
|
||||
use crate::fields;
|
||||
use glances_core::plugin::{Field, Plugin, PluginModel, UpdateContext};
|
||||
use glances_core::{Args, Config};
|
||||
use serde_json::{Value, json};
|
||||
use std::path::Path;
|
||||
use std::time::Instant;
|
||||
|
||||
pub struct FoldersPlugin {
|
||||
pub model: PluginModel,
|
||||
folders: Vec<FolderConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FolderConfig {
|
||||
indice: String,
|
||||
path: String,
|
||||
refresh: f64,
|
||||
careful: Option<f64>,
|
||||
warning: Option<f64>,
|
||||
critical: Option<f64>,
|
||||
timer: Instant,
|
||||
size: u64,
|
||||
errno: i64,
|
||||
}
|
||||
|
||||
impl FoldersPlugin {
|
||||
pub fn new(config: &Config, args: &Args) -> Self {
|
||||
let fields = fields! {
|
||||
"path" => Field::new("Absolute path."),
|
||||
"size" => Field::new("Folder size in bytes.").unit("byte"),
|
||||
"refresh" => Field::new("Refresh interval in seconds.").unit("second"),
|
||||
"errno" => Field::new("Return code when retrieving folder size (0 is no error).").unit("number"),
|
||||
"careful" => Field::new("Careful threshold in MB.").unit("megabyte"),
|
||||
"warning" => Field::new("Warning threshold in MB.").unit("megabyte"),
|
||||
"critical" => Field::new("Critical threshold in MB.").unit("megabyte"),
|
||||
};
|
||||
let mut model = PluginModel::new(
|
||||
"folders",
|
||||
fields,
|
||||
Vec::new(),
|
||||
Value::Array(Vec::new()),
|
||||
config,
|
||||
args.time.unwrap_or(2.0),
|
||||
args.disable_history,
|
||||
args.disable_config_exec,
|
||||
);
|
||||
model.set_item_key("path");
|
||||
|
||||
let mut folders = Vec::new();
|
||||
if config.has_section("folders") {
|
||||
let default_refresh = config.get_float_value("folders", "refresh", 60.0);
|
||||
for index in 1..=10 {
|
||||
let key = format!("folder_{index}_");
|
||||
let Some(path) = config.get_value("folders", &format!("{key}path"), None) else {
|
||||
continue;
|
||||
};
|
||||
if path.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let refresh = config.get_float_value("folders", &format!("{key}refresh"), default_refresh);
|
||||
let threshold = |name: &str| -> Option<f64> {
|
||||
config.get_value("folders", &format!("{key}{name}"), None).and_then(|v| v.parse().ok())
|
||||
};
|
||||
folders.push(FolderConfig {
|
||||
indice: index.to_string(),
|
||||
path,
|
||||
refresh,
|
||||
careful: threshold("careful"),
|
||||
warning: threshold("warning"),
|
||||
critical: threshold("critical"),
|
||||
timer: Instant::now() - std::time::Duration::from_secs(3600),
|
||||
size: 0,
|
||||
errno: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
Self { model, folders }
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for FoldersPlugin {
|
||||
fn name(&self) -> &str {
|
||||
"folders"
|
||||
}
|
||||
|
||||
fn model(&self) -> &PluginModel {
|
||||
&self.model
|
||||
}
|
||||
|
||||
fn model_mut(&mut self) -> &mut PluginModel {
|
||||
&mut self.model
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Option<&'static str> {
|
||||
Some("path")
|
||||
}
|
||||
|
||||
fn collect(&mut self, _ctx: &mut UpdateContext) -> anyhow::Result<Value> {
|
||||
let mut stats = Vec::new();
|
||||
for folder in self.folders.iter_mut() {
|
||||
if folder.timer.elapsed().as_secs_f64() >= folder.refresh {
|
||||
let (size, errno) = folder_size(Path::new(&folder.path), 0);
|
||||
folder.size = size;
|
||||
folder.errno = errno;
|
||||
folder.timer = Instant::now();
|
||||
}
|
||||
let mut item = serde_json::Map::new();
|
||||
item.insert("path".to_string(), json!(folder.path));
|
||||
item.insert("size".to_string(), json!(folder.size));
|
||||
item.insert("refresh".to_string(), json!(folder.refresh as i64));
|
||||
item.insert("errno".to_string(), json!(folder.errno));
|
||||
item.insert("careful".to_string(), json!(folder.careful));
|
||||
item.insert("warning".to_string(), json!(folder.warning));
|
||||
item.insert("critical".to_string(), json!(folder.critical));
|
||||
item.insert("key".to_string(), json!("path"));
|
||||
stats.push(Value::Object(item));
|
||||
}
|
||||
Ok(Value::Array(stats))
|
||||
}
|
||||
|
||||
fn update_views(&mut self) {
|
||||
let key = self.get_key();
|
||||
self.model.update_views(key);
|
||||
let items: Vec<(String, i64, u64, Option<f64>, Option<f64>, Option<f64>)> = match &self.model.stats {
|
||||
Value::Array(list) => list
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let path = item.get("path")?.as_str()?.to_string();
|
||||
let errno = item.get("errno")?.as_i64()?;
|
||||
let size = item.get("size")?.as_u64()?;
|
||||
let careful = item.get("careful").and_then(Value::as_f64);
|
||||
let warning = item.get("warning").and_then(Value::as_f64);
|
||||
let critical = item.get("critical").and_then(Value::as_f64);
|
||||
Some((path, errno, size, careful, warning, critical))
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
for (path, errno, size, careful, warning, critical) in items {
|
||||
if errno != 0 {
|
||||
continue;
|
||||
}
|
||||
let decoration = if critical.is_some_and(|c| size as f64 > c * 1_000_000.0) {
|
||||
glances_core::Decoration::Critical
|
||||
} else if warning.is_some_and(|c| size as f64 > c * 1_000_000.0) {
|
||||
glances_core::Decoration::Warning
|
||||
} else if careful.is_some_and(|c| size as f64 > c * 1_000_000.0) {
|
||||
glances_core::Decoration::Careful
|
||||
} else {
|
||||
glances_core::Decoration::Default
|
||||
};
|
||||
self.model.set_view_decoration(Some(&path), "size", decoration);
|
||||
}
|
||||
}
|
||||
|
||||
fn display_curse(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn render(&mut self, ctx: &glances_core::RenderContext) -> Vec<glances_core::StatLine> {
|
||||
let mut ret = Vec::new();
|
||||
if self.model.stats.as_array().map(|a| a.is_empty()).unwrap_or(true) || ctx.max_width == 0 {
|
||||
return ret;
|
||||
}
|
||||
let name_max_width = ctx.max_width.saturating_sub(12);
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", "FOLDERS", width = name_max_width),
|
||||
glances_core::Decoration::Title,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
ret.push(self.model.curse_add_line(format!("{:>9}", "Size"), glances_core::Decoration::Default, false, false, false));
|
||||
let items = self.model.sorted_stats();
|
||||
let Some(items) = items.as_array() else {
|
||||
return ret;
|
||||
};
|
||||
for i in items {
|
||||
let path = i.get("path").and_then(Value::as_str).unwrap_or("");
|
||||
let size = i.get("size").and_then(Value::as_f64).unwrap_or(0.0);
|
||||
let errno = i.get("errno").and_then(Value::as_i64).unwrap_or(0);
|
||||
ret.push(self.model.curse_new_line());
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", path.chars().take(name_max_width).collect::<String>(), width = name_max_width),
|
||||
glances_core::Decoration::Default,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
let msg = if errno != 0 {
|
||||
format!("?{:>8}", self.model.auto_unit(size, false, "K"))
|
||||
} else {
|
||||
format!("{:>9}", self.model.auto_unit(size, false, "K"))
|
||||
};
|
||||
ret.push(self.model.curse_add_line(
|
||||
msg,
|
||||
self.model.view_decoration(Some(path), "size"),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursive folder size (port of `folder_size`).
|
||||
fn folder_size(path: &Path, mut errno: i64) -> (u64, i64) {
|
||||
let mut size = 0u64;
|
||||
let entries = match std::fs::read_dir(path) {
|
||||
Ok(entries) => entries,
|
||||
Err(e) => return (0, e.raw_os_error().unwrap_or(0) as i64),
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let entry_path = entry.path();
|
||||
match entry.file_type() {
|
||||
Ok(file_type) if file_type.is_dir() => {
|
||||
let (sub_size, sub_errno) = folder_size(&entry_path, errno);
|
||||
size += sub_size;
|
||||
errno = sub_errno;
|
||||
}
|
||||
Ok(_) => match entry.metadata() {
|
||||
Ok(metadata) => size += metadata.len(),
|
||||
Err(e) => errno = e.raw_os_error().unwrap_or(0) as i64,
|
||||
},
|
||||
Err(e) => errno = e.raw_os_error().unwrap_or(0) as i64,
|
||||
}
|
||||
}
|
||||
(size, errno)
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
//! IP plugin: private and (optionally) public IP information.
|
||||
|
||||
use crate::fields;
|
||||
use glances_core::plugin::{Field, Plugin, PluginModel, UpdateContext};
|
||||
use glances_core::{Args, Config};
|
||||
use serde_json::{Value, json};
|
||||
use std::time::Instant;
|
||||
|
||||
pub struct IpPlugin {
|
||||
pub model: PluginModel,
|
||||
public_api: Option<String>,
|
||||
public_field: String,
|
||||
public_template: String,
|
||||
public_disabled: bool,
|
||||
public_username: Option<String>,
|
||||
public_password: Option<String>,
|
||||
public_interval: f64,
|
||||
public_timer: Instant,
|
||||
public_address: String,
|
||||
public_info: Option<Value>,
|
||||
}
|
||||
|
||||
impl IpPlugin {
|
||||
pub fn new(config: &Config, args: &Args) -> Self {
|
||||
let fields = fields! {
|
||||
"address" => Field::new("Private IP address"),
|
||||
"mask" => Field::new("Private IP mask"),
|
||||
"mask_cidr" => Field::new("Private IP mask in CIDR format").unit("number"),
|
||||
"gateway" => Field::new("Private IP gateway"),
|
||||
"public_address" => Field::new("Public IP address"),
|
||||
"public_info_human" => Field::new("Public IP information"),
|
||||
};
|
||||
let model = PluginModel::new(
|
||||
"ip",
|
||||
fields,
|
||||
Vec::new(),
|
||||
json!({}),
|
||||
config,
|
||||
args.time.unwrap_or(2.0),
|
||||
args.disable_history,
|
||||
args.disable_config_exec,
|
||||
);
|
||||
let public_api = model.get_conf_value("public_api", "").first().cloned();
|
||||
let public_disabled = model
|
||||
.get_conf_value("public_disabled", "")
|
||||
.first()
|
||||
.map(|v| v.to_lowercase() != "false")
|
||||
.unwrap_or(true)
|
||||
|| public_api.is_none();
|
||||
Self {
|
||||
model,
|
||||
public_api,
|
||||
public_field: "ip".to_string(),
|
||||
public_template: String::new(),
|
||||
public_disabled,
|
||||
public_username: None,
|
||||
public_password: None,
|
||||
public_interval: 300.0,
|
||||
public_timer: Instant::now() - std::time::Duration::from_secs(3600),
|
||||
public_address: String::new(),
|
||||
public_info: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_public(&mut self) {
|
||||
if self.public_disabled {
|
||||
return;
|
||||
}
|
||||
if self.public_timer.elapsed().as_secs_f64() < self.public_interval {
|
||||
return;
|
||||
}
|
||||
self.public_timer = Instant::now();
|
||||
let Some(api) = &self.public_api else {
|
||||
return;
|
||||
};
|
||||
let mut request = ureq::get(api)
|
||||
.config()
|
||||
.timeout_global(Some(std::time::Duration::from_secs(2)))
|
||||
.build();
|
||||
if let (Some(username), Some(password)) = (&self.public_username, &self.public_password) {
|
||||
request = request.header("Authorization", &format!("Basic {}:{}", username, password));
|
||||
}
|
||||
if let Ok(mut response) = request.call() {
|
||||
if let Ok(value) = response.body_mut().read_json::<Value>() {
|
||||
self.public_address =
|
||||
value.get(&self.public_field).and_then(Value::as_str).unwrap_or("").to_string();
|
||||
self.public_info = Some(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn public_info_human(&self) -> String {
|
||||
let Some(info) = &self.public_info else {
|
||||
return String::new();
|
||||
};
|
||||
if self.public_template.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let mut result = self.public_template.clone();
|
||||
for (key, value) in info.as_object().into_iter().flatten() {
|
||||
let text = match value {
|
||||
Value::String(s) => s.clone(),
|
||||
other => glances_core::py_str(other),
|
||||
};
|
||||
result = result.replace(&format!("{{{key}}}"), &text);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for IpPlugin {
|
||||
fn name(&self) -> &str {
|
||||
"ip"
|
||||
}
|
||||
|
||||
fn model(&self) -> &PluginModel {
|
||||
&self.model
|
||||
}
|
||||
|
||||
fn model_mut(&mut self) -> &mut PluginModel {
|
||||
&mut self.model
|
||||
}
|
||||
|
||||
fn collect(&mut self, ctx: &mut UpdateContext) -> anyhow::Result<Value> {
|
||||
let mut stats = serde_json::Map::new();
|
||||
let (address, mask) = crate::collectors::get_ip_address();
|
||||
if let Some(address) = &address {
|
||||
stats.insert("address".to_string(), json!(address));
|
||||
}
|
||||
if let Some(mask) = &mask {
|
||||
stats.insert("mask".to_string(), json!(mask));
|
||||
}
|
||||
stats.insert("mask_cidr".to_string(), json!(crate::collectors::ip_to_cidr(mask.as_deref())));
|
||||
self.refresh_public();
|
||||
if !self.public_address.is_empty() {
|
||||
let public = if ctx.args.hide_public_info {
|
||||
mask_public_ip(&self.public_address)
|
||||
} else {
|
||||
self.public_address.clone()
|
||||
};
|
||||
stats.insert("public_address".to_string(), json!(public));
|
||||
let human = self.public_info_human();
|
||||
if !human.is_empty() {
|
||||
stats.insert("public_info_human".to_string(), json!(human));
|
||||
}
|
||||
}
|
||||
Ok(Value::Object(stats))
|
||||
}
|
||||
|
||||
fn display_curse(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn render(&mut self, _ctx: &glances_core::RenderContext) -> Vec<glances_core::StatLine> {
|
||||
let mut ret = Vec::new();
|
||||
let s = &self.model.stats;
|
||||
let Some(address) = s.get("address").and_then(Value::as_str) else {
|
||||
return ret;
|
||||
};
|
||||
let mut text = address.to_string();
|
||||
if let Some(mask_cidr) = s.get("mask_cidr").and_then(Value::as_u64) {
|
||||
if mask_cidr > 0 {
|
||||
text += &format!("/{mask_cidr}");
|
||||
}
|
||||
}
|
||||
ret.push(self.model.curse_add_line(text, glances_core::Decoration::Default, false, false, false));
|
||||
if let Some(gateway) = s.get("gateway").and_then(Value::as_str) {
|
||||
ret.push(self.model.curse_add_line(format!(" gw {gateway}"), glances_core::Decoration::Default, true, false, false));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
fn mask_public_ip(address: &str) -> String {
|
||||
let parts: Vec<&str> = address.split('.').collect();
|
||||
if parts.len() == 4 {
|
||||
format!("{}.{}.{}.xxx", parts[0], parts[1], parts[2])
|
||||
} else {
|
||||
"xxx".to_string()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
//! Plugin implementations.
|
||||
//!
|
||||
//! The extended plugins (containers, GPU, ports, ...) are added in a later
|
||||
//! milestone.
|
||||
//! Remaining Python plugins (containers, GPU/NPU, connections, vms, cloud,
|
||||
//! smart) are not ported yet; see the README status section.
|
||||
|
||||
pub mod amps;
|
||||
pub mod core;
|
||||
pub mod cpu;
|
||||
pub mod diskio;
|
||||
pub mod folders;
|
||||
pub mod fs;
|
||||
pub mod ip;
|
||||
pub mod irq;
|
||||
pub mod load;
|
||||
pub mod mem;
|
||||
@@ -14,20 +17,26 @@ pub mod memswap;
|
||||
pub mod network;
|
||||
pub mod now;
|
||||
pub mod percpu;
|
||||
pub mod ports;
|
||||
pub mod processcount;
|
||||
pub mod processlist;
|
||||
pub mod programlist;
|
||||
pub mod psutilversion;
|
||||
pub mod quicklook;
|
||||
pub mod raid;
|
||||
pub mod sensors;
|
||||
pub mod system;
|
||||
pub mod uptime;
|
||||
pub mod version;
|
||||
pub mod wifi;
|
||||
|
||||
pub use amps::AmpsPlugin;
|
||||
pub use core::CorePlugin;
|
||||
pub use cpu::CpuPlugin;
|
||||
pub use diskio::DiskioPlugin;
|
||||
pub use folders::FoldersPlugin;
|
||||
pub use fs::FsPlugin;
|
||||
pub use ip::IpPlugin;
|
||||
pub use irq::IrqPlugin;
|
||||
pub use load::LoadPlugin;
|
||||
pub use mem::MemPlugin;
|
||||
@@ -35,12 +44,15 @@ pub use memswap::MemSwapPlugin;
|
||||
pub use network::NetworkPlugin;
|
||||
pub use now::NowPlugin;
|
||||
pub use percpu::PerCpuPlugin;
|
||||
pub use ports::PortsPlugin;
|
||||
pub use processcount::ProcessCountPlugin;
|
||||
pub use processlist::ProcessListPlugin;
|
||||
pub use programlist::ProgramListPlugin;
|
||||
pub use psutilversion::PsutilVersionPlugin;
|
||||
pub use quicklook::QuicklookPlugin;
|
||||
pub use raid::RaidPlugin;
|
||||
pub use sensors::SensorsPlugin;
|
||||
pub use system::SystemPlugin;
|
||||
pub use uptime::UptimePlugin;
|
||||
pub use version::VersionPlugin;
|
||||
pub use wifi::WifiPlugin;
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
//! Ports plugin: check the availability of configured hosts and ports.
|
||||
|
||||
use crate::fields;
|
||||
use glances_core::plugin::{Field, Plugin, PluginModel, UpdateContext};
|
||||
use glances_core::{Args, Config};
|
||||
use serde_json::{Value, json};
|
||||
use std::net::{TcpStream, ToSocketAddrs};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct PortsPlugin {
|
||||
pub model: PluginModel,
|
||||
entries: Vec<PortEntry>,
|
||||
default_timeout: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PortEntry {
|
||||
indice: String,
|
||||
host: String,
|
||||
port: Option<u16>,
|
||||
url: Option<String>,
|
||||
description: String,
|
||||
refresh: f64,
|
||||
timeout: f64,
|
||||
rtt_warning: Option<f64>,
|
||||
timer: Instant,
|
||||
status: Option<f64>,
|
||||
}
|
||||
|
||||
impl PortsPlugin {
|
||||
pub fn new(config: &Config, args: &Args) -> Self {
|
||||
let fields = fields! {
|
||||
"host" => Field::new("Measurement is be done on this host (or IP address)"),
|
||||
"port" => Field::new("Measurement is be done on this port (0 for ICMP)"),
|
||||
"url" => Field::new("Measurement is be done on this URL (credentials are redacted)"),
|
||||
"description" => Field::new("Human readable description for the host/port"),
|
||||
"refresh" => Field::new("Refresh time (in seconds) for this host/port"),
|
||||
"timeout" => Field::new("Timeout (in seconds) for the measurement"),
|
||||
"status" => Field::new("Measurement result (in seconds)").unit("second"),
|
||||
"rtt_warning" => Field::new("Warning threshold (in seconds) for the measurement").unit("second"),
|
||||
"indice" => Field::new("Unique indice for the host/port"),
|
||||
};
|
||||
let mut model = PluginModel::new(
|
||||
"ports",
|
||||
fields,
|
||||
Vec::new(),
|
||||
Value::Array(Vec::new()),
|
||||
config,
|
||||
args.time.unwrap_or(2.0),
|
||||
args.disable_history,
|
||||
args.disable_config_exec,
|
||||
);
|
||||
model.set_item_key("indice");
|
||||
|
||||
let default_timeout = config.get_float_value("ports", "timeout", 3.0);
|
||||
let refresh = config.get_float_value("ports", "refresh", 30.0);
|
||||
let mut entries = Vec::new();
|
||||
if config.has_section("ports") {
|
||||
// Default gateway entry.
|
||||
if config.get_bool_value("ports", "port_default_gateway", false) {
|
||||
if let Some(gateway) = crate::collectors::get_default_gateway() {
|
||||
entries.push(PortEntry {
|
||||
indice: "port_0".to_string(),
|
||||
host: gateway,
|
||||
port: None,
|
||||
url: None,
|
||||
description: "Default gateway".to_string(),
|
||||
refresh,
|
||||
timeout: default_timeout,
|
||||
rtt_warning: None,
|
||||
timer: Instant::now() - Duration::from_secs(3600),
|
||||
status: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
for index in 1..=255 {
|
||||
let key = format!("port_{index}_");
|
||||
let Some(host) = config.get_value("ports", &format!("{key}host"), None) else {
|
||||
continue;
|
||||
};
|
||||
if host.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let port = config.get_value("ports", &format!("{key}port"), None).and_then(|v| v.parse().ok());
|
||||
let description = config
|
||||
.get_value("ports", &format!("{key}description"), None)
|
||||
.unwrap_or_else(|| match port {
|
||||
Some(port) => format!("{host}:{port}"),
|
||||
None => host.clone(),
|
||||
});
|
||||
let timeout = config.get_float_value("ports", &format!("{key}timeout"), default_timeout);
|
||||
let rtt_warning = config
|
||||
.get_value("ports", &format!("{key}rtt_warning"), None)
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.map(|v| v / 1000.0);
|
||||
entries.push(PortEntry {
|
||||
indice: format!("port_{index}"),
|
||||
host,
|
||||
port,
|
||||
url: None,
|
||||
description,
|
||||
refresh,
|
||||
timeout,
|
||||
rtt_warning,
|
||||
timer: Instant::now() - Duration::from_secs(3600),
|
||||
status: None,
|
||||
});
|
||||
}
|
||||
for index in 1..=255 {
|
||||
let key = format!("web_{index}_");
|
||||
let Some(url) = config.get_value("ports", &format!("{key}url"), None) else {
|
||||
continue;
|
||||
};
|
||||
if url.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let description = config.get_value("ports", &format!("{key}description"), None).unwrap_or_else(|| url.clone());
|
||||
let timeout = config.get_float_value("ports", &format!("{key}timeout"), default_timeout);
|
||||
let rtt_warning = config
|
||||
.get_value("ports", &format!("{key}rtt_warning"), None)
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.map(|v| v / 1000.0);
|
||||
entries.push(PortEntry {
|
||||
indice: format!("web_{index}"),
|
||||
host: url.clone(),
|
||||
port: None,
|
||||
url: Some(url),
|
||||
description,
|
||||
refresh,
|
||||
timeout,
|
||||
rtt_warning,
|
||||
timer: Instant::now() - Duration::from_secs(3600),
|
||||
status: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
Self { model, entries, default_timeout }
|
||||
}
|
||||
|
||||
fn check(entry: &PortEntry) -> Option<f64> {
|
||||
let start = Instant::now();
|
||||
if let Some(url) = &entry.url {
|
||||
let timeout = Duration::from_secs_f64(entry.timeout.max(0.1));
|
||||
let request = ureq::get(url).config().timeout_global(Some(timeout)).build();
|
||||
match request.call() {
|
||||
Ok(_) => Some(start.elapsed().as_secs_f64()),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else if let Some(port) = entry.port {
|
||||
let timeout = Duration::from_secs_f64(entry.timeout.max(0.1));
|
||||
let address = format!("{}:{}", entry.host, port);
|
||||
let mut resolved = address.to_socket_addrs().ok()?;
|
||||
let socket = resolved.next()?;
|
||||
match TcpStream::connect_timeout(&socket, timeout) {
|
||||
Ok(_) => Some(start.elapsed().as_secs_f64()),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
// ICMP check with the ping command.
|
||||
let output = std::process::Command::new("ping")
|
||||
.args(["-c", "1", "-W", &format!("{}", entry.timeout.max(1.0) as i64), &entry.host])
|
||||
.output()
|
||||
.ok()?;
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
if output.status.success() {
|
||||
for part in text.split_whitespace() {
|
||||
if let Some(value) = part.strip_prefix("time=") {
|
||||
return value.parse::<f64>().ok().map(|ms| ms / 1000.0);
|
||||
}
|
||||
}
|
||||
Some(start.elapsed().as_secs_f64())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for PortsPlugin {
|
||||
fn name(&self) -> &str {
|
||||
"ports"
|
||||
}
|
||||
|
||||
fn model(&self) -> &PluginModel {
|
||||
&self.model
|
||||
}
|
||||
|
||||
fn model_mut(&mut self) -> &mut PluginModel {
|
||||
&mut self.model
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Option<&'static str> {
|
||||
Some("indice")
|
||||
}
|
||||
|
||||
fn collect(&mut self, _ctx: &mut UpdateContext) -> anyhow::Result<Value> {
|
||||
let mut stats = Vec::new();
|
||||
for entry in self.entries.iter_mut() {
|
||||
if entry.timer.elapsed().as_secs_f64() >= entry.refresh {
|
||||
entry.status = Self::check(entry);
|
||||
entry.timer = Instant::now();
|
||||
}
|
||||
let mut item = serde_json::Map::new();
|
||||
item.insert("host".to_string(), json!(entry.host));
|
||||
item.insert("port".to_string(), json!(entry.port));
|
||||
if let Some(url) = &entry.url {
|
||||
item.insert("url".to_string(), json!(redact_url(url)));
|
||||
}
|
||||
item.insert("description".to_string(), json!(entry.description));
|
||||
item.insert("refresh".to_string(), json!(entry.refresh));
|
||||
item.insert("timeout".to_string(), json!(entry.timeout));
|
||||
item.insert("status".to_string(), json!(entry.status));
|
||||
item.insert("rtt_warning".to_string(), json!(entry.rtt_warning));
|
||||
item.insert("indice".to_string(), json!(entry.indice));
|
||||
item.insert("key".to_string(), json!("indice"));
|
||||
stats.push(Value::Object(item));
|
||||
}
|
||||
let _ = self.default_timeout;
|
||||
Ok(Value::Array(stats))
|
||||
}
|
||||
|
||||
fn update_views(&mut self) {
|
||||
let key = self.get_key();
|
||||
self.model.update_views(key);
|
||||
let items: Vec<(String, Option<f64>, Option<f64>)> = match &self.model.stats {
|
||||
Value::Array(list) => list
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let indice = item.get("indice")?.as_str()?.to_string();
|
||||
let status = item.get("status").and_then(Value::as_f64);
|
||||
let warning = item.get("rtt_warning").and_then(Value::as_f64);
|
||||
Some((indice, status, warning))
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
for (indice, status, warning) in items {
|
||||
let decoration = match status {
|
||||
None => glances_core::Decoration::Critical,
|
||||
Some(status) if warning.is_some_and(|w| status > w) => glances_core::Decoration::Warning,
|
||||
Some(_) => glances_core::Decoration::Ok,
|
||||
};
|
||||
self.model.set_view_decoration(Some(&indice), "status", decoration);
|
||||
}
|
||||
}
|
||||
|
||||
fn display_curse(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn render(&mut self, ctx: &glances_core::RenderContext) -> Vec<glances_core::StatLine> {
|
||||
let mut ret = Vec::new();
|
||||
if self.model.stats.as_array().map(|a| a.is_empty()).unwrap_or(true) || ctx.max_width == 0 {
|
||||
return ret;
|
||||
}
|
||||
let name_max_width = ctx.max_width.saturating_sub(12);
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", "PORTS", width = name_max_width),
|
||||
glances_core::Decoration::Title,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
ret.push(self.model.curse_add_line(format!("{:>9}", "RTT"), glances_core::Decoration::Default, false, false, false));
|
||||
let items = self.model.sorted_stats();
|
||||
let Some(items) = items.as_array() else {
|
||||
return ret;
|
||||
};
|
||||
for i in items {
|
||||
let indice = i.get("indice").and_then(Value::as_str).unwrap_or("");
|
||||
let description = i.get("description").and_then(Value::as_str).unwrap_or("");
|
||||
let status = i.get("status").and_then(Value::as_f64);
|
||||
ret.push(self.model.curse_new_line());
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", description.chars().take(name_max_width).collect::<String>(), width = name_max_width),
|
||||
glances_core::Decoration::Default,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
let msg = match status {
|
||||
Some(status) => format!("{:>8.3}s", status),
|
||||
None => format!("{:>9}", "timeout"),
|
||||
};
|
||||
ret.push(self.model.curse_add_line(
|
||||
msg,
|
||||
self.model.view_decoration(Some(indice), "status"),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Hide the credentials embedded in a URL.
|
||||
fn redact_url(url: &str) -> String {
|
||||
if let Some(scheme_end) = url.find("://") {
|
||||
let rest = &url[scheme_end + 3..];
|
||||
if let Some(at) = rest.find('@') {
|
||||
return format!("{}://********@{}", &url[..scheme_end], &rest[at + 1..]);
|
||||
}
|
||||
}
|
||||
url.to_string()
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
//! RAID plugin: parse `/proc/mdstat`.
|
||||
|
||||
use crate::fields;
|
||||
use glances_core::plugin::{Plugin, PluginModel, UpdateContext};
|
||||
use glances_core::{Args, Config};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
pub struct RaidPlugin {
|
||||
pub model: PluginModel,
|
||||
}
|
||||
|
||||
impl RaidPlugin {
|
||||
pub fn new(config: &Config, args: &Args) -> Self {
|
||||
let model = PluginModel::new(
|
||||
"raid",
|
||||
fields! {},
|
||||
Vec::new(),
|
||||
json!({}),
|
||||
config,
|
||||
args.time.unwrap_or(2.0),
|
||||
args.disable_history,
|
||||
args.disable_config_exec,
|
||||
);
|
||||
Self { model }
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for RaidPlugin {
|
||||
fn name(&self) -> &str {
|
||||
"raid"
|
||||
}
|
||||
|
||||
fn model(&self) -> &PluginModel {
|
||||
&self.model
|
||||
}
|
||||
|
||||
fn model_mut(&mut self) -> &mut PluginModel {
|
||||
&mut self.model
|
||||
}
|
||||
|
||||
fn available(&mut self) -> bool {
|
||||
std::path::Path::new("/proc/mdstat").exists()
|
||||
}
|
||||
|
||||
fn collect(&mut self, _ctx: &mut UpdateContext) -> anyhow::Result<Value> {
|
||||
let mut stats = serde_json::Map::new();
|
||||
let Ok(content) = std::fs::read_to_string("/proc/mdstat") else {
|
||||
return Ok(Value::Object(stats));
|
||||
};
|
||||
let lines: Vec<&str> = content.lines().collect();
|
||||
let mut index = 0;
|
||||
while index < lines.len() {
|
||||
let line = lines[index];
|
||||
if !line.starts_with("md") || !line.contains(':') {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
let fields: Vec<&str> = line.split_whitespace().collect();
|
||||
if fields.len() < 3 {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
let name = fields[0].to_string();
|
||||
let status = fields[2].to_string();
|
||||
let raid_type = fields
|
||||
.iter()
|
||||
.find(|f| f.starts_with("raid") || f.starts_with("linear") || f.starts_with("multipath"))
|
||||
.map(|f| f.to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
// Devices (name[index]).
|
||||
let mut components = serde_json::Map::new();
|
||||
for field in fields.iter().skip(3) {
|
||||
if let Some(device) = field.split('[').next() {
|
||||
if !device.is_empty() {
|
||||
components.insert(device.to_string(), json!("active sync"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// The following line contains blocks and [used/available] [UU].
|
||||
let mut used = components.len();
|
||||
let mut available = components.len();
|
||||
if index + 1 < lines.len() {
|
||||
let detail = lines[index + 1];
|
||||
if let Some(start) = detail.rfind('[') {
|
||||
if let Some(end) = detail[start..].find(']') {
|
||||
let counts = &detail[start + 1..start + end];
|
||||
if let Some((u, a)) = counts.split_once('/') {
|
||||
used = u.parse().unwrap_or(used as i64) as usize;
|
||||
available = a.parse().unwrap_or(available as i64) as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(start) = detail.rfind('[') {
|
||||
if let Some(end) = detail[start..].find(']') {
|
||||
let flags = &detail[start + 1..start + end];
|
||||
if flags.len() == components.len() {
|
||||
let names: Vec<String> = components.keys().cloned().collect();
|
||||
for (position, flag) in flags.chars().enumerate() {
|
||||
if let Some(device) = names.get(position) {
|
||||
let status = match flag {
|
||||
'U' => "active sync",
|
||||
'_' => "faulty",
|
||||
'S' => "spare",
|
||||
_ => "unknown",
|
||||
};
|
||||
components.insert(device.clone(), json!(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stats.insert(
|
||||
name.clone(),
|
||||
json!({
|
||||
"name": name,
|
||||
"status": status,
|
||||
"type": raid_type,
|
||||
"used": used,
|
||||
"available": available,
|
||||
"components": components,
|
||||
}),
|
||||
);
|
||||
index += 2;
|
||||
}
|
||||
Ok(Value::Object(stats))
|
||||
}
|
||||
|
||||
fn display_curse(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn render(&mut self, ctx: &glances_core::RenderContext) -> Vec<glances_core::StatLine> {
|
||||
let mut ret = Vec::new();
|
||||
let Some(arrays) = self.model.stats.as_object() else {
|
||||
return ret;
|
||||
};
|
||||
if arrays.is_empty() || ctx.max_width == 0 {
|
||||
return ret;
|
||||
}
|
||||
let name_max_width = ctx.max_width.saturating_sub(12);
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", "RAID disks", width = name_max_width),
|
||||
glances_core::Decoration::Title,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
ret.push(self.model.curse_add_line(format!("{:>7}", "Used"), glances_core::Decoration::Default, false, false, false));
|
||||
ret.push(self.model.curse_add_line(format!("{:>7}", "Avail"), glances_core::Decoration::Default, false, false, false));
|
||||
let mut names: Vec<&String> = arrays.keys().collect();
|
||||
names.sort();
|
||||
for name in names {
|
||||
let Some(array) = arrays.get(name).and_then(Value::as_object) else {
|
||||
continue;
|
||||
};
|
||||
let status = array.get("status").and_then(Value::as_str).unwrap_or("");
|
||||
let used = array.get("used").and_then(Value::as_i64).unwrap_or(0);
|
||||
let available = array.get("available").and_then(Value::as_i64).unwrap_or(0);
|
||||
let array_type = array.get("type").and_then(Value::as_str).unwrap_or("unknown");
|
||||
let decoration = raid_alert(status, used, available, array_type);
|
||||
ret.push(self.model.curse_new_line());
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", name.chars().take(name_max_width).collect::<String>(), width = name_max_width),
|
||||
decoration,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
ret.push(self.model.curse_add_line(format!("{used:>7}"), glances_core::Decoration::Default, false, false, false));
|
||||
ret.push(self.model.curse_add_line(format!("{available:>7}"), glances_core::Decoration::Default, false, false, false));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn raid_alert(status: &str, used: i64, available: i64, array_type: &str) -> glances_core::Decoration {
|
||||
if status != "active" || used < available {
|
||||
return glances_core::Decoration::Critical;
|
||||
}
|
||||
if array_type == "raid0" {
|
||||
return glances_core::Decoration::Warning;
|
||||
}
|
||||
glances_core::Decoration::Ok
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
//! Wi-Fi plugin (reads `/proc/net/wireless`).
|
||||
|
||||
use crate::fields;
|
||||
use glances_core::plugin::{Field, Plugin, PluginModel, UpdateContext};
|
||||
use glances_core::{Args, Config, Decoration};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
pub struct WifiPlugin {
|
||||
pub model: PluginModel,
|
||||
}
|
||||
|
||||
impl WifiPlugin {
|
||||
pub fn new(config: &Config, args: &Args) -> Self {
|
||||
let fields = fields! {
|
||||
"ssid" => Field::new("Wi-Fi network name."),
|
||||
"quality_link" => Field::new("Signal quality level.").unit("dBm"),
|
||||
"quality_level" => Field::new("Signal strong level.").unit("dBm").alert(),
|
||||
};
|
||||
let mut model = PluginModel::new(
|
||||
"wifi",
|
||||
fields,
|
||||
Vec::new(),
|
||||
Value::Array(Vec::new()),
|
||||
config,
|
||||
args.time.unwrap_or(2.0),
|
||||
args.disable_history,
|
||||
args.disable_config_exec,
|
||||
);
|
||||
model.set_item_key("ssid");
|
||||
Self { model }
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for WifiPlugin {
|
||||
fn name(&self) -> &str {
|
||||
"wifi"
|
||||
}
|
||||
|
||||
fn model(&self) -> &PluginModel {
|
||||
&self.model
|
||||
}
|
||||
|
||||
fn model_mut(&mut self) -> &mut PluginModel {
|
||||
&mut self.model
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Option<&'static str> {
|
||||
Some("ssid")
|
||||
}
|
||||
|
||||
fn available(&mut self) -> bool {
|
||||
std::path::Path::new("/proc/net/wireless").exists()
|
||||
}
|
||||
|
||||
fn collect(&mut self, _ctx: &mut UpdateContext) -> anyhow::Result<Value> {
|
||||
let mut stats = Vec::new();
|
||||
let Ok(content) = std::fs::read_to_string("/proc/net/wireless") else {
|
||||
return Ok(Value::Array(stats));
|
||||
};
|
||||
for line in content.lines().skip(2) {
|
||||
let fields: Vec<&str> = line.split_whitespace().collect();
|
||||
if fields.len() < 4 {
|
||||
continue;
|
||||
}
|
||||
let ssid = fields[0].trim_end_matches(':').to_string();
|
||||
let quality_link = fields[2].trim_end_matches('.').parse::<f64>().unwrap_or(0.0);
|
||||
let quality_level = fields[3].trim_end_matches('.').parse::<f64>().unwrap_or(0.0);
|
||||
stats.push(json!({
|
||||
"key": "ssid",
|
||||
"ssid": ssid,
|
||||
"quality_link": quality_link,
|
||||
"quality_level": quality_level,
|
||||
}));
|
||||
}
|
||||
Ok(Value::Array(stats))
|
||||
}
|
||||
|
||||
fn update_views(&mut self) {
|
||||
let key = self.get_key();
|
||||
self.model.update_views(key);
|
||||
let items: Vec<(String, f64)> = match &self.model.stats {
|
||||
Value::Array(list) => list
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let ssid = item.get("ssid")?.as_str()?.to_string();
|
||||
let level = item.get("quality_level")?.as_f64()?;
|
||||
Some((ssid, level))
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
for (ssid, level) in items {
|
||||
// Signal quality: lower is better, so the comparison is inverted.
|
||||
let critical = self.model.get_limit("critical", "wifi");
|
||||
let warning = self.model.get_limit("warning", "wifi");
|
||||
let careful = self.model.get_limit("careful", "wifi");
|
||||
let decoration = if critical.is_some_and(|c| level < c) {
|
||||
Decoration::Critical
|
||||
} else if warning.is_some_and(|c| level < c) {
|
||||
Decoration::Warning
|
||||
} else if careful.is_some_and(|c| level < c) {
|
||||
Decoration::Careful
|
||||
} else if critical.is_none() && warning.is_none() && careful.is_none() {
|
||||
Decoration::Default
|
||||
} else {
|
||||
Decoration::Ok
|
||||
};
|
||||
self.model.set_view_decoration(Some(&ssid), "quality_level", decoration);
|
||||
}
|
||||
}
|
||||
|
||||
fn display_curse(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn render(&mut self, ctx: &glances_core::RenderContext) -> Vec<glances_core::StatLine> {
|
||||
let mut ret = Vec::new();
|
||||
if self.model.stats.as_array().map(|a| a.is_empty()).unwrap_or(true) || ctx.max_width == 0 {
|
||||
return ret;
|
||||
}
|
||||
let if_name_max_width = ctx.max_width.saturating_sub(5);
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", "WIFI", width = if_name_max_width),
|
||||
glances_core::Decoration::Title,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
ret.push(self.model.curse_add_line(format!("{:>7}", "dBm"), glances_core::Decoration::Default, false, false, false));
|
||||
let Some(items) = self.model.stats.as_array() else {
|
||||
return ret;
|
||||
};
|
||||
for i in items {
|
||||
let ssid = i.get("ssid").and_then(Value::as_str).unwrap_or("");
|
||||
let level = i.get("quality_level").and_then(Value::as_f64).unwrap_or(0.0);
|
||||
ret.push(self.model.curse_new_line());
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:<width$}", ssid.chars().take(if_name_max_width).collect::<String>(), width = if_name_max_width),
|
||||
glances_core::Decoration::Default,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
ret.push(self.model.curse_add_line(
|
||||
format!("{:>7}", level),
|
||||
self.model.view_decoration(Some(ssid), "quality_level"),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,19 @@ use ratatui::widgets::{Block, Borders, Clear, Paragraph, Wrap};
|
||||
use std::io::Stdout;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const LEFT_SIDEBAR: [&str; 8] = ["network", "diskio", "fs", "irq", "sensors", "now", "folders", "raid"];
|
||||
const LEFT_SIDEBAR: [&str; 11] = [
|
||||
"network",
|
||||
"ports",
|
||||
"wifi",
|
||||
"diskio",
|
||||
"fs",
|
||||
"irq",
|
||||
"folders",
|
||||
"raid",
|
||||
"sensors",
|
||||
"ip",
|
||||
"now",
|
||||
];
|
||||
const TOP_PLUGINS: [&str; 6] = ["cpu", "percpu", "mem", "memswap", "load", "gpu"];
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
Reference in New Issue
Block a user