Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d79799da90 | |||
| fc31a953aa | |||
| c1177c5ea5 | |||
| 8a0c71c494 | |||
| a9149178d4 | |||
| fd9a1114b9 | |||
| 91c5209266 | |||
| dac46a1652 | |||
| 33e1423dee | |||
| 23b331a4f8 | |||
| e7b35953ca | |||
| 12cf26d8a6 | |||
| f3e8b01d7e | |||
| 0564083ac7 | |||
| f53aef1821 | |||
| 985ee54669 | |||
| 3bb8f39e34 | |||
| 288ff34c01 | |||
| af48990cf7 | |||
| 944d9d663d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
/target
|
||||
target
|
||||
.env
|
||||
449
Cargo.lock
generated
449
Cargo.lock
generated
@@ -17,6 +17,65 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -64,9 +123,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.29"
|
||||
version = "1.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362"
|
||||
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@@ -77,6 +136,52 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@@ -93,6 +198,15 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "discord_client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@@ -110,6 +224,12 @@ version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "downcast"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
@@ -171,6 +291,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fragile"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@@ -193,6 +319,17 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
@@ -205,6 +342,12 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
@@ -213,6 +356,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
@@ -251,10 +395,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.11"
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -271,9 +421,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
@@ -369,9 +525,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
|
||||
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
@@ -512,9 +668,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.8"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
|
||||
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -537,6 +693,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@@ -609,6 +771,32 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mockall"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"downcast",
|
||||
"fragile",
|
||||
"mockall_derive",
|
||||
"predicates",
|
||||
"predicates-tree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mockall_derive"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
@@ -630,11 +818,12 @@ dependencies = [
|
||||
name = "node-notifier"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"discord_client",
|
||||
"dotenvy",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
"mockall",
|
||||
"rstest",
|
||||
"system_monitor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -680,6 +869,12 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.73"
|
||||
@@ -757,6 +952,41 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"predicates-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@@ -781,6 +1011,41 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.22"
|
||||
@@ -838,29 +1103,67 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.25"
|
||||
name = "rstest"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||
checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49"
|
||||
dependencies = [
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"rstest_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.28"
|
||||
version = "0.23.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
|
||||
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustls-pki-types",
|
||||
@@ -880,9 +1183,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.3"
|
||||
version = "0.103.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
|
||||
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -891,9 +1194,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
@@ -933,6 +1236,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
@@ -955,9 +1264,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
version = "1.0.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -985,9 +1294,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
@@ -997,12 +1306,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1011,6 +1320,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -1083,6 +1398,13 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system_monitor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sysinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
@@ -1096,6 +1418,12 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
@@ -1108,9 +1436,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.46.1"
|
||||
version = "1.47.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -1120,7 +1448,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"socket2",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1145,9 +1473,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.15"
|
||||
version = "0.7.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
|
||||
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -1156,6 +1484,23 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
@@ -1255,6 +1600,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
@@ -1525,7 +1876,7 @@ version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
"windows-targets 0.53.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1546,10 +1897,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.2"
|
||||
version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
@@ -1665,6 +2017,15 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
@@ -1744,9 +2105,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.2"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
|
||||
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -5,7 +5,11 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
dotenvy = "0.15.7"
|
||||
reqwest = { version = "0.12.22", features = ["blocking", "json"] }
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
sysinfo = "0.35.2"
|
||||
discord_client = { path = "lib/actors/discord_client" }
|
||||
system_monitor = { path = "lib/actors/system_monitor" }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
mockall = "0.13.1"
|
||||
rstest = "0.26.1"
|
||||
|
||||
|
||||
1741
lib/actors/discord_client/Cargo.lock
generated
Normal file
1741
lib/actors/discord_client/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
lib/actors/discord_client/Cargo.toml
Normal file
12
lib/actors/discord_client/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "discord_client"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.12.22", features = ["blocking", "json"] }
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "1.7.0"
|
||||
68
lib/actors/discord_client/src/lib.rs
Normal file
68
lib/actors/discord_client/src/lib.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
pub struct DiscordNotifier {
|
||||
webhook_url: String,
|
||||
username: String,
|
||||
avatar_url: String,
|
||||
}
|
||||
|
||||
impl DiscordNotifier {
|
||||
pub fn new(webhook_url: String, username: String, avatar_url: String) -> DiscordNotifier {
|
||||
DiscordNotifier {
|
||||
webhook_url,
|
||||
username,
|
||||
avatar_url,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_notification(&self, message: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let payload = serde_json::json!({
|
||||
"content": message,
|
||||
"username": self.username,
|
||||
"avatar_url": self.avatar_url
|
||||
});
|
||||
|
||||
client
|
||||
.post(&self.webhook_url)
|
||||
.json(&payload)
|
||||
.send()
|
||||
.map(|_| ())
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_notification() {
|
||||
use serde_json::json;
|
||||
|
||||
const USERNAME: &str = "TEST_USER";
|
||||
const AVATAR_URL: &str = "https://example.com/avatar.png";
|
||||
const MESSAGE: &str = "Test message";
|
||||
|
||||
let mut server = mockito::Server::new();
|
||||
|
||||
let mock = server
|
||||
.mock("POST", "/")
|
||||
.with_status(204)
|
||||
.match_header("content-type", "application/json")
|
||||
.match_body(mockito::Matcher::Json(json!({
|
||||
"content": MESSAGE,
|
||||
"username": USERNAME,
|
||||
"avatar_url": AVATAR_URL
|
||||
})))
|
||||
.create();
|
||||
|
||||
let webhook_url = server.url();
|
||||
|
||||
let notifier = DiscordNotifier::new(
|
||||
webhook_url.clone(),
|
||||
USERNAME.to_string(),
|
||||
AVATAR_URL.to_string(),
|
||||
);
|
||||
let result = notifier.send_notification(MESSAGE);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"{}",
|
||||
format!("Failed to send notification: {:?}", result.err())
|
||||
);
|
||||
mock.assert();
|
||||
}
|
||||
238
lib/actors/system_monitor/Cargo.lock
generated
Normal file
238
lib/actors/system_monitor/Cargo.lock
generated
Normal file
@@ -0,0 +1,238 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system_monitor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sysinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-link",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
7
lib/actors/system_monitor/Cargo.toml
Normal file
7
lib/actors/system_monitor/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "system_monitor"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sysinfo = "0.35.2"
|
||||
40
lib/actors/system_monitor/src/get_info.rs
Normal file
40
lib/actors/system_monitor/src/get_info.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use sysinfo::{Disks, System};
|
||||
|
||||
|
||||
pub fn get_cpu_usage(sys: &System, _: &Disks) -> f32 {
|
||||
sys.global_cpu_usage()
|
||||
}
|
||||
|
||||
pub fn get_memory_usage(sys: &System, _: &Disks) -> f32 {
|
||||
let total_memory = sys.total_memory() as f32;
|
||||
let used_memory = sys.used_memory() as f32;
|
||||
|
||||
used_memory / total_memory * 100.0
|
||||
}
|
||||
|
||||
pub fn get_swap_usage(sys: &System, _: &Disks) -> f32 {
|
||||
let total_swap = sys.total_swap() as f32;
|
||||
let used_swap = sys.used_swap() as f32;
|
||||
|
||||
used_swap / total_swap * 100.0
|
||||
}
|
||||
|
||||
pub fn get_disk_usage(_: &System, disks: &Disks) -> f32 {
|
||||
let mut total_used_space = 0.0;
|
||||
let mut total_space = 0.0;
|
||||
|
||||
for disk in disks {
|
||||
if let Some(mount_point) = disk.mount_point().to_str() {
|
||||
if mount_point == "/" || mount_point == "/home" {
|
||||
total_used_space += disk.total_space() as f32 - disk.available_space() as f32;
|
||||
total_space += disk.total_space() as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total_space == 0.0 {
|
||||
return 0.0; // Avoid division by zero
|
||||
}
|
||||
|
||||
(total_used_space / total_space) * 100.0
|
||||
}
|
||||
203
lib/actors/system_monitor/src/lib.rs
Normal file
203
lib/actors/system_monitor/src/lib.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use crate::resource_threshold::ResourceThreshold;
|
||||
use std::{collections::HashMap, fmt};
|
||||
use sysinfo::{Disks, System};
|
||||
|
||||
mod get_info;
|
||||
pub mod resource_threshold;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThresholdViolation {
|
||||
pub metric_name: String,
|
||||
pub current_value: f32,
|
||||
pub threshold: f32,
|
||||
pub severity: Severity,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum Severity {
|
||||
Warning,
|
||||
Critical,
|
||||
}
|
||||
|
||||
impl fmt::Display for Severity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Severity::Warning => write!(f, "Warning"),
|
||||
Severity::Critical => write!(f, "Critical"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystemMonitor {
|
||||
pub system: System,
|
||||
pub disks: Disks,
|
||||
pub thresholds: Vec<ResourceThreshold>,
|
||||
}
|
||||
|
||||
impl SystemMonitor {
|
||||
pub fn new(thresholds: Vec<ResourceThreshold>) -> Self {
|
||||
let mut system_monitor = SystemMonitor {
|
||||
system: System::new_all(),
|
||||
disks: Disks::new_with_refreshed_list(),
|
||||
thresholds,
|
||||
};
|
||||
system_monitor.refresh();
|
||||
|
||||
system_monitor
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
self.system.refresh_all();
|
||||
self.disks.refresh(true);
|
||||
}
|
||||
|
||||
pub fn check_thresholds(&self) -> Vec<ThresholdViolation> {
|
||||
let mut violations = Vec::new();
|
||||
|
||||
for threshold in &self.thresholds {
|
||||
let current_value = threshold.get_value(&self.system, &self.disks);
|
||||
|
||||
// Check critical threshold first (highest priority)
|
||||
if let Some(critical_threshold) = threshold.critical_threshold {
|
||||
if current_value > critical_threshold {
|
||||
violations.push(ThresholdViolation {
|
||||
metric_name: threshold.name.clone(),
|
||||
current_value,
|
||||
threshold: critical_threshold,
|
||||
severity: Severity::Critical,
|
||||
});
|
||||
continue; // Skip warning check if critical
|
||||
}
|
||||
}
|
||||
|
||||
// Check warning threshold
|
||||
if let Some(warning_threshold) = threshold.warning_threshold {
|
||||
if current_value > warning_threshold {
|
||||
violations.push(ThresholdViolation {
|
||||
metric_name: threshold.name.clone(),
|
||||
current_value,
|
||||
threshold: warning_threshold,
|
||||
severity: Severity::Warning,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
violations
|
||||
}
|
||||
|
||||
/// Get current system metrics based on configured thresholds
|
||||
pub fn get_metrics(&self) -> HashMap<String, f32> {
|
||||
let mut metrics = HashMap::new();
|
||||
|
||||
for threshold in &self.thresholds {
|
||||
let value = threshold.get_value(&self.system, &self.disks);
|
||||
metrics.insert(threshold.name.clone(), value);
|
||||
}
|
||||
|
||||
metrics
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SystemMonitor {
|
||||
fn default() -> Self {
|
||||
Self::new(resource_threshold::get_default_resource_thresholds())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct TestCase {
|
||||
name: &'static str,
|
||||
value: f32,
|
||||
warning_threshold: Option<f32>,
|
||||
critical_threshold: Option<f32>,
|
||||
expected_violation: Option<Severity>,
|
||||
}
|
||||
|
||||
fn create_test_case() -> Vec<TestCase> {
|
||||
vec![
|
||||
TestCase {
|
||||
name: "Everything is fine",
|
||||
value: 1.0,
|
||||
warning_threshold: Some(2.0),
|
||||
critical_threshold: Some(3.0),
|
||||
expected_violation: None,
|
||||
},
|
||||
TestCase {
|
||||
name: "Warning threshold triggered",
|
||||
value: 2.5,
|
||||
warning_threshold: Some(2.0),
|
||||
critical_threshold: Some(3.0),
|
||||
expected_violation: Some(Severity::Warning),
|
||||
},
|
||||
TestCase {
|
||||
name: "Critical threshold triggered",
|
||||
value: 3.5,
|
||||
warning_threshold: Some(1.0),
|
||||
critical_threshold: Some(2.0),
|
||||
expected_violation: Some(Severity::Critical),
|
||||
},
|
||||
TestCase {
|
||||
name: "Warning threshold triggered with critical empty",
|
||||
value: 3.5,
|
||||
warning_threshold: Some(3.0),
|
||||
critical_threshold: None,
|
||||
expected_violation: Some(Severity::Warning),
|
||||
},
|
||||
TestCase {
|
||||
name: "Critical threshold triggered with warning empty",
|
||||
value: 3.5,
|
||||
warning_threshold: None,
|
||||
critical_threshold: Some(3.0),
|
||||
expected_violation: Some(Severity::Critical),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_thresholds() {
|
||||
// Iterate through each test case
|
||||
for test_case in create_test_case() {
|
||||
let mut monitor = SystemMonitor::default();
|
||||
|
||||
let mut threshold = ResourceThreshold::new(
|
||||
test_case.name.to_string(),
|
||||
move |_, _| test_case.value
|
||||
);
|
||||
|
||||
// Apply thresholds conditionally
|
||||
if let Some(warning) = test_case.warning_threshold {
|
||||
threshold = threshold.with_warning_threshold(warning);
|
||||
}
|
||||
if let Some(critical) = test_case.critical_threshold {
|
||||
threshold = threshold.with_critical_threshold(critical);
|
||||
}
|
||||
|
||||
monitor.thresholds = vec![threshold];
|
||||
|
||||
let violations = monitor.check_thresholds();
|
||||
if let Some(expected_severity) = test_case.expected_violation {
|
||||
assert_eq!(
|
||||
violations.len(),
|
||||
1,
|
||||
"Expected one violation for test case: {}",
|
||||
test_case.name
|
||||
);
|
||||
assert_eq!(
|
||||
violations[0].severity, expected_severity,
|
||||
"Unexpected severity for test case: {}",
|
||||
test_case.name
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
violations.is_empty(),
|
||||
"Expected no violations for test case: {}",
|
||||
test_case.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
lib/actors/system_monitor/src/resource_threshold.rs
Normal file
62
lib/actors/system_monitor/src/resource_threshold.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::get_info;
|
||||
use sysinfo::{Disks, System};
|
||||
|
||||
pub struct ResourceThreshold {
|
||||
pub name: String,
|
||||
pub get_value_fn: Box<dyn Fn(&System, &Disks) -> f32>,
|
||||
pub warning_threshold: Option<f32>,
|
||||
pub critical_threshold: Option<f32>,
|
||||
}
|
||||
|
||||
impl ResourceThreshold {
|
||||
pub fn new<F>(name: String, get_value_fn: F) -> Self
|
||||
where
|
||||
F: Fn(&System, &Disks) -> f32 + 'static,
|
||||
{
|
||||
Self {
|
||||
name,
|
||||
get_value_fn: Box::new(get_value_fn),
|
||||
warning_threshold: None,
|
||||
critical_threshold: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_warning_threshold(mut self, warning_threshold: f32) -> Self {
|
||||
self.warning_threshold = Some(warning_threshold);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_critical_threshold(mut self, critical_threshold: f32) -> Self {
|
||||
self.critical_threshold = Some(critical_threshold);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_value(&self, sys: &System, disks: &Disks) -> f32 {
|
||||
(self.get_value_fn)(sys, disks)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_default_resource_thresholds() -> Vec<ResourceThreshold> {
|
||||
vec![
|
||||
ResourceThreshold::new("CPU Usage".to_string(), |sys, disks| {
|
||||
get_info::get_cpu_usage(sys, disks)
|
||||
})
|
||||
.with_warning_threshold(80.0)
|
||||
.with_critical_threshold(95.0),
|
||||
ResourceThreshold::new("Memory Usage".to_string(), |sys, disks| {
|
||||
get_info::get_memory_usage(sys, disks)
|
||||
})
|
||||
.with_warning_threshold(85.0)
|
||||
.with_critical_threshold(95.0),
|
||||
ResourceThreshold::new("Swap Usage".to_string(), |sys, disks| {
|
||||
get_info::get_swap_usage(sys, disks)
|
||||
})
|
||||
.with_warning_threshold(1.0)
|
||||
.with_critical_threshold(25.0),
|
||||
ResourceThreshold::new("Disk Usage".to_string(), |sys, disks| {
|
||||
get_info::get_disk_usage(sys, disks)
|
||||
})
|
||||
.with_warning_threshold(80.0)
|
||||
.with_critical_threshold(90.0),
|
||||
]
|
||||
}
|
||||
60
src/actors/driven/for_formatting_message/alert_formatter.rs
Normal file
60
src/actors/driven/for_formatting_message/alert_formatter.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::app::ports::driven::{ForFormattingMessage, ForGettingViolationData};
|
||||
|
||||
pub struct AlertFormatter;
|
||||
|
||||
impl AlertFormatter {
|
||||
fn get_message_for_metric(&self, metric: &str, level: &str) -> String {
|
||||
match (metric, level) {
|
||||
("CPU", "WARNING") => "High CPU usage detected".to_string(),
|
||||
("CPU", "CRITICAL") => "CPU overloaded - Intervention required!".to_string(),
|
||||
("Memory", "WARNING") => "High memory usage".to_string(),
|
||||
("Memory", "CRITICAL") => "Memory saturated - Risk of crash!".to_string(),
|
||||
("Swap", "WARNING") => "High swap usage".to_string(),
|
||||
("Swap", "CRITICAL") => "Swap saturated - Performance degraded!".to_string(),
|
||||
("Disk", "WARNING") => "Low disk space".to_string(),
|
||||
("Disk", "CRITICAL") => "Disk almost full - Urgent cleanup required!".to_string(),
|
||||
(metric, _) => format!("Level '{level}' problem detected on {metric}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ForFormattingMessage for AlertFormatter {
|
||||
fn format_violation(&self, violation: Box<dyn ForGettingViolationData>) -> String {
|
||||
let (emoji, level) = if violation.is_critical() {
|
||||
("🚨", "CRITICAL")
|
||||
} else {
|
||||
("⚠️", "WARNING")
|
||||
};
|
||||
|
||||
format!(
|
||||
"{} **{}** - {}\n\n**Current Value:** {:.2}%\n**Threshold:** {:.2}%",
|
||||
emoji,
|
||||
level,
|
||||
self.get_message_for_metric(&violation.get_metric_name(), level),
|
||||
violation.get_metric_value(),
|
||||
violation.get_threshold()
|
||||
)
|
||||
}
|
||||
|
||||
fn format_summary(&self, metrics: &HashMap<String, f32>) -> String {
|
||||
let mut result = "📊 **System Report**\n\n```\n".to_string();
|
||||
|
||||
result.push_str(
|
||||
&metrics
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{key}: {value:.1}%"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
);
|
||||
|
||||
result.push_str("\n```\n\n*Automatic monitoring*");
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn format_resolution(&self, metric_name: &str) -> String {
|
||||
format!("✅ {} has been resolved", metric_name)
|
||||
}
|
||||
}
|
||||
16
src/actors/driven/for_sending_notification/print.rs
Normal file
16
src/actors/driven/for_sending_notification/print.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use crate::app::ports::driven::ForSendingNotification;
|
||||
|
||||
pub struct StdoutNotificationActor;
|
||||
|
||||
impl StdoutNotificationActor {
|
||||
pub fn new() -> Self {
|
||||
StdoutNotificationActor
|
||||
}
|
||||
}
|
||||
|
||||
impl ForSendingNotification for StdoutNotificationActor {
|
||||
fn send_notification(&self, message: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Sending notification: {message}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
21
src/actors/driving/cron_alerts.rs
Normal file
21
src/actors/driving/cron_alerts.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use crate::app::ports::driving::ForMonitoringAlerts;
|
||||
|
||||
pub struct CronAlertsActor {
|
||||
alert_monitor: Box<dyn ForMonitoringAlerts>,
|
||||
period: std::time::Duration,
|
||||
}
|
||||
|
||||
impl CronAlertsActor {
|
||||
pub fn new(alert_monitor: Box<dyn ForMonitoringAlerts>, period: std::time::Duration) -> Self {
|
||||
Self { alert_monitor, period }
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
loop {
|
||||
if let Err(e) = self.alert_monitor.alert_on_threshold_violation() {
|
||||
eprintln!("Error occurred: {}", e);
|
||||
}
|
||||
std::thread::sleep(self.period);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/adapters/driven/for_getting_violation_data.rs
Normal file
1
src/adapters/driven/for_getting_violation_data.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod violation;
|
||||
35
src/adapters/driven/for_getting_violation_data/violation.rs
Normal file
35
src/adapters/driven/for_getting_violation_data/violation.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
pub struct Violation {
|
||||
name: String,
|
||||
is_critical: bool,
|
||||
metric_value: f32,
|
||||
threshold: f32,
|
||||
}
|
||||
|
||||
impl Violation {
|
||||
pub fn new(name: String, is_critical: bool, metric_value: f32, threshold: f32) -> Self {
|
||||
Violation {
|
||||
name,
|
||||
is_critical,
|
||||
metric_value,
|
||||
threshold,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::app::ports::driven::ForGettingViolationData for Violation {
|
||||
fn get_metric_name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn is_critical(&self) -> bool {
|
||||
self.is_critical
|
||||
}
|
||||
|
||||
fn get_metric_value(&self) -> f32 {
|
||||
self.metric_value
|
||||
}
|
||||
|
||||
fn get_threshold(&self) -> f32 {
|
||||
self.threshold
|
||||
}
|
||||
}
|
||||
1
src/adapters/driven/for_monitoring_system.rs
Normal file
1
src/adapters/driven/for_monitoring_system.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod system_monitor;
|
||||
40
src/adapters/driven/for_monitoring_system/system_monitor.rs
Normal file
40
src/adapters/driven/for_monitoring_system/system_monitor.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::{
|
||||
adapters::driven::for_getting_violation_data::violation::Violation,
|
||||
app::ports::driven::ForMonitoringSystem,
|
||||
};
|
||||
|
||||
pub struct SystemMonitorAdapter {
|
||||
system_monitor: system_monitor::SystemMonitor,
|
||||
}
|
||||
|
||||
impl SystemMonitorAdapter {
|
||||
pub fn new(system_monitor: system_monitor::SystemMonitor) -> Self {
|
||||
SystemMonitorAdapter { system_monitor }
|
||||
}
|
||||
|
||||
fn threshold_violation_to_for_getting_violation_data(
|
||||
&self,
|
||||
threshold_violation: &system_monitor::ThresholdViolation,
|
||||
) -> Box<dyn crate::app::ports::driven::ForGettingViolationData> {
|
||||
Box::new(Violation::new(
|
||||
threshold_violation.metric_name.clone(),
|
||||
threshold_violation.severity == system_monitor::Severity::Critical,
|
||||
threshold_violation.current_value,
|
||||
threshold_violation.threshold,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ForMonitoringSystem for SystemMonitorAdapter {
|
||||
fn check_thresholds(&self) -> Vec<Box<dyn crate::app::ports::driven::ForGettingViolationData>> {
|
||||
let thresholds = self.system_monitor.check_thresholds();
|
||||
thresholds
|
||||
.into_iter()
|
||||
.map(|t| self.threshold_violation_to_for_getting_violation_data(&t))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_metrics(&self) -> std::collections::HashMap<String, f32> {
|
||||
self.system_monitor.get_metrics()
|
||||
}
|
||||
}
|
||||
1
src/adapters/driven/for_sending_notification.rs
Normal file
1
src/adapters/driven/for_sending_notification.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod discord_client;
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::app::ports::driven::ForSendingNotification;
|
||||
|
||||
pub struct DiscordAdapter {
|
||||
discord_client: discord_client::DiscordNotifier,
|
||||
}
|
||||
|
||||
impl DiscordAdapter {
|
||||
pub fn new(discord_client: discord_client::DiscordNotifier) -> Self {
|
||||
DiscordAdapter { discord_client }
|
||||
}
|
||||
}
|
||||
|
||||
impl ForSendingNotification for DiscordAdapter {
|
||||
fn send_notification(&self, message: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.discord_client.send_notification(message)
|
||||
}
|
||||
}
|
||||
18
src/adapters/driving/for_monitoring_alerts.rs
Normal file
18
src/adapters/driving/for_monitoring_alerts.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::app::ports::driving::ForMonitoringAlerts;
|
||||
|
||||
pub struct ForMonitoringAlertsAdapter
|
||||
{
|
||||
app : crate::app::services::alert_service::AlertService
|
||||
}
|
||||
|
||||
impl ForMonitoringAlertsAdapter {
|
||||
pub fn new(app: crate::app::services::alert_service::AlertService) -> Self {
|
||||
ForMonitoringAlertsAdapter { app }
|
||||
}
|
||||
}
|
||||
|
||||
impl ForMonitoringAlerts for ForMonitoringAlertsAdapter {
|
||||
fn alert_on_threshold_violation(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.app.alert_on_threshold_violation()
|
||||
}
|
||||
}
|
||||
30
src/app/ports/driven.rs
Normal file
30
src/app/ports/driven.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait ForSendingNotification {
|
||||
fn send_notification(&self, message: &str) -> Result<(), Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait ForMonitoringSystem {
|
||||
fn check_thresholds(&self) -> Vec<Box<dyn ForGettingViolationData>>;
|
||||
fn get_metrics(&self) -> HashMap<String, f32>;
|
||||
}
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait ForFormattingMessage {
|
||||
fn format_violation(&self, violation: Box<dyn ForGettingViolationData>) -> String;
|
||||
fn format_summary(&self, metrics: &HashMap<String, f32>) -> String;
|
||||
fn format_resolution(&self, metric_name: &str) -> String;
|
||||
}
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait ForGettingViolationData {
|
||||
fn is_critical(&self) -> bool;
|
||||
fn get_metric_name(&self) -> String;
|
||||
fn get_metric_value(&self) -> f32;
|
||||
fn get_threshold(&self) -> f32;
|
||||
}
|
||||
3
src/app/ports/driving.rs
Normal file
3
src/app/ports/driving.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub trait ForMonitoringAlerts {
|
||||
fn alert_on_threshold_violation(&mut self) -> Result<(), Box<dyn std::error::Error>>;
|
||||
}
|
||||
218
src/app/services/alert_service.rs
Normal file
218
src/app/services/alert_service.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::app::ports::driven::{
|
||||
ForFormattingMessage, ForMonitoringSystem, ForSendingNotification,
|
||||
};
|
||||
|
||||
pub struct AlertService {
|
||||
notifier: Box<dyn ForSendingNotification>,
|
||||
monitor: Box<dyn ForMonitoringSystem>,
|
||||
formatter: Box<dyn ForFormattingMessage>,
|
||||
map_alerts_criticity: HashMap<String, bool>,
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
notifier: Box<dyn ForSendingNotification>,
|
||||
monitor: Box<dyn ForMonitoringSystem>,
|
||||
formatter: Box<dyn ForFormattingMessage>,
|
||||
) -> AlertService {
|
||||
AlertService {
|
||||
notifier,
|
||||
monitor,
|
||||
formatter,
|
||||
map_alerts_criticity: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
impl AlertService {
|
||||
/// Check for threshold violations and send alerts on level changes (escalation, improvement, resolution)
|
||||
pub fn alert_on_threshold_violation(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let violations = self.monitor.check_thresholds();
|
||||
let current_levels = self.violations_to_map(&violations);
|
||||
|
||||
self.process_resolved_violations(¤t_levels);
|
||||
self.process_current_violations(violations)?;
|
||||
|
||||
self.update_map_alerts_criticity(current_levels);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn violations_to_map(
|
||||
&self,
|
||||
violations: &[Box<dyn crate::app::ports::driven::ForGettingViolationData>],
|
||||
) -> HashMap<String, bool> {
|
||||
violations
|
||||
.iter()
|
||||
.map(|v| (v.get_metric_name(), v.is_critical()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn process_resolved_violations(&mut self, current_levels: &HashMap<String, bool>) {
|
||||
for (metric_name, &_previous_level) in &self.map_alerts_criticity {
|
||||
if !current_levels.contains_key(metric_name) {
|
||||
let message = self.formatter.format_resolution(metric_name);
|
||||
self.notifier.send_notification(&message).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_current_violations(
|
||||
&mut self,
|
||||
violations: Vec<Box<dyn crate::app::ports::driven::ForGettingViolationData>>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for violation in violations {
|
||||
let metric_name = violation.get_metric_name();
|
||||
let current_level = violation.is_critical();
|
||||
|
||||
let should_alert = match self.map_alerts_criticity.get(&metric_name) {
|
||||
None => true, // First time seeing this metric
|
||||
Some(&previous_level) => current_level != previous_level, // Any level change
|
||||
};
|
||||
|
||||
if should_alert {
|
||||
let message = self.formatter.format_violation(violation);
|
||||
self.notifier.send_notification(&message)?;
|
||||
}
|
||||
|
||||
// Update the level for this metric
|
||||
self.map_alerts_criticity.insert(metric_name, current_level);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_map_alerts_criticity(&mut self, current_levels: HashMap<String, bool>) {
|
||||
self.map_alerts_criticity = current_levels;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::app::{
|
||||
ports::driven::{
|
||||
ForGettingViolationData, MockForFormattingMessage, MockForGettingViolationData,
|
||||
MockForMonitoringSystem, MockForSendingNotification,
|
||||
},
|
||||
services::alert_service,
|
||||
};
|
||||
|
||||
const TEST_VALUE: f32 = 75.0;
|
||||
const TEST_THRESHOLD: f32 = 70.0;
|
||||
|
||||
#[rstest]
|
||||
// No violations
|
||||
#[case(HashMap::new(), HashMap::new(), vec![])]
|
||||
// Single critical violation
|
||||
#[case(HashMap::from([("CPU".to_string(), true)]), HashMap::new(), vec!["critical CPU".to_string(), "resolved CPU".to_string()])]
|
||||
// Single warning violation
|
||||
#[case(HashMap::from([("Memory".to_string(), false)]), HashMap::new(), vec!["warning Memory".to_string(), "resolved Memory".to_string()])]
|
||||
// Multiple violations with level changes and one resolution
|
||||
#[case(HashMap::from([("CPU".to_string(), true), ("Memory".to_string(), false), ("Disk".to_string(), false)]),
|
||||
HashMap::from([("CPU".to_string(), false), ("Disk".to_string(), true)]),
|
||||
vec![
|
||||
"critical CPU".to_string(),
|
||||
"warning Memory".to_string(),
|
||||
"warning Disk".to_string(),
|
||||
"resolved Memory".to_string(),
|
||||
"warning CPU".to_string(),
|
||||
"critical Disk".to_string()
|
||||
]
|
||||
)]
|
||||
// No level changes
|
||||
#[case(
|
||||
HashMap::from([("CPU".to_string(), true), ("Memory".to_string(), false)]),
|
||||
HashMap::from([("CPU".to_string(), true), ("Memory".to_string(), false)]),
|
||||
vec!["critical CPU".to_string(), "warning Memory".to_string()]
|
||||
)]
|
||||
fn test_alert_on_threshold_violation_scenarios(
|
||||
#[case] first_violations: HashMap<String, bool>,
|
||||
#[case] second_violations: HashMap<String, bool>,
|
||||
#[case] expected_notifications: Vec<String>,
|
||||
) {
|
||||
let mut mock_monitor = MockForMonitoringSystem::new();
|
||||
let mut mock_formatter = MockForFormattingMessage::new();
|
||||
let mut mock_notifier = MockForSendingNotification::new();
|
||||
|
||||
// Helper to build violations from a HashMap
|
||||
fn build_violations(map: &HashMap<String, bool>) -> Vec<Box<dyn ForGettingViolationData>> {
|
||||
map.iter()
|
||||
.map(|(name, &is_critical)| {
|
||||
let mut violation = MockForGettingViolationData::new();
|
||||
let name = name.clone();
|
||||
violation.expect_is_critical().return_const(is_critical);
|
||||
violation
|
||||
.expect_get_metric_name()
|
||||
.return_const(name.clone());
|
||||
violation.expect_get_metric_value().return_const(TEST_VALUE);
|
||||
violation
|
||||
.expect_get_threshold()
|
||||
.return_const(TEST_THRESHOLD);
|
||||
Box::new(violation) as Box<dyn ForGettingViolationData>
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// The test will call check_thresholds twice: first and second run
|
||||
let mut call_count = 0;
|
||||
let first_violations_clone = first_violations.clone();
|
||||
let second_violations_clone = second_violations.clone();
|
||||
mock_monitor.expect_check_thresholds().returning(move || {
|
||||
call_count += 1;
|
||||
if call_count == 1 {
|
||||
build_violations(&first_violations_clone)
|
||||
} else {
|
||||
build_violations(&second_violations_clone)
|
||||
}
|
||||
});
|
||||
|
||||
// Formatter returns a string based on the violation
|
||||
mock_formatter
|
||||
.expect_format_violation()
|
||||
.returning(|violation| {
|
||||
let level = if violation.is_critical() {
|
||||
"critical"
|
||||
} else {
|
||||
"warning"
|
||||
};
|
||||
format!("{} {}", level, violation.get_metric_name())
|
||||
});
|
||||
mock_formatter
|
||||
.expect_format_resolution()
|
||||
.returning(|metric_name| format!("resolved {}", metric_name));
|
||||
|
||||
// Collect notifications sent
|
||||
let notifications = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
let notifications_clone = notifications.clone();
|
||||
mock_notifier
|
||||
.expect_send_notification()
|
||||
.returning(move |message| {
|
||||
notifications_clone
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(message.to_string());
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let mut alert_service = alert_service::new(
|
||||
Box::new(mock_notifier),
|
||||
Box::new(mock_monitor),
|
||||
Box::new(mock_formatter),
|
||||
);
|
||||
|
||||
// First run
|
||||
alert_service.alert_on_threshold_violation().unwrap();
|
||||
// Second run
|
||||
alert_service.alert_on_threshold_violation().unwrap();
|
||||
|
||||
// Hashmap is not ordered, so we need to compare sets
|
||||
let sent = notifications.lock().unwrap();
|
||||
let sent_set: HashSet<_> = sent.iter().collect();
|
||||
let expected_set: HashSet<_> = expected_notifications.iter().collect();
|
||||
assert_eq!(sent_set, expected_set);
|
||||
}
|
||||
}
|
||||
83
src/app/services/metric_service.rs
Normal file
83
src/app/services/metric_service.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use crate::app::ports::driven::{
|
||||
ForFormattingMessage, ForMonitoringSystem, ForSendingNotification,
|
||||
};
|
||||
|
||||
pub struct MetricService {
|
||||
notifier: Box<dyn ForSendingNotification>,
|
||||
monitor: Box<dyn ForMonitoringSystem>,
|
||||
formatter: Box<dyn ForFormattingMessage>,
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
notifier: Box<dyn ForSendingNotification>,
|
||||
monitor: Box<dyn ForMonitoringSystem>,
|
||||
formatter: Box<dyn ForFormattingMessage>,
|
||||
) -> MetricService {
|
||||
MetricService {
|
||||
notifier,
|
||||
monitor,
|
||||
formatter,
|
||||
}
|
||||
}
|
||||
|
||||
impl MetricService {
|
||||
/// Send a final notification with all system information
|
||||
pub fn send_all_metrics_notification(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let summary = self.formatter.format_summary(&self.monitor.get_metrics());
|
||||
self.notifier.send_notification(&summary)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::app::ports::driven::{MockForFormattingMessage, MockForMonitoringSystem, MockForSendingNotification};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_send_all_metrics_notification() {
|
||||
let mut notifier = MockForSendingNotification::new();
|
||||
let mut monitor = MockForMonitoringSystem::new();
|
||||
let mut formatter = MockForFormattingMessage::new();
|
||||
|
||||
notifier.expect_send_notification().returning(|_| Ok(()));
|
||||
monitor.expect_check_thresholds().returning(|| vec![]);
|
||||
monitor.expect_get_metrics().returning(|| HashMap::new());
|
||||
formatter.expect_format_violation().returning(|_| "".to_string());
|
||||
formatter.expect_format_summary().returning(|_| "".to_string());
|
||||
|
||||
let service = new(
|
||||
Box::new(notifier),
|
||||
Box::new(monitor),
|
||||
Box::new(formatter),
|
||||
);
|
||||
|
||||
assert!(service.send_all_metrics_notification().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_all_metrics_notification_handles_send_error() {
|
||||
let mut notifier = MockForSendingNotification::new();
|
||||
let mut monitor = MockForMonitoringSystem::new();
|
||||
let mut formatter = MockForFormattingMessage::new();
|
||||
|
||||
monitor.expect_get_metrics().returning(|| HashMap::new());
|
||||
formatter.expect_format_summary().returning(|_| "Test summary".to_string());
|
||||
|
||||
notifier.expect_send_notification()
|
||||
.returning(|_| Err("Network error".into()));
|
||||
|
||||
let service = new(
|
||||
Box::new(notifier),
|
||||
Box::new(monitor),
|
||||
Box::new(formatter),
|
||||
);
|
||||
|
||||
assert!(service.send_all_metrics_notification().is_err());
|
||||
}
|
||||
}
|
||||
52
src/configurator.rs
Normal file
52
src/configurator.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use discord_client::DiscordNotifier;
|
||||
use dotenvy::dotenv;
|
||||
use system_monitor::SystemMonitor;
|
||||
|
||||
use crate::{actors, adapters, app};
|
||||
|
||||
pub fn get_cron_actor(
|
||||
duration: std::time::Duration,
|
||||
) -> actors::driving::cron_alerts::CronAlertsActor {
|
||||
dotenv().ok();
|
||||
let notifier = DiscordNotifier::new(
|
||||
std::env::var("DISCORD_WEBHOOK").expect("DISCORD_WEBHOOK environment variable not set"),
|
||||
"System Monitor".to_string(),
|
||||
"https://cdn.shopify.com/s/files/1/0262/1423/6212/files/Lord_of_the_Rings_eye_of_Sauron_-_Ghtic.com_-_Blog.png?v=1579680018".to_string(),
|
||||
);
|
||||
let discord_adapter =
|
||||
adapters::driven::for_sending_notification::discord_client::DiscordAdapter::new(notifier);
|
||||
|
||||
let monitor =
|
||||
SystemMonitor::new(system_monitor::resource_threshold::get_default_resource_thresholds());
|
||||
let system_monitor_adapter =
|
||||
adapters::driven::for_monitoring_system::system_monitor::SystemMonitorAdapter::new(monitor);
|
||||
|
||||
let formatter = actors::driven::for_formatting_message::alert_formatter::AlertFormatter;
|
||||
|
||||
let service = app::services::alert_service::new(
|
||||
Box::new(discord_adapter),
|
||||
Box::new(system_monitor_adapter),
|
||||
Box::new(formatter),
|
||||
);
|
||||
let service_adapter =
|
||||
crate::adapters::driving::for_monitoring_alerts::ForMonitoringAlertsAdapter::new(service);
|
||||
|
||||
actors::driving::cron_alerts::CronAlertsActor::new(Box::new(service_adapter), duration)
|
||||
}
|
||||
|
||||
pub fn get_get_service() -> app::services::metric_service::MetricService {
|
||||
let notifier = actors::driven::for_sending_notification::print::StdoutNotificationActor::new();
|
||||
|
||||
let monitor =
|
||||
SystemMonitor::new(system_monitor::resource_threshold::get_default_resource_thresholds());
|
||||
let system_monitor_adapter =
|
||||
adapters::driven::for_monitoring_system::system_monitor::SystemMonitorAdapter::new(monitor);
|
||||
|
||||
let formatter = actors::driven::for_formatting_message::alert_formatter::AlertFormatter;
|
||||
|
||||
app::services::metric_service::new(
|
||||
Box::new(notifier),
|
||||
Box::new(system_monitor_adapter),
|
||||
Box::new(formatter),
|
||||
)
|
||||
}
|
||||
157
src/main.rs
157
src/main.rs
@@ -1,115 +1,76 @@
|
||||
use dotenvy::dotenv;
|
||||
use sysinfo::{Disks, System};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
fn get_cpu_usage(sys: &System) -> f32 {
|
||||
sys.global_cpu_usage()
|
||||
mod app {
|
||||
pub mod services {
|
||||
pub mod alert_service;
|
||||
pub mod metric_service;
|
||||
}
|
||||
pub mod ports {
|
||||
pub mod driven;
|
||||
pub mod driving;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_memory_usage(sys: &System) -> f32 {
|
||||
let total_memory = sys.total_memory() as f32;
|
||||
let used_memory = sys.used_memory() as f32;
|
||||
|
||||
used_memory / total_memory * 100.0
|
||||
mod adapters {
|
||||
pub mod driven {
|
||||
pub mod for_getting_violation_data;
|
||||
pub mod for_monitoring_system;
|
||||
pub mod for_sending_notification;
|
||||
}
|
||||
pub mod driving {
|
||||
pub mod for_monitoring_alerts;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_swap_usage(sys: &System) -> f32 {
|
||||
let total_swap = sys.total_swap() as f32;
|
||||
let used_swap = sys.used_swap() as f32;
|
||||
|
||||
used_swap / total_swap * 100.0
|
||||
}
|
||||
|
||||
fn get_disk_usage(disks : &Disks) -> f32 {
|
||||
let mut total_used_space = 0.0;
|
||||
let mut total_space = 0.0;
|
||||
|
||||
for disk in disks {
|
||||
if let Some(mount_point) = disk.mount_point().to_str() {
|
||||
if mount_point == "/" || mount_point == "/home" {
|
||||
total_used_space += disk.total_space() as f32 - disk.available_space() as f32;
|
||||
total_space += disk.total_space() as f32;
|
||||
}
|
||||
mod actors {
|
||||
pub mod driven {
|
||||
pub mod for_formatting_message {
|
||||
pub mod alert_formatter;
|
||||
}
|
||||
pub mod for_sending_notification {
|
||||
pub mod print;
|
||||
}
|
||||
}
|
||||
|
||||
if total_space == 0.0 {
|
||||
return 0.0; // Avoid division by zero
|
||||
pub mod driving {
|
||||
pub mod cron_alerts;
|
||||
}
|
||||
|
||||
(total_used_space / total_space) * 100.0
|
||||
}
|
||||
|
||||
fn send_notification(webhook_url: &str, message: &str) {
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let payload = serde_json::json!({
|
||||
"content": message,
|
||||
"username": "System Monitor",
|
||||
"avatar_url": "https://cdn.shopify.com/s/files/1/0262/1423/6212/files/Lord_of_the_Rings_eye_of_Sauron_-_Ghtic.com_-_Blog.png?v=1579680018"
|
||||
});
|
||||
mod configurator;
|
||||
|
||||
match client.post(webhook_url).json(&payload).send() {
|
||||
Ok(response) => {
|
||||
if response.status().is_success() {
|
||||
println!("Notification sent successfully.");
|
||||
} else {
|
||||
eprintln!("Failed to send notification: {}", response.status());
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Error sending notification: {}", e),
|
||||
}
|
||||
#[derive(Parser)]
|
||||
#[command(name = "node-notifier")]
|
||||
#[command(about = "A system monitoring and notification tool")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Monitor system and alert on threshold violations
|
||||
Run {
|
||||
/// Duration between cron checks in seconds
|
||||
#[arg(short, long, default_value = "300")]
|
||||
duration: u64,
|
||||
},
|
||||
/// Send all system metrics notification
|
||||
Get,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
dotenv().ok();
|
||||
let cli = Cli::parse();
|
||||
|
||||
let discord_webhook =
|
||||
std::env::var("DISCORD_WEBHOOK").expect("DISCORD_WEBHOOK environment variable not set");
|
||||
|
||||
let mut sys = System::new_all();
|
||||
let mut disks: Disks = Disks::new_with_refreshed_list();
|
||||
sys.refresh_all();
|
||||
disks.refresh(true);
|
||||
|
||||
let cpu_usage = get_cpu_usage(&sys);
|
||||
if cpu_usage > 80.0 {
|
||||
let message = format!("High CPU usage detected: {:.2}%", cpu_usage);
|
||||
send_notification(&discord_webhook, &message);
|
||||
match cli.command {
|
||||
Commands::Run { duration } => {
|
||||
let duration = std::time::Duration::from_secs(duration);
|
||||
configurator::get_cron_actor(duration).start();
|
||||
}
|
||||
Commands::Get => {
|
||||
let service = configurator::get_get_service();
|
||||
if let Err(e) = service.send_all_metrics_notification() {
|
||||
eprintln!("Error sending final notification: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let memory_usage = get_memory_usage(&sys);
|
||||
if memory_usage > 80.0 {
|
||||
let message = format!("High memory usage detected: {:.2}%", memory_usage);
|
||||
send_notification(&discord_webhook, &message);
|
||||
}
|
||||
|
||||
let swap_usage = get_swap_usage(&sys);
|
||||
if swap_usage > 80.0 {
|
||||
let message = format!("High swap usage detected: {:.2}%", swap_usage);
|
||||
send_notification(&discord_webhook, &message);
|
||||
}
|
||||
|
||||
let disk_usage = get_disk_usage(&disks);
|
||||
if disk_usage > 80.0 {
|
||||
let message = format!("High disk usage detected: {:.2}%", disk_usage);
|
||||
send_notification(&discord_webhook, &message);
|
||||
}
|
||||
|
||||
println!("System Information:");
|
||||
println!("CPU usage: {:.2}%", cpu_usage);
|
||||
println!("Memory usage: {:.2}%", memory_usage);
|
||||
println!("Swap usage: {:.2}%", swap_usage);
|
||||
println!("Disk usage: {:.2}%", disk_usage);
|
||||
|
||||
// Send a final notification with all system information
|
||||
let final_message = format!(
|
||||
concat!(
|
||||
"System Information:\n",
|
||||
"CPU usage: {:.2}%\n",
|
||||
"Memory usage: {:.2}%\n",
|
||||
"Swap usage: {:.2}%\n",
|
||||
"Disk usage: {:.2}%"
|
||||
),
|
||||
cpu_usage, memory_usage, swap_usage, disk_usage
|
||||
);
|
||||
send_notification(&discord_webhook, &final_message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user