diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d0e622407..d1bf523d3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,11 +151,6 @@ jobs: steps: - name: checkout uses: actions/checkout@v2 - - name: build launchpad gui-vue - run: | - cd applications/launchpad/gui-vue - npm ci - npm run build - name: build collectibles web-app run: | cd applications/tari_collectibles/web-app diff --git a/Cargo.lock b/Cargo.lock index 79abbecab3..5c318d1c73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,15 @@ dependencies = [ "system-deps 6.0.2", ] +[[package]] +name = "atoi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" +dependencies = [ + "num-traits", +] + [[package]] name = "attohttpc" version = "0.19.1" @@ -1127,6 +1136,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + [[package]] name = "crc24" version = "0.1.6" @@ -1871,6 +1895,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dtoa" version = "0.4.8" @@ -1915,6 +1945,19 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "embed-resource" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" +dependencies = [ + "cc", + "rustc_version 0.4.0", + "toml", + "vswhom", + "winreg", +] + [[package]] name = "embed_plist" version = "1.2.2" @@ -2237,6 +2280,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +dependencies = [ + "futures-core", + "lock_api 0.4.7", + "parking_lot 0.11.2", +] + [[package]] name = "futures-io" version = "0.3.21" @@ -2674,11 +2728,20 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -2689,6 +2752,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown 0.11.2", +] + [[package]] name = "hdrhistogram" version = "7.5.0" @@ -2961,18 +3033,31 @@ dependencies = [ "byteorder", "color_quant", "num-iter", - "num-rational", + "num-rational 0.3.2", + "num-traits", +] + +[[package]] +name = "image" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational 0.4.1", "num-traits", ] [[package]] name = "indexmap" -version = "1.8.2" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.9.1", ] [[package]] @@ -3354,6 +3439,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -3982,6 +4076,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -4499,9 +4604,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ "fixedbitset 0.4.1", "indexmap", @@ -4739,6 +4844,20 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "plist" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +dependencies = [ + "base64 0.13.0", + "indexmap", + "line-wrap", + "serde", + "time 0.3.9", + "xml-rs", +] + [[package]] name = "plotters" version = "0.3.1" @@ -4923,7 +5042,7 @@ dependencies = [ "lazy_static", "log", "multimap", - "petgraph 0.6.2", + "petgraph 0.6.0", "prost", "prost-types", "regex", @@ -4967,7 +5086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" dependencies = [ "checked_int_cast", - "image", + "image 0.23.14", ] [[package]] @@ -5278,12 +5397,13 @@ dependencies = [ [[package]] name = "rfd" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f756b55bff8f256a1a8c24dbabb1430ac8110628e418a02e4a1c5ff67179f56" +checksum = "f121348fd3b9035ed11be1f028e8944263c30641f8c5deacf57a4320782fb402" dependencies = [ "block", "dispatch", + "embed-resource", "glib-sys", "gobject-sys", "gtk-sys", @@ -5421,6 +5541,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.0", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + [[package]] name = "rustls" version = "0.20.6" @@ -5429,7 +5562,7 @@ checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ "log", "ring", - "sct", + "sct 0.7.0", "webpki 0.22.0", ] @@ -5534,6 +5667,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sct" version = "0.7.0" @@ -5669,6 +5812,7 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ + "indexmap", "itoa 1.0.2", "ryu", "serde", @@ -6003,6 +6147,105 @@ dependencies = [ "der", ] +[[package]] +name = "sqlformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" +dependencies = [ + "itertools 0.10.3", + "nom 7.1.1", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4b94ab0f8c21ee4899b93b06451ef5d965f1a355982ee73684338228498440" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec28b91a01e1fe286d6ba66f68289a2286df023fc97444e1fd86c2fd6d5dc026" +dependencies = [ + "ahash", + "atoi", + "bitflags 1.3.2", + "byteorder", + "bytes 1.1.0", + "crc", + "crossbeam-channel 0.5.4", + "crossbeam-queue", + "crossbeam-utils 0.8.8", + "either", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "itoa 0.4.8", + "libc", + "libsqlite3-sys", + "log", + "memchr", + "once_cell", + "parking_lot 0.11.2", + "percent-encoding 2.1.0", + "rustls 0.19.1", + "serde", + "serde_json", + "sha2 0.9.9", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url 2.2.2", + "webpki 0.21.4", + "webpki-roots", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33c35d54774eed73d54568d47a6ac099aed8af5e1556a017c131be88217d5" +dependencies = [ + "dotenv", + "either", + "futures 0.3.21", + "heck 0.3.3", + "once_cell", + "proc-macro2", + "quote", + "serde_json", + "sha2 0.9.9", + "sqlx-core", + "sqlx-rt", + "syn", + "url 2.2.2", +] + +[[package]] +name = "sqlx-rt" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" +dependencies = [ + "once_cell", + "tokio 1.19.2", + "tokio-rustls 0.22.0", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -6062,6 +6305,16 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "strsim" version = "0.8.0" @@ -6213,9 +6466,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.10.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2497feadd60f2a5a7f124572d7a44b2aba589a0ad2a65d3aaf2d073c327c3b8" +checksum = "a71c32c2fa7bba46b01becf9cf470f6a781573af7e376c5e317a313ecce27545" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -6233,6 +6486,7 @@ dependencies = [ "glib", "glib-sys", "gtk", + "image 0.24.3", "instant", "jni 0.19.0", "lazy_static", @@ -6245,28 +6499,17 @@ dependencies = [ "once_cell", "parking_lot 0.11.2", "paste", + "png 0.17.5", "raw-window-handle", "scopeguard", "serde", - "tao-core-video-sys", "unicode-segmentation", + "uuid 0.8.2", "windows 0.37.0", "windows-implement", "x11-dl", ] -[[package]] -name = "tao-core-video-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" -dependencies = [ - "cfg-if 1.0.0", - "core-foundation-sys", - "libc", - "objc", -] - [[package]] name = "tar" version = "0.4.38" @@ -6821,20 +7064,28 @@ dependencies = [ "derivative", "env_logger 0.9.0", "futures 0.3.21", + "hex", + "indexmap", + "lazy_static", "log", "rand 0.8.5", "regex", + "reqwest", "serde", "serde_json", "strum 0.23.0", "strum_macros 0.23.1", + "tari_app_grpc", "tari_app_utilities", "tari_common", + "tari_common_types", "tari_comms", "tauri", "tauri-build", + "tauri-plugin-sql", "thiserror", "tokio 1.19.2", + "tonic", "tor-hash-passwd", ] @@ -6995,7 +7246,7 @@ dependencies = [ "prost", "rand 0.8.5", "reqwest", - "rustls", + "rustls 0.20.6", "semver 1.0.10", "serde", "serde_derive", @@ -7245,9 +7496,9 @@ dependencies = [ [[package]] name = "tauri" -version = "1.0.0-rc.15" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb533e95e09fd191ef8e0a0ee6b61701b5f32175e48f82854a71a8f8367bdb41" +checksum = "421641ec549d34935530886151a42ce5ecbbb57beb30e5eec1b22f8e08e10ee9" dependencies = [ "anyhow", "attohttpc", @@ -7299,13 +7550,14 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.0.0-rc.13" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f9a1c87ad53f584f970b06c9243b39d1c2aeca6116dd04c641f406133053b0" +checksum = "acafb1c515c5d14234a294461bd43c723639a84891a45f6a250fd3441ad2e8ed" dependencies = [ "anyhow", "cargo_toml", "heck 0.4.0", + "json-patch", "semver 1.0.10", "serde_json", "tauri-utils", @@ -7314,13 +7566,14 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "1.0.0-rc.9" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceb3b7cb66f1a6ca30f601cccfa01820477881c27412909a3e6f80b6a2f73815" +checksum = "048a7b404b92c86e7dc32458fd0963f042a76d520681e6f598d73a97c2feeeef" dependencies = [ "base64 0.13.0", "brotli", "ico", + "plist", "png 0.17.5", "proc-macro2", "quote", @@ -7331,15 +7584,16 @@ dependencies = [ "sha2 0.10.2", "tauri-utils", "thiserror", + "time 0.3.9", "uuid 1.1.2", "walkdir", ] [[package]] name = "tauri-macros" -version = "1.0.0-rc.9" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07883238ade4c96be38a6a0025f15cbb5e0539fe99ba92d444af9cdbc656b613" +checksum = "aaf70098bfab21efde9b2c089008b319ba333f4ee6e55c38bdea188dea86497f" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -7349,16 +7603,31 @@ dependencies = [ "tauri-utils", ] +[[package]] +name = "tauri-plugin-sql" +version = "0.0.1" +source = "git+https://github.com/tauri-apps/tauri-plugin-sql?branch=release#b8586fbbf8bb259170c588cb992b5dc5bd9c704e" +dependencies = [ + "futures 0.3.21", + "serde", + "serde_json", + "sqlx", + "tauri", + "thiserror", + "tokio 1.19.2", +] + [[package]] name = "tauri-runtime" -version = "0.7.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bad3a8ce06d4e71a52efef175446c8eb7e9109b6f988782fdc6a234526f226a" +checksum = "4e4cff3b4d9469727fa2107c4b3d2eda110df1ba45103fb420178e536362fae4" dependencies = [ "gtk", "http", "http-range", "infer", + "raw-window-handle", "serde", "serde_json", "tauri-utils", @@ -7370,14 +7639,15 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.7.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cbc41ea88305f3e5dc133cc5c717c6913a15f121f99e7a238a0135dac083e" +checksum = "3fa8c4edaf01d8b556e7172c844b1b4dd3399adcd1a606bd520fc3e65f698546" dependencies = [ "cocoa", "gtk", "percent-encoding 2.1.0", "rand 0.8.5", + "raw-window-handle", "tauri-runtime", "tauri-utils", "uuid 1.1.2", @@ -7389,9 +7659,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.0.0-rc.9" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09e7ec7933a833f3b64e932a9d32d94705687aa5caa3cacf43222876a6d7e24" +checksum = "12ff4b68d9faeb57c9c727bf58c9c9768d2b67d8e84e62ce6146e7859a2e9c6b" dependencies = [ "brotli", "ctor", @@ -7411,6 +7681,7 @@ dependencies = [ "thiserror", "url 2.2.2", "walkdir", + "windows 0.37.0", ] [[package]] @@ -7562,6 +7833,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ + "itoa 1.0.2", "libc", "num_threads", ] @@ -7664,13 +7936,24 @@ dependencies = [ "tokio 1.19.2", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio 1.19.2", + "webpki 0.21.4", +] + [[package]] name = "tokio-rustls" version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.6", "tokio 1.19.2", "webpki 0.22.0", ] @@ -7991,7 +8274,7 @@ dependencies = [ "radix_trie", "rand 0.8.5", "ring", - "rustls", + "rustls 0.20.6", "thiserror", "time 0.3.9", "tokio 1.19.2", @@ -8018,13 +8301,13 @@ dependencies = [ "log", "rand 0.8.5", "ring", - "rustls", + "rustls 0.20.6", "rustls-pemfile", "smallvec", "thiserror", "tinyvec", "tokio 1.19.2", - "tokio-rustls", + "tokio-rustls 0.23.4", "url 2.2.2", "webpki 0.22.0", ] @@ -8140,6 +8423,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" version = "0.4.1" @@ -8268,6 +8557,26 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "waker-fn" version = "1.1.0" @@ -8510,6 +8819,15 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki 0.21.4", +] + [[package]] name = "webview2-com" version = "0.16.0" @@ -8558,6 +8876,16 @@ dependencies = [ "libc", ] +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wildmatch" version = "2.1.0" @@ -8836,9 +9164,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e84a6f7f0ef90004a244a6dd4125b5fb78074b48c4369ab52b3cac68a863e" +checksum = "ce19dddbd3ce01dc8f14eb6d4c8f914123bf8379aaa838f6da4f981ff7104a3f" dependencies = [ "block", "cocoa", diff --git a/applications/launchpad/.gitignore b/applications/launchpad/.gitignore index b1d86c6ef2..157cc52b1f 100644 --- a/applications/launchpad/.gitignore +++ b/applications/launchpad/.gitignore @@ -21,3 +21,6 @@ gui/build gui/npm-debug.log* gui/yarn-debug.log* gui/yarn-error.log* + +docker_rig/logs/data +docker_rig/logs/positions.yml diff --git a/applications/launchpad/3rdparty.json b/applications/launchpad/3rdparty.json new file mode 100644 index 0000000000..50a57cf42c --- /dev/null +++ b/applications/launchpad/3rdparty.json @@ -0,0 +1,11 @@ +[ + { + "image_name": "monerod" + }, + { + "image_name": "tor" + }, + { + "image_name": "xmrig" + } +] diff --git a/applications/launchpad/README.md b/applications/launchpad/README.md new file mode 100644 index 0000000000..608708d984 --- /dev/null +++ b/applications/launchpad/README.md @@ -0,0 +1,131 @@ +# Tari Launchpad - Tauri edition + +a.k.a. _Tari one-click miner_. + +## Prerequisites + +1. Rust and cargo (https://www.rust-lang.org/tools/install). +2. NodeJs (v16.0 or higher) and Yarn (v 1.22 or higher). +3. Tauri CLI (`cargo install tauri-cli`). _Optional_. +4. [Docker](https://docs.docker.com/get-docker/) is not _strictly_ a pre-requisite, since the launchpad on-boarding + flow will install it for you, but you will need docker eventually, so putting it here. +5. Install the front-end dependencies + ```text + $ cd applications/launchpad + $ yarn + $ cd gui-react + $ yarn + ``` + +## Running a development version of launchpad + +These commands +* build the launchpad ReactJs front-end and launch it in development mode. +* build the backend in debug mode +* launch the application + +``` +$ cd applications/launchpad +$ yarn run tauri dev +``` + +### Debugging +The console relays debug messages from the launchpad backend (a Rust application). +The front-end is a standard ReactJs application wrapped inside a [Tauri](https://tauri.studio) desktop application. +You can open a standard browser console in the front-end to debug front-end issues. + + +**Tip:** If you receive the following error +`Unable to create base node...` there was a problem packaging the assets for the app. + +## Building a production release +To build a production release, which also includes a bundled installer (.dmg on mac, .deb on linux, .msi on windows), +you can execute: + +``` +$ cd applications/launchpad +$ yarn run tauri build +``` + + + +## Viewing logs and configuration files + +You can use the bundled Grafana environment that is packaged with launchpad to view log files. Or you can use your +favorite text editor instead. + +Logs and configuration files are stored in +* MacOs: `~/Library/Caches/tari/tmp/dibbler/{app}/log`, +* Linux: `~/.cache/tari/tmp/dibbler/{app}/log`, +* Windows: `???` + +You can edit the log configuration, `dibbler/config/log4rs.yml` to change the log level, output etc. Changes are +picked up on the fly and take effect within 30s. + +## Miscellaneous notes + +* The blockchain data is stores in docker volumes, and not on the host machine directly. This is due to crippling performance +limitations one suffers when mounting host file system from Windows or MacOS into docker containers. +This isn't a big drawback, since you seldom want or need to access the raw blockchain database files anyway. Are they're +[still accessible](#accessing-blockchain-data). But **ensure that you reserve enough space to store the Tari, and optionally, +Monero blockchains inside the Docker VM**. + +### Accessing blockchain data + +The blockchain data is stored in a docker volume for performance reasons. If you need to back up or access the LMDB +a blockchain data, you can use something like this to extract it to the host filesystem: + +`docker run --rm -v $(pwd):/backup -v blockchain:/blockchain ubuntu tar czvf /backup/backup.tar.gz /blockchain` + + +## Layout + + +-----------------------+ + | | + +---->| Console Wallet +------------------+ + | | | | + | +----------+------------+ | + | | | + | | gRPC | + | | | + | | | + | +----------v------------+ +------v-----+ + | | | Socks5 | | + | | Base Node +---------->| Tor |----> Network + | | | | | + | +----------^------------+ +------------+ + | | + | | + | | + | +----------+------------+ + | | | + +-----+ SHA3-Miner | + | | | + | +-----------------------+ + | + | + | + | +-----------------------+ + | | | + +-----+ XMRRig etc | + | | + +-----------------------+ + +## Building custom docker images + +The docker images for the base node, wallet etc. are designed to handle the broadest set of chipsets and +architectures. For this reason, they not be optimal for _your_ system. You can build custom images for launchpad +using the `build_images.sh` script in this folder. + +Refer to that script for further details and build options. + +There are a set of files in this folder that offer a convenient way of setting the environment up for some common +configurations. + +run `source {env_config}.env` to set up the environment. Currently, the presets are: + +* `local-performance-amd64.env`: For building local images with Intel/AMD and AVX-2 chipset support (about 2x + speedup on crypto operations) +* `local-performance-arm64.env`: For building local images for Apple M-series CPUs. +* `hosted-dual.env`: Replicates the CI enviroment. Builds safe multi-arch images and pushes them to the docker repo + (requires a write access token). \ No newline at end of file diff --git a/applications/launchpad/backend/Cargo.toml b/applications/launchpad/backend/Cargo.toml index 724370f14e..417d43936b 100644 --- a/applications/launchpad/backend/Cargo.toml +++ b/applications/launchpad/backend/Cargo.toml @@ -11,29 +11,43 @@ build = "src/build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] -tauri-build = { version = "1.0.0-rc.5", features = [] } +tauri-build = { version = "1.0.1", features = [] } [dependencies] -tari_app_utilities = { version = "^0.32", path = "../../tari_app_utilities" } +tari_common_types = { path = "../../../base_layer/common_types"} +tari_app_utilities = { path = "../../tari_app_utilities" } tari_comms = { version = "^0.32", path = "../../../comms/core" } +tari_app_grpc = { path = "../../tari_app_grpc" } tari_common = { path="../../../common"} bollard = "0.11.1" config = "0.13.0" env_logger = "0.9.0" +lazy_static = "1.3.0" log = "0.4.14" rand = "0.8.4" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } strum = "0.23.0" strum_macros = "0.23.0" -tauri = { version = "1.0.0-rc.6", features = ["api-all", "cli"] } +tauri = { version = "1.0.1", features = ["api-all", "cli", "macos-private-api"] } tor-hash-passwd = "1.0.1" thiserror = "1.0.30" tokio = { version = "1.9", features= ["sync"] } futures = "0.3" regex= "1.5.4" derivative = "2.2.0" +tonic = "0.6.2" +# need to force this version to avoid circular dependency in tauri-plugin-sql deps +# https://github.com/tkaitchuck/aHash/issues/95#issuecomment-881152315 +indexmap = "~1.6.2" +hex = "0.4.3" +reqwest = {version = "0.11", features= ["json"] } + +[dependencies.tauri-plugin-sql] +git = "https://github.com/tauri-apps/tauri-plugin-sql" +features = ["sqlite"] # or "postgres", or "mysql" +branch = "release" [features] default = [ "custom-protocol" ] diff --git a/applications/launchpad/backend/assets/defaults.ini b/applications/launchpad/backend/assets/defaults.ini new file mode 100644 index 0000000000..2c33905d13 --- /dev/null +++ b/applications/launchpad/backend/assets/defaults.ini @@ -0,0 +1,1241 @@ +##################### Grafana Configuration Defaults ##################### +# +# Do not modify this file in grafana installs +# + +# possible values : production, development +app_mode = production + +# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty +instance_name = ${HOSTNAME} + +# force migration will run migrations that might cause dataloss +force_migration = false + +#################################### Paths ############################### +[paths] +# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) +data = /var/grafana/data + +# Temporary files in `data` directory older than given duration will be removed +temp_data_lifetime = 24h + +# Directory where grafana can store logs +logs = /var/tari/grafana/logs + +# Directory where grafana will automatically scan and look for plugins +plugins = data/plugins + +# folder that contains provisioning config files that grafana will apply on startup and while running. +provisioning = conf/provisioning + +#################################### Server ############################## +[server] +# Protocol (http, https, h2, socket) +protocol = http + +# The ip address to bind to, empty will bind to all interfaces +http_addr = + +# The http port to use +http_port = 18300 + +# The public facing domain name used to access grafana from a browser +domain = localhost + +# Redirect to correct domain if host header does not match domain +# Prevents DNS rebinding attacks +enforce_domain = false + +# The full public facing url +root_url = %(protocol)s://%(domain)s:%(http_port)s/ + +# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. +serve_from_sub_path = false + +# Log web requests +router_logging = false + +# the path relative working path +static_root_path = public + +# enable gzip +enable_gzip = false + +# https certs & key file +cert_file = +cert_key = + +# Unix socket path +socket = /tmp/grafana.sock + +# CDN Url +cdn_url = + +# Sets the maximum time in minutes before timing out read of an incoming request and closing idle connections. +# `0` means there is no timeout for reading the request. +read_timeout = 0 + +#################################### Database ############################ +[database] +# You can configure the database connection by specifying type, host, name, user and password +# as separate properties or as on string using the url property. + +# Either "mysql", "postgres" or "sqlite3", it's your choice +type = sqlite3 +host = 127.0.0.1:3306 +name = grafana +user = root +# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" +password = +# Use either URL or the previous fields to configure the database +# Example: mysql://user:secret@host:port/database +url = + +# Max idle conn setting default is 2 +max_idle_conn = 2 + +# Max conn setting default is 0 (mean not set) +max_open_conn = + +# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours) +conn_max_lifetime = 14400 + +# Set to true to log the sql calls and execution times. +log_queries = + +# For "postgres", use either "disable", "require" or "verify-full" +# For "mysql", use either "true", "false", or "skip-verify". +ssl_mode = disable + +# Database drivers may support different transaction isolation levels. +# Currently, only "mysql" driver supports isolation levels. +# If the value is empty - driver's default isolation level is applied. +# For "mysql" use "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ" or "SERIALIZABLE". +isolation_level = + +ca_cert_path = +client_key_path = +client_cert_path = +server_cert_name = + +# For "sqlite3" only, path relative to data_path setting +path = grafana.db + +# For "sqlite3" only. cache mode setting used for connecting to the database +cache_mode = private + +# For "mysql" only if lockingMigration feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0. +locking_attempt_timeout_sec = 0 + +#################################### Cache server ############################# +[remote_cache] +# Either "redis", "memcached" or "database" default is "database" +type = database + +# cache connectionstring options +# database: will use Grafana primary database. +# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. +# memcache: 127.0.0.1:11211 +connstr = + +#################################### Data proxy ########################### +[dataproxy] + +# This enables data proxy logging, default is false +logging = false + +# How long the data proxy waits to read the headers of the response before timing out, default is 30 seconds. +# This setting also applies to core backend HTTP data sources where query requests use an HTTP client with timeout set. +timeout = 30 + +# How long the data proxy waits to establish a TCP connection before timing out, default is 10 seconds. +dialTimeout = 10 + +# How many seconds the data proxy waits before sending a keepalive request. +keep_alive_seconds = 30 + +# How many seconds the data proxy waits for a successful TLS Handshake before timing out. +tls_handshake_timeout_seconds = 10 + +# How many seconds the data proxy will wait for a server's first response headers after +# fully writing the request headers if the request has an "Expect: 100-continue" +# header. A value of 0 will result in the body being sent immediately, without +# waiting for the server to approve. +expect_continue_timeout_seconds = 1 + +# Optionally limits the total number of connections per host, including connections in the dialing, +# active, and idle states. On limit violation, dials will block. +# A value of zero (0) means no limit. +max_conns_per_host = 0 + +# The maximum number of idle connections that Grafana will keep alive. +max_idle_connections = 100 + +# How many seconds the data proxy keeps an idle connection open before timing out. +idle_conn_timeout_seconds = 90 + +# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request. +send_user_header = false + +# Limit the amount of bytes that will be read/accepted from responses of outgoing HTTP requests. +response_limit = 0 + +# Limits the number of rows that Grafana will process from SQL data sources. +row_limit = 1000000 + +#################################### Analytics ########################### +[analytics] +# Server reporting, sends usage counters to stats.grafana.org every 24 hours. +# No ip addresses are being tracked, only simple counters to track +# running instances, dashboard and error counts. It is very helpful to us. +# Change this option to false to disable reporting. +reporting_enabled = false + +# The name of the distributor of the Grafana instance. Ex hosted-grafana, grafana-labs +reporting_distributor = grafana-labs + +# Set to false to disable all checks to https://grafana.com +# for new versions of grafana. The check is used +# in some UI views to notify that a grafana update exists. +# This option does not cause any auto updates, nor send any information +# only a GET request to https://raw.githubusercontent.com/grafana/grafana/main/latest.json to get the latest version. +check_for_updates = false + +# Set to false to disable all checks to https://grafana.com +# for new versions of plugins. The check is used +# in some UI views to notify that a plugin update exists. +# This option does not cause any auto updates, nor send any information +# only a GET request to https://grafana.com to get the latest versions. +check_for_plugin_updates = false + +# Google Analytics universal tracking code, only enabled if you specify an id here +google_analytics_ua_id = + +# Google Tag Manager ID, only enabled if you specify an id here +google_tag_manager_id = + +# Rudderstack write key, enabled only if rudderstack_data_plane_url is also set +rudderstack_write_key = + +# Rudderstack data plane url, enabled only if rudderstack_write_key is also set +rudderstack_data_plane_url = + +# Rudderstack SDK url, optional, only valid if rudderstack_write_key and rudderstack_data_plane_url is also set +rudderstack_sdk_url = + +# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config +rudderstack_config_url = + +# Application Insights connection string. Specify an URL string to enable this feature. +application_insights_connection_string = + +# Optional. Specifies an Application Insights endpoint URL where the endpoint string is wrapped in backticks ``. +application_insights_endpoint_url = + +# Controls if the UI contains any links to user feedback forms +feedback_links_enabled = true + +#################################### Security ############################ +[security] +# disable creation of admin user on first start of grafana +disable_initial_admin_creation = false + +# default admin user, created on startup +admin_user = admin + +# default admin password, can be changed before first start of grafana, or in profile settings +admin_password = admin + +# used for signing +secret_key = SW2YcwTIb9zpOOhoPsMm + +# current key provider used for envelope encryption, default to static value specified by secret_key +encryption_provider = secretKey.v1 + +# list of configured key providers, space separated (Enterprise only): e.g., awskms.v1 azurekv.v1 +available_encryption_providers = + +# disable gravatar profile images +disable_gravatar = false + +# data source proxy whitelist (ip_or_domain:port separated by spaces) +data_source_proxy_whitelist = + +# disable protection against brute force login attempts +disable_brute_force_login_protection = false + +# set to true if you host Grafana behind HTTPS. default is false. +cookie_secure = false + +# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled" +cookie_samesite = lax + +# set to true if you want to allow browsers to render Grafana in a , + ) +} + +export default Logs diff --git a/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/Tooltip.tsx b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/Tooltip.tsx new file mode 100644 index 0000000000..ab8d68bc43 --- /dev/null +++ b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/Tooltip.tsx @@ -0,0 +1,61 @@ +import { useTheme } from 'styled-components' + +import Text from '../../../../../components/Text' +import * as Format from '../../../../../utils/Format' +import t from '../../../../../locales' + +import { TooltipWrapper, SeriesColorIndicator } from './styles' + +export type TooltipProps = { + display?: boolean + left?: number + top?: number + x?: Date + values?: { + service: string + unit: string + value: number | null + color: string + }[] +} + +const Tooltip = ({ display, left, top, values, x }: TooltipProps) => { + const theme = useTheme() + + return ( + + {Boolean(values) && ( + + )} + {Boolean(x) && ( + + {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} + {Format.dateTime(x!)} + + )} + + ) +} + +export default Tooltip diff --git a/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/index.tsx b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/index.tsx new file mode 100644 index 0000000000..7634af5b11 --- /dev/null +++ b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/index.tsx @@ -0,0 +1,357 @@ +import { useCallback, useEffect, useRef, useState, useMemo } from 'react' +import groupby from 'lodash.groupby' +import { useTheme } from 'styled-components' +import UplotReact from 'uplot-react' + +import { chartColors } from '../../../../../styles/styles/colors' +import IconButton from '../../../../../components/IconButton' +import Loading from '../../../../../components/Loading' +import { Dictionary } from '../../../../../types/general' +import VisibleIcon from '../../../../../styles/Icons/Eye' +import HiddenIcon from '../../../../../styles/Icons/EyeSlash' +import useIntersectionObserver from '../../../../../utils/useIntersectionObserver' +import * as Format from '../../../../../utils/Format' +import Text from '../../../../../components/Text' +import t from '../../../../../locales' +import { MinimalStatsEntry } from '../types' + +import Tooltip, { TooltipProps } from './Tooltip' +import { + ChartContainer, + Legend, + LegendItem, + SeriesColorIndicator, + TitleContainer, +} from './styles' + +const getTimestampInResolution = (timestampS: number, resolutionS: number) => + Math.floor(timestampS / resolutionS) * resolutionS + +const PerformanceChart = ({ + since, + now, + data, + getter, + title, + width, + percentage, + unit, + onFreeze, + loading, + resolution = 1, +}: { + since: Date + now: Date + data: MinimalStatsEntry[] + getter: (se: MinimalStatsEntry) => number | null + title: string + width: number + percentage?: boolean + unit?: string + onFreeze: (frozen: boolean) => void + loading?: boolean + resolution?: number +}) => { + const theme = useTheme() + const unitToDisplay = percentage ? '%' : unit || '' + const chartContainerRef = useRef() + const observerEntry = useIntersectionObserver(chartContainerRef, {}) + const inView = Boolean(observerEntry?.isIntersecting) + + const [latchedSinceS, setLatchedSinceS] = useState(since.getTime() / 1000) + const [latchedNowS, setLatchedNowS] = useState(now.getTime() / 1000) + const [frozen, setFrozen] = useState(false) + + useEffect(() => { + if (frozen) { + return + } + + setLatchedSinceS(since.getTime() / 1000) + }, [frozen, since]) + + useEffect(() => { + if (frozen) { + return + } + + setLatchedNowS(now.getTime() / 1000) + }, [frozen, now]) + + const xValues = useMemo(() => { + const x = [] + const latchedSinceInResolution = getTimestampInResolution( + latchedSinceS, + resolution, + ) + const latchedNowInResolution = getTimestampInResolution( + latchedNowS, + resolution, + ) + for ( + let i = 0; + i < latchedNowInResolution - latchedSinceInResolution; + i += resolution + ) { + x.push(latchedSinceInResolution + i) + } + + return x + }, [latchedNowS, latchedSinceS]) + const chartData = useMemo(() => { + const grouped = groupby(data, 'service') + const seriesData: Dictionary = {} + const sinceS = xValues[0] + let min = 0 + let max = 0 + Object.keys(grouped) + .sort() + .forEach(key => { + const yValues = new Array(xValues.length).fill(null) + if (resolution === 1) { + grouped[key].forEach(v => { + const idx = v.timestampS - sinceS + if (idx < yValues.length) { + yValues[idx] = getter(v) + min = Math.min(min, yValues[idx]) + max = Math.max(max, yValues[idx]) + } + }) + } else { + const groupedForResolution = groupby(grouped[key], v => + getTimestampInResolution(v.timestampS, resolution), + ) + + Object.entries(groupedForResolution).forEach( + ([resolutionTimestamp, current]) => { + const sum = current.reduce((a, c) => a + (getter(c) || 0), 0) + const idx = (Number(resolutionTimestamp) - sinceS) / resolution + + if (idx < yValues.length) { + yValues[idx] = sum / current.length + min = Math.min(min, yValues[idx]) + max = Math.max(max, yValues[idx]) + } + }, + ) + } + + seriesData[key] = yValues + }) + return { + seriesData, + min, + max, + } + }, [xValues, getter, resolution]) + const [tooltipState, setTooltipState] = useState(null) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const setTooltipValues = useCallback((u: any) => { + const { left, top, idx } = u.cursor + const x = u.data[0][idx] + const chartingAreaRect = u.root.getBoundingClientRect() + const values: TooltipProps['values'] = [] + for (let i = 1; i < u.data.length; i++) { + values.push({ + service: u.series[i].label, + unit: u.series[i].unit, + value: u.data[i][idx]?.toFixed(2), + color: chartColors[i - 1], + }) + } + + setTooltipState(st => ({ + ...st, + left: left + chartingAreaRect.left, + top: top + chartingAreaRect.top, + x: new Date(x * 1000), + values, + })) + }, []) + + // keeping stable reference to onFreezeCallback to avoid changing + // mouseEnter and mouseLeave references + // if new references are passed to uPloat - it is rerendered + // and cursor disappears + const freezeCallback = useRef<((frozen: boolean) => void) | null>(null) + useEffect(() => { + freezeCallback.current = onFreeze + }, [onFreeze]) + const mouseLeave = useCallback((_e: MouseEvent) => { + setFrozen(false) + if (freezeCallback.current) { + freezeCallback.current(false) + } + setTooltipState(st => ({ ...st, display: false })) + + return null + }, []) + const mouseEnter = useCallback((_e: MouseEvent) => { + setFrozen(true) + if (freezeCallback.current) { + freezeCallback.current(true) + } + setTooltipState(st => ({ ...st, display: true })) + + return null + }, []) + + const [hiddenSeries, setHiddenSeries] = useState([]) + + const options = useMemo( + () => ({ + width, + height: 175, + legend: { + show: false, + }, + hooks: { + setCursor: [setTooltipValues], + }, + cursor: { + bind: { + mouseenter: () => mouseEnter, + mouseleave: () => mouseLeave, + }, + }, + scales: { + '%': { + auto: false, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + range: (_u: any, _dataMin: number, _dataMax: number) => { + return [0, Math.max(100, chartData.max)] as [ + number | null, + number | null, + ] + }, + }, + y: { + auto: false, + min: chartData.min, + max: chartData.max, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + range: (_u: any, dataMin: number, dataMax: number) => + [dataMin, dataMax] as [number | null, number | null], + }, + }, + series: [ + {}, + ...Object.keys(chartData.seriesData).map((key, id) => ({ + unit: unitToDisplay, + auto: false, + show: !hiddenSeries.includes(key), + scale: percentage ? '%' : 'y', + label: key, + stroke: chartColors[id], + fill: `${chartColors[id]}33`, + })), + ], + axes: [ + { + grid: { + show: true, + stroke: theme.inverted.resetBackground, + width: 0.5, + }, + ticks: { + show: true, + stroke: theme.inverted.resetBackground, + width: 0.5, + }, + show: true, + side: 2, + labelSize: 8 + 12 + 8, + stroke: theme.inverted.secondary, + values: ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + _uPlot: any, + splits: number[], + _axisIdx: number, + _foundSpace: number, + _foundIncr: number, + ) => { + return splits.map(split => Format.localHour(new Date(split * 1000))) + }, + }, + { + scale: percentage ? '%' : 'y', + show: true, + side: 3, + values: ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + _uPlot: any, + splits: number[], + _axisIdx: number, + _foundSpace: number, + _foundIncr: number, + ) => { + return splits + }, + stroke: theme.inverted.secondary, + grid: { + show: true, + stroke: theme.inverted.resetBackground, + width: 0.5, + }, + ticks: { + show: true, + stroke: theme.inverted.resetBackground, + width: 0.5, + }, + }, + ], + }), + [mouseEnter, mouseLeave, chartData, hiddenSeries, width, percentage], + ) + + const toggleSeries = (name: string) => { + setHiddenSeries(hidden => { + if (hidden.includes(name)) { + return hidden.filter(h => h !== name) + } + + return [...hidden, name] + }) + } + + return ( + + + + {title} [{unitToDisplay}] + + + +
+ + {inView && ( + + )} + + {Object.keys(chartData.seriesData).map((name, seriesId) => ( + + + + {t.common.containers[name]} + + toggleSeries(name)}> + {hiddenSeries.includes(name) ? : } + + + ))} + +
+
+ ) +} + +export default PerformanceChart diff --git a/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/styles.ts b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/styles.ts new file mode 100644 index 0000000000..ea548da055 --- /dev/null +++ b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceChart/styles.ts @@ -0,0 +1,67 @@ +import styled from 'styled-components' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const ChartContainer = styled.div<{ ref?: any }>` + position: relative; + color: ${({ theme }) => theme.inverted.primary}; + background-color: ${({ theme }) => theme.inverted.backgroundSecondary}; + padding: ${({ theme }) => theme.spacing()}; + padding-left: ${({ theme }) => theme.spacing(0.5)}; + border-radius: ${({ theme }) => theme.borderRadius()}; + max-width: 100%; + margin-top: ${({ theme }) => theme.spacing()}; + display: flex; + flex-direction: column; + align-items: center; +` + +export const TooltipWrapper = styled.div` + position: fixed; + background-color: ${({ theme }) => theme.inverted.background}; + border-radius: ${({ theme }) => theme.borderRadius()}; + padding: ${({ theme }) => theme.spacing()}; + transform: translate(-100%, -50%); + margin-left: ${({ theme }) => theme.spacing()}; + z-index: 9001; + min-width: 175px; + & ul { + list-style-type: none; + margin: 0; + padding: 0; + } + + & li { + display: flex; + align-items: center; + column-gap: ${({ theme }) => theme.spacing(0.25)}; + } +` + +export const Legend = styled.div` + display: flex; + align-items: center; + flex-wrap: wrap; + margin-left: ${({ theme }) => theme.spacing()}; + column-gap: ${({ theme }) => theme.spacing()}; +` + +export const LegendItem = styled.div` + display: flex; + align-items: center; + column-gap: ${({ theme }) => theme.spacing(0.5)}; + min-height: 1em; +` + +export const SeriesColorIndicator = styled.div<{ color: string }>` + width: 1em; + height: 0.1em; + border-radius: 2px; + background-color: ${({ color }) => color}; +` + +export const TitleContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + column-gap: ${({ theme }) => theme.spacing(0.5)}; +` diff --git a/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceControls.tsx b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceControls.tsx new file mode 100644 index 0000000000..9298b8b368 --- /dev/null +++ b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/PerformanceControls.tsx @@ -0,0 +1,121 @@ +import { useMemo } from 'react' +import { useTheme } from 'styled-components' + +import Select from '../../../../components/Select' +import { Option } from '../../../../components/Select/types' +import FilterIcon from '../../../../styles/Icons/Filter' +import RefreshRateIcon from '../../../../styles/Icons/RotateRight' +import t from '../../../../locales' + +export interface TimeWindowOption extends Option { + resolution: number +} + +const renderWindowOptions: TimeWindowOption[] = [ + { + value: 30 * 60 * 1000, + key: '30m', + label: t.expertView.performance.renderWindowOptionsLabels.last30m, + resolution: 1, + }, + { + value: 60 * 60 * 1000, + key: '1h', + label: t.expertView.performance.renderWindowOptionsLabels.last1h, + resolution: 1, + }, + { + value: 2 * 60 * 60 * 1000, + key: '2h', + label: t.expertView.performance.renderWindowOptionsLabels.last2h, + resolution: 1, + }, + { + value: 8 * 60 * 60 * 1000, + key: '8h', + label: t.expertView.performance.renderWindowOptionsLabels.last8h, + resolution: 60, + }, + { + value: 24 * 60 * 60 * 1000, + key: '24h', + label: t.expertView.performance.renderWindowOptionsLabels.last24h, + resolution: 60, + }, +] +export const defaultRenderWindow = renderWindowOptions[0] + +const refreshRateOptions = [ + { + value: 1000, + key: '1s', + label: t.expertView.performance.refreshRateOptionsLabels.every1s, + }, + { + value: 10 * 1000, + key: '10s', + label: t.expertView.performance.refreshRateOptionsLabels.every10s, + }, + { + value: 60 * 1000, + key: '60s', + label: t.expertView.performance.refreshRateOptionsLabels.every60s, + }, +] +export const defaultRefreshRate = refreshRateOptions[0] + +const PerformanceControls = ({ + refreshRate, + onRefreshRateChange, + timeWindow, + onTimeWindowChange, +}: { + refreshRate: Option + onRefreshRateChange: (option: Option) => void + timeWindow: TimeWindowOption + onTimeWindowChange: (option: TimeWindowOption) => void +}) => { + const theme = useTheme() + + const selectStyleOverrides = useMemo( + () => ({ + icon: { + color: theme.secondary, + }, + value: { + color: theme.placeholderText, + backgroundColor: theme.inverted.backgroundSecondary, + borderColor: (open?: boolean) => + open ? theme.accent : theme.inverted.backgroundSecondary, + }, + }), + [theme], + ) + + return ( +
+ + } + fullWidth={false} + value={refreshRate} + options={refreshRateOptions} + onChange={onRefreshRateChange} + styles={selectStyleOverrides} + /> +
+ ) +} + +export default PerformanceControls diff --git a/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/index.tsx b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/index.tsx new file mode 100644 index 0000000000..e771a15773 --- /dev/null +++ b/applications/launchpad/gui-react/src/containers/Dashboard/ExpertView/Performance/index.tsx @@ -0,0 +1,220 @@ +import { useEffect, useRef, useState, useMemo } from 'react' +import { listen } from '@tauri-apps/api/event' + +import t from '../../../../locales' +import { selectNetwork } from '../../../../store/baseNode/selectors' +import { selectExpertView } from '../../../../store/app/selectors' +import { selectAllContainerEventsChannels } from '../../../../store/containers/selectors' +import { extractStatsFromEvent } from '../../../../store/containers/thunks' +import { StatsEventPayload } from '../../../../store/containers/types' +import { useAppSelector } from '../../../../store/hooks' +import getStatsRepository from '../../../../persistence/statsRepository' +import { Option } from '../../../../components/Select/types' + +import PerformanceControls, { + defaultRenderWindow, + defaultRefreshRate, + TimeWindowOption, +} from './PerformanceControls' +import PerformanceChart from './PerformanceChart' +import { MinimalStatsEntry } from './types' + +const CPU_GETTER = (se: MinimalStatsEntry) => se.cpu +const MEMORY_GETTER = (se: MinimalStatsEntry) => se.memory +const NETWORK_GETTER = (se: MinimalStatsEntry) => + (se.download || 0) / (1024 * 1024) + +/** + * @name PerformanceContainer + * @description container component for performance statistics, renders filtering controls and performance charts + * manages refresh rate and synchronizes refresh ticks for all charts + * delegates chart rendering etc to other components + * + */ +const PerformanceContainer = () => { + const configuredNetwork = useAppSelector(selectNetwork) + const expertView = useAppSelector(selectExpertView) + const statsRepository = useMemo(getStatsRepository, []) + const allContainerEventsChannels = useAppSelector( + selectAllContainerEventsChannels, + ) + const unsubscribeFunctions = useRef<(() => void)[]>() + + const [loadingData, setLoadingData] = useState(false) + const [timeWindow, setTimeWindow] = + useState(defaultRenderWindow) + const [refreshRate, setRefreshRate] = useState