diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d849ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +dist +dist-ssr +*.local +target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c257fbb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1311 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "async-trait" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6137c6234afb339e75e764c866e3594900f0211e1315d33779f269bbe2ec6967" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "base64 0.21.0", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-macros" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fbf955307ff8addb48d2399393c9e2740dd491537ec562b66ab364fc4a38841" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "ntapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "stats_server" +version = "0.1.0" +dependencies = [ + "axum", + "serde_json", + "sysinfo", + "tokio", + "tracing-subscriber", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sysinfo" +version = "0.30.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87341a165d73787554941cd5ef55ad728011566fe714e987d1b976c15dbc3a83" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.42.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ee8f2f5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "stats_server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = { version = "0.6.9", features = ["macros", "ws"] } +serde_json = "1.0.93" +sysinfo = "0.30.11" +tokio = { version = "1.25.0", features = ["full"] } +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } + +[[bin]] +name = "stats_server" +path = "src/stats-server/main.rs" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d6babe --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..29794af --- /dev/null +++ b/biome.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.7.1/schema.json", + "organizeImports": { + "enabled": true + }, + "files": { + "ignore": ["src/stats-server", "dist"], + "ignoreUnknown": true + }, + "formatter": { + "indentStyle": "space", + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noNonNullAssertion": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "jsxQuoteStyle": "single", + "arrowParentheses": "asNeeded" + } + } +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..3f166eb Binary files /dev/null and b/bun.lockb differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..c8d5e84 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + SysMon Web + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..43dae7a --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "sysmon-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host & cargo run", + "build": "tsc && vite build && cargo build --release", + "preview": "vite preview --host & cargo run --release" + }, + "dependencies": { + "@tanstack/react-query": "^5.32.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "recharts": "^2.12.6" + }, + "devDependencies": { + "@biomejs/biome": "1.7.1", + "@types/node": "^20.12.8", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..c6398d9 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client/App.css b/src/client/App.css new file mode 100644 index 0000000..e0a2584 --- /dev/null +++ b/src/client/App.css @@ -0,0 +1,26 @@ +#root { + background-color: var(--color-neutral1); + display: grid; + grid-auto-rows: calc(50svh - 16px); + padding: 8px; + gap: 8px; + + @media (min-width: 500px) { + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(2, 1fr); + height: 100vh; + } + + > div { + border-radius: 16px; + background-color: var(--color-neutral0); + padding: 16px; + width: 100%; + overflow: hidden; + } +} + +.chart-card { + display: grid; + grid-template-rows: repeat(2, max-content) 1fr; +} \ No newline at end of file diff --git a/src/client/App.tsx b/src/client/App.tsx new file mode 100644 index 0000000..7263508 --- /dev/null +++ b/src/client/App.tsx @@ -0,0 +1,45 @@ +import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'; +import './App.css'; +import { fetchDynamicData, fetchStaticData } from './api'; +import { Cpu } from './components/chart-cards/cpu'; +import { Disks } from './components/chart-cards/disks'; +import { Memory } from './components/chart-cards/memory'; +import { Network } from './components/chart-cards/network'; +import { Static } from './components/chart-cards/static'; +import { Temps } from './components/chart-cards/temps'; + +const queryClient = new QueryClient(); + +const Main = () => { + const staticQuery = useQuery({ + queryKey: ['static'], + queryFn: fetchStaticData, + }); + + const dynamicQuery = useQuery({ + queryKey: ['dynamic'], + queryFn: fetchDynamicData, + refetchInterval: 500, + }); + + const isLoading = staticQuery.isLoading || dynamicQuery.isLoading; + + return ( + !isLoading && ( + <> + + + + + + + + ) + ); +}; + +export const App = () => ( + +
+ +); diff --git a/src/client/api.ts b/src/client/api.ts new file mode 100644 index 0000000..5f0daa4 --- /dev/null +++ b/src/client/api.ts @@ -0,0 +1,17 @@ +const getApiUrl = (path: string) => { + const url = new URL(window.location.href); + url.port = '3001'; + url.pathname = path; + + return url; +}; + +export const fetchStaticData = async (): Promise => { + const response = await fetch(getApiUrl('/api/static')); + return await response.json(); +}; + +export const fetchDynamicData = async (): Promise => { + const response = await fetch(getApiUrl('/api/dynamic')); + return await response.json(); +}; diff --git a/src/client/components/chart-cards/cpu.tsx b/src/client/components/chart-cards/cpu.tsx new file mode 100644 index 0000000..9bbbd47 --- /dev/null +++ b/src/client/components/chart-cards/cpu.tsx @@ -0,0 +1,44 @@ +import { useQuery } from '@tanstack/react-query'; +import { useEffect, useMemo, useState } from 'react'; +import { ChartCard } from './index'; + +export const Cpu = () => { + const { data: staticData } = useQuery({ queryKey: ['static'] }); + const { data: dynamicData } = useQuery({ queryKey: ['dynamic'] }); + const [history, setHistory] = useState(new Array(150).fill([])); + + useEffect(() => { + if (dynamicData) { + setHistory(history => { + const newHistory = history.slice(1); + newHistory.push(dynamicData.cpu_usage); + + return newHistory; + }); + } + }, [dynamicData]); + + const total_cpus = useMemo(() => history.at(-1)?.length ?? 0, [history]); + + if (!staticData || !dynamicData) { + return
; + } + + return ( + !!total_cpus && ( + + {staticData.cpu.brand} + {` (${total_cpus} threads)`} + + } + domain={[0, 100]} + formatOptions={{ prefix: false, units: '%' }} + data={history} + total={total_cpus} + /> + ) + ); +}; diff --git a/src/client/components/chart-cards/disks.tsx b/src/client/components/chart-cards/disks.tsx new file mode 100644 index 0000000..1689a51 --- /dev/null +++ b/src/client/components/chart-cards/disks.tsx @@ -0,0 +1,37 @@ +import { useQuery } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import { ChartCard } from './index'; + +export const Disks = () => { + const { data: dynamicData } = useQuery({ queryKey: ['dynamic'] }); + const [history, setHistory] = useState(new Array(150).fill([])); + + useEffect(() => { + if (dynamicData) { + setHistory(history => { + const newHistory = history.slice(1); + newHistory.push([dynamicData.disks.read, dynamicData.disks.write]); + + return newHistory; + }); + } + }, [dynamicData]); + + if (!dynamicData) { + return
; + } + + return ( + + ); +}; diff --git a/src/client/components/chart-cards/index.tsx b/src/client/components/chart-cards/index.tsx new file mode 100644 index 0000000..632eeea --- /dev/null +++ b/src/client/components/chart-cards/index.tsx @@ -0,0 +1,66 @@ +import { Legend, type LegendProps } from '@/components/legend'; +import { getFillColor, getStrokeColor } from '@/utils/colors'; +import { type FormatOptions, formatValue } from '@/utils/format'; +import type { ReactNode } from 'react'; +import { Area, AreaChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis } from 'recharts'; +import type { AxisDomain } from 'recharts/types/util/types'; + +type Props = { + title: ReactNode; + subtitle?: ReactNode; + legend?: LegendProps; + formatOptions?: FormatOptions; + domain?: AxisDomain; + hueOffset?: number; + data: number[][]; + total: number; +}; + +export const ChartCard = ({ data, domain, legend, hueOffset = 0, title, subtitle, formatOptions, total }: Props) => { + return ( +
+

{title}

+ + {subtitle} + + {legend && } + + + + + + formatValue(value, formatOptions)} + /> + + {Array.from({ length: total }).map((_, index) => ( + // biome-ignore lint/correctness/useJsxKeyInIterable: order irrelevant + + ))} + + + + +
+ ); +}; diff --git a/src/client/components/chart-cards/memory.tsx b/src/client/components/chart-cards/memory.tsx new file mode 100644 index 0000000..87c0252 --- /dev/null +++ b/src/client/components/chart-cards/memory.tsx @@ -0,0 +1,37 @@ +import { useQuery } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import { ChartCard } from './index'; + +export const Memory = () => { + const { data: staticData } = useQuery({ queryKey: ['static'] }); + const { data: dynamicData } = useQuery({ queryKey: ['dynamic'] }); + const [history, setHistory] = useState(new Array(150).fill([])); + + useEffect(() => { + if (dynamicData) { + setHistory(history => { + const newHistory = history.slice(1); + newHistory.push([dynamicData.mem_usage, dynamicData.swap_usage]); + + return newHistory; + }); + } + }, [dynamicData]); + + if (!staticData || !dynamicData) { + return
; + } + + return ( + + ); +}; diff --git a/src/client/components/chart-cards/network.tsx b/src/client/components/chart-cards/network.tsx new file mode 100644 index 0000000..6731194 --- /dev/null +++ b/src/client/components/chart-cards/network.tsx @@ -0,0 +1,37 @@ +import { useQuery } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import { ChartCard } from './index'; + +export const Network = () => { + const { data: dynamicData } = useQuery({ queryKey: ['dynamic'] }); + const [history, setHistory] = useState(new Array(150).fill([])); + + useEffect(() => { + if (dynamicData) { + setHistory(history => { + const newHistory = history.slice(1); + newHistory.push([dynamicData.network.down, dynamicData.network.up]); + + return newHistory; + }); + } + }, [dynamicData]); + + if (!dynamicData) { + return
; + } + + return ( + + ); +}; diff --git a/src/client/components/chart-cards/static.tsx b/src/client/components/chart-cards/static.tsx new file mode 100644 index 0000000..a7f7875 --- /dev/null +++ b/src/client/components/chart-cards/static.tsx @@ -0,0 +1,63 @@ +import { useAnimationFrame } from '@/hooks/use-animation-frame'; +import { useQuery } from '@tanstack/react-query'; +import { useRef, useState } from 'react'; + +const formatUptime = (value: number) => { + const seconds = String(Math.floor(value % 60)).padStart(2, '0'); + const minutes = String(Math.floor((value / 60) % 60)).padStart(2, '0'); + const hours = String(Math.floor((value / (60 * 60)) % 24)).padStart(2, '0'); + const days = Math.floor((value / (60 * 60 * 24)) % 365); + const years = Math.floor(value / (60 * 60 * 24 * 365)); + + let formatted = ''; + + if (years >= 1) { + formatted += `${years}y `; + } + + if (days >= 1 || years >= 1) { + formatted += `${days}d `; + } + + return `${formatted}${hours}:${minutes}:${seconds}`; +}; + +const Uptime = ({ boot_time }: Pick) => { + const [uptime, setUptime] = useState(formatUptime(Date.now() / 1000 - boot_time)); + const lastUpdate = useRef(0); + + useAnimationFrame(dt => { + lastUpdate.current += dt; + if (lastUpdate.current > 1000) { + setUptime(formatUptime(Date.now() / 1000 - boot_time)); + } + }); + + return ( + <> + Uptime +

{uptime}

+ + ); +}; + +export const Static = () => { + const { data: staticData } = useQuery({ queryKey: ['static'] }); + + return ( + staticData && ( +
+

{staticData.host_name}

+ + OS +

+ {staticData.name} {staticData.os_version} +

+ + Kernel +

{staticData.kernel_version}

+ {staticData.boot_time && } +
+ ) + ); +}; diff --git a/src/client/components/chart-cards/temps.tsx b/src/client/components/chart-cards/temps.tsx new file mode 100644 index 0000000..3c40f3f --- /dev/null +++ b/src/client/components/chart-cards/temps.tsx @@ -0,0 +1,39 @@ +import { useQuery } from '@tanstack/react-query'; +import { useEffect, useMemo, useState } from 'react'; +import { ChartCard } from './index'; + +export const Temps = () => { + const { data: staticData } = useQuery({ queryKey: ['static'] }); + const { data: dynamicData } = useQuery({ queryKey: ['dynamic'] }); + const [history, setHistory] = useState(new Array(150).fill([])); + + useEffect(() => { + if (dynamicData) { + setHistory(history => { + const newHistory = history.slice(1); + newHistory.push(dynamicData.temps); + + return newHistory; + }); + } + }, [dynamicData]); + + const total_sensors = useMemo(() => history.at(-1)?.length ?? 0, [history]); + + if (!staticData || !dynamicData) { + return
; + } + + return ( + + ); +}; diff --git a/src/client/components/legend/index.css b/src/client/components/legend/index.css new file mode 100644 index 0000000..bf27718 --- /dev/null +++ b/src/client/components/legend/index.css @@ -0,0 +1,19 @@ +.legend-wrapper { + display: flex; + flex-wrap: wrap; + gap: 12; + width: 100%; + overflow: hidden; + + > div { + flex: 1; + } + + small { + display: inline-block; + max-width: 128px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} \ No newline at end of file diff --git a/src/client/components/legend/index.tsx b/src/client/components/legend/index.tsx new file mode 100644 index 0000000..cb70e2b --- /dev/null +++ b/src/client/components/legend/index.tsx @@ -0,0 +1,21 @@ +import { getTextColor } from '@/utils/colors'; +import { type FormatOptions, formatValue } from '@/utils/format'; +import './index.css'; + +export type LegendProps = { + labels: string[]; + values: number[]; + formatOptions?: FormatOptions; + hueOffset?: number; +}; + +export const Legend = ({ labels, values, formatOptions, hueOffset = 0 }: LegendProps) => ( +
+ {labels.map((label, index) => ( +
+ {label} +

{formatValue(values[index], formatOptions)}

+
+ ))} +
+); diff --git a/src/client/hooks/use-animation-frame.ts b/src/client/hooks/use-animation-frame.ts new file mode 100644 index 0000000..96f2211 --- /dev/null +++ b/src/client/hooks/use-animation-frame.ts @@ -0,0 +1,24 @@ +import { useEffect, useRef } from 'react'; + +export const useAnimationFrame = (callback: (dt: number) => void) => { + const requestRef = useRef(); + const previousTimeRef = useRef(); + + useEffect(() => { + const animate: FrameRequestCallback = time => { + if (previousTimeRef.current !== undefined) { + const deltaTime = time - previousTimeRef.current; + callback(deltaTime); + } + previousTimeRef.current = time; + requestRef.current = requestAnimationFrame(animate); + }; + + requestRef.current = requestAnimationFrame(animate); + return () => { + if (requestRef.current) { + cancelAnimationFrame(requestRef.current); + } + }; + }, [callback]); +}; diff --git a/src/client/index.css b/src/client/index.css new file mode 100644 index 0000000..69b7e80 --- /dev/null +++ b/src/client/index.css @@ -0,0 +1,33 @@ +:root { + --color-neutral0: #002b36; + --color-neutral1: #073642; + --color-neutral2: #586e75; + --color-neutral3: #657b83; + --color-neutral4: #839496; + --color-neutral5: #93a1a1; + --color-neutral6: #eee8d5; + --color-neutral7: #fdf6e3; + + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color: var(--color-neutral6); + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; +} + +* { + box-sizing: border-box; +} + +* { + margin-top: 0; +} \ No newline at end of file diff --git a/src/client/main.tsx b/src/client/main.tsx new file mode 100644 index 0000000..559a7c9 --- /dev/null +++ b/src/client/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { App } from './App.tsx'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/src/client/types.ts b/src/client/types.ts new file mode 100644 index 0000000..01a19eb --- /dev/null +++ b/src/client/types.ts @@ -0,0 +1,31 @@ +type StaticData = { + boot_time: number; + components: string[]; + cpu: { + brand: string; + name: string; + vendor_id: string; + }; + host_name: string; + kernel_version: string; + name: string; + os_version: string; + total_memory: number; + total_swap: number; + uptime: number; +}; + +type DynamicData = { + cpu_usage: number[]; + disks: { + read: number; + write: number; + }; + mem_usage: number; + network: { + down: number; + up: number; + }; + swap_usage: number; + temps: number[]; +}; diff --git a/src/client/utils/colors.ts b/src/client/utils/colors.ts new file mode 100644 index 0000000..585211a --- /dev/null +++ b/src/client/utils/colors.ts @@ -0,0 +1,3 @@ +export const getTextColor = (hue: number) => `hsl(${hue} 50 50)`; +export const getFillColor = (hue: number) => `hsl(${hue} 50 50 / 10%)`; +export const getStrokeColor = (hue: number) => `hsl(${hue} 50 50 / 60%)`; diff --git a/src/client/utils/format.ts b/src/client/utils/format.ts new file mode 100644 index 0000000..9076487 --- /dev/null +++ b/src/client/utils/format.ts @@ -0,0 +1,20 @@ +const prefixes = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; + +export type FormatOptions = { decimals?: number; prefix?: boolean; si?: boolean; units?: string }; + +export const formatValue = (value: number, { decimals = 1, prefix = true, si, units = 'B' }: FormatOptions = {}) => { + if (!value) { + return `0\u2009${units}`; + } + + if (!prefix) { + return `${value}\u2009${units}`; + } + + const k = si ? 1000 : 1024; + const i = Math.floor(Math.log(value) / Math.log(k)); + + return `${Number((value / k ** i).toFixed(Math.max(0, decimals)))}\u2009${prefixes[i]}${ + i > 0 && !si ? 'i' : '' + }${units}`; +}; diff --git a/src/client/vite-env.d.ts b/src/client/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/client/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/stats-server/disks.rs b/src/stats-server/disks.rs new file mode 100644 index 0000000..1cb6235 --- /dev/null +++ b/src/stats-server/disks.rs @@ -0,0 +1,56 @@ +pub mod disks { + use serde_json::json; + use std::{ + fs::File, + io::{Read, Seek, SeekFrom}, + }; + + pub struct DiskUsage { + read: u64, + write: u64, + } + + impl DiskUsage { + fn new() -> Self { + DiskUsage { read: 0, write: 0 } + } + } + + pub struct DiskStats { + fd: File, + prev: DiskUsage, + } + + impl DiskStats { + pub fn new() -> Self { + let fd = File::open("/proc/diskstats").unwrap(); + DiskStats { + fd, + prev: DiskUsage::new(), + } + } + + pub fn refresh(&mut self) -> DiskUsage { + let mut curr = DiskUsage::new(); + let mut io_data = String::new(); + self.fd.read_to_string(&mut io_data).unwrap(); + for line in io_data.lines() { + let fields: Vec<_> = line.split_whitespace().collect(); + curr.read += fields[5].parse::().unwrap() * 512 * 8; + curr.write += fields[9].parse::().unwrap() * 512 * 8; + } + self.fd.seek(SeekFrom::Start(0)).unwrap(); + curr + } + + pub fn diff(&mut self) -> serde_json::Value { + let curr = self.refresh(); + let diff = json!({ + "read": curr.read - self.prev.read, + "write": curr.write - self.prev.write, + }); + self.prev = curr; + diff + } + } +} diff --git a/src/stats-server/main.rs b/src/stats-server/main.rs new file mode 100644 index 0000000..eff073e --- /dev/null +++ b/src/stats-server/main.rs @@ -0,0 +1,149 @@ +use axum::{extract::State, http::Response, response::IntoResponse, routing::get, Router, Server}; +use serde_json::{json, Value}; +use std::{ + sync::{Arc, Mutex}, + time::{Duration, Instant}, +}; +use sysinfo::{Components, CpuRefreshKind, MemoryRefreshKind, Networks, RefreshKind, System}; + +use crate::disks::disks::DiskStats; + +mod disks; + +#[derive(Clone)] +struct AppState { + latest: Arc>, + data: Arc>, +} + +impl Default for AppState { + fn default() -> Self { + AppState { + latest: Arc::new(Mutex::new(Instant::now())), + data: Arc::new(Mutex::new(json!({}))), + } + } +} + +#[tokio::main] +async fn main() { + let app_state = AppState::default(); + + let router = Router::new() + .route("/api/dynamic", get(dynamic_sysinfo_get)) + .route("/api/static", get(static_sysinfo_get)) + .with_state(app_state.clone()); + + // Update system usage in the background + tokio::task::spawn_blocking(move || { + let mut sys = System::new(); + let mut components = Components::new_with_refreshed_list(); + let mut networks = Networks::new_with_refreshed_list(); + let mut disk_stats = DiskStats::new(); + + loop { + let now = Instant::now(); + let latest = app_state.latest.lock().unwrap().clone(); + + if (now - latest).as_millis() < 10000 { + sys.refresh_cpu(); + sys.refresh_memory(); + components.refresh(); + networks.refresh(); + disk_stats.refresh(); + + let cpu_usage: Vec<_> = sys.cpus().iter().map(|cpu| cpu.cpu_usage()).collect(); + let mem_usage = sys.used_memory(); + let swap_usage = sys.used_swap(); + let temps: Vec<_> = components + .iter() + .map(|component| component.temperature()) + .collect(); + let (net_down, net_up) = + networks + .iter() + .fold((0, 0), |(down, up), (_name, network)| { + (down + network.received(), up + network.transmitted()) + }); + + { + let mut data = app_state.data.lock().unwrap(); + *data = json!({ + "cpu_usage": cpu_usage, + "mem_usage": mem_usage, + "swap_usage": swap_usage, + "temps": temps, + "network": { + "down": net_down, + "up": net_up + }, + "disks": disk_stats.diff() + }); + } + } + + std::thread::sleep(Duration::from_millis(200)); + } + }); + + let server = Server::bind(&"0.0.0.0:3001".parse().unwrap()).serve(router.into_make_service()); + let addr = server.local_addr(); + println!("Listening on {addr}"); + + server.await.unwrap(); +} + +#[axum::debug_handler] +async fn dynamic_sysinfo_get(State(state): State) -> impl IntoResponse { + let data = state.data.lock().unwrap().clone(); + + { + let mut latest = state.latest.lock().unwrap(); + *latest = Instant::now() + } + + Response::builder() + .header(axum::http::header::ORIGIN, "*") + .header(axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .header("content-type", "application/json") + .body(data.to_string()) + .unwrap() +} + +async fn static_sysinfo_get() -> impl IntoResponse { + let sys = System::new_with_specifics( + RefreshKind::new() + .with_cpu(CpuRefreshKind::everything()) + .with_memory(MemoryRefreshKind::everything()), + ); + + let components = Components::new_with_refreshed_list(); + + Response::builder() + .header(axum::http::header::ORIGIN, "*") + .header(axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .header("content-type", "application/json") + .body( + json!({ + "boot_time": System::boot_time(), + "uptime": System::uptime(), + "name": System::name(), + "kernel_version": System::kernel_version(), + "os_version": System::os_version(), + "host_name": System::host_name(), + "total_memory": sys.total_memory(), + "total_swap": sys.total_swap(), + "cpu": { + "name": sys.cpus()[0].name(), + "vendor_id": sys.cpus()[0].vendor_id(), + "brand": sys.cpus()[0].brand(), + }, + "components": components + .iter() + .map(|component| component.label()) + .collect::>() + }) + .to_string(), + ) + .unwrap() +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..74dcae1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/client/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..235b204 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,13 @@ +import path from 'node:path'; +import react from '@vitejs/plugin-react-swc'; +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve('./src/client'), + }, + }, +});