added tui
This commit is contained in:
1 parent
5108a23425
commit
78f302fc71
3 files changed
+1052
-52
No files matched your search
Generated
+371
-10
@@ -64,6 +64,12 @@ version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
@@ -98,7 +104,7 @@ version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -116,18 +122,162 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
@@ -143,6 +293,30 @@ dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.0"
|
||||
@@ -183,12 +357,24 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
@@ -207,6 +393,23 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e2e4cd95294a85c3b4446e63ef054eea43e0205b1fd60120c16b74ff7ff96ad"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"crossterm",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"paste",
|
||||
"strum",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
@@ -222,15 +425,45 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"futures",
|
||||
"ratatui",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.6"
|
||||
@@ -240,6 +473,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
@@ -262,6 +501,28 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.110"
|
||||
@@ -281,7 +542,7 @@ checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.1.0",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
@@ -307,6 +568,18 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
@@ -319,19 +592,50 @@ version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[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-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -343,6 +647,21 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
@@ -350,28 +669,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
@@ -384,24 +721,48 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
|
||||
@@ -7,3 +7,6 @@ edition = "2024"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
bytes = "1.0"
|
||||
ratatui = "0.23"
|
||||
crossterm = "0.27"
|
||||
futures = "0.3"
|
||||
+678
-42
@@ -1,12 +1,47 @@
|
||||
use bytes::BytesMut;
|
||||
use clap::{Parser, Subcommand};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use ratatui::{
|
||||
Terminal,
|
||||
backend::CrosstermBackend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Clear, Gauge, Paragraph},
|
||||
};
|
||||
use std::io::{self as std_io};
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::{Duration, Instant};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::{Duration, Instant, interval};
|
||||
|
||||
const DEFAULT_PORT: u16 = 8888;
|
||||
const BUFFER_SIZE: usize = 8192;
|
||||
const UPDATE_INTERVAL_MS: u64 = 500;
|
||||
|
||||
// Структура для автоматического восстановления терминала
|
||||
struct TerminalGuard;
|
||||
|
||||
impl TerminalGuard {
|
||||
fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
enable_raw_mode()?;
|
||||
execute!(std_io::stdout(), EnterAlternateScreen)?;
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TerminalGuard {
|
||||
fn drop(&mut self) {
|
||||
let _ = disable_raw_mode();
|
||||
let _ = execute!(std_io::stdout(), LeaveAlternateScreen);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about)]
|
||||
@@ -31,6 +66,40 @@ enum Commands {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct BenchmarkStats {
|
||||
total_bytes: u64,
|
||||
current_speed: f64,
|
||||
average_speed: f64,
|
||||
duration: Duration,
|
||||
is_running: bool,
|
||||
clients_connected: usize,
|
||||
logs: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for BenchmarkStats {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
total_bytes: 0,
|
||||
current_speed: 0.0,
|
||||
average_speed: 0.0,
|
||||
duration: Duration::from_secs(0),
|
||||
is_running: true,
|
||||
clients_connected: 0,
|
||||
logs: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BenchmarkStats {
|
||||
fn add_log(&mut self, message: String) {
|
||||
self.logs.push(message);
|
||||
if self.logs.len() > 50 {
|
||||
self.logs.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
@@ -52,68 +121,635 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
async fn server(port: u16) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let stats = Arc::new(Mutex::new(BenchmarkStats::default()));
|
||||
let stats_clone = stats.clone();
|
||||
|
||||
let tui_handle = tokio::spawn(async move {
|
||||
if let Err(e) = run_server_tui(stats_clone).await {
|
||||
eprintln!("TUI error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let listener = TcpListener::bind(("0.0.0.0", port)).await?;
|
||||
println!("Server listening on port {}", port);
|
||||
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.is_running = true;
|
||||
stats_guard.add_log(format!("Server started on port {}", port));
|
||||
}
|
||||
|
||||
let mut client_tasks = vec![];
|
||||
let mut check_interval = interval(Duration::from_millis(100));
|
||||
|
||||
loop {
|
||||
let (mut socket, addr) = listener.accept().await?;
|
||||
println!("Client connected from {}", addr);
|
||||
tokio::select! {
|
||||
_ = check_interval.tick() => {
|
||||
// Проверяем, нужно ли завершать работу
|
||||
let should_exit = {
|
||||
let stats_guard = stats.lock().await;
|
||||
!stats_guard.is_running
|
||||
};
|
||||
if should_exit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
accept_result = listener.accept() => {
|
||||
match accept_result {
|
||||
Ok((socket, addr)) => {
|
||||
let log_msg = format!("Client connected from {}", addr);
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.clients_connected += 1;
|
||||
stats_guard.add_log(log_msg);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = BytesMut::with_capacity(BUFFER_SIZE);
|
||||
let mut total_received = 0u64;
|
||||
let start_time = Instant::now();
|
||||
|
||||
loop {
|
||||
match socket.read_buf(&mut buf).await {
|
||||
Ok(0) => break, // Соединение закрыто
|
||||
Ok(n) => {
|
||||
total_received += n as u64;
|
||||
buf.clear();
|
||||
let stats_for_client = stats.clone();
|
||||
let task = tokio::spawn(async move {
|
||||
handle_client(socket, stats_for_client).await;
|
||||
});
|
||||
client_tasks.push(task);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Read error: {}", e);
|
||||
break;
|
||||
let log_msg = format!("Accept error: {}", e);
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.add_log(log_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let duration = start_time.elapsed();
|
||||
let rate = (total_received as f64) / duration.as_secs_f64() / 1024.0 / 1024.0;
|
||||
println!(
|
||||
"Received {} bytes in {:.2?} ({:.2} MB/s)",
|
||||
total_received, duration, rate
|
||||
);
|
||||
});
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.add_log("Received CTRL+C, shutting down...".to_string());
|
||||
stats_guard.is_running = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ждем завершения всех клиентских задач
|
||||
for task in client_tasks {
|
||||
let _ = task.await;
|
||||
}
|
||||
|
||||
// Останавливаем TUI
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.is_running = false;
|
||||
}
|
||||
|
||||
// Даем TUI время завершиться
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
tui_handle.abort();
|
||||
|
||||
println!("Server shutdown complete");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn client(host: &str, port: u16, seconds: u64) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut stream = TcpStream::connect((host, port)).await?;
|
||||
println!("Connected to server {}:{}", host, port);
|
||||
|
||||
let data = vec![0u8; BUFFER_SIZE];
|
||||
let mut total_sent = 0u64;
|
||||
async fn handle_client(mut socket: TcpStream, stats: Arc<Mutex<BenchmarkStats>>) {
|
||||
let mut buf = BytesMut::with_capacity(BUFFER_SIZE);
|
||||
let mut total_received = 0u64;
|
||||
let start_time = Instant::now();
|
||||
let timeout = Duration::from_secs(seconds);
|
||||
let mut last_update = Instant::now();
|
||||
let mut last_total = 0u64;
|
||||
|
||||
while start_time.elapsed() < timeout {
|
||||
match stream.write_all(&data).await {
|
||||
Ok(_) => total_sent += data.len() as u64,
|
||||
loop {
|
||||
match socket.read_buf(&mut buf).await {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
total_received += n as u64;
|
||||
buf.clear();
|
||||
|
||||
let now = Instant::now();
|
||||
let time_since_last_update = now.duration_since(last_update).as_secs_f64();
|
||||
|
||||
if time_since_last_update >= 0.5 {
|
||||
let current_speed = ((total_received - last_total) as f64)
|
||||
/ time_since_last_update
|
||||
/ 1024.0
|
||||
/ 1024.0;
|
||||
let total_duration = now.duration_since(start_time).as_secs_f64();
|
||||
let average_speed = total_received as f64 / total_duration / 1024.0 / 1024.0;
|
||||
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.total_bytes = total_received;
|
||||
stats_guard.current_speed = current_speed;
|
||||
stats_guard.average_speed = average_speed;
|
||||
stats_guard.duration = now.duration_since(start_time);
|
||||
}
|
||||
|
||||
last_update = now;
|
||||
last_total = total_received;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Write error: {}", e);
|
||||
let log_msg = format!("Read error: {}", e);
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.add_log(log_msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let duration = start_time.elapsed();
|
||||
let rate = (total_sent as f64) / duration.as_secs_f64() / 1024.0 / 1024.0;
|
||||
println!(
|
||||
"Sent {} bytes in {:.2?} ({:.2} MB/s)",
|
||||
total_sent, duration, rate
|
||||
);
|
||||
let rate = total_received as f64 / duration.as_secs_f64() / 1024.0 / 1024.0;
|
||||
|
||||
stream.shutdown().await?;
|
||||
let log_msg = format!(
|
||||
"Connection finished: {:.2} MB in {:.2?} ({:.2} MB/s)",
|
||||
total_received as f64 / 1024.0 / 1024.0,
|
||||
duration,
|
||||
rate
|
||||
);
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.add_log(log_msg);
|
||||
stats_guard.clients_connected = stats_guard.clients_connected.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
async fn client(host: &str, port: u16, seconds: u64) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let stats = Arc::new(Mutex::new(BenchmarkStats::default()));
|
||||
let stats_clone = stats.clone();
|
||||
|
||||
let tui_handle = tokio::spawn(async move {
|
||||
if let Err(e) = run_client_tui(stats_clone, seconds.clone()).await {
|
||||
eprintln!("TUI error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let mut stream = TcpStream::connect((host, port)).await?;
|
||||
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.is_running = true;
|
||||
stats_guard.add_log(format!("Connected to {}:{}", host, port));
|
||||
}
|
||||
|
||||
let data = vec![0u8; BUFFER_SIZE];
|
||||
let mut total_sent = 0u64;
|
||||
let start_time = Instant::now();
|
||||
let timeout = Duration::from_secs(seconds);
|
||||
let mut last_update = Instant::now();
|
||||
let mut last_total = 0u64;
|
||||
let mut check_interval = interval(Duration::from_millis(100));
|
||||
|
||||
'main_loop: while start_time.elapsed() < timeout {
|
||||
tokio::select! {
|
||||
_ = check_interval.tick() => {
|
||||
// Проверяем, нужно ли завершать работу
|
||||
let should_exit = {
|
||||
let stats_guard = stats.lock().await;
|
||||
!stats_guard.is_running
|
||||
};
|
||||
if should_exit {
|
||||
break 'main_loop;
|
||||
}
|
||||
}
|
||||
_ = async {
|
||||
match stream.write_all(&data).await {
|
||||
Ok(_) => {
|
||||
total_sent += data.len() as u64;
|
||||
|
||||
let now = Instant::now();
|
||||
let time_since_last_update = now.duration_since(last_update).as_secs_f64();
|
||||
|
||||
if time_since_last_update >= 0.5 {
|
||||
let current_speed = ((total_sent - last_total) as f64) / time_since_last_update / 1024.0 / 1024.0;
|
||||
let total_duration = now.duration_since(start_time).as_secs_f64();
|
||||
let average_speed = total_sent as f64 / total_duration / 1024.0 / 1024.0;
|
||||
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.total_bytes = total_sent;
|
||||
stats_guard.current_speed = current_speed;
|
||||
stats_guard.average_speed = average_speed;
|
||||
stats_guard.duration = now.duration_since(start_time);
|
||||
}
|
||||
|
||||
last_update = now;
|
||||
last_total = total_sent;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let log_msg = format!("Write error: {}", e);
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.add_log(log_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
} => {}
|
||||
}
|
||||
}
|
||||
|
||||
let duration = start_time.elapsed();
|
||||
let rate = total_sent as f64 / duration.as_secs_f64() / 1024.0 / 1024.0;
|
||||
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.total_bytes = total_sent;
|
||||
stats_guard.current_speed = rate;
|
||||
stats_guard.average_speed = rate;
|
||||
stats_guard.duration = duration;
|
||||
stats_guard.is_running = false;
|
||||
stats_guard.add_log(format!("Benchmark completed: {:.2} MB/s", rate));
|
||||
}
|
||||
|
||||
// Даем TUI время отобразить финальные результаты
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
tui_handle.abort();
|
||||
let _ = stream.shutdown().await;
|
||||
|
||||
println!("Client shutdown complete");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_server_tui(
|
||||
stats: Arc<Mutex<BenchmarkStats>>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let _guard = TerminalGuard::new()?;
|
||||
let mut stdout = std_io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let mut update_interval = interval(Duration::from_millis(UPDATE_INTERVAL_MS));
|
||||
|
||||
loop {
|
||||
let stats_guard = stats.lock().await;
|
||||
|
||||
terminal.draw(|f| {
|
||||
let rect = f.size();
|
||||
f.render_widget(Clear, rect);
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(8),
|
||||
Constraint::Min(5),
|
||||
Constraint::Length(3),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(rect);
|
||||
|
||||
// Title
|
||||
let title = Paragraph::new(Line::from(vec![
|
||||
Span::styled("📡 ", Style::default().fg(Color::Yellow)),
|
||||
Span::styled(
|
||||
"NETWORK BENCHMARK SERVER",
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
]))
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(title, chunks[0]);
|
||||
|
||||
// Stats in horizontal layout
|
||||
let stats_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(25),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(chunks[1]);
|
||||
|
||||
// Clients
|
||||
let clients_block = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
"CONNECTED CLIENTS\n",
|
||||
Style::default().fg(Color::White),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!("{}", stats_guard.clients_connected),
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(clients_block, stats_chunks[0]);
|
||||
|
||||
// Total Data
|
||||
let total_mb = stats_guard.total_bytes as f64 / 1024.0 / 1024.0;
|
||||
let total_block = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
"TOTAL DATA\n",
|
||||
Style::default().fg(Color::White),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!("{:.2} MB", total_mb),
|
||||
Style::default()
|
||||
.fg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(total_block, stats_chunks[1]);
|
||||
|
||||
// Current Speed
|
||||
let current_speed_block = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
"CURRENT SPEED\n",
|
||||
Style::default().fg(Color::White),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!("{:.2} MB/s", stats_guard.current_speed),
|
||||
get_speed_style(stats_guard.current_speed),
|
||||
)),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(current_speed_block, stats_chunks[2]);
|
||||
|
||||
// Average Speed
|
||||
let average_speed_block = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
"AVERAGE SPEED\n",
|
||||
Style::default().fg(Color::White),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!("{:.2} MB/s", stats_guard.average_speed),
|
||||
Style::default()
|
||||
.fg(Color::Magenta)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(average_speed_block, stats_chunks[3]);
|
||||
|
||||
// Logs
|
||||
let logs: Vec<Line> = stats_guard
|
||||
.logs
|
||||
.iter()
|
||||
.rev()
|
||||
.take(10)
|
||||
.map(|log| Line::from(Span::raw(log.clone())))
|
||||
.collect();
|
||||
|
||||
let logs_block = Paragraph::new(logs)
|
||||
.block(Block::default().borders(Borders::ALL).title("Logs"))
|
||||
.style(Style::default().fg(Color::Gray));
|
||||
f.render_widget(logs_block, chunks[2]);
|
||||
|
||||
// Help
|
||||
let help_text = if stats_guard.is_running {
|
||||
"Press 'q' to quit | Server is running..."
|
||||
} else {
|
||||
"Press 'q' to quit | Server is shutting down..."
|
||||
};
|
||||
|
||||
let help = Paragraph::new(help_text)
|
||||
.block(Block::default().borders(Borders::ALL).title("Help"))
|
||||
.style(Style::default().fg(Color::Yellow));
|
||||
f.render_widget(help, chunks[3]);
|
||||
})?;
|
||||
|
||||
drop(stats_guard);
|
||||
|
||||
if event::poll(Duration::from_millis(100))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
// Устанавливаем флаг завершения
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.is_running = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_interval.tick().await;
|
||||
|
||||
{
|
||||
let stats_guard = stats.lock().await;
|
||||
if !stats_guard.is_running {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_client_tui(
|
||||
stats: Arc<Mutex<BenchmarkStats>>,
|
||||
seconds: u64,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let _guard = TerminalGuard::new()?;
|
||||
let mut stdout = std_io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let mut update_interval = interval(Duration::from_millis(UPDATE_INTERVAL_MS));
|
||||
|
||||
loop {
|
||||
let stats_guard = stats.lock().await;
|
||||
|
||||
terminal.draw(|f| {
|
||||
let rect = f.size();
|
||||
f.render_widget(Clear, rect);
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(9),
|
||||
Constraint::Length(6),
|
||||
Constraint::Min(5),
|
||||
Constraint::Length(3),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(rect);
|
||||
|
||||
// Title
|
||||
let title = Paragraph::new(vec![Line::from(vec![
|
||||
Span::styled("🚀 ", Style::default().fg(Color::Green)),
|
||||
Span::styled(
|
||||
"NETWORK BENCHMARK CLIENT",
|
||||
Style::default()
|
||||
.fg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
])])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(title, chunks[0]);
|
||||
|
||||
// Stats in horizontal layout
|
||||
let stats_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(25),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(chunks[1]);
|
||||
|
||||
// Total Data
|
||||
let total_mb = stats_guard.total_bytes as f64 / 1024.0 / 1024.0;
|
||||
let total_block = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
"TOTAL DATA\n",
|
||||
Style::default().fg(Color::White),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!("{:.2} MB", total_mb),
|
||||
Style::default()
|
||||
.fg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(total_block, stats_chunks[0]);
|
||||
|
||||
// Current Speed
|
||||
let current_speed_block = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
"CURRENT SPEED\n",
|
||||
Style::default().fg(Color::White),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!("{:.2} MB/s", stats_guard.current_speed),
|
||||
get_speed_style(stats_guard.current_speed),
|
||||
)),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(current_speed_block, stats_chunks[1]);
|
||||
|
||||
// Average Speed
|
||||
let average_speed_block = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
"AVERAGE SPEED\n",
|
||||
Style::default().fg(Color::White),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!("{:.2} MB/s", stats_guard.average_speed),
|
||||
Style::default()
|
||||
.fg(Color::Magenta)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(average_speed_block, stats_chunks[2]);
|
||||
|
||||
// Duration
|
||||
let duration_block = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
"DURATION\n",
|
||||
Style::default().fg(Color::White),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!("{:.2?}", stats_guard.duration),
|
||||
Style::default()
|
||||
.fg(Color::Blue)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(duration_block, stats_chunks[3]);
|
||||
|
||||
// Progress
|
||||
let progress = if stats_guard.duration.as_secs() > 0 {
|
||||
stats_guard.duration.as_secs_f64() % seconds as f64 / seconds as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let gauge = Gauge::default()
|
||||
.block(Block::default().borders(Borders::ALL).title("Activity"))
|
||||
.gauge_style(Style::default().fg(Color::Cyan))
|
||||
.percent((progress * 100.0) as u16);
|
||||
f.render_widget(gauge, chunks[2]);
|
||||
|
||||
// Logs
|
||||
let logs: Vec<Line> = stats_guard
|
||||
.logs
|
||||
.iter()
|
||||
.rev()
|
||||
.take(10)
|
||||
.map(|log| Line::from(Span::raw(log.clone())))
|
||||
.collect();
|
||||
|
||||
let logs_block = Paragraph::new(logs)
|
||||
.block(Block::default().borders(Borders::ALL).title("Logs"))
|
||||
.style(Style::default().fg(Color::Gray));
|
||||
f.render_widget(logs_block, chunks[3]);
|
||||
|
||||
// Status
|
||||
let status_text = if stats_guard.is_running {
|
||||
"🟢 Benchmark in progress..."
|
||||
} else {
|
||||
"✅ Benchmark completed!"
|
||||
};
|
||||
|
||||
let help_text = format!("{}\nPress 'q' to quit", status_text);
|
||||
let help = Paragraph::new(help_text)
|
||||
.block(Block::default().borders(Borders::ALL).title("Status"))
|
||||
.style(Style::default().fg(Color::Yellow));
|
||||
f.render_widget(help, chunks[4]);
|
||||
})?;
|
||||
|
||||
drop(stats_guard);
|
||||
|
||||
if event::poll(Duration::from_millis(100))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
// Устанавливаем флаг завершения
|
||||
{
|
||||
let mut stats_guard = stats.lock().await;
|
||||
stats_guard.is_running = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_interval.tick().await;
|
||||
|
||||
{
|
||||
let stats_guard = stats.lock().await;
|
||||
if !stats_guard.is_running {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_speed_style(speed: f64) -> Style {
|
||||
if speed > 100.0 {
|
||||
Style::default()
|
||||
.fg(Color::LightGreen)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if speed > 50.0 {
|
||||
Style::default().fg(Color::Yellow)
|
||||
} else if speed > 10.0 {
|
||||
Style::default().fg(Color::LightRed)
|
||||
} else {
|
||||
Style::default().fg(Color::Red)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user