diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 1907f3dc3..9ad4c0daa 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -60,6 +60,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -71,9 +80,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.59" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" +checksum = "a26fa4d7e3f2eebadf743988fc8aec9fa9a9e82611acafd77c1462ed6262440a" [[package]] name = "arrayref" @@ -110,10 +119,10 @@ checksum = "bc4c00309ed1c8104732df4a5fa9acc3b796b6f8531dfbd5ce0078c86f997244" dependencies = [ "darling 0.10.2", "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "swc_macros_common", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -132,9 +141,9 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -143,9 +152,9 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -198,6 +207,17 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "barrage" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5951c75bdabb58753d140dd5802f12ff3a483cb2e16fb5276e111b94b19e87" +dependencies = [ + "concurrent-queue", + "event-listener", + "spin 0.9.4", +] + [[package]] name = "base32" version = "0.4.0" @@ -218,9 +238,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" +checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" [[package]] name = "basic-cookies" @@ -258,8 +278,8 @@ dependencies = [ "lazycell", "log", "peeking_take_while", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "regex", "rustc-hash", "shlex", @@ -413,9 +433,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byteorder" @@ -429,6 +449,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + [[package]] name = "cc" version = "1.0.73" @@ -458,15 +484,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "serde", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -534,9 +562,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.16" +version = "3.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +checksum = "68d43934757334b5c0519ff882e1ab9647ac0258b47c24c4f490d78e42697fd5" dependencies = [ "atty", "bitflags", @@ -583,6 +611,15 @@ dependencies = [ "tracing-error 0.2.0", ] +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + [[package]] name = "const-oid" version = "0.9.0" @@ -608,7 +645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" dependencies = [ "percent-encoding", - "time 0.3.12", + "time 0.3.14", "version_check", ] @@ -624,7 +661,7 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time 0.3.12", + "time 0.3.14", "url", ] @@ -646,9 +683,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" dependencies = [ "libc", ] @@ -668,6 +705,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "crossbeam-queue" version = "0.3.6" @@ -792,10 +838,10 @@ checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "strsim 0.9.3", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -806,10 +852,10 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "strsim 0.10.0", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -819,8 +865,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core 0.10.2", - "quote 1.0.20", - "syn 1.0.98", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -830,19 +876,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core 0.13.4", - "quote 1.0.20", - "syn 1.0.98", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] name = "dashmap" -version = "5.3.4" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ "cfg-if 1.0.0", - "hashbrown 0.12.3", + "hashbrown", "lock_api", + "once_cell", "parking_lot_core 0.9.3", ] @@ -916,9 +963,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05520711837dd592d2861319ea3cf2dfd81e39bb92e41758ee9172f3623daebd" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -939,10 +986,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "rustc_version 0.4.0", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -1030,10 +1077,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82" [[package]] -name = "dotenv" -version = "0.15.0" +name = "dotenvy" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +checksum = "da3db6fcad7c1fc4abdd99bf5276a4db30d6a819127903a709ed41e5ff016e84" +dependencies = [ + "dirs 4.0.0", +] [[package]] name = "dprint-swc-ext" @@ -1078,9 +1128,9 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" dependencies = [ "serde", ] @@ -1100,7 +1150,7 @@ dependencies = [ "bollard", "chrono", "ciborium", - "clap 3.2.16", + "clap 3.2.19", "color-eyre", "cookie_store", "current_platform", @@ -1123,6 +1173,7 @@ dependencies = [ "indexmap", "isocountry", "itertools 0.10.3", + "josekit", "js_engine", "jsonpath_lib", "lazy_static", @@ -1155,7 +1206,7 @@ dependencies = [ "serde_json", "serde_with", "serde_yaml", - "sha2 0.10.2", + "sha2 0.10.3", "sha2 0.9.9", "simple-logging", "sqlx", @@ -1176,6 +1227,7 @@ dependencies = [ "trust-dns-server", "typed-builder", "url", + "uuid", ] [[package]] @@ -1227,9 +1279,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ "heck 0.4.0", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -1239,9 +1291,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b940da354ae81ef0926c5eaa428207b8f4f091d3956c891dfbd124162bed99" dependencies = [ "pmutil", - "proc-macro2 1.0.42", + "proc-macro2 1.0.43", "swc_macros_common", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -1322,6 +1374,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1364,14 +1426,14 @@ dependencies = [ [[package]] name = "from_variant" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0951635027ca477be98f8774abd6f0345233439d63f307e47101acb40c7cc63d" +checksum = "f0981e470d2ab9f643df3921d54f1952ea100c39fdb6a3fdc820e20d2291df6c" dependencies = [ "pmutil", - "proc-macro2 1.0.42", + "proc-macro2 1.0.43", "swc_macros_common", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -1392,9 +1454,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -1407,9 +1469,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -1417,15 +1479,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -1445,38 +1507,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-channel", "futures-core", @@ -1492,9 +1554,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -1545,9 +1607,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe69f1cbdb6e28af2bac214e943b99ce8a0a06b447d15d3e61161b0423139f3f" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -1558,9 +1620,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "h2" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ "bytes", "fnv", @@ -1581,12 +1643,6 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - [[package]] name = "hashbrown" version = "0.12.3" @@ -1602,7 +1658,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" dependencies = [ - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -1685,7 +1741,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.2", + "itoa 1.0.3", ] [[package]] @@ -1701,9 +1757,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -1741,7 +1797,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.2", + "itoa 1.0.3", "pin-project-lite", "socket2", "tokio", @@ -1793,6 +1849,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "iana-time-zone" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1865,7 +1935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", "serde", ] @@ -1892,9 +1962,9 @@ checksum = "1c068d4c6b922cd6284c609cfa6dec0e41615c9c5a1a4ba729a970d8daba05fb" dependencies = [ "Inflector", "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -1933,9 +2003,27 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "josekit" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee6af62ad98bdf699ad2ecc8323479a1fdc7aa5faa6043d93119d83f6c5fca8" +dependencies = [ + "anyhow", + "base64 0.13.0", + "flate2", + "once_cell", + "openssl", + "regex", + "serde", + "serde_json", + "thiserror", + "time 0.3.14", +] [[package]] name = "js-sys" @@ -1959,7 +2047,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "sha2 0.10.2", + "sha2 0.10.3", "swc_atoms", "swc_common", "swc_config", @@ -2154,9 +2242,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.126" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libloading" @@ -2176,9 +2264,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" dependencies = [ "autocfg", "scopeguard", @@ -2221,9 +2309,9 @@ dependencies = [ [[package]] name = "md-5" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +checksum = "274fd6bd98a3c75c9515d9393b063099f60f9b47f09ee20a34fd76287fd017f4" dependencies = [ "digest 0.10.3", ] @@ -2481,9 +2569,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -2506,9 +2594,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" [[package]] name = "opaque-debug" @@ -2550,9 +2638,9 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -2586,15 +2674,15 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.2.0" +version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" [[package]] name = "owo-colors" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking_lot" @@ -2657,15 +2745,16 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "patch-db" version = "0.1.0" dependencies = [ "async-trait", + "barrage", "fd-lock-rs", "futures", "imbl 1.0.1", @@ -2688,8 +2777,8 @@ name = "patch-db-macro" version = "0.1.0" dependencies = [ "patch-db-macro-internals", - "proc-macro2 1.0.42", - "syn 1.0.98", + "proc-macro2 1.0.43", + "syn 1.0.99", ] [[package]] @@ -2697,9 +2786,9 @@ name = "patch-db-macro-internals" version = "0.1.0" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -2711,7 +2800,7 @@ dependencies = [ "digest 0.10.3", "hmac 0.12.1", "password-hash", - "sha2 0.10.2", + "sha2 0.10.3", ] [[package]] @@ -2775,9 +2864,9 @@ dependencies = [ "phf_generator", "phf_shared", "proc-macro-hack", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -2797,22 +2886,22 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -2849,9 +2938,9 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -2882,10 +2971,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -2907,9 +2997,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] @@ -2947,18 +3037,16 @@ dependencies = [ [[package]] name = "psl-types" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8eda7c62d9ecaafdf8b62374c006de0adf61666ae96a96ba74a37134aa4e470" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "publicsuffix" -version = "2.1.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "292972edad6bbecc137ab84c5e36421a4a6c979ea31d3cc73540dd04315b33e1" +checksum = "aeeedb0b429dc462f30ad27ef3de97058b060016f47790c066757be38ef792b4" dependencies = [ - "byteorder", - "hashbrown 0.11.2", "idna", "psl-types", ] @@ -2986,11 +3074,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ - "proc-macro2 1.0.42", + "proc-macro2 1.0.43", ] [[package]] @@ -3234,7 +3322,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -3258,7 +3346,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5bfeb75c188f3af65774d5fe92f97dac2cede5e313c643c7a1b82a8e53b0e6" dependencies = [ - "clap 3.2.16", + "clap 3.2.19", "futures", "hyper", "lazy_static", @@ -3280,9 +3368,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecb48bdaace41cfbb514b3e541ae0fc1ac0fb8283498215ad8a3d22ca2ea5ae5" dependencies = [ - "proc-macro2 1.0.42", + "proc-macro2 1.0.43", "rpc-toolkit-macro-internals", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -3291,9 +3379,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a9e2bae02a2beecad48d87255e51cab941d0c89a2bcee05a03a77803a0a282" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -3347,7 +3435,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.12", + "semver 1.0.13", ] [[package]] @@ -3364,18 +3452,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ "base64 0.13.0", ] [[package]] name = "rustversion" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c8ad4f0c00e1eb5bc7614d236a7f1300e3dbd76b68cac8e06fb00b015ad8d8" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rusty-fork" @@ -3391,9 +3479,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "schannel" @@ -3429,9 +3517,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" dependencies = [ "bitflags", "core-foundation", @@ -3461,9 +3549,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" [[package]] name = "semver-parser" @@ -3473,18 +3561,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.141" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af873f2c95b99fcb0bd0fe622a43e29514658873c8ceba88c4cb88833a22500" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ "serde", ] @@ -3509,23 +3597,23 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.141" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75743a150d003dd863b51dc809bcad0d73f2102c53632f1e954e738192a3413f" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "indexmap", - "itoa 1.0.2", + "itoa 1.0.3", "ryu", "serde", ] @@ -3537,7 +3625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.2", + "itoa 1.0.3", "ryu", "serde", ] @@ -3572,9 +3660,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling 0.13.4", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -3615,9 +3703,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "899bf02746a2c92bf1053d9327dadb252b01af1f81f90cdb902411f518bc7215" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -3662,9 +3750,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" [[package]] name = "simple-logging" @@ -3710,9 +3798,9 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -3740,6 +3828,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" + [[package]] name = "spki" version = "0.6.0" @@ -3763,9 +3857,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f82cbe94f41641d6c410ded25bbf5097c240cefdf8e3b06d04198d0a96af6a4" +checksum = "788841def501aabde58d3666fcea11351ec3962e6ea75dbcd05c84a71d68bcd1" dependencies = [ "sqlx-core", "sqlx-macros", @@ -3773,9 +3867,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b69bf218860335ddda60d6ce85ee39f6cf6e5630e300e19757d1de15886a093" +checksum = "8c21d3b5e7cadfe9ba7cdc1295f72cc556c750b4419c27c219c0693198901f8e" dependencies = [ "ahash", "atoi", @@ -3787,6 +3881,7 @@ dependencies = [ "crc", "crossbeam-queue", "dirs 4.0.0", + "dotenvy", "either", "event-listener", "futures-channel", @@ -3798,10 +3893,10 @@ dependencies = [ "hkdf", "hmac 0.12.1", "indexmap", - "itoa 1.0.2", + "itoa 1.0.3", "libc", "log", - "md-5 0.10.1", + "md-5 0.10.2", "memchr", "once_cell", "paste", @@ -3812,7 +3907,7 @@ dependencies = [ "serde", "serde_json", "sha-1", - "sha2 0.10.2", + "sha2 0.10.3", "smallvec", "sqlformat", "sqlx-rt", @@ -3826,31 +3921,31 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40c63177cf23d356b159b60acd27c54af7423f1736988502e36bae9a712118f" +checksum = "4adfd2df3557bddd3b91377fc7893e8fa899e9b4061737cbade4e1bb85f1b45c" dependencies = [ - "dotenv", + "dotenvy", "either", "heck 0.4.0", "hex", "once_cell", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "serde", "serde_json", - "sha2 0.10.2", + "sha2 0.10.3", "sqlx-core", "sqlx-rt", - "syn 1.0.98", + "syn 1.0.99", "url", ] [[package]] name = "sqlx-rt" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874e93a365a598dc3dadb197565952cb143ae4aa716f7bcc933a8d836f6bf89f" +checksum = "7be52fc7c96c136cedea840ed54f7d446ff31ad670c9dea95ebcb998530971a3" dependencies = [ "once_cell", "tokio", @@ -3898,8 +3993,8 @@ checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ "phf_generator", "phf_shared", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", ] [[package]] @@ -3909,10 +4004,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f584cc881e9e5f1fd6bf827b0444aa94c30d8fe6378cf241071b5f5700b2871f" dependencies = [ "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "swc_macros_common", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -4006,10 +4101,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb64bc03d90fd5c90d6ab917bb2b1d7fbd31957df39e31ea24a3f554b4372251" dependencies = [ "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "swc_macros_common", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -4054,10 +4149,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59949619b2ef45eedb6c399d05f2c3c7bc678b5074b3103bb670f9e05bb99042" dependencies = [ "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "swc_macros_common", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -4138,10 +4233,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18712e4aab969c6508dff3540ade6358f1e013464aa58b3d30da2ab2d9fcbbed" dependencies = [ "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "swc_macros_common", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -4255,9 +4350,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c8f200a2eaed938e7c1a685faaa66e6d42fa9e17da5f62572d3cbc335898f5e" dependencies = [ "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -4267,9 +4362,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5dca3f08d02da4684c3373150f7c045128f81ea00f0c434b1b012bc65a6cce3" dependencies = [ "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -4290,10 +4385,10 @@ checksum = "c3b9b72892df873972549838bf84d6c56234c7502148a7e23b5a3da6e0fedfb8" dependencies = [ "Inflector", "pmutil", - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "swc_macros_common", - "syn 1.0.98", + "syn 1.0.99", ] [[package]] @@ -4309,12 +4404,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", + "proc-macro2 1.0.43", + "quote 1.0.21", "unicode-ident", ] @@ -4324,9 +4419,9 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", "unicode-xid 0.2.3", ] @@ -4418,22 +4513,22 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -4469,12 +4564,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ - "itoa 1.0.2", - "js-sys", + "itoa 1.0.3", "libc", "num_threads", "time-macros", @@ -4537,9 +4631,9 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -4682,9 +4776,9 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -4791,7 +4885,7 @@ dependencies = [ "radix_trie", "rand 0.8.5", "thiserror", - "time 0.3.12", + "time 0.3.14", "tokio", "trust-dns-proto", ] @@ -4837,7 +4931,7 @@ dependencies = [ "log", "serde", "thiserror", - "time 0.3.12", + "time 0.3.14", "tokio", "toml", "trust-dns-client", @@ -4882,9 +4976,9 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", ] [[package]] @@ -4907,9 +5001,9 @@ checksum = "69fe8d9274f490a36442acb4edfd0c4e473fdfc6a8b5cd32f28a0235761aedbe" [[package]] name = "unicode-ident" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" @@ -4984,6 +5078,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom 0.2.7", +] + [[package]] name = "v8" version = "0.43.1" @@ -4994,7 +5097,7 @@ dependencies = [ "fslock", "lazy_static", "libc", - "which 4.2.5", + "which 4.3.0", ] [[package]] @@ -5083,9 +5186,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", "wasm-bindgen-shared", ] @@ -5107,7 +5210,7 @@ version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ - "quote 1.0.20", + "quote 1.0.21", "wasm-bindgen-macro-support", ] @@ -5117,9 +5220,9 @@ version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5170,13 +5273,13 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static", "libc", + "once_cell", ] [[package]] @@ -5323,8 +5426,8 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ - "proc-macro2 1.0.42", - "quote 1.0.20", - "syn 1.0.98", + "proc-macro2 1.0.43", + "quote 1.0.21", + "syn 1.0.99", "synstructure", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 5a377f572..26bb8d125 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -80,6 +80,7 @@ imbl = "2.0.0" indexmap = { version = "1.9.1", features = ["serde"] } isocountry = "0.3.2" itertools = "0.10.3" +josekit = "0.8.1" js_engine = { path = '../libs/js_engine', optional = true } jsonpath_lib = "0.3.0" lazy_static = "1.4.0" @@ -141,6 +142,7 @@ tracing-subscriber = { version = "0.3.14", features = ["env-filter"] } trust-dns-server = "0.21.2" typed-builder = "0.10.0" url = { version = "2.2.2", features = ["serde"] } +uuid = { version = "1.1.2", features = ["v4"] } [profile.test] opt-level = 3 diff --git a/backend/embassy-init.service b/backend/embassy-init.service index 3322f4bf9..88d17700e 100644 --- a/backend/embassy-init.service +++ b/backend/embassy-init.service @@ -6,7 +6,8 @@ Wants=avahi-daemon.service nginx.service tor.service [Service] Type=oneshot -Environment=RUST_LOG=embassy_init=debug,embassy=debug,js_engine=debug +Environment=RUST_LOG=embassy_init=debug,embassy=debug,js_engine=debug,patch_db=trace +Environment=RUST_LIB_BACKTRACE=full ExecStart=/usr/local/bin/embassy-init RemainAfterExit=true StandardOutput=file:/var/log/embassy-init.out.log diff --git a/backend/src/assets/adjectives.txt b/backend/src/assets/adjectives.txt new file mode 100644 index 000000000..c482d50e9 --- /dev/null +++ b/backend/src/assets/adjectives.txt @@ -0,0 +1,1296 @@ +ominous +sturdy +angelic +frontal +legal +murky +rougher +formal +local +bold +grouchy +grazing +bumpy +wonky +boxed +factual +sunny +trim +selfish +humble +plastic +broke +shorter +rustic +brittle +narrow +astute +icky +sullen +bemused +creative +snowy +sane +sedate +deviant +icy +spoiled +blond +concave +cyan +lucky +lively +risky +wounded +greater +limber +social +glued +painful +warm +nutty +amused +carried +amateur +meager +working +faded +stark +reborn +darkened +entire +charred +speedy +clear +kinky +impish +ageless +prewar +correct +molten +admired +uneasy +higher +tragic +inane +magenta +urban +nearby +grouped +noisy +rural +fetid +waxed +dandy +cocky +aqua +dingy +unbent +lewd +quiet +mellow +unvalued +itchy +clunky +snug +opaque +bulky +smug +helpful +velvet +violet +valid +wobbly +dodgy +rare +cocoa +selfless +complex +simple +prim +blank +obsolete +surplus +funky +chubby +daring +spongy +tinted +prepaid +geeky +unsure +broken +wimpy +obscene +monthly +brown +erased +freaky +decent +rimless +cordless +tainted +huffy +yawning +toxic +puffy +inner +smart +lesser +mute +mighty +deluxe +thatch +frosty +vulgar +darkish +fun +annoying +swollen +loco +magic +generic +tan +trendy +blind +worried +stray +pungent +fluid +mixed +soviet +ruby +rabid +silky +regular +winter +ethnic +sour +sleepy +frail +dicey +heavy +rude +asleep +loony +singing +exterior +bendy +feeble +intact +robust +foamy +pale +crazed +sloping +soaked +next +thorny +voting +squeaky +pregame +dismal +teak +rumbling +furry +hazel +quaint +older +custard +golden +paltry +phony +smooth +trivial +happier +unboxed +chalky +learned +younger +calm +jagged +hateful +still +round +final +unhappy +jumbo +obedient +germy +mangy +pickled +untamed +puny +pink +mild +mini +able +related +auburn +giddy +tapered +flabby +cruel +awake +artsy +dull +virtual +dry +useless +winking +nerdy +drastic +dimpled +slick +purple +jolly +maple +humorous +swank +ready +level +shrill +amusing +grim +crabby +canned +anxious +refried +undying +revised +spicy +refined +aquatic +foggy +chosen +grey +bespoke +flavored +baggy +foul +shocked +unlucky +yapping +insecure +daft +sleazy +unused +short +your +snarky +tweed +rosy +brief +welcome +buffed +enamel +inept +abrupt +taboo +hungry +audible +bitter +untidy +stale +strong +moldy +brass +crisp +temp +acidic +top +old +limited +neon +cushy +angled +potent +rotten +snappy +floppy +good +fatal +darned +somber +stunned +unloved +vicious +endless +wavy +louder +musty +no +distant +savage +devout +feisty +rogue +uneven +excess +main +crimson +illicit +normal +faux +quick +trite +evil +guilty +kosher +beloved +wooden +indigo +gentle +raunchy +subtle +finer +stained +wanted +informal +cute +cedar +returned +square +shady +prissy +bronze +meaty +taller +vegan +flying +cloudy +vague +snazzy +twisty +primary +timid +liquid +left +nifty +red +alright +smoky +western +okay +fine +armored +rinsing +denim +glib +routine +silver +harsh +near +useful +preachy +tedious +arctic +bluish +primal +medical +sore +buff +patchy +chief +swell +adept +ideal +yummy +gutsy +key +mocha +harmful +loyal +oblong +dense +violent +great +lousy +emerald +large +magnum +dancing +chewy +ashamed +teal +secular +curly +fertile +furtive +some +ruined +spry +pliable +beige +bony +frantic +wary +bawdy +muscled +past +jumpy +legit +glossy +fishy +corny +small +crying +beefy +pompous +tough +other +proper +nimble +vital +foolish +dainty +rough +herbal +brainy +afraid +frilly +hectic +frigid +bogus +skilled +tasty +private +slobbery +plump +shaved +fatty +initial +zippy +oldest +bad +nasty +lame +careless +flaxen +moonlit +spare +candied +crafty +hollow +eager +pointy +caustic +wronged +dinky +manmade +niche +fair +varied +melting +blazing +crass +renewed +waxy +bald +obese +big +passive +slimy +iced +ultimate +hairy +dyed +elusive +sunken +emphatic +yeasty +overt +frugal +ditzy +remote +clumsy +diabetic +ladylike +swampy +aging +fur +lined +bossy +dorky +mobile +crushed +slate +immense +easiest +pointed +rented +psycho +minty +expert +new +maroon +elfish +zany +drafty +ceramic +felt +same +hideous +marine +elastic +oozy +novel +hasty +weary +stuffy +capable +inert +default +central +sweaty +sloped +smoked +creepy +vexed +bionic +regal +cranky +steep +open +floral +movable +varsity +docile +basic +coping +meek +loose +fried +plush +fuzzy +another +creaky +white +tubular +angular +edgy +visible +curvy +neutral +low +woozy +edible +dill +yucky +camo +hopeless +polite +smoggy +wacky +crude +imposing +west +shaky +rebel +soft +mythic +sheer +flat +aft +wriggly +citric +noble +crazy +blurry +insane +spooky +touchy +unique +bare +funny +sincere +eldest +unusual +granite +prime +sooty +engaged +awful +tangible +manual +weaker +lukewarm +junky +amber +gothic +light +steamy +scabby +unkind +empty +porous +subdued +frizzy +yellow +tall +foreign +anemic +lean +cuddly +wrong +upbeat +greedy +stout +dotted +alive +profound +frozen +lavish +wax +onyx +milky +veteran +thin +classic +gruesome +personal +taxable +lasting +random +valiant +pulpy +stiff +dirty +retired +secret +lacy +online +bloody +royal +wild +found +nervous +viable +dusty +peachy +sudsy +moody +askew +sad +little +kissable +convex +grubby +broad +employed +glass +muscular +avian +direct +goofy +absent +boiling +loving +gaudy +micro +muggy +happy +nearest +strange +growing +ashy +brisk +sporty +blunt +stoic +high +mundane +newborn +longer +needy +feral +plain +front +mature +lucid +washed +husky +eerie +unseen +weepy +strict +real +hyper +orange +raw +stormy +foxy +latex +dang +tired +fragile +tactful +active +squishy +wealthy +portly +magical +curved +fasting +worn +silent +wiry +replica +tame +backlit +fragrant +nice +adapted +wet +first +whole +unarmed +suitable +sober +green +oiled +benign +only +erratic +lonely +balsa +poor +damp +spiffy +groggy +losing +black +vast +rusted +leaky +blocky +futile +decaf +prompt +ironic +sulky +giant +folded +tender +mean +powered +tacky +single +hot +moist +lush +slim +ripe +messy +aloof +blotchy +shaggy +billowy +boring +demented +usual +bloated +cheap +medium +secure +subpar +holy +trusty +steel +gory +solid +starchy +worst +better +nosy +tepid +irate +wispy +bored +sharper +damn +epic +dreary +neat +dank +crooked +urgent +stupid +mousy +nude +filthy +usable +smutty +burly +kooky +tangled +rusty +illegal +lethal +optimal +sudden +marble +mint +padded +closed +onerous +lit +uneaten +bushy +bootleg +robotic +scary +petite +late +bent +kind +radiant +ivory +alpine +joyous +batty +copper +ample +armed +extra +wise +vintage +butch +sharp +tied +glum +sticky +free +pure +stinky +super +humid +dark +grimy +senior +sneaky +wide +detached +twitchy +brawny +his +hefty +tidy +ancient +thinner +badass +fancy +barbed +native +fresh +buggy +exposed +gummy +pudgy +chrome +deaf +busy +bamboo +amazing +dear +scenic +knit +fruity +young +demur +sugary +deep +fantasy +antique +brunette +vanilla +leather +undead +torn +extinct +vivid +serious +lost +amiable +sassy +poison +slushy +mossy +hardy +hard +favored +cheesy +bizarre +lead +sacred +married +custom +static +sensual +idle +pagan +bland +wrinkly +safest +odd +lustful +breezy +hybrid +natural +maimed +rocky +frosted +fond +salty +skinny +sage +blue +sizable +spiral +mammoth +crunchy +gifted +turbo +female +live +warming +dire +woody +massive +festive +lax +upright +tight +fat +sloppy +silly +shoddy +acrid +angry +gloomy +onshore +unfair +rapid +virgin +fluffy +frisky +eroded +best +warped +gold +steady +slow +swift +postwar +rich +brutal +feudal +whacky +partial +dreaded +common +any +elegant +isolated +thick +homeless +loud +playful +bright +disco +far +jubilant +woven +dizzy +joyful +stuck +weak +fake +dumb +costly +verdant +soapy +content +unjust +healthy +agile +superb +elated +catchy +proud +dreamy +hunky +mega +chunky +undated +rotund +rad +drippy +satin +exotic +drab +surly +slinky +whiny +early +fleshy +curled +marbled +mashed +deadly +brave +botched +pastel +rubber +naked +ebony +frayed +general +dental +groovy +dazzling +devious +elite +junior +last +tangy +huge +chic +bonus +eastern +upscale +shiny +gross +jaded +perky +average +lumpy +used +skiing +warmer +nuclear +upset +ex +awkward +kinder +watery +utopian +hissy +ornate +greasy +long +baroque +worthy +bovine +weird +chaotic +lazy +covert +bottom +modest +obvious +nomadic +public +patient +handy +posh +antsy +crucial +popular +current +creamy +caring +similar +sleek +saggy +extreme +vain +weedy +fizzy +mad +tense +comfy +uptight +vile +macho +glowing +lurid +durable +cozy +fabric +fit +male +plaid +boxy +finicky +fast +stern +napping +glad +fickle +dynamic +perfect +burnt +cool +windy +tardy +thrifty +defiant +crispy +bouncy +juicy +saffron +just +wool +crummy +her +oaky +soggy +naughty +flirty +tiny +supreme +tested +azure +soupy +coarse +wicked +thirsty +tart +tapping +modern +leafy +stony +singed +minor +painted +logical +former +deeper +sterile +tin +pleasant +firm +kindly +coveted +eternal +pushy +runny +austere +stocky +rigid +flashy +tricky +upstate +scruffy +testy +absurd +cold +lilac +pebbly +oval +creased +clever +viral +hidden +modular +sultry +mushy +recent +dried +chaste +bubbly +earthy +jealous +shabby +copied +iffy +official +core +alert +crusty +shy +jovial +safe +shifty +dusky +roomy +petty +grumpy +educated +sandy +numb +vibrant +armless +retro +starlit +sly +wood +aged +enraged +flawed +indoor +fixed +metal +fussy +muddy +witty +gray +bleak +honest +grand +full +my +jelly +shallow +simpler +cosmic +scarlet +musky +donated +heroic +naive +salted +painless +trained +inky +zesty +scarce +boiled +rookie +scented +measly +quirky +exempt +sweet +pesky +spotty +easy +sublime +elderly +faster +clean +daily +wired +bouncing +organic +gaunt +swanky +mouthy +tipsy +candid +scared +homely +arcane +weekly +rowdy +impure +stable +vapid +buttery +oily +pretty +unsafe +gnarly +limp +more diff --git a/backend/src/assets/nouns.txt b/backend/src/assets/nouns.txt new file mode 100644 index 000000000..9c23b82aa --- /dev/null +++ b/backend/src/assets/nouns.txt @@ -0,0 +1,7776 @@ +scowls +zest +regime +heist +films +bling +lent +hassle +collar +flock +mountain +river +caveman +row +spyglass +visions +jelly +cymbal +pedestrian +joke +nets +money +songbird +scuba +toddy +litter +ginger +gyms +paws +kits +human +mandolin +molt +lyric +clot +plural +racism +senator +museums +goose +rams +artwork +thongs +reveler +dweeb +scale +distress +looms +burial +native +trio +spirits +refuse +worrier +rave +mercy +prong +plethora +mojo +captives +edicts +creamer +grips +devices +hobo +dips +irony +dominion +kilo +manicure +sequel +locust +yew +pills +penalty +veggie +airbag +charge +lance +safe +regalia +kindling +gent +cloud +volley +democrats +marks +flutes +fracture +crepe +fork +jails +union +seltzer +bores +coats +motel +fairies +uncle +garage +expense +role +context +form +brush +puma +thief +snare +pixy +pageant +hedge +colonies +bishops +vessel +glimpses +peanuts +ordeal +cakes +miles +dogmas +slang +pranks +sibling +laxative +gnat +dip +fuel +cone +edit +imprint +musk +derby +crush +ticks +abstinence +statues +fidelity +rein +swans +poodle +cads +skunks +togas +gutters +comics +apology +lobbies +bundle +helpline +cook +hunter +idol +loader +blow +relative +feminism +sword +rugs +corner +gumball +sail +caps +rarity +putt +class +squads +tourist +mourners +goblets +scooter +shark +tuition +card +wallet +luge +alcohol +swing +studio +arson +player +convent +jogging +disc +hide +babes +bridges +switch +thunder +spearman +bonuses +daddy +sorcery +cookbook +net +selector +pelican +hubcap +umps +ticket +blizzard +escape +phonics +handball +dupe +ink +prongs +lamps +rocks +snap +inlay +hatbox +quakes +scalp +spare +canister +echo +award +pitcher +robots +fringe +dregs +wines +gust +joey +conduct +gyration +night +cab +bees +entries +bagel +ivory +squire +hairdo +energy +talent +agency +elder +bonfires +bacon +dues +plantation +joust +charter +occasion +icing +salad +walk +clamp +diaper +scull +hosts +spuds +stall +mines +wives +cortex +heroes +clasps +lane +paint +snobs +farms +sweep +tunnel +cups +bear +fangs +cruelty +moans +proof +swats +owners +wasabi +riots +cheese +photon +militia +gurus +sparks +chisel +borough +scream +effort +trifles +edge +metro +rats +mediums +novice +internet +missile +manhood +petal +spoilage +bread +oil +curry +sills +bit +gun +festival +scrooge +quality +escort +dub +dares +slot +bangs +rite +socket +candle +fairy +harpy +anthems +damages +hound +leaf +serf +splice +profit +fingers +gout +flaxseed +lust +feast +surprise +trolls +zodiacs +scoundrel +activists +deity +sloth +bottles +limo +hamlet +flash +comet +salon +garbage +entree +picnics +cursor +fabrics +kidney +dancers +pellet +scopes +gulp +doggy +porch +lute +junction +chill +ammo +letters +codes +snake +disease +sympathy +mishap +sprains +ripple +crusaders +baby +facility +courier +finale +writing +fragrance +fees +mocker +earwig +tantrum +hacking +flatware +vicinity +minks +pram +session +earful +shunt +grudge +straw +playtime +girth +straps +exhaust +pushover +can +bits +terror +metal +chariot +men +terms +trombone +bias +concepts +quake +purl +insults +tipper +manger +keyboard +pope +coeds +lobes +oafs +reader +views +orders +empress +activist +jungles +severity +brail +doctrine +tractor +rodeo +shelves +rod +toga +lips +bend +sums +darkrooms +grunge +bite +carving +gang +heel +rotors +couples +lanes +dissenter +groups +balconies +clods +monogram +beard +genres +backhoe +plums +alumni +schedule +taper +whim +riff +stories +peer +chop +statutes +posers +slacks +burro +urns +adversary +cage +blocks +figs +acts +well +light +reseller +weight +ratios +heathen +whisk +plan +babe +prose +puff +jokers +colonel +puppet +guise +retainer +subplot +rodent +underdog +diva +pugs +glut +fighter +aprons +dare +clones +gravy +sender +gate +muff +snowsuit +devourer +gallon +affair +nuke +oboe +mirrors +timothy +ricotta +spool +pear +locket +fact +village +laptop +mower +hair +revolver +brawls +ankle +dots +hour +grill +chin +recovery +result +helms +stags +starlet +fallacy +wisps +opiate +flowers +thrones +bout +farmland +contacts +trickery +footwear +cat +briefs +frill +link +pedal +flyover +path +stews +paradox +reactor +version +push +salts +quilt +output +pikes +affairs +conscript +lock +landfill +superman +apache +fraction +pupils +gloss +wafer +nurse +air +habitat +repeater +mars +deals +play +fob +mothball +chefs +victor +bingo +footsie +passcode +tragedy +botanists +tasks +friar +ramps +glory +stripe +wolves +muscle +penguin +upside +factoid +rubble +pancreas +festivals +triumph +refining +fence +threat +antlers +tint +giant +father +flirts +gear +syrup +pandas +hurler +theft +daze +haggler +adhesive +stretch +removal +harp +isle +gratuity +fink +families +sandbank +tapioca +onus +boats +laces +incense +sizzle +rain +pith +winter +flakes +glee +smashup +tides +poles +skeet +petals +reward +pothole +brig +renderer +fritter +thistle +gasoline +lynx +jury +spout +hoax +roars +stain +razor +power +bio +moments +creel +spud +tears +luster +uptake +domains +balloon +subs +pueblo +moan +retiree +ring +wing +symptom +ceiling +warranty +strolls +grimace +trilogy +tool +images +disco +ump +nirvana +bet +phantom +hags +axle +serving +plow +reach +gym +pelt +caves +duet +pouch +homage +senate +roger +solvent +sardine +boot +elective +hulks +shelter +gophers +resource +baboon +flooding +trend +album +beetle +spindle +coma +flora +scripts +oils +scar +shimmy +torso +population +sandworm +malt +honeybee +pauper +pears +letdown +tavern +squall +crane +nooks +lake +shelving +gulls +naming +cameras +mesa +character +strobe +number +gore +indexes +heat +cougar +placard +tribute +murmurs +junta +cake +ad +reason +diapers +jellies +squabble +contours +rogue +stereo +twerp +slog +swag +duchess +waif +error +customs +curb +crusts +wounds +gardens +wrecker +volcano +bandanna +feasts +punishment +needs +sedation +dance +hinge +handle +advisor +bargain +flipper +car +toolbox +stub +barista +lair +almond +panda +courses +cokes +playlist +domino +pauses +lox +gaskets +discovery +welds +fervor +crypts +vote +ships +fame +volume +audio +fear +browser +hail +streams +nod +punch +novices +breeches +shrieks +dribble +brakes +progeny +softy +baseball +march +hardhat +rims +meadow +falls +word +gusts +dude +pip +marmot +magic +velocity +mitt +outage +handgun +relic +chapter +steaks +pawns +upheaval +towns +famine +crashes +chai +squiggle +headlock +quiver +clasp +oath +wiz +malts +vans +gambling +barbarian +decibels +handset +naturist +florist +larks +barges +bonds +bomber +bowls +robotics +enemy +ports +steak +polls +randy +omelet +solids +lodges +pixel +loads +oyster +crimps +theist +wails +heroics +genre +herb +pork +reproach +bevel +seashore +rematch +lama +adage +tidbit +innovation +home +twang +breast +vagrant +pagan +nudge +hoops +ribbon +ancestry +hacker +disgrace +jot +olive +trends +lead +seas +fishhook +sale +costume +sill +tong +nosh +monument +wings +market +sternum +freeware +roster +luncheon +bliss +fire +cube +pizzas +ears +vegan +belch +brie +pruning +carts +wad +beavers +hardener +axles +totem +chain +try +vice +ravine +goblet +bid +spelt +wit +ghost +chowder +cries +glimpse +mystery +curbs +brake +faults +dealers +turkey +sanctuary +eyelid +tanks +members +mailbox +yelps +forge +firs +sight +senators +tenant +province +debts +edges +leg +mailroom +breed +mildew +treaty +jays +puffin +duplex +herring +cardinal +mole +promoter +woodland +meatball +shower +yeti +disk +cartoons +byte +peaches +mirth +ode +gift +gallery +cot +ointment +jogs +bran +vicar +fats +lids +squeak +band +tick +oracle +hats +van +turmoil +chums +frown +cysts +dumpling +vendors +sorority +junkies +panic +moods +amputee +vitality +deaths +myths +dusk +slave +wasps +eggplant +equation +issues +tummy +focus +gland +hopes +audience +suction +luck +mummy +smirk +oaf +splendor +tutu +pendant +estrogen +dummy +rung +pant +makeup +chemist +journey +boxer +hood +stripes +marbling +earphone +theories +walnuts +eggshell +loop +vents +platypus +bob +rider +encore +operas +doodle +ferry +shim +apricot +prints +stoops +estimate +snort +rakes +grains +outpour +petunia +smack +rubber +force +concept +brisket +atheist +quota +bundles +chow +furniture +passes +mast +calcium +chip +jalapeno +empire +grout +stools +feet +gosling +rituals +cadets +extent +roll +keys +frames +smog +violin +cumin +studs +floor +lockers +recon +city +iguana +tours +thong +daughter +alpaca +crop +jammer +plumbing +blast +cottages +limeade +lizard +leash +soaps +scraps +networks +mane +grandson +detox +cow +dock +admiral +limit +jock +archery +dollars +colors +swimwear +tarps +risk +bolt +inbox +bee +glades +beaks +tablet +foes +ransom +deacon +tour +delay +impacts +paste +mountains +dragon +wrist +stench +grills +toggle +noise +tripods +wish +chef +thinker +chunks +trash +spell +safety +credential +flake +donation +bonnets +designer +egret +scratch +worker +outdoors +suns +jetty +llama +mimosa +flypaper +patina +trunk +tabasco +distance +blimp +ridge +prophecy +yip +scan +actresses +boys +digit +suitcase +cranes +sixties +toucan +landlady +basket +kiln +zombies +grandfather +rye +lawns +kitty +slipper +robes +hands +flair +drain +pansy +archive +dikes +mascara +hooves +crow +footers +cult +impulse +ingot +pottery +desk +bong +rhino +clarinets +fires +vows +bubble +manhole +cut +ski +chick +produce +belly +chainsaw +eel +glider +copies +dugout +total +panther +hurdle +scarcity +decree +kinsman +goldfish +peg +expo +cog +bin +cooks +wills +ego +delta +ounce +militant +chemists +voters +pig +webs +niece +cape +clinics +nip +prisoner +yoke +traumas +pillow +matter +microphone +loans +tripod +reject +slush +crawlers +fluids +protector +braid +climate +preset +wail +staple +reels +node +taunt +jurist +galley +mirror +artifact +newton +diamonds +skirmish +digs +zone +gash +urgency +vices +oaths +flood +purses +buffer +ladle +aces +lava +clove +piano +caption +tedium +janitors +shed +almanac +postbox +meerkat +sandpit +plate +grenade +cello +hose +colas +hit +office +mooch +squatter +pistol +rake +cyclists +hazards +anteater +job +peck +medal +pianist +bozo +jaw +pad +temple +stooge +stop +snipers +purist +respect +sombrero +cops +trimmer +reed +krypton +objective +overcoat +balcony +tribes +humps +ruse +womb +venture +ignition +rehab +cramp +gloves +anthills +lawyer +gatherer +bird +refinery +crafter +tic +ponies +mall +magician +trickle +escapade +falcons +drops +galleria +daggers +sable +tacks +podium +pinch +crusader +spoke +addict +lilac +bulges +screwdriver +panty +unifier +horse +chord +snafu +pumice +yak +shotgun +teacup +workers +bases +sear +llamas +rolls +liquor +cranks +splices +kinship +surgery +coaches +scrap +brim +yam +ledge +poetry +country +sodas +speeds +sensors +script +ovary +curling +opacity +rules +rum +nutcase +groans +stoppage +ankles +oak +culprit +pucker +earl +bunker +divot +kazoo +balls +rockfish +roof +healer +infidel +burns +mechanism +example +dander +grief +tuxedo +claim +undead +felt +steed +tonic +casket +sari +fan +peace +fantasy +kerchief +justice +crags +engine +cardigan +pattern +lentil +duration +motive +figure +retread +mortuary +pooch +plumber +coast +pennant +loon +works +medicine +types +notices +nanny +algae +stamina +carbs +chum +feedback +octaves +buttons +crock +mayo +anatomy +sinners +desires +nunnery +hoe +linguini +plaza +teabag +marlin +tools +parody +doubts +decline +rears +defect +hurdles +aquarium +tamale +monitor +runway +yells +rewards +view +sandals +sewer +ovaries +detours +dam +auction +trances +porthole +inactivity +lavender +lens +shows +thigh +peon +poison +misinformation +filth +zeros +warrior +lilt +prefix +bulge +charcoal +sleds +moat +arena +prods +quarters +file +preacher +riptide +kettles +resort +fuses +couple +sting +daffodil +coworker +bells +pickle +hardware +crouton +lore +blitz +debate +elitism +armpit +omens +neuron +accident +delirium +scab +roach +mazes +pushpin +sprite +shore +slurp +souls +voter +heights +stroller +probes +basins +elves +ending +defeats +pension +forces +strip +ruts +quarks +auto +bruises +subset +dialog +passerby +altar +exporter +skid +golf +leeches +futons +crown +splinter +cavalry +hay +blooper +prisms +opponent +chirp +prawns +maze +lands +husbands +camps +glasses +camera +dress +fez +zap +privacy +ping +showcase +kale +cent +beets +preamble +urn +granny +cache +raises +hulk +fields +griddle +gerbil +rumor +waves +tenor +wreck +deputies +magazine +parkas +flattery +ox +chests +blanket +alehouse +ravines +princess +necks +women +raisins +advice +swarms +samba +daytime +renewal +jolts +veto +shorts +promos +emperor +intro +belt +scribe +basil +avatar +lamb +agencies +bog +wasp +grinch +sax +ward +quadrant +tomahawk +sip +string +knocks +cover +destiny +hydrant +bologna +cobras +dorks +stallion +hangover +molds +sheep +scum +lairs +maize +thrower +boutique +nicotine +goal +cigars +oracles +castle +boa +ladders +corn +brows +sum +viaduct +apostle +observer +slugs +groin +grip +purifier +neck +reentry +nebula +wombs +tech +cartons +party +lunacy +earmark +smoke +haven +bone +comments +disposal +chimp +minx +slab +bulls +neutron +purse +order +setting +threats +skiff +monotype +psychic +zeal +longbow +computer +surplus +drafts +filters +maverick +lion +atlas +pump +clique +register +scrubber +measles +tests +trusts +jungle +sniper +rugby +salvage +composers +capsules +part +look +theism +bug +oblivion +splash +owl +employer +flasks +skewers +clogs +support +pegs +tee +urge +sis +hive +rifle +lung +week +fuzz +badge +mowers +grubs +genie +provider +antacids +colts +garnet +taxis +comets +tarp +wok +comma +waiter +gourd +clues +clients +yowls +bookmark +cask +crease +cuddle +forks +torch +wedding +magpie +librarian +sheets +toad +crimes +demon +thrush +bean +area +falcon +crevice +eras +handwork +atrium +odors +shrapnel +soup +girdle +stat +rate +cash +ding +mutation +bins +pout +gangway +violence +want +casts +caviar +tarmac +substance +steam +brute +whiskey +gasket +vitamin +helmets +flub +felons +tinkle +appetite +liquid +lashes +forms +liar +remnant +cider +tires +spools +section +accusation +curls +dome +tether +throne +virtues +tomes +agenda +foxes +prison +pamphlet +biped +cinder +turtle +clicker +foxhole +fusion +brunch +amulet +suspect +charger +yurts +pie +movers +events +oboes +cicada +hotel +galaxies +peat +muss +criteria +dish +boxes +revival +varsity +fridge +cougars +bind +armful +science +canteen +gizzard +crutch +ethanol +handoff +gallons +sprawl +exits +hilt +crystals +cosmos +logs +corral +parka +researcher +cousin +nephew +voice +escapist +earring +does +taboos +trail +forgery +snowplow +soap +memory +fobs +players +self +attic +rump +earwax +puritan +jocks +scorpion +duckling +starch +heaters +glob +crosses +scare +leader +youth +hearts +jailers +tropics +digits +kitchen +hives +kook +molar +custard +spleen +pushup +paddle +carafe +dike +tarts +dowry +insult +paprika +doom +sides +attorney +sips +climb +kitchens +battle +banners +bank +lien +crack +notes +baggage +whisper +tactics +identity +polygon +cord +leaps +slabs +record +logos +names +swamp +brims +tub +slug +overtone +vow +hickory +epiphany +equinox +giants +meme +dye +bags +joint +salons +plows +bill +satire +tiaras +gypsy +face +nouns +mists +spin +iris +siding +pasture +beaver +clothing +contest +jarl +roundup +demeanor +icons +twigs +post +jitters +speller +sanctity +gale +custody +flings +prams +porridge +steps +grids +rook +looter +pastime +reams +curtsy +bra +tease +flow +birth +skits +pagers +pebbles +walnut +check +mobster +yodel +jars +visit +rubbers +elevator +contract +candy +thunderbolt +swig +savior +railcar +senders +edibles +bead +thimble +readers +motion +sulfide +kelp +rebels +stains +pits +visas +dweller +pedigree +jokes +outpost +thugs +serpents +spares +sprites +skull +brigade +leotard +scrabble +experts +humor +freebie +ladybug +commerce +dismay +seat +document +acting +flags +cough +hip +crust +leap +cornhusk +coupons +timber +sonata +punks +vet +offer +wrester +servers +garland +twine +unit +messes +favors +salary +niche +singer +blogs +purr +squash +journal +freak +papa +pasta +ceremony +mauls +model +hanger +scam +gulps +stanza +dagger +sensor +plunder +bikes +agate +frock +curves +corgi +creek +cufflink +toast +nations +dads +thrills +kitten +swath +tombs +pecks +crick +beam +quicksand +washer +cues +deeds +grunts +lettuce +firms +locks +compounds +cycling +flukes +consoles +abdomens +aliment +platform +squeal +beast +loves +guild +gelatin +slates +fig +rice +stalks +loan +squirt +hangar +cruises +jowls +knack +club +decency +linens +periods +criminal +harmony +threads +creed +fists +board +minefield +sulfur +stew +wetsuit +recliner +yield +alerts +cobs +drizzle +group +runs +words +jaguars +magazines +bride +realm +stair +losses +water +torque +synopsis +springs +skirt +divinity +ore +seeds +tree +toss +peel +skinhead +fish +envelopes +cans +paw +studies +deskwork +anise +starfish +problem +expedition +introvert +spots +honor +lodge +shots +space +boy +bellies +honey +friends +reflux +kink +tapestry +gems +span +growls +cereal +show +irritant +packs +rental +veils +postcard +roads +infantry +bongo +shacks +efforts +imps +locale +roadway +chat +label +legwork +mound +legroom +rotor +times +immunity +roast +wells +dinners +banjos +twitch +vacation +stick +moonwalk +passport +smudges +venues +chap +synergy +morals +ride +tutors +tar +bandage +morale +cosigner +boars +craving +delicacy +cables +branch +radar +sublevel +planet +dirk +grade +bag +serve +remedies +kayaks +thrust +rebates +spas +beauty +skies +abs +heroism +creeks +arsonist +arrival +swimsuit +cousins +viewer +pads +labs +launch +purge +twister +fiddles +pile +moocher +south +tribe +junk +verbs +spotter +villages +name +shock +breasts +cob +dune +bans +fronds +worm +anchors +deceit +bleach +moderator +arch +axes +coasts +dictator +tin +jersey +kinfolk +dayroom +outbreak +felon +cycle +soloist +mosaic +ends +mushroom +runner +pepper +boots +headwind +bicep +mister +berms +outtakes +ladles +stalk +dams +ram +briar +burp +belts +mafia +noose +brooms +frogs +servant +tail +matchbox +chart +worms +gut +shill +opal +gazebo +watch +snout +step +doubling +jarhead +dreams +jargon +biscuit +critic +zebras +forges +chemical +angel +cuts +links +razors +anteaters +mints +procurer +fans +champions +hub +shank +angler +taxation +rebirth +creeps +grocer +wax +nerds +fake +man +conflicts +teen +thermos +oceans +beach +brink +presto +barley +vodka +history +bore +laugh +dill +slobs +cockpit +donor +shin +hexagons +bluff +mug +granola +hermits +crusade +norm +game +escargot +sharpie +powers +turner +echoes +cleavage +lid +ribs +corsage +latitude +upstairs +manes +tricycle +daisy +sofas +reviver +leak +storage +impulses +plays +bud +department +pretext +mat +rep +pact +fryer +hammock +bull +jests +way +spokes +titbit +dwelling +crab +cobra +hobbies +puns +spur +docks +rudder +jog +twins +historic +trench +managers +camels +fiddle +noises +cocoon +zeppelin +sulfite +omnivore +jukebox +rockets +fluke +newt +cinch +scanner +siesta +blur +parcels +clam +farmers +blemishes +wage +daybed +brawn +tacking +parmesan +hut +mugs +tines +chains +decoy +scope +hue +quark +samples +tables +prune +judo +lesson +fajita +schools +school +blade +antler +tip +ship +thud +karma +sushi +turban +pebble +flanks +qualm +advocate +ghoul +void +artisan +pea +villain +hull +lasers +writer +pastrami +sugar +diet +equipment +sprout +snore +room +columns +septum +stuffing +grit +earth +ninjas +passage +tot +headsman +recap +duels +subsidy +particle +renegade +doorknob +rim +lyrics +combo +modes +wheel +place +dimple +bubbles +ball +blame +tweak +vandal +portion +mules +visors +dash +huntsman +shuttle +bullet +sect +hounds +refill +season +assassin +spoof +jumpers +hobby +tidings +faction +pride +sabers +feed +sequence +din +act +jar +flux +audits +pupil +con +banister +hardship +speed +profile +jaguar +flare +populace +educator +spear +friction +fee +deceiver +ruses +grub +passenger +menace +toys +idols +dentists +overbite +monkey +fights +reply +drive +secretary +dragster +frolic +blood +macro +paramour +teacher +server +haunch +sizes +tangle +scales +refund +druids +rank +bankers +circuses +pushes +payday +foal +shackle +kilowatt +shroud +piglets +trough +squabs +sabotage +deluge +fungus +cluck +battery +impact +gondola +halls +pock +decks +climbs +barbs +marine +length +tug +treason +vibe +tram +riches +habit +carnivals +records +grants +master +boulder +badger +assistant +urology +condor +radio +kerosene +mood +prunes +whims +pacifier +veal +plugs +cursors +virus +birds +cola +notification +precinct +chaos +mayday +perks +morphine +tightwad +reefs +mutants +orchard +scams +bombers +knots +camp +compilation +dockyard +lawyers +sails +shrouds +couch +category +methods +nutshell +wart +array +drugs +bunk +shakes +phases +punk +teeth +flower +travels +relics +peter +mourner +toe +handclap +valuables +sheath +soybean +stud +trait +whisks +hunt +shopper +quintet +coke +talc +antidotes +tulip +cotton +jaybird +dandruff +sarcasm +hatchet +summer +freeway +valves +ark +snail +marmots +knight +antelope +winnings +protest +reeds +lottery +inch +sheen +pants +dot +throats +kites +toes +moss +minutes +curfew +coward +lobby +lumber +tone +papyrus +closet +vixen +salute +nibble +muffin +cue +tab +rides +cancer +waist +docs +gangrene +spore +stint +daisies +spark +strips +mates +gamer +user +orbits +excess +examples +bunch +shredder +math +snakes +subway +region +salsa +sufferer +hedgehog +displays +pity +shard +visa +bunkers +powwow +door +rivet +giblet +robe +team +hem +slips +crime +mascots +kayaker +pubs +rest +anaconda +levers +prey +dev +format +cellmate +rower +popes +fizz +enzyme +overlord +lakes +bidet +moths +prologue +commander +jeep +condo +sandwich +welts +epoxy +lotto +dudes +rot +twig +cross +cockatoo +dolt +blender +spam +buoy +start +nag +thumps +unease +hawks +sketch +enemies +strobes +halos +bond +rooky +earpiece +queen +aroma +cradles +twilight +togs +defenses +library +pews +armpits +sand +spite +smears +vehicle +segment +sandbar +flop +manual +geese +learner +lab +knives +lining +sites +squirts +referee +teepee +league +stir +road +bane +stray +tiling +crops +artist +whiz +sauce +sorts +blush +outcast +gander +traps +peaks +shortcut +cable +unions +prisons +margins +monarch +fall +pools +menus +port +marbles +martyr +barbers +lily +outing +cashews +chops +seam +rust +lager +styles +love +varnish +tide +lions +boil +vaults +booth +hackers +lime +hat +sinew +zombie +dogs +attics +ripples +laurel +judge +looters +curl +clerks +spire +dart +bumps +sultan +cynic +steer +stores +lady +ales +noun +fib +sacrifice +sumo +posse +doe +reboot +rotunda +anklet +airline +ale +fang +promo +handrail +landmass +tent +gloater +priority +rerun +trance +time +fly +lungs +greed +poppy +pans +nerve +eon +monoxide +range +dividers +rigging +clothes +storm +bung +decades +dibs +reflex +shrink +tones +pong +hulls +kick +gateway +gallows +newspaper +reptile +deer +grandpa +eyelids +clicks +madam +badges +mob +shield +brawl +livers +jazz +flotilla +turtles +notch +goop +king +yarn +punctuation +chaplain +broom +larva +process +nylon +shifter +cacti +berries +polish +furnace +farmer +soy +factory +landlord +miss +fleet +snitch +soils +suds +reel +valley +ghouls +vitamins +ranges +walls +pencil +jest +booty +symbols +earplugs +contour +catnip +guide +tendon +daydream +vagrancy +lobster +knife +perjurer +movie +dividend +bakers +eggroll +drums +fawn +shawl +sidewalk +shawls +captions +crepes +splatter +cottage +cadet +photo +tory +strength +spectacle +gymnast +mange +ties +tiger +vines +shout +buns +reformer +icon +book +cords +shovel +awls +zebra +chair +vets +prawn +stance +permit +clown +sow +folds +host +bonfire +twirls +stilts +suits +claps +dole +vector +ripcord +mask +hacks +tray +potion +kiosks +cynics +titanium +shadows +organ +grin +page +cell +source +footnote +mites +gunsmith +endings +fanatic +flamingo +refusal +adherent +visitor +tap +yokes +tendency +consumer +robin +competitor +survivor +government +ace +base +silkworm +spike +synopses +mama +clip +stay +eve +jawline +scouts +detour +guns +credit +creels +chaps +month +appendix +stilt +nobody +sweats +craze +emerald +peacock +dragons +pointer +upgrade +emotion +scoops +line +morning +bedpans +feat +clay +blouse +bait +overflow +specimen +criticism +cocaine +jape +moth +victory +pomp +drill +diamond +talk +rings +tubes +fedora +episodes +state +skittles +theme +thrusts +rule +editors +embolism +coils +hemlock +omen +camisole +jackal +hornets +dynasty +kings +things +divorcee +dresses +hazard +arbor +pound +flute +countries +couriers +glass +spiders +overture +jaunt +shoal +thrift +elbow +replay +shrinks +matron +tomb +onlooker +anvils +repose +troops +garden +aisles +anxiety +roses +orcs +cud +knock +fail +bench +period +rodents +coconut +hazelnut +pug +stubble +poems +skater +winery +laser +girls +mahogany +distaste +flight +snags +stocks +awards +lever +function +picks +year +corks +shamrock +ditch +smells +landmine +jabs +beams +dolly +pokers +equity +delegates +brandy +crumbs +arborist +vertigo +pundit +scimitar +doves +encampment +curio +jeers +fathers +archives +duffel +glue +haiku +holes +watches +vision +buffet +kibble +petri +tans +coves +index +oppressor +pitch +cheer +taco +negotiator +packet +volumes +troupe +cur +throws +dogma +vise +railway +flights +search +scrutiny +paladin +circlet +virtue +interest +pot +animator +stature +trifle +rails +medium +aisle +vista +stamps +winners +stalls +dishes +ligament +bassoon +contests +squab +brat +statue +store +jigsaw +knobs +bagels +fuss +security +chairs +glimmer +aunt +demand +stunts +spores +baton +deed +life +spits +access +queens +pirate +vein +picnic +knapsack +recount +bids +stitch +study +melodies +smelt +sherry +senior +placemat +cruncher +mover +footer +watt +sailor +plots +patio +upswing +stimuli +vortex +ethics +bogs +pretense +feuds +macaw +click +vest +back +treats +eggnog +finger +orzo +kiosk +quills +cruise +deviancy +romp +planner +evidence +odor +coral +snowbird +blasts +yogis +pack +overseer +list +aviator +barn +centaur +cashew +roar +forum +growl +snorts +spaces +charms +module +perm +ravens +ads +hatchery +hoaxes +bro +mural +footing +winks +pursuit +teargas +blare +racing +inns +whisky +judges +hook +surface +tourism +jeans +lipstick +cluster +lover +nymph +importer +droplet +goddess +sneeze +uniform +answers +eclair +rags +swirl +weapon +hitch +seaweed +whales +ladder +gourds +phase +machete +seagull +corporal +machine +member +mulch +monsieur +fools +vowel +control +beak +people +shrew +elk +scarves +subfloor +flatfoot +remark +tracer +snag +bowling +plates +parent +clover +sources +crowd +unrest +clump +angles +embassy +joggers +pacifism +turbines +codex +mesh +silt +excuses +ranks +designs +filter +crabmeat +earlobe +rind +twinge +parabola +tails +verse +fossil +vibes +email +explorer +bow +buck +matcher +quarrel +caulk +quiz +payment +figurine +kicker +crate +outlet +hash +sludge +den +clink +pew +tactic +bricks +beards +joyride +radiator +nose +liter +snack +parades +oxymoron +aspirin +pit +eclairs +swarm +runt +hassles +snarl +acorn +mail +huts +knowledge +gala +champagne +defiance +screw +malware +metals +geology +drum +fences +tinsel +growth +turbans +feminist +landing +armrest +palms +addicts +proton +helper +brands +vanes +teamwork +horn +giggle +houses +laps +odes +piglet +essay +buckle +throng +erasure +bonbons +molars +swings +consumers +rise +barbecue +ancestor +vogue +supplier +moons +footwork +ground +overhang +harness +papers +serfs +liberty +markers +pretzel +memo +greeting +sweater +clod +win +jogger +war +molecule +vantage +hugs +spatula +joker +division +mint +sector +mind +welder +pancake +trader +kisser +hatred +toll +client +flip +raft +cogs +muskets +soda +driveway +veneer +hart +employment +axiom +barber +twist +loss +mead +faculty +ruble +paranoia +snowshoe +census +gloom +dodo +tank +plywood +creak +spoils +outs +bodies +motivation +pollen +lights +jug +taboo +smith +poncho +jobs +rancher +cast +fungi +awning +trick +soot +lawn +heron +spinster +charity +tact +foothill +oars +turbofan +piles +blinker +mist +padding +preface +limits +hamlets +oysters +enforcer +chocolates +sedan +yodels +wink +retrial +trot +reliance +archers +shortage +jeer +choice +winner +jerk +dens +birthday +meats +buses +weaver +article +chores +shirt +smash +dog +peril +author +town +nudes +concrete +fumble +cherry +cyclist +showman +harem +bribe +dances +yowl +agates +hybrid +ruler +dancer +nap +kids +rope +silk +silencer +cells +motives +lace +mumps +prompter +lagoons +newbie +cores +loaves +entity +valet +ploy +parakeet +weeds +monarchy +sheaths +scorer +nags +apron +squib +follicle +headlamp +puddle +bugs +mantra +slop +liars +fur +wielder +serves +yogi +clavicle +budget +monorail +will +sandbox +riddle +frog +masses +wand +creases +lances +motors +mule +matrix +bills +drapery +ravioli +bays +thought +bolo +hinges +brads +hug +debates +rubs +percent +sinks +cannon +tramps +shame +refunds +fudge +launcher +maturity +humus +gluten +stairs +drips +sausage +grime +wren +circus +density +outrage +rudders +guy +lemon +taxes +oxen +bands +license +guitars +till +trainer +doll +fair +orphans +parish +harps +sardines +parade +samurai +bass +overtime +antacid +fissure +cleft +marrow +gene +vulture +chest +pulp +graduate +nutmeg +mimic +systems +vase +wins +crayon +patience +bike +burgers +gem +prowess +crutches +hole +squeegee +parrot +founder +bulletin +igloo +anchovy +perk +eternity +tugboat +chive +trial +latte +rinses +doorbell +undertow +nerves +thrill +suntan +voodoo +deities +beaches +stork +scroll +symphony +lagoon +duvet +bikers +critter +bleep +exes +imbecile +folk +reins +koala +majesty +scowl +pixels +sole +museum +lapse +fastball +spires +planks +nukes +set +attire +dosage +waste +graphics +angels +counting +gardener +sap +fiend +parlor +prices +umpire +sage +grower +offices +jackets +calendars +tiers +eraser +outlook +zipper +mamba +care +expert +bucket +rips +tampons +cheeses +girdles +guff +ear +hobbit +latches +scuff +timer +carp +arcade +shop +pins +vole +puck +sculptor +jumble +barrier +sister +bladders +upstroke +task +price +harpist +lark +pyramid +tampon +truth +boils +savage +wares +purveyor +moon +fleets +flagman +tribunal +preplan +flea +strudel +tape +pair +bards +jokester +hours +bonbon +cleavers +spider +pun +case +polo +dales +roles +cannons +default +boasts +puzzles +seniors +headway +tarot +smell +grits +warden +vests +mischief +issue +shindig +prep +mic +orca +gender +stranger +poets +film +type +clack +abyss +jumps +segments +fate +senses +utensil +weeks +maid +marigold +molasses +research +chapters +cleric +table +caramel +outfit +knee +deck +denim +giraffe +daycare +sprig +czar +letter +caravan +junkyard +tribune +organs +knoll +duo +sermon +windshield +nodes +cribs +sycamore +paints +freewill +vises +sons +relays +bun +erosion +latch +sprints +hippie +rockstar +moped +subtype +wardrobe +leases +cart +lunchbox +calzone +hangars +courts +adult +kilobyte +bible +land +deviant +banshee +fern +story +death +angle +villa +outlaw +liver +aorta +cowls +assistance +beans +moose +invites +chemicals +mesas +palm +abdomen +sigh +volts +bed +canisters +boon +moonbeam +culprits +combs +everyone +flocks +gesture +footage +stingray +lamp +press +scarf +mammary +janitor +demands +robber +stove +tannery +caddie +pies +swords +snares +chives +grid +course +football +nails +talcum +loot +haste +dawn +heater +teapots +afterlife +glance +crates +demons +migrant +discounts +audiences +beepers +eardrum +lumberjack +spouts +whip +pounds +concerts +dunce +cavern +coins +jerky +mold +sweat +curtain +halo +lizards +popcorn +dispute +cup +wife +eclipse +ecology +elms +swab +calories +gifts +flounder +crayons +mount +tallow +canoe +voucher +phobias +ores +optics +bums +arks +sitter +glitter +blips +helm +diners +propane +teapot +warning +rampage +plastic +shrine +venom +turmeric +servo +epilepsy +landside +diagrams +estate +jacket +scallion +slip +mile +cracker +mayhem +talons +bistro +nights +sheet +earache +revision +washout +yacht +inputs +arms +carnage +regret +quotes +roughage +buyers +citation +trays +boards +ebook +velvet +soups +breach +butler +semester +bipod +herbs +skincare +clock +heft +carton +exorcist +firm +rabbit +mutts +dust +defense +essence +runts +broiler +target +jade +storms +pleasure +blades +wards +cubes +pickles +beggar +alarm +bedroom +delusion +yogurt +foothold +gopher +kilt +hint +tier +strand +eye +dowel +brokers +fanfare +luxury +hedges +alien +dorm +culture +chalk +army +bloke +tops +rooms +rubdown +jams +verdict +mine +spade +lotus +tax +freckles +apple +revolts +clutter +glop +desktop +pictures +rays +tickle +nerd +header +dose +boom +county +nemesis +tune +sects +lovers +navies +sleeve +style +patrol +antennae +pizza +comrades +brook +lobe +wildland +trainee +cricket +covers +scans +meter +skates +bales +whips +sorcerer +settler +tear +assembly +repairs +loaf +piers +pets +foot +finals +curtains +stinger +cowl +tack +maids +fruit +armor +trumpet +annoyance +age +plans +flame +ware +leaks +opera +sundae +lives +skins +homeowner +rumps +hiker +payphone +weekend +dean +date +licks +herds +nature +critics +canary +stashes +polka +stoop +streets +lords +holidays +width +pill +spine +pusher +fragment +layer +dynamite +liqueurs +root +thump +envelope +zoology +sirens +smocks +tramp +guys +awl +goals +serpent +pox +skills +chargers +olives +massager +clots +despair +throttle +gears +hunch +cowbird +biker +key +imp +gigabyte +rivers +trucker +knights +henchman +pipe +stipend +rival +tricks +hamper +clank +violins +lease +bronco +supermom +continent +blimps +tennis +shrub +hare +bonanza +jackals +eagles +hula +leads +traitor +doorman +colonist +aid +train +reporter +patriot +venue +traffic +isotope +activities +score +coach +wool +turnip +cologne +bros +headache +raps +suet +slit +nursery +dislike +defender +stash +payroll +spoons +cupid +doctors +muscles +obituary +binder +mutt +absentee +clan +spades +outback +crowds +uprising +trousers +places +winch +doors +handcuff +seed +barb +crouch +utility +mush +proxy +track +tooth +strains +dollar +actress +ozone +chump +smiles +autumn +burrow +baskets +fugitive +yeast +vale +shingle +hope +avocado +flap +aircraft +hornet +foods +facts +sprouts +forts +beer +clipart +atriums +coot +paths +driver +smuggler +dope +font +carnival +volt +cycles +glazing +news +revolt +star +gradient +cowards +headset +hits +release +moms +keno +quip +sunset +trees +copiers +cliques +tuft +sexes +iota +poser +traction +shallot +rhythm +housing +goggles +devils +daylight +squares +sesame +pager +wads +policy +deal +inks +juice +coaster +resin +deli +muskrat +flavor +cyst +startup +pacemaker +arcades +foam +trinity +wheels +wash +knickers +juicer +overhaul +claw +auctions +bar +berry +thicket +swill +applause +foe +rose +nicks +nieces +disks +linen +orbs +pouches +licorice +wildcard +mankind +ruins +nest +pecans +diffuser +poll +burger +streaks +moves +lies +sorceress +dwarf +trapdoor +unicorns +dump +sauces +pats +outsider +drunkard +sob +barge +sores +shrines +few +marker +cupcake +robins +totems +flam +eyebrow +replies +vineyard +revenge +rifling +elephant +purpose +dice +thorns +prank +quilts +mess +motorway +yawn +goldmine +platinum +colt +gown +banks +probe +dream +equator +skill +consultant +idiocy +onward +waffle +bobcats +griffon +greeter +margin +mage +celery +octane +mime +faces +bistros +phrases +palaces +ibex +tales +map +jetpack +actions +nests +baritones +swat +makeover +smut +run +students +wine +wonk +debtor +oxidant +sled +gauze +brunt +ashes +warlock +wigs +vampire +subtitle +gongs +pipeline +trouble +groan +swamps +weather +heirloom +valve +hero +fiber +stunner +lemur +cuffs +talisman +cures +cork +touch +jail +shift +phones +nachos +gravity +juggler +spinout +manhunt +spears +seats +choir +chamber +cultures +years +handyman +idealist +tassel +pilgrim +oar +android +mop +lift +height +rail +rouge +tux +pavement +prudes +pesos +animal +radios +moonrise +hump +riveter +uranium +okra +geek +decibel +turret +guitar +weasel +myth +boudoir +subtotal +borders +chore +tents +crew +bus +inn +files +ivy +grape +parties +grace +grounds +swipe +tutor +peanut +pages +decor +litters +waltz +desks +slaw +spat +question +crests +lot +inches +leverage +motor +friend +raccoon +jay +stencil +kin +crag +canals +voltage +thesis +help +keel +coconuts +spices +sheds +trip +knolls +reverend +gripe +refrain +logic +illness +mass +claims +strides +dunes +wands +flaw +reunion +topics +gizmo +shriek +island +denizen +gruel +overrun +donator +strike +junkie +rascal +homes +dials +entryway +curses +alley +robot +perms +ovum +spoon +remarks +crypt +oats +pint +repair +vapors +outhouse +lice +shape +joy +riot +beau +log +limes +bile +pest +visitors +saga +lapdog +chimps +cots +plants +login +poultry +moats +valium +composer +tigers +agent +shrimp +pegboard +raisin +saints +options +posts +exchange +tapes +asp +doorstep +shove +buffoon +ideology +sickle +protons +honks +split +throat +wisp +theory +drams +pores +commando +surname +website +husk +loft +tycoon +albums +bath +juvenile +kangaroo +stabs +shells +artery +areas +resident +seminar +silicon +analyzer +napkin +elf +fool +splints +walrus +digest +harems +wraith +legume +skewer +outcome +operator +carol +fondling +trapper +airport +moaner +concert +inchworm +quarry +clout +mutiny +peeks +handsaw +authors +fowl +wiper +hunk +break +slaps +tinker +traces +ramrod +jowl +capsule +bunny +sack +cabs +carrot +saber +silos +manager +spelling +spread +ferret +ramen +captain +shale +bang +god +duke +wrench +hogs +covenant +contents +synapse +calf +captive +nit +world +monks +bikinis +geeks +genitals +printer +poker +mayor +nuptials +tube +thorn +spinner +marinas +dew +mice +comment +stock +lethargy +screws +tunes +hikes +lunch +lard +cereals +flan +rasp +mockup +wiretap +yearling +gimmick +spirit +bicycle +huddle +chipmunk +astrology +mongrel +effect +charisma +democrat +top +heaps +thug +cliff +castles +wages +holiday +aqueduct +athlete +beagle +coroner +arrays +element +laptops +plaster +kegs +teat +sleet +orcas +embargo +horns +stems +emporium +cloth +mammals +objects +fetish +rescuer +commute +calipers +animals +canyon +hops +broker +sham +bale +panorama +drinks +rocket +sweets +cars +lag +hunks +ritual +cure +fumes +jinx +bonnet +knob +taproot +cows +headgear +rebuttal +squid +mutant +pail +ogres +welt +bulbs +brushes +ploys +nacho +risks +plenty +gas +cabins +photons +scones +symbol +tyrants +degrees +rifts +acrobats +halogen +wharf +goats +stylus +bushes +trampoline +shave +lentils +quid +sticks +scalps +suburb +dentist +strainer +macaroni +occupier +proverb +hoods +hotels +specks +virgins +shaft +column +jive +aunts +rods +egos +quail +console +mum +jumper +slogan +movies +dent +sapling +brochure +quest +tenure +bakeries +era +stucco +poet +trucks +opposite +biology +champ +turns +anthem +expenses +diameter +labor +cranium +baking +hymn +hyena +tubas +bets +plat +twitter +casinos +salads +owner +chimes +brew +pang +congress +ropes +pleat +implant +druid +counties +snips +teriyaki +crooks +bottle +playroom +decal +neighbor +kidnaper +stardom +orange +herd +shrubs +craft +orgies +arrow +family +meteors +method +perfume +egotism +steroid +flaps +request +rush +starter +refresh +murmur +discount +chicken +murals +tots +cherub +dropout +thrall +pups +router +block +employee +dial +dodos +insect +color +strokes +monk +brood +comic +gowns +unloader +hell +balms +token +narrator +armband +starship +islands +voyage +libraries +ginseng +muses +termite +remover +cartoon +ribcage +credits +ingots +labels +lie +oregano +teens +darkroom +byway +jailer +stream +poise +pentagon +pipes +sanctum +vats +nugget +nativity +reburial +loom +hybrids +fins +crumpet +troughs +blossom +blog +item +language +breath +ash +campers +gavel +bumpers +skipper +corners +dram +fin +grating +guts +clouds +panes +garb +gains +vagabond +plume +critters +kite +gauntlet +counter +drawl +weirdo +lounge +snow +app +townhome +bagpipes +cats +admirer +guru +tart +cuff +kimono +elastic +anglers +thespian +trout +guzzler +generator +vocalist +agents +smithy +zip +text +foyer +peak +rusk +theology +lapels +nectar +mobs +query +blaze +avatars +vial +paycheck +property +food +romance +weld +usher +stacks +raider +bail +conduit +penknife +archer +bark +headroom +delegate +slant +slits +berm +swim +pillbox +plea +kilts +beat +vat +infant +shrug +alarms +formula +riddance +eggs +sense +oligarch +barrels +skeleton +booze +grange +evasion +galaxy +gaff +ranches +espresso +mulberry +wants +bluffs +vacancy +fleas +rocker +slacker +camel +yell +worry +farmhouse +meshes +odds +tongs +urchin +punt +bomb +noodles +inside +lecture +nudity +planes +squat +goods +gumdrop +jasmine +oranges +parsnip +drug +bites +awnings +swagger +snouts +supper +lemurs +pines +charts +prize +footlocker +jab +sprigs +grain +eater +denture +bison +details +climates +ladies +arenas +channel +truce +mustard +crumb +nomad +reaction +rift +untruth +coin +damage +gumbo +faith +fortress +lemons +roots +whey +streak +glaucoma +kiwi +mimes +nudist +broth +scalpel +oases +slats +mutes +troop +slicer +obscenity +twit +stoves +tingle +suitor +sundress +marble +cactus +wiring +tad +noel +hairs +stamp +needle +survival +endnote +funnel +organism +spans +meeting +test +clog +adults +sash +economy +screens +occupant +seals +juniper +omission +limbs +hashes +cement +royalty +marathon +goblin +spruce +tags +fife +scorn +globes +heir +gnats +octagon +redo +miracle +trident +bust +laborer +cove +flannels +stage +scallop +cigar +stroll +creator +facelift +photos +shoe +nation +grader +trauma +grins +watermelon +trustee +freight +genetics +sinkers +widow +chicks +tote +granddad +shorty +antenna +farce +treasury +coca +kimonos +hermit +wind +prayer +heads +rut +strays +battles +hoody +lodging +mansion +password +demotion +monster +pelvis +suite +python +filler +trillion +rioter +glucose +race +goblins +scars +wonder +epidural +exam +leashes +pacifist +relish +parcel +setback +ducts +producer +staff +garter +gamble +norms +goof +title +cards +bows +bloat +workload +slider +secrecy +park +demise +church +rear +exec +mayors +muzzle +tomcat +teaspoon +calamity +onion +costs +medics +crevices +pane +oops +snaps +rigor +beasts +graphs +states +remix +cornea +wafers +goatee +doc +services +legumes +apes +bandits +sentry +quips +coating +cabana +spring +haul +seafood +muck +saves +anthrax +globe +treasure +cafe +domain +clerk +climber +pixie +prodigy +caddy +apostles +mumbling +leech +implants +princes +tacos +conflict +whale +minimum +boas +mandate +shack +goat +frame +halves +barriers +opus +sty +dates +finance +embers +code +diagram +poem +harbor +playoff +mermaid +desire +nocks +mounds +ushers +poplar +sport +rumors +aphid +husks +animation +riddles +wanted +deadbolt +tiara +bowl +dyslexia +basin +brats +dazzler +tunic +preview +guest +utopia +side +trunks +duel +rates +bay +tempo +swigs +cubicles +taps +branches +zinc +rock +detail +snooper +temper +stem +drainer +speech +hills +fort +spender +negligee +lass +seer +exiles +lyre +tome +vanity +meters +toffee +ogre +errors +sports +copier +scholar +joules +material +hotdog +widows +tale +wizard +duds +crayfish +humanist +swine +pore +dimples +chant +video +spectrum +pots +bunches +keep +action +maker +ice +stone +giggles +wealth +brow +hoop +son +tentacle +loons +spins +nemo +quarts +poster +recipe +hemp +exponent +platter +stump +lures +safes +zones +collars +coots +crabs +fetus +hen +varmint +botanist +meat +brains +pirates +wig +use +glands +acorns +strap +tweezers +baseballs +mommy +agendas +sass +replica +pests +tilde +milk +sunlight +crunch +vastness +relapse +jackpot +fund +call +nut +cargo +stylist +status +checks +scout +blister +lanterns +typist +object +sponge +children +truffle +topic +gecko +epilogue +lump +flaws +divots +funds +mixture +impurity +attendees +plot +slurs +excerpt +cults +showroom +yoyo +hyphens +showoff +acids +roost +slide +doorstop +antidote +skin +fury +qualms +gunk +nemeses +fluid +cowboy +corridor +twists +goo +laundry +swirls +glutton +binders +brain +savages +viola +stumps +sub +pagoda +microbe +sneer +morsel +drainage +seal +argument +butcher +seams +avenues +mixes +lure +pliers +arsenal +disputes +ape +drunks +tailor +deployment +railing +tinfoil +diner +voices +pedicure +feud +mascot +brothers +cap +hunger +distrust +ferocity +parsley +machines +mark +spinach +cod +nucleus +work +message +quart +art +racks +safari +osmosis +geranium +sprain +grams +toxin +coal +bridge +might +taxi +steroids +frond +maul +peas +variety +poses +vine +drip +clumps +rivulet +hints +update +choices +creation +creature +earwigs +melon +panel +troll +waters +bouts +prop +outfield +hacksaw +irons +sailors +colander +cub +sandal +fad +skis +peppers +sobs +calls +banner +bongs +armories +cents +favor +capitol +share +liftoff +pesto +earmuff +canon +shampoo +casualty +apostate +tissues +strain +miner +futon +casino +unicorn +upstart +spy +showdown +gap +marshes +flak +crest +iron +shot +optimist +pajamas +feather +knuckles +renter +raptor +revenue +nana +service +pocket +spook +pantry +boundary +putty +spree +armoire +moisture +donkey +skunk +rant +dinner +poacher +cupcakes +sponsor +exercise +darts +teas +quests +mace +bribes +defeat +squeeze +doorpost +predator +combat +barns +cabbage +benches +herald +blizzards +prism +mill +obstacle +grass +answer +hoes +siren +treat +traverse +pockets +vane +snorkel +elites +lack +crud +fishbowl +mollusk +screen +syndrome +diary +grades +nick +forest +germs +helmet +tweets +sulfate +feline +ban +jet +spikes +draft +cavity +clowns +buckets +guidance +navy +amulets +hubs +corridors +setup +database +getaway +folks +expanse +grant +detergent +zoo +pick +months +miners +morality +truck +mote +raise +lullaby +slime +giveaway +heists +breeze +toy +trace +goofball +gait +phone +fray +marches +jam +site +knees +find +sales +values +painter +overlap +bard +roasts +strings +odometer +tiles +candles +stars +badgers +series +duck +huff +axe +git +quill +harbors +eagle +garters +inlet +cleaver +coder +fraud +pearl +sandbag +fountain +guides +blip +demo +jacks +retreat +stripper +depot +vials +mite +device +flashes +cilantro +canopy +cherubs +hasp +curds +tomboy +pilot +phoenix +mouths +hologram +lecturer +citizen +student +cloaks +factor +opossum +clap +sign +anvil +honk +verb +signal +bruin +clef +queue +bats +screams +hall +vapor +marina +stimulus +jesters +rover +brewery +otters +flask +wire +smasher +dame +joints +muse +palace +steeple +enquirer +fiat +maggot +outburst +plateau +dowels +stands +titans +wheat +taste +uses +audit +iodine +garment +ducks +linseed +latrine +groove +confetti +rebate +stones +smile +yard +gristle +corncob +whoop +veteran +duty +tomato +patient +manors +reversal +graph +latex +hamster +optic +crews +lye +ponds +input +germ +boardroom +swinger +unicycle +jerks +point +fight +brick +dory +convents +shirts +debut +finch +peels +zoos +feeling +prince +pods +stanzas +vocation +polenta +updates +lap +weed +swaps +tarn +elm +hexagram +chips +otter +value +femur +canal +bib +breeds +heap +ramp +veil +pan +rubies +havens +fructose +patch +fiction +ruckus +drives +spot +witches +joist +saloon +bruise +stride +datebook +creep +axon +parking +insects +gadgets +doormat +snowfall +backups +parkway +disorder +cheeks +cruiser +shelf +nephews +cloves +cartload +java +shoptalk +mana +familiar +boast +spill +burrito +sitcom +soccer +humans +rinds +hypnosis +wires +mowing +ribbons +creatures +bauble +refuge +gremlin +tackle +peach +karaoke +cream +groom +subgroup +splint +dingo +pleats +monsoon +woes +putdown +medic +debt +leeks +husband +igloos +hype +lord +chords +shapes +display +sun +trophy +police +arches +naps +fibs +squad +alcoves +troy +turf +bikini +calendar +ways +body +parks +sin +bat +election +fillet +autos +comb +pita +smock +stings +deputy +puffs +games +habits +premises +banker +opium +prelude +guard +skit +ratio +shire +bobcat +subtext +pelts +sampling +babies +jockey +ham +laws +jewelry +slum +fables +ranger +doornail +gavels +grave +glare +spawn +chants +modem +daybreak +street +rage +leggings +biopsy +note +boiler +negation +jailbird +earthquake +choirs +kisses +bunks +suit +pike +dime +crook +noses +songs +earthworm +salsas +plating +rig +cartel +hexagon +border +stress +buzzword +sprint +catacomb +signs +elders +dojo +props +nail +suffix +fleece +media +burglary +box +diets +lads +warmth +cuckoo +cradle +ocelot +beds +walker +flies +skulls +onset +wane +snoop +cones +bulb +hankie +tars +evacuee +routine +eatery +heart +helium +portal +mud +perjury +stink +rag +glitch +junkman +nurses +copy +warlord +outcomes +recluse +peons +mantis +visor +diver +yams +drones +enamel +flogging +lifter +gals +duff +glamour +levies +dipper +rebar +fest +theater +patrols +delays +yoga +wager +jack +network +emphasis +clinic +canyons +buffers +virgin +quartet +lots +skeptic +invite +sulk +pole +harvest +pacts +newts +cane +being +maggots +girl +warp +society +gobs +orb +registry +crewman +faxes +bugle +postage +bandit +freaks +wildcat +perimeter +wishes +radish +suffrage +brad +stand +mother +melody +slice +toil +watts +trios +itch +batons +subject +hazing +tyrant +pub +wildfire +tremor +recess +bungee +dabs +lists +cufflinks +ocean +dashes +trowel +talks +fads +meteor +baguette +saws +beret +heading +liquids +axel +witch +pony +pay +motto +image +dud +actor +proposal +kennel +orbit +gangs +tongue +director +address +quirk +bishop +tow +lurch +shafts +units +cutlass +horses +latrines +trump +maniac +energies +diabetes +teams +vipers +scurvy +dad +fencing +needles +cascade +zippers +surfer +dumpster +meals +haze +snuff +baker +sphere +lulls +gnu +buffalos +pagans +graves +squires +births +aphids +cookie +orgasm +claws +scents +pin +baths +gliders +aluminum +field +brutes +tundra +vestibule +spouse +nude +ante +universe +tubs +monopoly +thumb +travel +almonds +frosting +relation +races +lather +bully +strife +howls +scheme +mages +proofs +gong +engraver +noodle +lyricism +arts +disaster +thirst +hubcaps +sugars +admin +grading +pod +program +errand +spice +crystal +shake +tire +stardust +future +hardhead +uncles +fright +fluff +sea +dilation +sedative +books +haircut +abacus +kettle +captains +wall +mugshot +district +specialist +umpires +web +milkmaid +litmus +jewel +playset +maneuver +lapel +spheres +dignity +sod +orc +gerbils +rigs +entry +sphinx +dimes +handler +takes +toads +guards +receiver +hawk +buds +ovens +pianos +hoof +boss +shards +mantle +froth +appetizer +hyphen +yearbook +drains +slash +deflator +wars +cobweb +nutrient +law +overlay +clutch +sacks +showbiz +sangria +prizes +overpass +cities +pho +load +tv +physician +surveyor +uproar +riverbed +achievement +lotion +fare +mare +genders +basement +extras +pains +headband +sediment +vigil +sock +catalog +closets +bazooka +pedals +gully +beet +feathers +recital +tuba +sharks +footprint +offers +fakes +barons +court +spec +illusion +spa +zit +throw +antipasto +parts +natives +buyer +anthill +grog +bucks +salami +burps +fault +suspense +blinkers +bathtub +wiki +memento +crank +primer +haunts +facilities +backs +pigs +cave +hurray +roulette +reggae +knot +effects +hackle +stroke +snacks +flatworm +surge +gamers +possum +slap +stops +curve +groves +patron +furs +manor +purchase +totals +helpers +gossip +ration +rites +pawn +basis +wino +mucus +orator +yards +fox +rogues +buckles +pardon +cairn +grudges +wipes +content +oat +bulk +matches +legs +seabird +rafts +arbors +charities +socks +nips +snails +hearth +grooves +attendant +mission +tern +medallion +paper +hooks +notice +max +wisdom +thighs +flagpole +drool +fevers +wimps +lefty +foil +chambers +plane +wood +eddy +savanna +starts +hams +posture +event +beef +ohms +floss +outages +mounts +epidemic +bola +leek +colony +majority +wraps +maw +aviators +thanks +today +silo +burr +doctor +cassette +flu +clarinet +stopper +exams +platoon +stake +pangs +centaurs +mouth +reminder +jugular +quote +tadpole +buzz +juror +nettle +crescent +ideas +heckler +batch +parasite +partner +batches +bandana +hand +lotions +brand +pumpkin +magnolia +pal +developer +handles +jingle +meet +landline +nuts +parole +stowaway +gin +cavities +banking +chard +clips +bookcase +gates +convict +scamp +huntress +cases +sets +crafts +grease +bourbon +guests +alfalfa +shuffle +mobility +forager +guilds +catapult +exploit +gaps +avenue +raffle +graffiti +parasail +historian +frat +hens +cleat +habitant +gator +railroad +headrest +stool +passion +hill +nub +analyst +inlets +quirks +frocks +rickshaw +brine +perp +cubicle +crux +wave +warps +wombats +burn +rodeos +aria +acrobat +curator +excuse +frost +drapes +rink +stages +bell +octets +imposter +yurt +ruck +curd +syntax +sinner +dolls +flames +pinky +wannabe +yawns +sprees +bolts +fencer +canoes +puzzle +smear +worlds +package +controls +tyke +hoot +spills +hike +beeper +pulses +fiends +canteens +crib +raven +pup +pens +trivia +pen +tons +marimba +stunt +aliens +pine +mastiff +dingbat +firewall +walks +shiv +cylinder +emus +magma +pain +polkas +maximum +slump +schoolboy +banjo +ranch +comedy +wreckage +heritage +legacy +satchel +silica +sponges +tipoff +rack +brothel +buddy +citizens +playmate +shares +capes +tattoo +outline +lull +house +clickers +tang +prowler +music +drone +ref +witchcraft +pier +attendee +campus +tine +afro +grouch +pose +armchair +blazes +cuticle +rhyme +trinket +info +steward +rearview +grunt +rinse +lint +woods +upload +musical +silks +senorita +souvenir +sonar +grandma +sucker +simile +emoticon +squealer +trials +bakery +spoiler +seminars +google +seashell +checkbook +arrows +eyes +jester +pacific +cheers +baristas +flab +throngs +cheek +species +episode +carrots +pips +diploma +dents +yelp +clash +marines +idealism +disdain +soul +emblem +ferns +blouses +ruin +dispatch +backpack +savings +blight +delusions +weapons +outreach +turbojet +mango +rap +baboons +boat +rinks +meal +points +soil +hitches +hockey +meow +wildlife +clang +handful +martini +masts +modules +forearm +ralph +batteries +punisher +tile +braids +ease +dirt +cheddar +winds +nettles +shell +travesty +fuse +match +democracy +craw +bars +stratus +mats +manuals +protester +lofts +gaze +hummus +tea +cusp +pucks +coupon +towel +panama +flecks +exorcism +rat +sashes +nibs +figment +wait +flint +phobia +dealer +companies +wrath +morons +pectin +ants +instalment +pampers +blend +proxies +leaves +surf +pet +sinker +egg +monogamy +locker +clams +inventory +sliver +karate +italics +miso +dangers +mode +oasis +frenzy +kiss +edition +traits +freedom +cages +flavors +decade +mustang +rookie +cowboys +mate +tower +devotee +days +alcove +bookstore +lyricist +waitress +pellets +icepack +handbook +wits +sables +musket +chime +cafes +peso +oaks +clans +pond +mills +elite +sneak +avocados +wedge +tirade +doorway +stupor +bears +denials +crybaby +coil +reps +slur +cubs +pairs +idea +bump +guardian +croc +studios +mops +dairy +rune +sound +decimal +elbows +spray +gum +copilot +kayak +dropper +grove +technician +rifles +jujitsu +winters +caravans +scent +upkeep +potato +flag +tabloid +divers +districts +oddity +lasso +moment +smirks +opals +glow +cheetah +devil +roaches +sandfish +sling +dominoes +cherries +thumbs +draw +triceps +sessions +gal +mistake +rupture +recoil +gat +flares +patios +humility +users +overview +victim +static +build +flour +lout +tofu +priest +feds +nape +customer +deletion +sequels +trips +clone +payback +coat +footrest +trap +remedy +bibs +turn +founding +joystick +celebrity +saint +slots +crusher +skier +crash +visits +exit +markets +airplane +boar +sodium +reverb +loops +print +lobsters +nook +quotas +design +fist +gizmos +prom +comrade +dwarves +denial +healers +widget +ex +nods +interns +hues +song +wombat +napalm +ellipse +emu +torches +sample +commuter +duct +misses +rainstorm +lantern +cornmeal +plum +glaze +androids +clubs +pandemic +contact +burials +ballet +crows +endpoint +numbers +petition +slack +hippo +pranker +adages +wolf +hack +drama +treatment +gasp +trek +product +tabs +ketchup +glues +hairpin +armory +need +goatskin +capitols +pop +hoses +coleslaw +yolk +rants +detector +mouse +busts +jawbone +beads +ninja +notepad +wreath +spurs +cattle +baron +ghosts +dustpan +schemes +matador +survey +trapeze +nickname +prayers +hips +maps +snip +thing +strands +sundial +hydrogen +skimmer +raids +heels +caboose +sleigh +chaff +supply +jump +howl +mix +smudge +trades +flesh +corset +slaves +dawdler +fax +owls +titan +purebred +earflap +canvas +hicks +ruby +musician +staples +champion +grad +elixir +statute +magnet +hoard +beeswax +gadget +fonts +acid +washroom +sarong +carb +pause +scrolls +floods +gran +rally +bombs +drink +snippet +editor +manatee +finisher +kid +barrel +glacier +waiver +kinks +danger +runes +lumps +fares +mothers +infants +conch +hydrants +vermin +tunnels +gyro +handcart +bacteria +octopus +butter +padlock +clamps +headwear +duress +blemish +playpen +timing +engines +idioms +baubles +viper +handgrip +tweet +octave +wick +bumper +sniff +skillet +tusks +washtub +riders +birch +skydiver +texts +patches +clue +saw +cost +cucumber +clerics +anime +loins +fondue +activity +coop +videos +gnomes +regions +stoner +lad +skate +legend +gravel +moles +havoc +blurb +premium +belches +lip +landmark +remorse +depth +scone +scotch +voles +minute +stack +warts +singers +lunatic +drudge +hijacker +pavilion +forehead +dryad +cravings +webcam +ebooks +masks +cliffs +orphan +pool +glowworm +intern +chariots +menu +shadow +wagon +download +tusk +numeral +braces +napkins +dive +toilet +bones +tibia +pancakes +gutter +marinade +farm +potions +cracks +animators +ragweed +linguist +thread +footgear +plug +creeds +asteroid +humorist +slope +apples +apricots +oxygen +handling +jewels +vendor +scene +scads +buffalo +radiance +trust +willow +camper +swivel +hop +final +wound +core +charlatan +podcast +reef +tips +glove +magnets +footpath +bladder +bot +plant +jurors +circle +faucet +fog +burg +violator +dove +dolphin +rungs +penny +hog +tulips +perch +hangout +size +clocks +gulf +lunches +shops +stag +cramps +window +data +decoys +day +stadium +deviator +cinema +gnome +items +kit +ton +wipe +shanty +plasma +fears +noon +struggle +truism +jolt +pastor +deferral +cause +slants +skyline +spies +papaya +bum +onions +juncture +pops +mom +marsh +tells +finalist +tissue +fold +sands +snowball +soak +garnish +vault +glances +jets +bevy +armies +end +grandkid +storks +arc +fender +gob +clashes +tights +slowpoke +progress +exposure +cheater +crowbar +information +coasters +teenager +biplane +north +square +health +tuna +chasm +violets +charters +exterior +cloak +talker +rip +fabric +curler +compound +dairies +powder +sauna +blob +slums +sisters +backup +rent +moor +wrinkle +risotto +stuff +wrists +pledge +windows +cartels +pints +garlic +sky +antiques +tort +pecan +curse +malls +kilns +gasps +shade +manpower +filing +rattle +typo +bush +actors +fable +coffee +scabs +comedies +trains +drills +collage +fiasco +vent +errands +alpacas +flax +salmon +vigor +cinemas +mammal +swimmer +mandarin +munchkin +boost +roux +phrase +option +gentleman +child +ant +contracts +devotion +oxford +phrasing +pulse +relay +dough +drought +jaws +writ +glade +cabin +cop +chaw +tag +vendetta +balm +classes +canopies +motes +rents +button +circles +arm +head +pleas +steamboat +biz +primate +prude +hanky +granite +tugs +etching +ream +cleats +geometry +emission +purity +hick +shoes +usage +gel +pass +kilogram +binoculars +mullets +snooze +doughboy +polymer +genes +term +lines +rib +oldie +chalice +nubs +memes +degree +empathy +trade +zodiac +altars +spells +scoop +canes +umbrella +spiral +anchor +pearls +snowman +swan +pupa +puppy +logo +cringe +axis +fever +plank +wizards +hunters +banana +bunt +woman +peroxide +oven +sofa +broncos +reasons +crimp +residue +sink +decals +feta +wimp +limb +minds +soaks +chute +fir +pillar +gram +spines +salt +hex +mania +chit +ewe +tie +gauge +compost +talon +hutch +charm +fauna +graders +dollop +company +roosts +praise +scammer +yetis +pace +delivery +cameo +chunk +donors +ditches +strategy +keg +tartar +locusts +liqueur +brace +rug +beings +looks +blubber +gif +turbine +pumps +pyre +blobs +gill +tiff +thuds +hides +stable +domes +spacebar +gull +tango +finish +mongoose +system +citadel +scandal +astronaut +drop +veins diff --git a/backend/src/backup/backup_bulk.rs b/backend/src/backup/backup_bulk.rs index bc9ca8237..cc00c9c72 100644 --- a/backend/src/backup/backup_bulk.rs +++ b/backend/src/backup/backup_bulk.rs @@ -22,7 +22,6 @@ use crate::auth::check_password_against_db; use crate::backup::{BackupReport, ServerBackupReport}; use crate::context::RpcContext; use crate::db::model::BackupProgress; -use crate::db::util::WithRevision; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::TmpMountGuard; @@ -135,7 +134,7 @@ pub async fn backup_all( )] package_ids: Option>, #[arg] password: String, -) -> Result, Error> { +) -> Result<(), Error> { let mut db = ctx.db.handle(); check_password_against_db(&mut ctx.secret_store.acquire().await?, &password).await?; let fs = target_id @@ -159,7 +158,7 @@ pub async fn backup_all( if old_password.is_some() { backup_guard.change_password(&password)?; } - let revision = assure_backing_up(&mut db, &package_ids).await?; + assure_backing_up(&mut db, &package_ids).await?; tokio::task::spawn(async move { let backup_res = perform_backup(&ctx, &mut db, backup_guard, &package_ids).await; let backup_progress = crate::db::DatabaseModel::new() @@ -238,17 +237,14 @@ pub async fn backup_all( .await .expect("failed to change server status"); }); - Ok(WithRevision { - response: (), - revision, - }) + Ok(()) } #[instrument(skip(db, packages))] async fn assure_backing_up( db: &mut PatchDbHandle, packages: impl IntoIterator, -) -> Result>, Error> { +) -> Result<(), Error> { let mut tx = db.begin().await?; let mut backing_up = crate::db::DatabaseModel::new() .server_info() @@ -279,7 +275,8 @@ async fn assure_backing_up( .collect(), ); backing_up.save(&mut tx).await?; - Ok(tx.commit(None).await?) + tx.commit().await?; + Ok(()) } #[instrument(skip(ctx, db, backup_guard))] diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index 9390283ee..02d3bdf15 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -20,7 +20,6 @@ use super::target::BackupTargetId; use crate::backup::backup_bulk::OsBackup; use crate::context::{RpcContext, SetupContext}; use crate::db::model::{PackageDataEntry, StaticFiles}; -use crate::db::util::WithRevision; use crate::disk::mount::backup::{BackupMountGuard, PackageBackupMountGuard}; use crate::disk::mount::filesystem::ReadOnly; use crate::disk::mount::guard::TmpMountGuard; @@ -50,7 +49,7 @@ pub async fn restore_packages_rpc( #[arg(parse(parse_comma_separated))] ids: Vec, #[arg(rename = "target-id")] target_id: BackupTargetId, #[arg] password: String, -) -> Result, Error> { +) -> Result<(), Error> { let mut db = ctx.db.handle(); let fs = target_id .load(&mut ctx.secret_store.acquire().await?) @@ -114,10 +113,7 @@ pub async fn restore_packages_rpc( } }); - Ok(WithRevision { - response: (), - revision, - }) + Ok(()) } async fn approximate_progress( @@ -418,7 +414,7 @@ async fn assure_restoring( guards.push((manifest, guard)); } - Ok((tx.commit(None).await?, guards)) + Ok((tx.commit().await?, guards)) } #[instrument(skip(ctx, guard))] diff --git a/backend/src/bin/embassy-init.rs b/backend/src/bin/embassy-init.rs index 87ec83f0f..024ed2261 100644 --- a/backend/src/bin/embassy-init.rs +++ b/backend/src/bin/embassy-init.rs @@ -7,11 +7,9 @@ use embassy::context::{DiagnosticContext, SetupContext}; use embassy::disk::fsck::RepairStrategy; use embassy::disk::main::DEFAULT_PASSWORD; use embassy::disk::REPAIR_DISK_PATH; -use embassy::hostname::get_product_key; use embassy::init::STANDBY_MODE_PATH; use embassy::middleware::cors::cors; use embassy::middleware::diagnostic::diagnostic; -use embassy::middleware::encrypt::encrypt; #[cfg(feature = "avahi")] use embassy::net::mdns::MdnsController; use embassy::shutdown::Shutdown; @@ -50,12 +48,7 @@ async fn setup_or_init(cfg_path: Option<&str>) -> Result<(), Error> { .invoke(embassy::ErrorKind::Nginx) .await?; let ctx = SetupContext::init(cfg_path).await?; - let keysource_ctx = ctx.clone(); - let keysource = move || { - let ctx = keysource_ctx.clone(); - async move { ctx.product_key().await } - }; - let encrypt = encrypt(keysource); + let encrypt = embassy::middleware::encrypt::encrypt(ctx.clone()); tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this CHIME.play().await?; rpc_server!({ @@ -103,7 +96,7 @@ async fn setup_or_init(cfg_path: Option<&str>) -> Result<(), Error> { .await?; } tracing::info!("Loaded Disk"); - embassy::init::init(&cfg, &get_product_key().await?).await?; + embassy::init::init(&cfg).await?; } Ok(()) diff --git a/backend/src/bin/embassyd.rs b/backend/src/bin/embassyd.rs index 3dc6772cf..8292fc929 100644 --- a/backend/src/bin/embassyd.rs +++ b/backend/src/bin/embassyd.rs @@ -7,6 +7,7 @@ use embassy::core::rpc_continuations::RequestGuid; use embassy::db::subscribe; use embassy::middleware::auth::auth; use embassy::middleware::cors::cors; +use embassy::middleware::db::db as db_middleware; use embassy::middleware::diagnostic::diagnostic; #[cfg(feature = "avahi")] use embassy::net::mdns::MdnsController; @@ -40,7 +41,6 @@ fn err_to_500(e: Error) -> Response { #[instrument] async fn inner_main(cfg_path: Option<&str>) -> Result, Error> { let (rpc_ctx, shutdown) = { - embassy::hostname::sync_hostname().await?; let rpc_ctx = RpcContext::init( cfg_path, Arc::new( @@ -82,11 +82,13 @@ async fn inner_main(cfg_path: Option<&str>) -> Result, Error> { }); let mut db = rpc_ctx.db.handle(); + embassy::hostname::sync_hostname(&mut db).await?; let receipts = embassy::context::rpc::RpcSetNginxReceipts::new(&mut db).await?; rpc_ctx.set_nginx_conf(&mut db, receipts).await?; drop(db); let auth = auth(rpc_ctx.clone()); + let db_middleware = db_middleware(rpc_ctx.clone()); let ctx = rpc_ctx.clone(); let server = rpc_server!({ command: embassy::main_api, @@ -95,6 +97,7 @@ async fn inner_main(cfg_path: Option<&str>) -> Result, Error> { middleware: [ cors, auth, + db_middleware, ] }) .with_graceful_shutdown({ @@ -112,29 +115,6 @@ async fn inner_main(cfg_path: Option<&str>) -> Result, Error> { .await }); - let rev_cache_ctx = rpc_ctx.clone(); - let revision_cache_task = tokio::spawn(async move { - let mut sub = rev_cache_ctx.db.subscribe(); - let mut shutdown = rev_cache_ctx.shutdown.subscribe(); - loop { - let rev = match tokio::select! { - a = sub.recv() => a, - _ = shutdown.recv() => break, - } { - Ok(a) => a, - Err(_) => { - rev_cache_ctx.revision_cache.write().await.truncate(0); - continue; - } - }; // TODO: handle falling behind - let mut cache = rev_cache_ctx.revision_cache.write().await; - cache.push_back(rev); - if cache.len() > rev_cache_ctx.revision_cache_size { - cache.pop_front(); - } - } - }); - let ws_ctx = rpc_ctx.clone(); let ws_server = { let builder = Server::bind(&ws_ctx.bind_ws); @@ -268,12 +248,6 @@ async fn inner_main(cfg_path: Option<&str>) -> Result, Error> { ErrorKind::Unknown )) .map_ok(|_| tracing::debug!("Metrics daemon Shutdown")), - revision_cache_task - .map_err(|e| Error::new( - eyre!("{}", e).wrap_err("Revision Cache daemon panicked!"), - ErrorKind::Unknown - )) - .map_ok(|_| tracing::debug!("Revision Cache daemon Shutdown")), ws_server .map_err(|e| Error::new(e, ErrorKind::Network)) .map_ok(|_| tracing::debug!("WebSocket Server Shutdown")), diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index 662aad905..519d70b5e 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -15,7 +15,6 @@ use tracing::instrument; use crate::context::RpcContext; use crate::db::model::{CurrentDependencies, CurrentDependencyInfo, CurrentDependents}; -use crate::db::util::WithRevision; use crate::dependencies::{ add_dependent_to_current_dependents_lists, break_transitive, heal_all_dependents_transitive, BreakTransitiveReceipts, BreakageRes, Dependencies, DependencyConfig, DependencyError, @@ -237,7 +236,8 @@ pub async fn get( #[command( subcommands(self(set_impl(async, context(RpcContext))), set_dry), - display(display_none) + display(display_none), + metadata(sync_db = true) )] #[instrument] pub fn set( @@ -247,9 +247,8 @@ pub fn set( format: Option, #[arg(long = "timeout")] timeout: Option, #[arg(stdin, parse(parse_stdin_deserializable))] config: Option, - #[arg(rename = "expire-id", long = "expire-id")] expire_id: Option, -) -> Result<(PackageId, Option, Option, Option), Error> { - Ok((id, config, timeout.map(|d| *d), expire_id)) +) -> Result<(PackageId, Option, Option), Error> { + Ok((id, config, timeout.map(|d| *d))) } /// So, the new locking finds all the possible locks and lifts them up into a bundle of locks. @@ -407,12 +406,7 @@ impl ConfigReceipts { #[instrument(skip(ctx))] pub async fn set_dry( #[context] ctx: RpcContext, - #[parent_data] (id, config, timeout, _): ( - PackageId, - Option, - Option, - Option, - ), + #[parent_data] (id, config, timeout): (PackageId, Option, Option), ) -> Result { let mut db = ctx.db.handle(); let mut tx = db.begin().await?; @@ -439,8 +433,8 @@ pub async fn set_dry( #[instrument(skip(ctx))] pub async fn set_impl( ctx: RpcContext, - (id, config, timeout, expire_id): (PackageId, Option, Option, Option), -) -> Result, Error> { + (id, config, timeout): (PackageId, Option, Option), +) -> Result<(), Error> { let mut db = ctx.db.handle(); let mut tx = db.begin().await?; let mut breakages = BTreeMap::new(); @@ -457,10 +451,8 @@ pub async fn set_impl( &locks, ) .await?; - Ok(WithRevision { - response: (), - revision: tx.commit(expire_id).await?, - }) + tx.commit().await?; + Ok(()) } #[instrument(skip(ctx, db, receipts))] diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index 208d8f4e2..2247674ae 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -22,7 +22,6 @@ use tracing::instrument; use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation}; use crate::db::model::{Database, InstalledPackageDataEntry, PackageDataEntry}; -use crate::hostname::{derive_hostname, derive_id, get_product_key}; use crate::init::{init_postgres, pgloader}; use crate::install::cleanup::{cleanup_failed, uninstall, CleanupFailedReceipts}; use crate::manager::ManagerMap; @@ -71,23 +70,18 @@ impl RpcContextConfig { .as_deref() .unwrap_or_else(|| Path::new("/embassy-data")) } - pub async fn db(&self, secret_store: &PgPool, product_key: &str) -> Result { - let sid = derive_id(product_key); - let hostname = derive_hostname(&sid); + pub async fn db(&self, secret_store: &PgPool) -> Result { let db_path = self.datadir().join("main").join("embassy.db"); let db = PatchDb::open(&db_path) .await .with_ctx(|_| (crate::ErrorKind::Filesystem, db_path.display().to_string()))?; - if !db.exists(&::default()).await? { + if !db.exists(&::default()).await { db.put( &::default(), &Database::init( - sid, - &hostname, &os_key(&mut secret_store.acquire().await?).await?, password_hash(&mut secret_store.acquire().await?).await?, ), - None, ) .await?; } @@ -216,7 +210,7 @@ impl RpcContext { let (shutdown, _) = tokio::sync::broadcast::channel(1); let secret_store = base.secret_store().await?; tracing::info!("Opened Pg DB"); - let db = base.db(&secret_store, &get_product_key().await?).await?; + let db = base.db(&secret_store).await?; tracing::info!("Opened PatchDB"); let docker = Docker::connect_with_unix_defaults()?; tracing::info!("Connected to Docker"); @@ -231,6 +225,7 @@ impl RpcContext { .unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]), secret_store.clone(), None, + &mut db.handle(), ) .await?; tracing::info!("Initialized Net Controller"); diff --git a/backend/src/context/setup.rs b/backend/src/context/setup.rs index 14eeab11b..f6aadabd9 100644 --- a/backend/src/context/setup.rs +++ b/backend/src/context/setup.rs @@ -2,29 +2,28 @@ use std::net::{IpAddr, SocketAddr}; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; -use std::time::Duration; use patch_db::json_ptr::JsonPointer; use patch_db::PatchDb; +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::Context; use serde::{Deserialize, Serialize}; use sqlx::postgres::PgConnectOptions; use sqlx::PgPool; use tokio::fs::File; -use tokio::process::Command; use tokio::sync::broadcast::Sender; use tokio::sync::RwLock; use tracing::instrument; use url::Host; use crate::db::model::Database; -use crate::hostname::{derive_hostname, derive_id, get_product_key}; use crate::init::{init_postgres, pgloader}; use crate::net::tor::os_key; use crate::setup::{password_hash, RecoveryStatus}; use crate::util::io::from_yaml_async_reader; -use crate::util::{AsyncFileExt, Invoke}; +use crate::util::AsyncFileExt; use crate::{Error, ResultExt}; #[derive(Clone, Serialize, Deserialize)] @@ -69,6 +68,9 @@ pub struct SetupContextSeed { pub bind_rpc: SocketAddr, pub shutdown: Sender<()>, pub datadir: PathBuf, + /// Used to encrypt for hidding from snoopers for setups create password + /// Set via path + pub current_secret: RwLock>, pub selected_v2_drive: RwLock>, pub cached_product_key: RwLock>>, pub recovery_status: RwLock>>, @@ -88,6 +90,7 @@ impl SetupContext { bind_rpc: cfg.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()), shutdown, datadir, + current_secret: RwLock::new(None), selected_v2_drive: RwLock::new(None), cached_product_key: RwLock::new(None), recovery_status: RwLock::new(None), @@ -100,19 +103,13 @@ impl SetupContext { let db = PatchDb::open(&db_path) .await .with_ctx(|_| (crate::ErrorKind::Filesystem, db_path.display().to_string()))?; - if !db.exists(&::default()).await? { - let pkey = self.product_key().await?; - let sid = derive_id(&*pkey); - let hostname = derive_hostname(&sid); + if !db.exists(&::default()).await { db.put( &::default(), &Database::init( - sid, - &hostname, &os_key(&mut secret_store.acquire().await?).await?, password_hash(&mut secret_store.acquire().await?).await?, ), - None, ) .await?; } @@ -134,22 +131,17 @@ impl SetupContext { } Ok(secret_store) } - #[instrument(skip(self))] - pub async fn product_key(&self) -> Result, Error> { - Ok( - if let Some(k) = { - let guard = self.cached_product_key.read().await; - let res = guard.clone(); - drop(guard); - res - } { - k - } else { - let k = Arc::new(get_product_key().await?); - *self.cached_product_key.write().await = Some(k.clone()); - k - }, - ) + + /// So we assume that there will only be one client that will ask for a secret, + /// And during that time do we upsert to a new key + pub async fn update_secret(&self) -> Result { + let new_secret: String = thread_rng() + .sample_iter(&Alphanumeric) + .take(30) + .map(char::from) + .collect(); + *self.current_secret.write().await = Some(new_secret.clone()); + Ok(new_secret) } } diff --git a/backend/src/control.rs b/backend/src/control.rs index 72b20b041..71125edff 100644 --- a/backend/src/control.rs +++ b/backend/src/control.rs @@ -6,7 +6,6 @@ use rpc_toolkit::command; use tracing::instrument; use crate::context::RpcContext; -use crate::db::util::WithRevision; use crate::dependencies::{ break_all_dependents_transitive, heal_all_dependents_transitive, BreakageRes, DependencyError, DependencyReceipt, TaggedDependencyError, @@ -61,12 +60,9 @@ impl StartReceipts { } } -#[command(display(display_none))] +#[command(display(display_none), metadata(sync_db = true))] #[instrument(skip(ctx))] -pub async fn start( - #[context] ctx: RpcContext, - #[arg] id: PackageId, -) -> Result, Error> { +pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> { let mut db = ctx.db.handle(); let mut tx = db.begin().await?; let receipts = StartReceipts::new(&mut tx, &id).await?; @@ -77,7 +73,7 @@ pub async fn start( .await?; heal_all_dependents_transitive(&ctx, &mut tx, &id, &receipts.dependency_receipt).await?; - let revision = tx.commit(None).await?; + tx.commit().await?; drop(receipts); ctx.managers @@ -87,10 +83,7 @@ pub async fn start( .synchronize() .await; - Ok(WithRevision { - revision, - response: (), - }) + Ok(()) } #[derive(Clone)] pub struct StopReceipts { @@ -150,7 +143,11 @@ async fn stop_common( Ok(()) } -#[command(subcommands(self(stop_impl(async)), stop_dry), display(display_none))] +#[command( + subcommands(self(stop_impl(async)), stop_dry), + display(display_none), + metadata(sync_db = true) +)] pub fn stop(#[arg] id: PackageId) -> Result { Ok(id) } @@ -173,23 +170,19 @@ pub async fn stop_dry( } #[instrument(skip(ctx))] -pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result, Error> { +pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<(), Error> { let mut db = ctx.db.handle(); let mut tx = db.begin().await?; stop_common(&mut tx, &id, &mut BTreeMap::new()).await?; - Ok(WithRevision { - revision: tx.commit(None).await?, - response: (), - }) + tx.commit().await?; + + Ok(()) } -#[command(display(display_none))] -pub async fn restart( - #[context] ctx: RpcContext, - #[arg] id: PackageId, -) -> Result, Error> { +#[command(display(display_none), metadata(sync_db = true))] +pub async fn restart(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> { let mut db = ctx.db.handle(); let mut tx = db.begin().await?; @@ -208,9 +201,7 @@ pub async fn restart( } *status = Some(MainStatus::Restarting); status.save(&mut tx).await?; + tx.commit().await?; - Ok(WithRevision { - revision: tx.commit(None).await?, - response: (), - }) + Ok(()) } diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs index 6bed11514..c7f7aa725 100644 --- a/backend/src/db/mod.rs +++ b/backend/src/db/mod.rs @@ -1,6 +1,5 @@ pub mod model; pub mod package; -pub mod util; use std::future::Future; use std::sync::Arc; @@ -14,7 +13,7 @@ use rpc_toolkit::hyper::{Body, Error as HyperError, Request, Response}; use rpc_toolkit::yajrc::RpcError; use serde::{Deserialize, Serialize}; use serde_json::Value; -use tokio::sync::{broadcast, oneshot}; +use tokio::sync::oneshot; use tokio::task::JoinError; use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; use tokio_tungstenite::tungstenite::protocol::CloseFrame; @@ -23,7 +22,6 @@ use tokio_tungstenite::WebSocketStream; use tracing::instrument; pub use self::model::DatabaseModel; -use self::util::WithRevision; use crate::context::RpcContext; use crate::middleware::auth::{HasValidSession, HashSessionToken}; use crate::util::serde::{display_serializable, IoFormat}; @@ -37,7 +35,7 @@ async fn ws_handler< session: Option<(HasValidSession, HashSessionToken)>, ws_fut: WSFut, ) -> Result<(), Error> { - let (dump, sub) = ctx.db.dump_and_sub().await; + let (dump, sub) = ctx.db.dump_and_sub().await?; let mut stream = ws_fut .await .with_kind(crate::ErrorKind::Network)? @@ -79,7 +77,7 @@ async fn subscribe_to_session_kill( async fn deal_with_messages( _has_valid_authentication: HasValidSession, mut kill: oneshot::Receiver<()>, - mut sub: broadcast::Receiver>, + mut sub: patch_db::Subscriber, mut stream: WebSocketStream, ) -> Result<(), Error> { loop { @@ -95,8 +93,8 @@ async fn deal_with_messages( .with_kind(crate::ErrorKind::Network)?; return Ok(()) } - new_rev = sub.recv().fuse() => { - let rev = new_rev.with_kind(crate::ErrorKind::Database)?; + new_rev = sub.recv_async().fuse() => { + let rev = new_rev.expect("UNREACHABLE: patch-db is dropped"); stream .send(Message::Text(serde_json::to_string(&rev).with_kind(crate::ErrorKind::Serialization)?)) .await @@ -184,24 +182,11 @@ pub async fn revisions( #[allow(unused_variables)] #[arg(long = "format")] format: Option, -) -> Result { - let cache = ctx.revision_cache.read().await; - if cache - .front() - .map(|rev| rev.id <= since + 1) - .unwrap_or(false) - { - Ok(RevisionsRes::Revisions( - cache - .iter() - .skip_while(|rev| rev.id < since + 1) - .cloned() - .collect(), - )) - } else { - drop(cache); - Ok(RevisionsRes::Dump(ctx.db.dump().await)) - } +) -> Result { + Ok(match ctx.db.sync(since).await? { + Ok(revs) => RevisionsRes::Revisions(revs), + Err(dump) => RevisionsRes::Dump(dump), + }) } #[command(display(display_serializable))] @@ -210,8 +195,8 @@ pub async fn dump( #[allow(unused_variables)] #[arg(long = "format")] format: Option, -) -> Result { - Ok(ctx.db.dump().await) +) -> Result { + Ok(ctx.db.dump().await?) } #[command(subcommands(ui))] @@ -228,13 +213,11 @@ pub async fn ui( #[allow(unused_variables)] #[arg(long = "format")] format: Option, -) -> Result, Error> { +) -> Result<(), Error> { let ptr = "/ui" .parse::() .with_kind(crate::ErrorKind::Database)? + &pointer; - Ok(WithRevision { - response: (), - revision: ctx.db.put(&ptr, &value, None).await?, - }) + ctx.db.put(&ptr, &value).await?; + Ok(()) } diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 380fff242..82c9a6424 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -12,6 +12,7 @@ use serde_json::Value; use torut::onion::TorSecretKeyV3; use crate::config::spec::{PackagePointerSpec, SystemPointerSpec}; +use crate::hostname::{generate_hostname, generate_id}; use crate::install::progress::InstallProgress; use crate::net::interface::InterfaceId; use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId}; @@ -32,21 +33,20 @@ pub struct Database { pub ui: Value, } impl Database { - pub fn init( - id: String, - hostname: &str, - tor_key: &TorSecretKeyV3, - password_hash: String, - ) -> Self { + pub fn init(tor_key: &TorSecretKeyV3, password_hash: String) -> Self { + let id = generate_id(); + let my_hostname = generate_hostname(); + let lan_address = my_hostname.lan_address().parse().unwrap(); // TODO Database { server_info: ServerInfo { id, version: Current::new().semver().into(), + hostname: Some(my_hostname.0), last_backup: None, last_wifi_region: None, eos_version_compat: Current::new().compat().clone(), - lan_address: format!("https://{}.local", hostname).parse().unwrap(), + lan_address, tor_address: format!("http://{}", tor_key.public().get_onion_address()) .parse() .unwrap(), @@ -83,6 +83,7 @@ impl DatabaseModel { #[serde(rename_all = "kebab-case")] pub struct ServerInfo { pub id: String, + pub hostname: Option, pub version: Version, pub last_backup: Option>, /// Used in the wifi to determine the region to set the system to diff --git a/backend/src/db/util.rs b/backend/src/db/util.rs deleted file mode 100644 index 1d5f33748..000000000 --- a/backend/src/db/util.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::sync::Arc; - -use patch_db::Revision; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct WithRevision { - pub response: T, - pub revision: Option>, -} diff --git a/backend/src/hostname.rs b/backend/src/hostname.rs index cc441ca37..dadb6f038 100644 --- a/backend/src/hostname.rs +++ b/backend/src/hostname.rs @@ -1,34 +1,53 @@ -use digest::Digest; -use tokio::fs::File; -use tokio::io::AsyncWriteExt; +use patch_db::DbHandle; +use rand::{thread_rng, Rng}; use tokio::process::Command; use tracing::instrument; use crate::util::Invoke; -use crate::{Error, ErrorKind, ResultExt}; +use crate::{Error, ErrorKind}; +#[derive(Clone, serde::Deserialize, serde::Serialize, Debug)] +pub struct Hostname(pub String); -pub const PRODUCT_KEY_PATH: &'static str = "/embassy-os/product_key.txt"; - -#[instrument] -pub async fn get_hostname() -> Result { - Ok(derive_hostname(&get_id().await?)) +lazy_static::lazy_static! { + static ref ADJECTIVES: Vec = include_str!("./assets/adjectives.txt").lines().map(|x| x.to_string()).collect(); + static ref NOUNS: Vec = include_str!("./assets/nouns.txt").lines().map(|x| x.to_string()).collect(); +} +impl AsRef for Hostname { + fn as_ref(&self) -> &str { + &self.0 + } } -pub fn derive_hostname(id: &str) -> String { - format!("embassy-{}", id) +impl Hostname { + pub fn lan_address(&self) -> String { + format!("https://{}.local", self.0) + } +} + +pub fn generate_hostname() -> Hostname { + let mut rng = thread_rng(); + let adjective = &ADJECTIVES[rng.gen_range(0..ADJECTIVES.len())]; + let noun = &NOUNS[rng.gen_range(0..NOUNS.len())]; + Hostname(format!("{adjective}-{noun}")) +} + +pub fn generate_id() -> String { + let id = uuid::Uuid::new_v4(); + id.to_string() } #[instrument] -pub async fn get_current_hostname() -> Result { +pub async fn get_current_hostname() -> Result { let out = Command::new("hostname") .invoke(ErrorKind::ParseSysInfo) .await?; let out_string = String::from_utf8(out)?; - Ok(out_string.trim().to_owned()) + Ok(Hostname(out_string.trim().to_owned())) } #[instrument] -pub async fn set_hostname(hostname: &str) -> Result<(), Error> { +pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> { + let hostname: &String = &hostname.0; let _out = Command::new("hostnamectl") .arg("set-hostname") .arg(hostname) @@ -37,38 +56,36 @@ pub async fn set_hostname(hostname: &str) -> Result<(), Error> { Ok(()) } -#[instrument] -pub async fn get_product_key() -> Result { - let out = tokio::fs::read_to_string(PRODUCT_KEY_PATH) +#[instrument(skip(handle))] +pub async fn get_id(handle: &mut Db) -> Result { + let id = crate::db::DatabaseModel::new() + .server_info() + .id() + .get(handle, false) + .await?; + Ok(id.to_string()) +} + +pub async fn get_hostname(handle: &mut Db) -> Result { + if let Ok(hostname) = crate::db::DatabaseModel::new() + .server_info() + .hostname() + .get(handle, false) .await - .with_ctx(|_| (crate::ErrorKind::Filesystem, PRODUCT_KEY_PATH))?; - Ok(out.trim().to_owned()) + { + if let Some(hostname) = hostname.to_owned() { + return Ok(Hostname(hostname)); + } + } + let id = get_id(handle).await?; + if id.len() != 8 { + return Ok(generate_hostname()); + } + return Ok(Hostname(format!("embassy-{}", id))); } - -#[instrument] -pub async fn set_product_key(key: &str) -> Result<(), Error> { - let mut pkey_file = File::create(PRODUCT_KEY_PATH).await?; - pkey_file.write_all(key.as_bytes()).await?; - Ok(()) -} - -pub fn derive_id(key: &str) -> String { - let mut hasher = sha2::Sha256::new(); - hasher.update(key.as_bytes()); - let res = hasher.finalize(); - hex::encode(&res[0..4]) -} - -#[instrument] -pub async fn get_id() -> Result { - let key = get_product_key().await?; - Ok(derive_id(&key)) -} - -// cat /embassy-os/product_key.txt | shasum -a 256 | head -c 8 | awk '{print "embassy-"$1}' | xargs hostnamectl set-hostname && systemctl restart avahi-daemon -#[instrument] -pub async fn sync_hostname() -> Result<(), Error> { - set_hostname(&format!("embassy-{}", get_id().await?)).await?; +#[instrument(skip(handle))] +pub async fn sync_hostname(handle: &mut Db) -> Result<(), Error> { + set_hostname(&get_hostname(handle).await?).await?; Command::new("systemctl") .arg("restart") .arg("avahi-daemon") diff --git a/backend/src/init.rs b/backend/src/init.rs index a5d12c411..e8de39455 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -8,7 +8,6 @@ use tokio::process::Command; use crate::context::rpc::RpcContextConfig; use crate::db::model::ServerStatus; -use crate::disk::mount::util::unmount; use crate::install::PKG_DOCKER_DIR; use crate::util::Invoke; use crate::Error; @@ -152,7 +151,11 @@ pub async fn init_postgres(datadir: impl AsRef) -> Result<(), Error> { Ok(()) } -pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error> { +pub struct InitResult { + pub db: patch_db::PatchDb, +} + +pub async fn init(cfg: &RpcContextConfig) -> Result { let should_rebuild = tokio::fs::metadata(SYSTEM_REBUILD_PATH).await.is_ok(); let secret_store = cfg.secret_store().await?; let log_dir = cfg.datadir().join("main/logs"); @@ -213,9 +216,14 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error crate::ssh::sync_keys_from_db(&secret_store, "/home/start9/.ssh/authorized_keys").await?; tracing::info!("Synced SSH Keys"); - let db = cfg.db(&secret_store, product_key).await?; + let db = cfg.db(&secret_store).await?; let mut handle = db.handle(); + crate::db::DatabaseModel::new() + .server_info() + .lock(&mut handle, LockType::Write) + .await?; + let receipts = InitReceipts::new(&mut handle).await?; crate::net::wifi::synchronize_wpa_supplicant_conf( @@ -258,5 +266,5 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error tracing::info!("System initialized."); - Ok(()) + Ok(InitResult { db }) } diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index b61766854..8d4301fce 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -375,7 +375,7 @@ where if tokio::fs::metadata(&volumes).await.is_ok() { tokio::fs::remove_dir_all(&volumes).await?; } - tx.commit(None).await?; + tx.commit().await?; remove_tor_keys(secrets, &entry.manifest.id).await?; Ok(()) } diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index bd3848c8b..089527d63 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -32,7 +32,6 @@ use crate::db::model::{ CurrentDependencies, CurrentDependencyInfo, CurrentDependents, InstalledPackageDataEntry, PackageDataEntry, RecoveredPackageInfo, StaticDependencyInfo, StaticFiles, }; -use crate::db::util::WithRevision; use crate::dependencies::{ add_dependent_to_current_dependents_lists, break_all_dependents_transitive, reconfigure_dependents_with_live_pointers, BreakTransitiveReceipts, BreakageRes, @@ -115,7 +114,8 @@ impl std::fmt::Display for MinMax { #[command( custom_cli(cli_install(async, context(CliContext))), - display(display_none) + display(display_none), + metadata(sync_db = true) )] #[instrument(skip(ctx))] pub async fn install( @@ -127,7 +127,7 @@ pub async fn install( String, >, #[arg(long = "version-priority", rename = "version-priority")] version_priority: Option, -) -> Result, Error> { +) -> Result<(), Error> { let version_str = match &version_spec { None => "*", Some(v) => &*v, @@ -287,7 +287,7 @@ pub async fn install( } } pde.save(&mut tx).await?; - let res = tx.commit(None).await?; + tx.commit().await?; drop(db_handle); tokio::spawn(async move { @@ -323,10 +323,7 @@ pub async fn install( } }); - Ok(WithRevision { - revision: res, - response: (), - }) + Ok(()) } #[command(rpc_only, display(display_none))] @@ -427,7 +424,7 @@ pub async fn sideload( } } pde.save(&mut tx).await?; - tx.commit(None).await?; + tx.commit().await?; if let Err(e) = download_install_s9pk( &new_ctx, @@ -559,7 +556,7 @@ async fn cli_install( ctx, "package.install", params, - PhantomData::>, + PhantomData::<()>, ) .await? .result?; @@ -570,7 +567,8 @@ async fn cli_install( #[command( subcommands(self(uninstall_impl(async)), uninstall_dry), - display(display_none) + display(display_none), + metadata(sync_db = true) )] pub async fn uninstall(#[arg] id: PackageId) -> Result { Ok(id) @@ -601,7 +599,7 @@ pub async fn uninstall_dry( } #[instrument(skip(ctx))] -pub async fn uninstall_impl(ctx: RpcContext, id: PackageId) -> Result, Error> { +pub async fn uninstall_impl(ctx: RpcContext, id: PackageId) -> Result<(), Error> { let mut handle = ctx.db.handle(); let mut tx = handle.begin().await?; @@ -629,7 +627,7 @@ pub async fn uninstall_impl(ctx: RpcContext, id: PackageId) -> Result Result Result, Error> { +) -> Result<(), Error> { let mut handle = ctx.db.handle(); let mut tx = handle.begin().await?; let mut sql_tx = ctx.secret_store.begin().await?; @@ -699,13 +698,10 @@ pub async fn delete_recovered( } cleanup::remove_tor_keys(&mut sql_tx, &id).await?; - let res = tx.commit(None).await?; + tx.commit().await?; sql_tx.commit().await?; - Ok(WithRevision { - revision: res, - response: (), - }) + Ok(()) } pub struct DownloadInstallReceipts { @@ -858,7 +854,7 @@ pub async fn download_install_s9pk( tracing::error!("Failed to clean up {}@{}: {}", pkg_id, version, e); tracing::debug!("{:?}", e); } else { - tx.commit(None).await?; + tx.commit().await?; } Err(e) } else { @@ -1147,7 +1143,7 @@ pub async fn install_s9pk( if let Some(mut hdl) = rdr.scripts().await? { tokio::io::copy( &mut hdl, - &mut File::create(dbg!(script_dir.join("embassy.js"))).await?, + &mut File::create(script_dir.join("embassy.js")).await?, ) .await?; } @@ -1505,7 +1501,7 @@ pub async fn install_s9pk( } sql_tx.commit().await?; - tx.commit(None).await?; + tx.commit().await?; tracing::info!("Install {}@{}: Complete", pkg_id, version); diff --git a/backend/src/logs.rs b/backend/src/logs.rs index ebd3fe376..6f654b196 100644 --- a/backend/src/logs.rs +++ b/backend/src/logs.rs @@ -120,7 +120,7 @@ pub struct LogResponse { end_cursor: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -#[serde(rename_all = "kebab-case", tag = "type")] +#[serde(rename_all = "kebab-case")] pub struct LogFollowResponse { start_cursor: Option, guid: RequestGuid, diff --git a/backend/src/manager/health.rs b/backend/src/manager/health.rs index 6f72c4cd1..6419e8a7a 100644 --- a/backend/src/manager/health.rs +++ b/backend/src/manager/health.rs @@ -128,20 +128,17 @@ pub async fn check( let status = receipts.status.get(&mut checkpoint).await?; - match status { - MainStatus::Running { health, started } => { - receipts - .status - .set( - &mut checkpoint, - MainStatus::Running { - health: health_results.clone(), - started, - }, - ) - .await?; - } - _ => (), + if let MainStatus::Running { health: _, started } = status { + receipts + .status + .set( + &mut checkpoint, + MainStatus::Running { + health: health_results.clone(), + started, + }, + ) + .await?; } let current_dependents = receipts.current_dependents.get(&mut checkpoint).await?; diff --git a/backend/src/middleware/db.rs b/backend/src/middleware/db.rs new file mode 100644 index 000000000..0d9c1d40b --- /dev/null +++ b/backend/src/middleware/db.rs @@ -0,0 +1,84 @@ +use color_eyre::eyre::eyre; +use futures::future::BoxFuture; +use futures::FutureExt; +use http::HeaderValue; +use rpc_toolkit::hyper::http::Error as HttpError; +use rpc_toolkit::hyper::{Body, Request, Response}; +use rpc_toolkit::rpc_server_helpers::{ + noop4, DynMiddleware, DynMiddlewareStage2, DynMiddlewareStage3, +}; +use rpc_toolkit::yajrc::RpcMethod; +use rpc_toolkit::Metadata; + +use crate::context::RpcContext; +use crate::{Error, ResultExt}; + +pub fn db(ctx: RpcContext) -> DynMiddleware { + Box::new( + move |_: &mut Request, + metadata: M| + -> BoxFuture>, HttpError>> { + let ctx = ctx.clone(); + async move { + let m2: DynMiddlewareStage2 = Box::new(move |req, rpc_req| { + async move { + let seq = req.headers.remove("x-patch-sequence"); + let sync_db = metadata + .get(rpc_req.method.as_str(), "sync_db") + .unwrap_or(false); + + let m3: DynMiddlewareStage3 = Box::new(move |res, _| { + async move { + if sync_db && seq.is_some() { + match async { + let seq = seq + .ok_or_else(|| { + Error::new( + eyre!("Missing X-Patch-Sequence"), + crate::ErrorKind::InvalidRequest, + ) + })? + .to_str() + .with_kind(crate::ErrorKind::InvalidRequest)? + .parse()?; + let res = ctx.db.sync(seq).await?; + let json = match res { + Ok(revs) => serde_json::to_vec(&revs), + Err(dump) => serde_json::to_vec(&[dump]), + } + .with_kind(crate::ErrorKind::Serialization)?; + Ok::<_, Error>( + url::form_urlencoded::byte_serialize(&json) + .collect::(), + ) + } + .await + { + Ok(a) => res + .headers + .append("X-Patch-Updates", HeaderValue::from_str(&a)?), + Err(e) => res.headers.append( + "X-Patch-Error", + HeaderValue::from_str( + &url::form_urlencoded::byte_serialize( + e.to_string().as_bytes(), + ) + .collect::(), + )?, + ), + }; + } + Ok(Ok(noop4())) + } + .boxed() + }); + Ok(Ok(m3)) + } + .boxed() + }); + Ok(Ok(m2)) + } + .boxed() + }, + ) +} diff --git a/backend/src/middleware/encrypt.rs b/backend/src/middleware/encrypt.rs index b140764f8..3a3a59849 100644 --- a/backend/src/middleware/encrypt.rs +++ b/backend/src/middleware/encrypt.rs @@ -1,4 +1,3 @@ -use std::future::Future; use std::sync::Arc; use aes::cipher::{CipherKey, NewCipher, Nonce, StreamCipher}; @@ -17,6 +16,7 @@ use rpc_toolkit::yajrc::RpcMethod; use rpc_toolkit::Metadata; use sha2::Sha256; +use crate::context::SetupContext; use crate::util::Apply; use crate::Error; @@ -35,7 +35,7 @@ pub fn encrypt_slice(input: impl AsRef<[u8]>, password: impl AsRef<[u8]>) -> Vec let prefix: [u8; 32] = rand::random(); let aeskey = pbkdf2(password.as_ref(), &prefix[16..]); let ctr = Nonce::::from_slice(&prefix[..16]); - let mut aes = Aes256Ctr::new(&aeskey, &ctr); + let mut aes = Aes256Ctr::new(&aeskey, ctr); let mut res = Vec::with_capacity(32 + input.as_ref().len()); res.extend_from_slice(&prefix[..]); res.extend_from_slice(input.as_ref()); @@ -50,7 +50,7 @@ pub fn decrypt_slice(input: impl AsRef<[u8]>, password: impl AsRef<[u8]>) -> Vec let (prefix, rest) = input.as_ref().split_at(32); let aeskey = pbkdf2(password.as_ref(), &prefix[16..]); let ctr = Nonce::::from_slice(&prefix[..16]); - let mut aes = Aes256Ctr::new(&aeskey, &ctr); + let mut aes = Aes256Ctr::new(&aeskey, ctr); let mut res = rest.to_vec(); aes.apply_keystream(&mut res); res @@ -92,20 +92,20 @@ impl Stream for DecryptStream { aes.apply_keystream(&mut res); res.into() } else { - if this.ctr.len() < 16 && buf.len() > 0 { + if this.ctr.len() < 16 && !buf.is_empty() { let to_read = std::cmp::min(16 - this.ctr.len(), buf.len()); this.ctr.extend_from_slice(&buf[0..to_read]); buf = &buf[to_read..]; } - if this.salt.len() < 16 && buf.len() > 0 { + if this.salt.len() < 16 && !buf.is_empty() { let to_read = std::cmp::min(16 - this.salt.len(), buf.len()); this.salt.extend_from_slice(&buf[0..to_read]); buf = &buf[to_read..]; } if this.ctr.len() == 16 && this.salt.len() == 16 { let aeskey = pbkdf2(this.key.as_bytes(), &this.salt); - let ctr = Nonce::::from_slice(&this.ctr); - let mut aes = Aes256Ctr::new(&aeskey, &ctr); + let ctr = Nonce::::from_slice(this.ctr); + let mut aes = Aes256Ctr::new(&aeskey, ctr); let mut res = buf.to_vec(); aes.apply_keystream(&mut res); *this.aes = Some(aes); @@ -132,7 +132,7 @@ impl EncryptStream { let prefix: [u8; 32] = rand::random(); let aeskey = pbkdf2(key.as_bytes(), &prefix[16..]); let ctr = Nonce::::from_slice(&prefix[..16]); - let aes = Aes256Ctr::new(&aeskey, &ctr); + let aes = Aes256Ctr::new(&aeskey, ctr); EncryptStream { body, aes, @@ -169,42 +169,42 @@ fn encrypted(headers: &HeaderMap) -> bool { .and_then(|h| { h.to_str() .ok()? - .split(",") + .split(',') .any(|s| s == "aesctr256") .apply(Some) }) .unwrap_or_default() } -pub fn encrypt< - F: Fn() -> Fut + Send + Sync + Clone + 'static, - Fut: Future, Error>> + Send + Sync + 'static, - M: Metadata, ->( - keysource: F, -) -> DynMiddleware { +pub fn encrypt(ctx: SetupContext) -> DynMiddleware { Box::new( move |req: &mut Request, metadata: M| -> BoxFuture>, HttpError>> { - let keysource = keysource.clone(); + let keysource = ctx.clone(); async move { let encrypted = encrypted(req.headers()); + let current_secret: Option = keysource.current_secret.read().await.clone(); let key = if encrypted { - let key = match keysource().await { - Ok(s) => s, - Err(e) => { + let key = match current_secret { + Some(s) => s, + None => { let (res_parts, _) = Response::new(()).into_parts(); return Ok(Err(to_response( req.headers(), res_parts, - Err(e.into()), + Err(Error::new( + eyre!("No Secret has been set"), + crate::ErrorKind::RateLimited, + ) + .into()), |_| StatusCode::OK, )?)); } }; let body = std::mem::take(req.body_mut()); - *req.body_mut() = Body::wrap_stream(DecryptStream::new(key.clone(), body)); + *req.body_mut() = + Body::wrap_stream(DecryptStream::new(Arc::new(key.clone()), body)); Some(key) } else { None @@ -213,7 +213,7 @@ pub fn encrypt< async move { if !encrypted && metadata - .get(&rpc_req.method.as_str(), "authenticated") + .get(rpc_req.method.as_str(), "authenticated") .unwrap_or(true) { let (res_parts, _) = Response::new(()).into_parts(); diff --git a/backend/src/middleware/mod.rs b/backend/src/middleware/mod.rs index 0cc0b7bf8..5af2b8121 100644 --- a/backend/src/middleware/mod.rs +++ b/backend/src/middleware/mod.rs @@ -1,4 +1,5 @@ pub mod auth; pub mod cors; +pub mod db; pub mod diagnostic; pub mod encrypt; diff --git a/backend/src/net/mdns.rs b/backend/src/net/mdns.rs index 2a5f680e3..beea99cad 100644 --- a/backend/src/net/mdns.rs +++ b/backend/src/net/mdns.rs @@ -3,14 +3,14 @@ use std::net::Ipv4Addr; use avahi_sys::{ self, avahi_client_errno, avahi_entry_group_add_service, avahi_entry_group_commit, - avahi_entry_group_free, avahi_entry_group_reset, avahi_free, avahi_strerror, AvahiClient, - AvahiEntryGroup, + avahi_entry_group_free, avahi_free, avahi_strerror, AvahiClient, AvahiEntryGroup, }; use color_eyre::eyre::eyre; use libc::c_void; use tokio::process::Command; use tokio::sync::Mutex; use torut::onion::TorSecretKeyV3; +use tracing::instrument; use super::interface::InterfaceId; use crate::s9pk::manifest::PackageId; @@ -59,17 +59,64 @@ impl MdnsController { } pub struct MdnsControllerInner { - hostname: Vec, - hostname_raw: *const libc::c_char, - entry_group: *mut AvahiEntryGroup, + entry_group: Option, services: BTreeMap<(PackageId, InterfaceId), TorSecretKeyV3>, - _client_error: std::pin::Pin>, } unsafe impl Send for MdnsControllerInner {} unsafe impl Sync for MdnsControllerInner {} impl MdnsControllerInner { - fn load_services(&mut self) { + fn init() -> Self { + MdnsControllerInner { + entry_group: Some(MdnsEntryGroup::init(&BTreeMap::new())), + services: BTreeMap::new(), + } + } + fn sync(&mut self) { + drop(self.entry_group.take()); + self.entry_group = Some(MdnsEntryGroup::init(&self.services)); + } + fn add<'a, I: IntoIterator>( + &mut self, + pkg_id: &PackageId, + interfaces: I, + ) { + self.services.extend( + interfaces + .into_iter() + .map(|(interface_id, key)| ((pkg_id.clone(), interface_id), key)), + ); + self.sync(); + } + fn remove>(&mut self, pkg_id: &PackageId, interfaces: I) { + for interface_id in interfaces { + self.services.remove(&(pkg_id.clone(), interface_id)); + } + self.sync(); + } + fn free(&self) {} +} + +fn log_str_error(action: &str, e: i32) { + unsafe { + let e_str = avahi_strerror(e); + tracing::error!( + "Could not {}: {:?}", + action, + std::ffi::CStr::from_ptr(e_str) + ); + } +} + +struct MdnsEntryGroup { + hostname: Vec, + hostname_raw: *const libc::c_char, + entry_group: *mut AvahiEntryGroup, + _client_error: std::pin::Pin>, +} +impl MdnsEntryGroup { + #[instrument(skip(self))] + fn load_services(&mut self, services: &BTreeMap<(PackageId, InterfaceId), TorSecretKeyV3>) { unsafe { tracing::debug!("Loading services for mDNS"); let mut res; @@ -101,7 +148,7 @@ impl MdnsControllerInner { "Published {:?}", std::ffi::CStr::from_ptr(self.hostname_raw) ); - for key in self.services.values() { + for key in services.values() { let lan_address = key .public() .get_onion_address() @@ -131,9 +178,10 @@ impl MdnsControllerInner { } } } - fn init() -> Self { + fn init(services: &BTreeMap<(PackageId, InterfaceId), TorSecretKeyV3>) -> Self { unsafe { tracing::debug!("Initializing mDNS controller"); + let simple_poll = avahi_sys::avahi_simple_poll_new(); let poll = avahi_sys::avahi_simple_poll_get(simple_poll); let mut box_err = Box::pin(0 as i32); @@ -168,15 +216,13 @@ impl MdnsControllerInner { // assume fixed length prefix on hostname due to local address hostname_buf[0] = (buflen - 8) as u8; // set the prefix length to len - 8 (leading byte, .local, nul) for the main address hostname_buf[buflen - 7] = 5; // set the prefix length to 5 for "local" - - let mut res = MdnsControllerInner { + let mut res = MdnsEntryGroup { hostname: hostname_buf, hostname_raw, entry_group: group, - services: BTreeMap::new(), _client_error: box_err, }; - res.load_services(); + res.load_services(services); let commit_err = avahi_entry_group_commit(res.entry_group); if commit_err < avahi_sys::AVAHI_OK { log_str_error("reset Avahi entry group", commit_err); @@ -185,62 +231,17 @@ impl MdnsControllerInner { res } } - fn sync(&mut self) { - unsafe { - let mut res; - res = avahi_entry_group_reset(self.entry_group); - if res < avahi_sys::AVAHI_OK { - log_str_error("reset Avahi entry group", res); - panic!("Failed to load Avahi services: reset"); - } - self.load_services(); - res = avahi_entry_group_commit(self.entry_group); - if res < avahi_sys::AVAHI_OK { - log_str_error("commit Avahi entry group", res); - panic!("Failed to load Avahi services: commit"); - } - } - } - fn add<'a, I: IntoIterator>( - &mut self, - pkg_id: &PackageId, - interfaces: I, - ) { - self.services.extend( - interfaces - .into_iter() - .map(|(interface_id, key)| ((pkg_id.clone(), interface_id), key)), - ); - self.sync(); - } - fn remove>(&mut self, pkg_id: &PackageId, interfaces: I) { - for interface_id in interfaces { - self.services.remove(&(pkg_id.clone(), interface_id)); - } - self.sync(); - } } -impl Drop for MdnsControllerInner { +impl Drop for MdnsEntryGroup { fn drop(&mut self) { unsafe { avahi_free(self.hostname_raw as *mut c_void); avahi_entry_group_free(self.entry_group); + // avahi_client_free(self.avahi_client); } } } -fn log_str_error(action: &str, e: i32) { - unsafe { - let e_str = avahi_strerror(e); - tracing::error!( - "Could not {}: {:?}", - action, - std::ffi::CStr::from_ptr(e_str) - ); - avahi_free(e_str as *mut c_void); - } -} - unsafe extern "C" fn entry_group_callback( _group: *mut avahi_sys::AvahiEntryGroup, state: avahi_sys::AvahiEntryGroupState, diff --git a/backend/src/net/mod.rs b/backend/src/net/mod.rs index 112823157..a2ec1f5e8 100644 --- a/backend/src/net/mod.rs +++ b/backend/src/net/mod.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; +use patch_db::DbHandle; use rpc_toolkit::command; use sqlx::PgPool; use torut::onion::{OnionAddressV3, TorSecretKeyV3}; @@ -14,6 +15,7 @@ use self::mdns::MdnsController; use self::nginx::NginxController; use self::ssl::SslManager; use self::tor::TorController; +use crate::hostname::get_hostname; use crate::net::dns::DnsController; use crate::net::interface::TorConfig; use crate::net::nginx::InterfaceMetadata; @@ -50,24 +52,26 @@ pub struct NetController { pub dns: DnsController, } impl NetController { - #[instrument(skip(db))] - pub async fn init( + #[instrument(skip(db, handle))] + pub async fn init( embassyd_addr: SocketAddr, embassyd_tor_key: TorSecretKeyV3, tor_control: SocketAddr, dns_bind: &[SocketAddr], db: PgPool, import_root_ca: Option<(PKey, X509)>, + handle: &mut Db, ) -> Result { let ssl = match import_root_ca { - None => SslManager::init(db).await, + None => SslManager::init(db, handle).await, Some(a) => SslManager::import_root_ca(db, a.0, a.1).await, }?; + let hostname = get_hostname(handle).await?; Ok(Self { tor: TorController::init(embassyd_addr, embassyd_tor_key, tor_control).await?, #[cfg(feature = "avahi")] mdns: MdnsController::init(), - nginx: NginxController::init(PathBuf::from("/etc/nginx"), &ssl).await?, + nginx: NginxController::init(PathBuf::from("/etc/nginx"), &ssl, &hostname).await?, ssl, dns: DnsController::init(dns_bind).await?, }) diff --git a/backend/src/net/nginx.rs b/backend/src/net/nginx.rs index f85431a88..e3fc1ba85 100644 --- a/backend/src/net/nginx.rs +++ b/backend/src/net/nginx.rs @@ -9,7 +9,7 @@ use tracing::instrument; use super::interface::{InterfaceId, LanPortConfig}; use super::ssl::SslManager; -use crate::hostname::get_hostname; +use crate::hostname::Hostname; use crate::s9pk::manifest::PackageId; use crate::util::serde::Port; use crate::util::Invoke; @@ -20,9 +20,15 @@ pub struct NginxController { inner: Mutex, } impl NginxController { - pub async fn init(nginx_root: PathBuf, ssl_manager: &SslManager) -> Result { + pub async fn init( + nginx_root: PathBuf, + ssl_manager: &SslManager, + host_name: &Hostname, + ) -> Result { Ok(NginxController { - inner: Mutex::new(NginxControllerInner::init(&nginx_root, ssl_manager).await?), + inner: Mutex::new( + NginxControllerInner::init(&nginx_root, ssl_manager, host_name).await?, + ), nginx_root, }) } @@ -53,13 +59,17 @@ pub struct NginxControllerInner { } impl NginxControllerInner { #[instrument] - async fn init(nginx_root: &Path, ssl_manager: &SslManager) -> Result { + async fn init( + nginx_root: &Path, + ssl_manager: &SslManager, + host_name: &Hostname, + ) -> Result { let inner = NginxControllerInner { interfaces: BTreeMap::new(), }; // write main ssl key/cert to fs location let (key, cert) = ssl_manager - .certificate_for(&get_hostname().await?, &"embassy".parse().unwrap()) + .certificate_for(&host_name.lan_address(), &"embassy".parse().unwrap()) .await?; let ssl_path_key = nginx_root.join(format!("ssl/embassy_main.key.pem")); let ssl_path_cert = nginx_root.join(format!("ssl/embassy_main.cert.pem")); diff --git a/backend/src/net/ssl.rs b/backend/src/net/ssl.rs index 3a56399da..42f40a869 100644 --- a/backend/src/net/ssl.rs +++ b/backend/src/net/ssl.rs @@ -11,6 +11,7 @@ use openssl::nid::Nid; use openssl::pkey::{PKey, Private}; use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509}; use openssl::*; +use patch_db::DbHandle; use sqlx::PgPool; use tokio::process::Command; use tokio::sync::Mutex; @@ -161,13 +162,14 @@ lazy_static::lazy_static! { } impl SslManager { - #[instrument(skip(db))] - pub async fn init(db: PgPool) -> Result { + #[instrument(skip(db, handle))] + pub async fn init(db: PgPool, handle: &mut Db) -> Result { let store = SslStore::new(db)?; + let id = crate::hostname::get_id(handle).await?; let (root_key, root_cert) = match store.load_root_certificate().await? { None => { let root_key = generate_key()?; - let server_id = crate::hostname::get_id().await?; + let server_id = id; let root_cert = make_root_cert(&root_key, &server_id)?; store.save_root_certificate(&root_key, &root_cert).await?; Ok::<_, Error>((root_key, root_cert)) @@ -511,56 +513,56 @@ fn make_leaf_cert( Ok(cert) } -#[tokio::test] -async fn ca_details_persist() -> Result<(), Error> { - let pool = sqlx::Pool::::connect("postgres::memory:").await?; - sqlx::migrate!() - .run(&pool) - .await - .with_kind(crate::ErrorKind::Database)?; - let mgr = SslManager::init(pool.clone()).await?; - let root_cert0 = mgr.root_cert; - let int_key0 = mgr.int_key; - let int_cert0 = mgr.int_cert; - let mgr = SslManager::init(pool).await?; - let root_cert1 = mgr.root_cert; - let int_key1 = mgr.int_key; - let int_cert1 = mgr.int_cert; - - assert_eq!(root_cert0.to_pem()?, root_cert1.to_pem()?); - assert_eq!( - int_key0.private_key_to_pem_pkcs8()?, - int_key1.private_key_to_pem_pkcs8()? - ); - assert_eq!(int_cert0.to_pem()?, int_cert1.to_pem()?); - Ok(()) -} - -#[tokio::test] -async fn certificate_details_persist() -> Result<(), Error> { - let pool = sqlx::Pool::::connect("postgres::memory:").await?; - sqlx::migrate!() - .run(&pool) - .await - .with_kind(crate::ErrorKind::Database)?; - let mgr = SslManager::init(pool.clone()).await?; - let package_id = "bitcoind".parse().unwrap(); - let (key0, cert_chain0) = mgr.certificate_for("start9", &package_id).await?; - let (key1, cert_chain1) = mgr.certificate_for("start9", &package_id).await?; - - assert_eq!( - key0.private_key_to_pem_pkcs8()?, - key1.private_key_to_pem_pkcs8()? - ); - assert_eq!( - cert_chain0 - .iter() - .map(|cert| cert.to_pem().unwrap()) - .collect::>>(), - cert_chain1 - .iter() - .map(|cert| cert.to_pem().unwrap()) - .collect::>>() - ); - Ok(()) -} +// #[tokio::test] +// async fn ca_details_persist() -> Result<(), Error> { +// let pool = sqlx::Pool::::connect("postgres::memory:").await?; +// sqlx::migrate!() +// .run(&pool) +// .await +// .with_kind(crate::ErrorKind::Database)?; +// let mgr = SslManager::init(pool.clone()).await?; +// let root_cert0 = mgr.root_cert; +// let int_key0 = mgr.int_key; +// let int_cert0 = mgr.int_cert; +// let mgr = SslManager::init(pool).await?; +// let root_cert1 = mgr.root_cert; +// let int_key1 = mgr.int_key; +// let int_cert1 = mgr.int_cert; +// +// assert_eq!(root_cert0.to_pem()?, root_cert1.to_pem()?); +// assert_eq!( +// int_key0.private_key_to_pem_pkcs8()?, +// int_key1.private_key_to_pem_pkcs8()? +// ); +// assert_eq!(int_cert0.to_pem()?, int_cert1.to_pem()?); +// Ok(()) +// } +// +// #[tokio::test] +// async fn certificate_details_persist() -> Result<(), Error> { +// let pool = sqlx::Pool::::connect("postgres::memory:").await?; +// sqlx::migrate!() +// .run(&pool) +// .await +// .with_kind(crate::ErrorKind::Database)?; +// let mgr = SslManager::init(pool.clone()).await?; +// let package_id = "bitcoind".parse().unwrap(); +// let (key0, cert_chain0) = mgr.certificate_for("start9", &package_id).await?; +// let (key1, cert_chain1) = mgr.certificate_for("start9", &package_id).await?; +// +// assert_eq!( +// key0.private_key_to_pem_pkcs8()?, +// key1.private_key_to_pem_pkcs8()? +// ); +// assert_eq!( +// cert_chain0 +// .iter() +// .map(|cert| cert.to_pem().unwrap()) +// .collect::>>(), +// cert_chain1 +// .iter() +// .map(|cert| cert.to_pem().unwrap()) +// .collect::>>() +// ); +// Ok(()) +// } diff --git a/backend/src/nginx/main-ui.conf.template b/backend/src/nginx/main-ui.conf.template index bac5343a1..7532c41b5 100644 --- a/backend/src/nginx/main-ui.conf.template +++ b/backend/src/nginx/main-ui.conf.template @@ -16,6 +16,8 @@ server {{ server_name .{lan_hostname}; + proxy_buffers 4 512k; + proxy_buffer_size 512k; proxy_buffering off; proxy_request_buffering off; proxy_socket_keepalive on; @@ -71,6 +73,8 @@ server {{ server_name .{tor_hostname}; + proxy_buffers 4 512k; + proxy_buffer_size 512k; proxy_buffering off; proxy_request_buffering off; proxy_socket_keepalive on; diff --git a/backend/src/notifications.rs b/backend/src/notifications.rs index a239da0e5..69cb20340 100644 --- a/backend/src/notifications.rs +++ b/backend/src/notifications.rs @@ -12,7 +12,6 @@ use tracing::instrument; use crate::backup::BackupReport; use crate::context::RpcContext; -use crate::db::util::WithRevision; use crate::s9pk::manifest::PackageId; use crate::util::display_none; use crate::util::serde::display_serializable; @@ -29,7 +28,7 @@ pub async fn list( #[context] ctx: RpcContext, #[arg] before: Option, #[arg] limit: Option, -) -> Result>, Error> { +) -> Result, Error> { let limit = limit.unwrap_or(40); let mut handle = ctx.db.handle(); match before { @@ -72,11 +71,8 @@ pub async fn list( }) .collect::, Error>>()?; // set notification count to zero - let r = model.put(&mut handle, &0).await?; - Ok(WithRevision { - response: notifs, - revision: r, - }) + model.put(&mut handle, &0).await?; + Ok(notifs) } Some(before) => { let records = sqlx::query!( @@ -113,10 +109,7 @@ pub async fn list( }) }) .collect::, Error>>()?; - Ok(WithRevision { - response: res, - revision: None, - }) + Ok(res) } } } diff --git a/backend/src/setup.rs b/backend/src/setup.rs index 4ad8c7da2..3e654e35a 100644 --- a/backend/src/setup.rs +++ b/backend/src/setup.rs @@ -10,6 +10,7 @@ use digest::generic_array::GenericArray; use digest::OutputSizeUser; use futures::future::BoxFuture; use futures::{FutureExt, TryFutureExt, TryStreamExt}; +use josekit::jwk::Jwk; use nix::unistd::{Gid, Uid}; use openssl::x509::X509; use patch_db::{DbHandle, LockType}; @@ -37,7 +38,7 @@ use crate::disk::mount::filesystem::ReadOnly; use crate::disk::mount::guard::TmpMountGuard; use crate::disk::util::{pvscan, recovery_info, DiskListResponse, EmbassyOsRecoveryInfo}; use crate::disk::REPAIR_DISK_PATH; -use crate::hostname::PRODUCT_KEY_PATH; +use crate::hostname::{get_hostname, Hostname}; use crate::id::Id; use crate::init::init; use crate::install::PKG_PUBLIC_DIR; @@ -62,7 +63,7 @@ where Ok(password) } -#[command(subcommands(status, disk, attach, execute, recovery, cifs, complete))] +#[command(subcommands(status, disk, attach, execute, recovery, cifs, complete, get_secret))] pub fn setup() -> Result<(), Error> { Ok(()) } @@ -70,14 +71,12 @@ pub fn setup() -> Result<(), Error> { #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct StatusRes { - product_key: bool, migrating: bool, } #[command(rpc_only, metadata(authenticated = false))] pub async fn status(#[context] ctx: SetupContext) -> Result { Ok(StatusRes { - product_key: tokio::fs::metadata(PRODUCT_KEY_PATH).await.is_ok(), migrating: ctx.recovery_status.read().await.is_some(), }) } @@ -123,31 +122,7 @@ pub async fn attach( ErrorKind::DiskManagement, )); } - let product_key = ctx.product_key().await?; - let product_key_path = Path::new("/embassy-data/main/product_key.txt"); - if tokio::fs::metadata(product_key_path).await.is_ok() { - let pkey = Arc::new( - tokio::fs::read_to_string(product_key_path) - .await? - .trim() - .to_owned(), - ); - if pkey != product_key { - crate::disk::main::export(&*guid, &ctx.datadir).await?; - return Err(Error::new( - eyre!( - "The EmbassyOS product key does not match the supplied drive: {}", - pkey - ), - ErrorKind::ProductKeyMismatch, - )); - } - } - init( - &RpcContextConfig::load(ctx.config_path.as_ref()).await?, - &*product_key, - ) - .await?; + init(&RpcContextConfig::load(ctx.config_path.as_ref()).await?).await?; let secrets = ctx.secret_store().await?; let db = ctx.db(&secrets).await?; let mut secrets_handle = secrets.acquire().await?; @@ -168,16 +143,17 @@ pub async fn attach( let tor_key = crate::net::tor::os_key(&mut secrets_tx).await?; - db_tx.commit(None).await?; + db_tx.commit().await?; secrets_tx.commit().await?; + let hostname = get_hostname(&mut db_handle).await?; - let (_, root_ca) = SslManager::init(secrets).await?.export_root_ca().await?; + let (_, root_ca) = SslManager::init(secrets, &mut db_handle) + .await? + .export_root_ca() + .await?; let setup_result = SetupResult { tor_address: format!("http://{}", tor_key.public().get_onion_address()), - lan_address: format!( - "https://embassy-{}.local", - crate::hostname::derive_id(&*product_key) - ), + lan_address: hostname.lan_address(), root_ca: String::from_utf8(root_ca.to_pem()?)?, }; *ctx.setup_result.write().await = Some((guid, setup_result.clone())); @@ -222,6 +198,29 @@ pub async fn recovery_status( ctx.recovery_status.read().await.clone().transpose() } +/// We want to be able to get a secret, a shared private key with the frontend +/// This way the frontend can send a secret, like the password for the setup/ recovory +/// without knowing the password over clearnet. We use the public key shared across the network +/// since it is fine to share the public, and encrypt against the public. +#[command(rename = "get-secret", rpc_only, metadata(authenticated = false))] +pub async fn get_secret( + #[context] ctx: SetupContext, + #[arg] pubkey: Jwk, +) -> Result { + let secret = ctx.update_secret().await?; + let mut header = josekit::jwe::JweHeader::new(); + header.set_algorithm("ECDH-ES"); + header.set_content_encryption("A256GCM"); + + let encrypter = josekit::jwe::alg::ecdh_es::EcdhEsJweAlgorithm::EcdhEs + .encrypter_from_jwk(&pubkey) + .unwrap(); + + Ok(josekit::jwe::serialize_compact(secret.as_bytes(), &header, &encrypter).unwrap()) + // Need to encrypt from the public key sent + // then encode via hex +} + #[command(subcommands(verify_cifs))] pub fn cifs() -> Result<(), Error> { Ok(()) @@ -269,14 +268,11 @@ pub async fn execute( ) .await { - Ok((tor_addr, root_ca)) => { + Ok((hostname, tor_addr, root_ca)) => { tracing::info!("Setup Successful! Tor Address: {}", tor_addr); Ok(SetupResult { tor_address: format!("http://{}", tor_addr), - lan_address: format!( - "https://embassy-{}.local", - crate::hostname::derive_id(&ctx.product_key().await?) - ), + lan_address: hostname.lan_address(), root_ca: String::from_utf8(root_ca.to_pem()?)?, }) } @@ -299,33 +295,14 @@ pub async fn complete(#[context] ctx: SetupContext) -> Result, recovery_password: Option, -) -> Result<(OnionAddressV3, X509), Error> { +) -> Result<(Hostname, OnionAddressV3, X509), Error> { if ctx.recovery_status.read().await.is_some() { return Err(Error::new( eyre!("Cannot execute setup while in recovery!"), @@ -374,12 +351,11 @@ pub async fn execute_inner( recovery_password, ) .await?; - init( - &RpcContextConfig::load(ctx.config_path.as_ref()).await?, - &ctx.product_key().await?, - ) - .await?; - let res = (tor_addr, root_ca.clone()); + let db = init(&RpcContextConfig::load(ctx.config_path.as_ref()).await?) + .await? + .db; + let hostname = get_hostname(&mut db.handle()).await?; + let res = (hostname.clone(), tor_addr, root_ca.clone()); tokio::spawn(async move { if let Err(e) = recover_fut .and_then(|_| async { @@ -387,10 +363,7 @@ pub async fn execute_inner( guid, SetupResult { tor_address: format!("http://{}", tor_addr), - lan_address: format!( - "https://embassy-{}.local", - crate::hostname::derive_id(&ctx.product_key().await?) - ), + lan_address: hostname.lan_address(), root_ca: String::from_utf8(root_ca.to_pem()?)?, }, )); @@ -412,23 +385,19 @@ pub async fn execute_inner( res } else { let (tor_addr, root_ca) = fresh_setup(&ctx, &embassy_password).await?; - init( - &RpcContextConfig::load(ctx.config_path.as_ref()).await?, - &ctx.product_key().await?, - ) - .await?; + let db = init(&RpcContextConfig::load(ctx.config_path.as_ref()).await?) + .await? + .db; *ctx.setup_result.write().await = Some(( guid, SetupResult { tor_address: format!("http://{}", tor_addr), - lan_address: format!( - "https://embassy-{}.local", - crate::hostname::derive_id(&ctx.product_key().await?) - ), + lan_address: get_hostname(&mut db.handle()).await?.lan_address(), root_ca: String::from_utf8(root_ca.to_pem()?)?, }, )); - (tor_addr, root_ca) + let hostname = get_hostname(&mut db.handle()).await?; + (hostname, tor_addr, root_ca) }; Ok(res) @@ -455,7 +424,8 @@ async fn fresh_setup( ) .execute(&mut sqlite_pool.acquire().await?) .await?; - let (_, root_ca) = SslManager::init(sqlite_pool.clone()) + let db = ctx.db(&sqlite_pool).await?; + let (_, root_ca) = SslManager::init(sqlite_pool.clone(), &mut db.handle()) .await? .export_root_ca() .await?; diff --git a/backend/src/update/mod.rs b/backend/src/update/mod.rs index 22ef0faa8..d48c13a21 100644 --- a/backend/src/update/mod.rs +++ b/backend/src/update/mod.rs @@ -24,7 +24,6 @@ use tracing::instrument; use crate::context::RpcContext; use crate::db::model::UpdateProgress; -use crate::db::util::WithRevision; use crate::disk::mount::filesystem::block_dev::BlockDev; use crate::disk::mount::filesystem::{FileSystem, ReadWrite}; use crate::disk::mount::guard::TmpMountGuard; @@ -46,26 +45,24 @@ lazy_static! { /// An user/ daemon would call this to update the system to the latest version and do the updates available, /// and this will return something if there is an update, and in that case there will need to be a restart. -#[command(rename = "update", display(display_update_result))] +#[command( + rename = "update", + display(display_update_result), + metadata(sync_db = true) +)] #[instrument(skip(ctx))] pub async fn update_system( #[context] ctx: RpcContext, #[arg(rename = "marketplace-url")] marketplace_url: Url, -) -> Result, Error> { - let noop = WithRevision { - response: UpdateResult::NoUpdates, - revision: None, - }; +) -> Result { if UPDATED.load(Ordering::SeqCst) { - return Ok(noop); - } - match maybe_do_update(ctx, marketplace_url).await? { - None => Ok(noop), - Some(r) => Ok(WithRevision { - response: UpdateResult::Updating, - revision: Some(r), - }), + return Ok(UpdateResult::NoUpdates); } + Ok(if maybe_do_update(ctx, marketplace_url).await?.is_some() { + UpdateResult::Updating + } else { + UpdateResult::NoUpdates + }) } /// What is the status of the updates? @@ -76,8 +73,8 @@ pub enum UpdateResult { Updating, } -fn display_update_result(status: WithRevision, _: &ArgMatches) { - match status.response { +fn display_update_result(status: UpdateResult, _: &ArgMatches) { + match status { UpdateResult::Updating => { println!("Updating..."); } @@ -190,7 +187,7 @@ async fn maybe_do_update( downloaded: 0, }); status.save(&mut tx).await?; - let rev = tx.commit(None).await?; + let rev = tx.commit().await?; tokio::spawn(async move { let mut db = ctx.db.handle(); diff --git a/backend/src/version/mod.rs b/backend/src/version/mod.rs index d40be7fe9..afe94eacb 100644 --- a/backend/src/version/mod.rs +++ b/backend/src/version/mod.rs @@ -14,8 +14,9 @@ mod v0_3_0_2; mod v0_3_0_3; mod v0_3_1; mod v0_3_1_1; +mod v0_3_1_2; -pub type Current = v0_3_1_1::Version; +pub type Current = v0_3_1_2::Version; #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(untagged)] @@ -26,6 +27,7 @@ enum Version { V0_3_0_3(Wrapper), V0_3_1(Wrapper), V0_3_1_1(Wrapper), + V0_3_1_2(Wrapper), Other(emver::Version), } @@ -47,6 +49,7 @@ impl Version { Version::V0_3_0_3(Wrapper(x)) => x.semver(), Version::V0_3_1(Wrapper(x)) => x.semver(), Version::V0_3_1_1(Wrapper(x)) => x.semver(), + Version::V0_3_1_2(Wrapper(x)) => x.semver(), Version::Other(x) => x.clone(), } } @@ -179,6 +182,7 @@ pub async fn init( Version::V0_3_0_3(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, Version::V0_3_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, Version::V0_3_1_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, + Version::V0_3_1_2(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, Version::Other(_) => { return Err(Error::new( eyre!("Cannot downgrade"), diff --git a/backend/src/version/v0_3_1_2.rs b/backend/src/version/v0_3_1_2.rs new file mode 100644 index 000000000..9bf66efe0 --- /dev/null +++ b/backend/src/version/v0_3_1_2.rs @@ -0,0 +1,61 @@ +use emver::VersionRange; + +use crate::hostname::{generate_id, get_hostname, sync_hostname}; + +use super::v0_3_0::V0_3_0_COMPAT; +use super::*; + +const V0_3_1_2: emver::Version = emver::Version::new(0, 3, 1, 2); + +#[derive(Clone, Debug)] +pub struct Version; +#[async_trait] +impl VersionT for Version { + type Previous = v0_3_1_1::Version; + fn new() -> Self { + Version + } + fn semver(&self) -> emver::Version { + V0_3_1_2 + } + fn compat(&self) -> &'static VersionRange { + &*V0_3_0_COMPAT + } + async fn up(&self, db: &mut Db) -> Result<(), Error> { + let hostname = get_hostname(db).await?; + crate::db::DatabaseModel::new() + .server_info() + .hostname() + .put(db, &Some(hostname.0)) + .await?; + crate::db::DatabaseModel::new() + .server_info() + .id() + .put(db, &generate_id()) + .await?; + + sync_hostname(db).await?; + let mut ui = crate::db::DatabaseModel::new() + .ui() + .get(db, false) + .await? + .clone(); + if let serde_json::Value::Object(ref mut ui) = ui { + ui.insert("ack-instructions".to_string(), serde_json::json!({})); + } + crate::db::DatabaseModel::new().ui().put(db, &ui).await?; + Ok(()) + } + async fn down(&self, db: &mut Db) -> Result<(), Error> { + let mut ui = crate::db::DatabaseModel::new() + .ui() + .get(db, false) + .await? + .clone(); + if let serde_json::Value::Object(ref mut ui) = ui { + ui.remove("ack-instructions"); + } + crate::db::DatabaseModel::new().ui().put(db, &ui).await?; + Ok(()) + } +} diff --git a/build/initialization.service b/build/initialization.service index f4514e4bf..611f81f25 100644 --- a/build/initialization.service +++ b/build/initialization.service @@ -4,8 +4,6 @@ After=network-online.target systemd-time-wait-sync.service [Service] Type=oneshot -Restart=on-failure -RestartSec=5s ExecStart=/usr/local/bin/initialization.sh RemainAfterExit=true StandardOutput=append:/var/log/initialization.log diff --git a/build/initialization.sh b/build/initialization.sh index 00a51da31..65edd4b6b 100755 --- a/build/initialization.sh +++ b/build/initialization.sh @@ -1,7 +1,15 @@ #!/bin/bash -# Update repositories, install dependencies, do some initial configurations, set hostname, enable embassy-init, and config Tor -set -e +function flatline { + echo -n "0" > /sys/class/pwm/pwmchip0/export + sleep 0.5 + echo -n "2272727" > /sys/class/pwm/pwmchip0/pwm0/period + echo -n "1136364" > /sys/class/pwm/pwmchip0/pwm0/duty_cycle + echo -n "1" > /sys/class/pwm/pwmchip0/pwm0/enable + exit 1 +} + +(set -e # introduce start9 username and embassy as default password if ! awk -F: '{ print $1 }' /etc/passwd | grep start9 @@ -146,9 +154,10 @@ systemctl disable nc-broadcast.service systemctl disable initialization.service sudo systemctl restart NetworkManager -sync - echo "fs.inotify.max_user_watches=1048576" > /etc/sysctl.d/97-embassy.conf -# TODO: clean out ssh host keys +sync + reboot +) || flatline + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 89cb2e22e..d7876917f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,11 +29,13 @@ "dompurify": "^2.3.6", "fast-json-patch": "^3.1.1", "fuse.js": "^6.4.6", + "jose": "^4.9.0", "js-yaml": "^4.1.0", "marked": "^4.0.0", "monaco-editor": "^0.33.0", "mustache": "^4.2.0", "ng-qrcode": "^7.0.0", + "node-jose": "^2.1.1", "patch-db-client": "file: ../../../patch-db/client", "pbkdf2": "^3.1.2", "rxjs": "^7.5.6", @@ -56,6 +58,7 @@ "@types/marked": "^4.0.3", "@types/mustache": "^4.1.2", "@types/node": "^16.9.1", + "@types/node-jose": "^1.1.10", "@types/pbkdf2": "^3.1.0", "@types/uuid": "^8.3.1", "husky": "^4.3.8", @@ -3721,6 +3724,15 @@ "integrity": "sha512-aFcUkv7EddxxOa/9f74DINReQ/celqH8DiB3fRYgVDM2Xm5QJL8sl80QKuAnGvwAsMn+H3IFA6WCrQh1CY7m1A==", "dev": true }, + "node_modules/@types/node-jose": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.10.tgz", + "integrity": "sha512-7L0ucJTugW4x/sYpQ+c5IudAwr0pFuxDVnZLpHKWpff7p1lVa3wTuNvnrzFBNeLojE+UY0cVCwNGXLxXsMIrzw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -4469,7 +4481,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -4485,6 +4496,14 @@ } ] }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6127,6 +6146,11 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "node_modules/esbuild": { "version": "0.15.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.5.tgz", @@ -7860,7 +7884,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -8418,6 +8441,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.9.0.tgz", + "integrity": "sha512-RgaqEOZLkVO+ViN3KkN44XJt9g7+wMveUv59sVLaTxONcUPc8ZpfqOCeLphVBZyih2dgkvZ0Ap1CNcokvY7Uyw==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8866,8 +8897,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash._baseassign": { "version": "3.2.0", @@ -9111,6 +9141,11 @@ "node": ">=8" } }, + "node_modules/long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + }, "node_modules/lru-cache": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", @@ -9731,7 +9766,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, "engines": { "node": ">= 6.13.0" } @@ -9824,6 +9858,50 @@ "he": "1.2.0" } }, + "node_modules/node-jose": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.1.1.tgz", + "integrity": "sha512-19nyuUGShNmFmVTeqDfP6ZJCiikbcjI0Pw2kykBCH7rl8AZgSiDZK2Ww8EDaMrOSbRg6IlfIMhI5ZvCklmOhzg==", + "dependencies": { + "base64url": "^3.0.1", + "buffer": "^6.0.3", + "es6-promise": "^4.2.8", + "lodash": "^4.17.21", + "long": "^5.2.0", + "node-forge": "^1.2.1", + "pako": "^2.0.4", + "process": "^0.11.10", + "uuid": "^8.3.2" + } + }, + "node_modules/node-jose/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/node-jose/node_modules/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, "node_modules/node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", @@ -11464,6 +11542,14 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -17246,6 +17332,15 @@ "integrity": "sha512-aFcUkv7EddxxOa/9f74DINReQ/celqH8DiB3fRYgVDM2Xm5QJL8sl80QKuAnGvwAsMn+H3IFA6WCrQh1CY7m1A==", "dev": true }, + "@types/node-jose": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.10.tgz", + "integrity": "sha512-7L0ucJTugW4x/sYpQ+c5IudAwr0pFuxDVnZLpHKWpff7p1lVa3wTuNvnrzFBNeLojE+UY0cVCwNGXLxXsMIrzw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -17863,8 +17958,12 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" }, "batch": { "version": "0.6.1", @@ -19096,6 +19195,11 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "esbuild": { "version": "0.15.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.5.tgz", @@ -20296,8 +20400,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "5.2.0", @@ -20703,6 +20806,11 @@ } } }, + "jose": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.9.0.tgz", + "integrity": "sha512-RgaqEOZLkVO+ViN3KkN44XJt9g7+wMveUv59sVLaTxONcUPc8ZpfqOCeLphVBZyih2dgkvZ0Ap1CNcokvY7Uyw==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -21035,8 +21143,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash._baseassign": { "version": "3.2.0", @@ -21239,6 +21346,11 @@ } } }, + "long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + }, "lru-cache": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", @@ -21697,8 +21809,7 @@ "node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, "node-gyp": { "version": "9.1.0", @@ -21770,6 +21881,38 @@ "he": "1.2.0" } }, + "node-jose": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.1.1.tgz", + "integrity": "sha512-19nyuUGShNmFmVTeqDfP6ZJCiikbcjI0Pw2kykBCH7rl8AZgSiDZK2Ww8EDaMrOSbRg6IlfIMhI5ZvCklmOhzg==", + "requires": { + "base64url": "^3.0.1", + "buffer": "^6.0.3", + "es6-promise": "^4.2.8", + "lodash": "^4.17.21", + "long": "^5.2.0", + "node-forge": "^1.2.1", + "pako": "^2.0.4", + "process": "^0.11.10", + "uuid": "^8.3.2" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + } + } + }, "node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", @@ -22889,6 +23032,11 @@ "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5ad1fdaff..5b6304df5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,11 +43,13 @@ "dompurify": "^2.3.6", "fast-json-patch": "^3.1.1", "fuse.js": "^6.4.6", + "jose": "^4.9.0", "js-yaml": "^4.1.0", "marked": "^4.0.0", "monaco-editor": "^0.33.0", "mustache": "^4.2.0", "ng-qrcode": "^7.0.0", + "node-jose": "^2.1.1", "patch-db-client": "file: ../../../patch-db/client", "pbkdf2": "^3.1.2", "rxjs": "^7.5.6", @@ -70,6 +72,7 @@ "@types/marked": "^4.0.3", "@types/mustache": "^4.1.2", "@types/node": "^16.9.1", + "@types/node-jose": "^1.1.10", "@types/pbkdf2": "^3.1.0", "@types/uuid": "^8.3.1", "husky": "^4.3.8", diff --git a/frontend/projects/diagnostic-ui/src/app/pages/home/home.page.ts b/frontend/projects/diagnostic-ui/src/app/pages/home/home.page.ts index 6009c4a32..c91ed099b 100644 --- a/frontend/projects/diagnostic-ui/src/app/pages/home/home.page.ts +++ b/frontend/projects/diagnostic-ui/src/app/pages/home/home.page.ts @@ -156,7 +156,6 @@ export class HomePage { console.error(e) } }, - cssClass: 'enter-click', }, ], cssClass: 'alert-warning-message', diff --git a/frontend/projects/diagnostic-ui/src/app/services/api/live-api.service.ts b/frontend/projects/diagnostic-ui/src/app/services/api/live-api.service.ts index b61a97ac9..ca0794745 100644 --- a/frontend/projects/diagnostic-ui/src/app/services/api/live-api.service.ts +++ b/frontend/projects/diagnostic-ui/src/app/services/api/live-api.service.ts @@ -1,46 +1,61 @@ import { Injectable } from '@angular/core' -import { HttpService } from '@start9labs/shared' +import { + HttpService, + isRpcError, + RpcError, + RPCOptions, +} from '@start9labs/shared' import { ApiService, GetErrorRes } from './api.service' import { LogsRes, ServerLogsReq } from '@start9labs/shared' @Injectable() -export class LiveApiService extends ApiService { - constructor(private readonly http: HttpService) { - super() - } +export class LiveApiService implements ApiService { + constructor(private readonly http: HttpService) {} getError(): Promise { - return this.http.rpcRequest({ + return this.rpcRequest({ method: 'diagnostic.error', params: {}, }) } restart(): Promise { - return this.http.rpcRequest({ + return this.rpcRequest({ method: 'diagnostic.restart', params: {}, }) } forgetDrive(): Promise { - return this.http.rpcRequest({ + return this.rpcRequest({ method: 'diagnostic.disk.forget', params: {}, }) } repairDisk(): Promise { - return this.http.rpcRequest({ + return this.rpcRequest({ method: 'diagnostic.disk.repair', params: {}, }) } getLogs(params: ServerLogsReq): Promise { - return this.http.rpcRequest({ + return this.rpcRequest({ method: 'diagnostic.logs', params, }) } + + private async rpcRequest(opts: RPCOptions): Promise { + const res = await this.http.rpcRequest(opts) + + const rpcRes = res.body + + if (isRpcError(rpcRes)) { + throw new RpcError(rpcRes.error) + } + + return rpcRes.result + } } diff --git a/frontend/projects/diagnostic-ui/src/app/services/api/mock-api.service.ts b/frontend/projects/diagnostic-ui/src/app/services/api/mock-api.service.ts index b99e0979d..848915a49 100644 --- a/frontend/projects/diagnostic-ui/src/app/services/api/mock-api.service.ts +++ b/frontend/projects/diagnostic-ui/src/app/services/api/mock-api.service.ts @@ -4,11 +4,7 @@ import { ApiService, GetErrorRes } from './api.service' import { LogsRes, ServerLogsReq, Log } from '@start9labs/shared' @Injectable() -export class MockApiService extends ApiService { - constructor() { - super() - } - +export class MockApiService implements ApiService { async getError(): Promise { await pauseFor(1000) return { diff --git a/frontend/projects/marketplace/src/pages/show/additional/additional.component.ts b/frontend/projects/marketplace/src/pages/show/additional/additional.component.ts index 3c9a415b3..ba38cc80b 100644 --- a/frontend/projects/marketplace/src/pages/show/additional/additional.component.ts +++ b/frontend/projects/marketplace/src/pages/show/additional/additional.component.ts @@ -50,7 +50,6 @@ export class AdditionalComponent { { text: 'Ok', handler: (version: string) => this.version.emit(version), - cssClass: 'enter-click', }, ], }) diff --git a/frontend/projects/setup-wizard/src/app/app-routing.module.ts b/frontend/projects/setup-wizard/src/app/app-routing.module.ts index 9e35ea007..c78bc431c 100644 --- a/frontend/projects/setup-wizard/src/app/app-routing.module.ts +++ b/frontend/projects/setup-wizard/src/app/app-routing.module.ts @@ -1,45 +1,32 @@ import { NgModule } from '@angular/core' import { PreloadAllModules, RouterModule, Routes } from '@angular/router' -import { NavGuard, RecoveryNavGuard } from './guards/nav-guard' const routes: Routes = [ - { path: '', redirectTo: '/product-key', pathMatch: 'full' }, - { - path: 'product-key', - loadChildren: () => - import('./pages/product-key/product-key.module').then( - m => m.ProductKeyPageModule, - ), - }, + { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', loadChildren: () => import('./pages/home/home.module').then(m => m.HomePageModule), - canActivate: [NavGuard], }, { path: 'recover', loadChildren: () => import('./pages/recover/recover.module').then(m => m.RecoverPageModule), - canActivate: [RecoveryNavGuard], }, { path: 'embassy', loadChildren: () => import('./pages/embassy/embassy.module').then(m => m.EmbassyPageModule), - canActivate: [NavGuard], }, { path: 'loading', loadChildren: () => import('./pages/loading/loading.module').then(m => m.LoadingPageModule), - canActivate: [NavGuard], }, { path: 'success', loadChildren: () => import('./pages/success/success.module').then(m => m.SuccessPageModule), - canActivate: [NavGuard], }, ] diff --git a/frontend/projects/setup-wizard/src/app/app.component.ts b/frontend/projects/setup-wizard/src/app/app.component.ts index 9914d0c41..5af215d99 100644 --- a/frontend/projects/setup-wizard/src/app/app.component.ts +++ b/frontend/projects/setup-wizard/src/app/app.component.ts @@ -2,7 +2,6 @@ import { Component } from '@angular/core' import { NavController } from '@ionic/angular' import { ApiService } from './services/api/api.service' import { ErrorToastService } from '@start9labs/shared' -import { StateService } from './services/state.service' @Component({ selector: 'app-root', @@ -14,21 +13,12 @@ export class AppComponent { private readonly apiService: ApiService, private readonly errorToastService: ErrorToastService, private readonly navCtrl: NavController, - private readonly stateService: StateService, ) {} async ngOnInit() { try { - const status = await this.apiService.getStatus() - if (status.migrating || status['product-key']) { - this.stateService.hasProductKey = true - this.stateService.isMigrating = status.migrating - await this.navCtrl.navigateForward(`/product-key`) - } else { - this.stateService.hasProductKey = false - this.stateService.isMigrating = false - await this.navCtrl.navigateForward(`/recover`) - } + const { migrating } = await this.apiService.getStatus() + await this.navCtrl.navigateForward(migrating ? '/loading' : '/home') } catch (e: any) { this.errorToastService.present(e) } diff --git a/frontend/projects/setup-wizard/src/app/app.module.ts b/frontend/projects/setup-wizard/src/app/app.module.ts index 7aa804e78..097710642 100644 --- a/frontend/projects/setup-wizard/src/app/app.module.ts +++ b/frontend/projects/setup-wizard/src/app/app.module.ts @@ -1,4 +1,4 @@ -import { ErrorHandler, NgModule } from '@angular/core' +import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { RouteReuseStrategy } from '@angular/router' import { HttpClientModule } from '@angular/common/http' @@ -15,8 +15,6 @@ import { AppRoutingModule } from './app-routing.module' import { SuccessPageModule } from './pages/success/success.module' import { HomePageModule } from './pages/home/home.module' import { LoadingPageModule } from './pages/loading/loading.module' -import { ProdKeyModalModule } from './modals/prod-key-modal/prod-key-modal.module' -import { ProductKeyPageModule } from './pages/product-key/product-key.module' import { RecoverPageModule } from './pages/recover/recover.module' import { WorkspaceConfig } from '@start9labs/shared' @@ -35,8 +33,6 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig SuccessPageModule, HomePageModule, LoadingPageModule, - ProdKeyModalModule, - ProductKeyPageModule, RecoverPageModule, ], providers: [ diff --git a/frontend/projects/setup-wizard/src/app/guards/nav-guard.ts b/frontend/projects/setup-wizard/src/app/guards/nav-guard.ts deleted file mode 100644 index 3eafa03f6..000000000 --- a/frontend/projects/setup-wizard/src/app/guards/nav-guard.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Injectable } from '@angular/core' -import { CanActivate, Router } from '@angular/router' -import { RPCEncryptedService } from '../services/rpc-encrypted.service' -import { StateService } from '../services/state.service' - -@Injectable({ - providedIn: 'root', -}) -export class NavGuard implements CanActivate { - constructor( - private readonly router: Router, - private readonly encrypted: RPCEncryptedService, - ) {} - - canActivate(): boolean { - if (this.encrypted.productKey) { - return true - } else { - this.router.navigateByUrl('product-key') - return false - } - } -} - -@Injectable({ - providedIn: 'root', -}) -export class RecoveryNavGuard implements CanActivate { - constructor( - private readonly router: Router, - private readonly encrypted: RPCEncryptedService, - private readonly stateService: StateService, - ) {} - - canActivate(): boolean { - if (this.encrypted.productKey || !this.stateService.hasProductKey) { - return true - } else { - this.router.navigateByUrl('product-key') - return false - } - } -} diff --git a/frontend/projects/setup-wizard/src/app/modals/password/password.page.ts b/frontend/projects/setup-wizard/src/app/modals/password/password.page.ts index 2005bd02c..fea991965 100644 --- a/frontend/projects/setup-wizard/src/app/modals/password/password.page.ts +++ b/frontend/projects/setup-wizard/src/app/modals/password/password.page.ts @@ -1,7 +1,6 @@ import { Component, Input, ViewChild } from '@angular/core' import { IonInput, ModalController } from '@ionic/angular' import { - DiskInfo, CifsBackupTarget, DiskBackupTarget, } from 'src/app/services/api/api.service' @@ -15,7 +14,7 @@ import * as argon2 from '@start9labs/argon2' export class PasswordPage { @ViewChild('focusInput') elem?: IonInput @Input() target?: CifsBackupTarget | DiskBackupTarget - @Input() storageDrive?: DiskInfo + @Input() storageDrive = false pwError = '' password = '' diff --git a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.module.ts b/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.module.ts deleted file mode 100644 index bf5c17901..000000000 --- a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { FormsModule } from '@angular/forms' -import { ProdKeyModal } from './prod-key-modal.page' - -@NgModule({ - declarations: [ - ProdKeyModal, - ], - imports: [ - CommonModule, - FormsModule, - IonicModule, - ], - exports: [ - ProdKeyModal, - ], -}) -export class ProdKeyModalModule { } diff --git a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.html b/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.html deleted file mode 100644 index 23f39404b..000000000 --- a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - Enter Product Key - - - - - -
-
-
-

Enter your 0.2.x Product Key to establish an encrypted connection with your new Embassy.

-
- - - -
-

{{ error }}

-
-
- -
-
- - - - - Cancel - - - Submit - - - - diff --git a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.scss b/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.scss deleted file mode 100644 index ac7528a0e..000000000 --- a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -ion-content { - --ion-text-color: var(--ion-color-dark); -} \ No newline at end of file diff --git a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.ts b/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.ts deleted file mode 100644 index 87a265dea..000000000 --- a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Component, Input, ViewChild } from '@angular/core' -import { IonInput, LoadingController, ModalController } from '@ionic/angular' -import { ApiService, DiskBackupTarget } from 'src/app/services/api/api.service' -import { RPCEncryptedService } from 'src/app/services/rpc-encrypted.service' - -@Component({ - selector: 'prod-key-modal', - templateUrl: 'prod-key-modal.page.html', - styleUrls: ['prod-key-modal.page.scss'], -}) -export class ProdKeyModal { - @ViewChild('focusInput') elem?: IonInput - @Input() target!: DiskBackupTarget - - error = '' - productKey = '' - unmasked = false - - constructor( - private readonly modalController: ModalController, - private readonly apiService: ApiService, - private readonly loadingCtrl: LoadingController, - private readonly encrypted: RPCEncryptedService, - ) {} - - ngAfterViewInit() { - setTimeout(() => this.elem?.setFocus(), 400) - } - - async verifyProductKey() { - if (!this.productKey || !this.target.logicalname) return - - const loader = await this.loadingCtrl.create({ - message: 'Verifying Product Key', - }) - await loader.present() - - try { - await this.apiService.set02XDrive(this.target.logicalname) - this.encrypted.productKey = this.productKey - await this.apiService.verifyProductKey() - this.modalController.dismiss({ productKey: this.productKey }, 'success') - } catch (e) { - this.encrypted.productKey = undefined - this.error = 'Invalid Product Key' - } finally { - loader.dismiss() - } - } - - cancel() { - this.modalController.dismiss() - } -} diff --git a/frontend/projects/setup-wizard/src/app/pages/embassy/embassy.page.ts b/frontend/projects/setup-wizard/src/app/pages/embassy/embassy.page.ts index cf6b451f2..727cd602b 100644 --- a/frontend/projects/setup-wizard/src/app/pages/embassy/embassy.page.ts +++ b/frontend/projects/setup-wizard/src/app/pages/embassy/embassy.page.ts @@ -64,12 +64,7 @@ export class EmbassyPage { const alert = await this.alertCtrl.create({ header: 'Warning', message: `One or more devices you connected had to be reconfigured to support the current hardware platform. Please unplug and replug the following device(s), then refresh the page:
${list}`, - buttons: [ - { - role: 'cancel', - text: 'OK', - }, - ], + buttons: ['OK'], }) await alert.present() } @@ -95,40 +90,45 @@ export class EmbassyPage { text: 'Continue', handler: () => { if (this.stateService.recoveryPassword) { - this.setupEmbassy(drive, this.stateService.recoveryPassword) + this.setupEmbassy( + drive.logicalname, + this.stateService.recoveryPassword, + ) } else { - this.presentModalPassword(drive) + this.presentModalPassword(drive.logicalname) } }, - cssClass: 'enter-click', }, ], }) await alert.present() } else { if (this.stateService.recoveryPassword) { - this.setupEmbassy(drive, this.stateService.recoveryPassword) + this.setupEmbassy(drive.logicalname, this.stateService.recoveryPassword) } else { - this.presentModalPassword(drive) + this.presentModalPassword(drive.logicalname) } } } - private async presentModalPassword(drive: DiskInfo): Promise { + private async presentModalPassword(logicalname: string): Promise { const modal = await this.modalController.create({ component: PasswordPage, componentProps: { - storageDrive: drive, + storageDrive: true, }, }) modal.onDidDismiss().then(async ret => { if (!ret.data || !ret.data.password) return - this.setupEmbassy(drive, ret.data.password) + this.setupEmbassy(logicalname, ret.data.password) }) await modal.present() } - private async setupEmbassy(drive: DiskInfo, password: string): Promise { + private async setupEmbassy( + logicalname: string, + password: string, + ): Promise { const loader = await this.loadingCtrl.create({ message: 'Initializing data drive. This could take a while...', }) @@ -136,7 +136,7 @@ export class EmbassyPage { await loader.present() try { - await this.stateService.setupEmbassy(drive.logicalname, password) + await this.stateService.setupEmbassy(logicalname, password) if (!!this.stateService.recoverySource) { await this.navCtrl.navigateForward(`/loading`) } else { diff --git a/frontend/projects/setup-wizard/src/app/pages/home/home.module.ts b/frontend/projects/setup-wizard/src/app/pages/home/home.module.ts index e1ab32d7e..306796314 100644 --- a/frontend/projects/setup-wizard/src/app/pages/home/home.module.ts +++ b/frontend/projects/setup-wizard/src/app/pages/home/home.module.ts @@ -4,9 +4,8 @@ import { IonicModule } from '@ionic/angular' import { FormsModule } from '@angular/forms' import { HomePage } from './home.page' import { PasswordPageModule } from '../../modals/password/password.module' - import { HomePageRoutingModule } from './home-routing.module' - +import { SwiperModule } from 'swiper/angular' @NgModule({ imports: [ @@ -15,7 +14,8 @@ import { HomePageRoutingModule } from './home-routing.module' IonicModule, HomePageRoutingModule, PasswordPageModule, + SwiperModule, ], declarations: [HomePage], }) -export class HomePageModule { } +export class HomePageModule {} diff --git a/frontend/projects/setup-wizard/src/app/pages/home/home.page.html b/frontend/projects/setup-wizard/src/app/pages/home/home.page.html index e8669ac68..6d20433a0 100644 --- a/frontend/projects/setup-wizard/src/app/pages/home/home.page.html +++ b/frontend/projects/setup-wizard/src/app/pages/home/home.page.html @@ -2,36 +2,78 @@ - -
- +
+
- - - + - - Start Fresh - Get started with a brand new Embassy - - - - - - - Recover - Restore from backup or recover an old Embassy - - + + + + Embassy Setup + (recover) + + + + + + + + +

Start Fresh

+

Get started with a brand new Embassy

+
+
+ + + +

Recover

+

+ Restore from backup or use an existing Embassy data drive +

+
+
+
+ + + + +

+ Restore From Backup +

+

Recover an Embassy from encrypted backup

+
+
+ + + +

+ Use Existing Drive +

+

Attach and use a valid Embassy data drive

+
+
+
+
diff --git a/frontend/projects/setup-wizard/src/app/pages/home/home.page.scss b/frontend/projects/setup-wizard/src/app/pages/home/home.page.scss index e69de29bb..96fa4fa70 100644 --- a/frontend/projects/setup-wizard/src/app/pages/home/home.page.scss +++ b/frontend/projects/setup-wizard/src/app/pages/home/home.page.scss @@ -0,0 +1,15 @@ +.back-button { + position: absolute; + left: 16px; + top: 24px; + z-index: 1000000; +} + +ion-item { + --background: var(--ion-color-medium); + --color: var(--ion-color-dark); +} + +p { + color: var(--ion-color-dark); +} \ No newline at end of file diff --git a/frontend/projects/setup-wizard/src/app/pages/home/home.page.ts b/frontend/projects/setup-wizard/src/app/pages/home/home.page.ts index 02cbc27ad..17514b4a4 100644 --- a/frontend/projects/setup-wizard/src/app/pages/home/home.page.ts +++ b/frontend/projects/setup-wizard/src/app/pages/home/home.page.ts @@ -1,9 +1,112 @@ import { Component } from '@angular/core' +import { + AlertController, + IonicSlides, + LoadingController, + ModalController, + NavController, +} from '@ionic/angular' +import { PasswordPage } from 'src/app/modals/password/password.page' +import { ApiService } from 'src/app/services/api/api.service' +import { RPCEncryptedService } from 'src/app/services/rpc-encrypted.service' +import { StateService } from 'src/app/services/state.service' +import SwiperCore, { Swiper } from 'swiper' +import { ErrorToastService } from '@start9labs/shared' + +SwiperCore.use([IonicSlides]) @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'], }) -export class HomePage { } +export class HomePage { + swiper?: Swiper + guid?: string | null + error = false + constructor( + private readonly unencrypted: ApiService, + private readonly encrypted: RPCEncryptedService, + private readonly modalCtrl: ModalController, + private readonly alertCtrl: AlertController, + private readonly loadingCtrl: LoadingController, + private readonly stateService: StateService, + private readonly navCtrl: NavController, + private readonly errToastService: ErrorToastService, + ) {} + + async ngOnInit() { + try { + this.encrypted.secret = await this.unencrypted.getSecret() + const { disks } = await this.unencrypted.getDrives() + this.guid = disks.find(d => !!d.guid)?.guid + } catch (e: any) { + this.error = true + this.errToastService.present(e) + } + } + + async ionViewDidEnter() { + if (this.swiper) { + this.swiper.allowTouchMove = false + } + } + + setSwiperInstance(swiper: any) { + this.swiper = swiper + } + + next() { + this.swiper?.slideNext(500) + } + + previous() { + this.swiper?.slidePrev(500) + } + + async import() { + if (this.guid) { + const modal = await this.modalCtrl.create({ + component: PasswordPage, + componentProps: { storageDrive: true }, + }) + modal.onDidDismiss().then(res => { + if (res.data && res.data.password) { + this.importDrive(res.data.password) + } + }) + await modal.present() + } else { + const alert = await this.alertCtrl.create({ + header: 'Drive Not Found', + message: + 'Please make sure the drive is a valid Embassy data drive (not a backup) and is firmly connected, then refresh the page.', + }) + await alert.present() + } + } + + private async importDrive(password: string) { + const loader = await this.loadingCtrl.create({ + message: 'Importing Drive', + }) + await loader.present() + try { + await this.stateService.importDrive(this.guid!, password) + await this.navCtrl.navigateForward(`/success`) + } catch (e: any) { + this.errToastService.present(e) + } finally { + loader.dismiss() + } + } +} + +function decodeHex(hex: string) { + let str = '' + for (let n = 0; n < hex.length; n += 2) { + str += String.fromCharCode(parseInt(hex.substring(n, 2), 16)) + } + return str +} diff --git a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key-routing.module.ts b/frontend/projects/setup-wizard/src/app/pages/product-key/product-key-routing.module.ts deleted file mode 100644 index 2eb03b0ad..000000000 --- a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key-routing.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { ProductKeyPage } from './product-key.page' - -const routes: Routes = [ - { - path: '', - component: ProductKeyPage, - }, -] - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class ProductKeyPageRoutingModule { } diff --git a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.module.ts b/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.module.ts deleted file mode 100644 index fc5680353..000000000 --- a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { FormsModule } from '@angular/forms' -import { ProductKeyPage } from './product-key.page' -import { PasswordPageModule } from '../../modals/password/password.module' -import { ProductKeyPageRoutingModule } from './product-key-routing.module' - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - IonicModule, - ProductKeyPageRoutingModule, - PasswordPageModule, - ], - declarations: [ProductKeyPage], -}) -export class ProductKeyPageModule { } diff --git a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.html b/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.html deleted file mode 100644 index 2c587e824..000000000 --- a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - -
- -
- - - - Product Key - Enter your product key to establish an encrypted connection with your Embassy - - - -
- - - - - - -
-

{{ error }}

-
-
- - Submit - -
-
-
-
-
-
-
diff --git a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.scss b/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.scss deleted file mode 100644 index e161e4ff5..000000000 --- a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.scss +++ /dev/null @@ -1,5 +0,0 @@ -ion-item { - --border-style: solid; - --border-width: 1px; - --border-color: var(--ion-color-medium); -} \ No newline at end of file diff --git a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.ts b/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.ts deleted file mode 100644 index f0e73bca3..000000000 --- a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Component, ViewChild } from '@angular/core' -import { IonInput, LoadingController, NavController } from '@ionic/angular' -import { ApiService } from 'src/app/services/api/api.service' -import { RPCEncryptedService } from 'src/app/services/rpc-encrypted.service' -import { StateService } from 'src/app/services/state.service' - -@Component({ - selector: 'app-product-key', - templateUrl: 'product-key.page.html', - styleUrls: ['product-key.page.scss'], -}) -export class ProductKeyPage { - @ViewChild('focusInput') elem?: IonInput - productKey = '' - error = '' - - constructor( - private readonly navCtrl: NavController, - private readonly stateService: StateService, - private readonly apiService: ApiService, - private readonly loadingCtrl: LoadingController, - private readonly encrypted: RPCEncryptedService, - ) {} - - ionViewDidEnter() { - setTimeout(() => this.elem?.setFocus(), 400) - } - - async submit() { - if (!this.productKey) return (this.error = 'Must enter product key') - - const loader = await this.loadingCtrl.create({ - message: 'Verifying Product Key', - }) - await loader.present() - - try { - this.encrypted.productKey = this.productKey - await this.apiService.verifyProductKey() - if (this.stateService.isMigrating) { - await this.navCtrl.navigateForward(`/loading`) - } else { - await this.navCtrl.navigateForward(`/home`) - } - } catch (e) { - this.error = 'Invalid Product Key' - this.encrypted.productKey = undefined - } finally { - loader.dismiss() - } - } -} diff --git a/frontend/projects/setup-wizard/src/app/pages/recover/recover.module.ts b/frontend/projects/setup-wizard/src/app/pages/recover/recover.module.ts index eee8c35d1..eaf04f506 100644 --- a/frontend/projects/setup-wizard/src/app/pages/recover/recover.module.ts +++ b/frontend/projects/setup-wizard/src/app/pages/recover/recover.module.ts @@ -5,7 +5,6 @@ import { FormsModule } from '@angular/forms' import { UnitConversionPipesModule } from '@start9labs/shared' import { DriveStatusComponent, RecoverPage } from './recover.page' import { PasswordPageModule } from '../../modals/password/password.module' -import { ProdKeyModalModule } from '../../modals/prod-key-modal/prod-key-modal.module' import { RecoverPageRoutingModule } from './recover-routing.module' import { CifsModalModule } from 'src/app/modals/cifs-modal/cifs-modal.module' @@ -17,7 +16,6 @@ import { CifsModalModule } from 'src/app/modals/cifs-modal/cifs-modal.module' IonicModule, RecoverPageRoutingModule, PasswordPageModule, - ProdKeyModalModule, UnitConversionPipesModule, CifsModalModule, ], diff --git a/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts b/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts index 2f330ac9f..5f077bfd9 100644 --- a/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts +++ b/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts @@ -1,17 +1,10 @@ import { Component, Input } from '@angular/core' -import { - AlertController, - IonicSafeString, - LoadingController, - ModalController, - NavController, -} from '@ionic/angular' +import { AlertController, ModalController, NavController } from '@ionic/angular' import { CifsModal } from 'src/app/modals/cifs-modal/cifs-modal.page' import { ApiService, DiskBackupTarget } from 'src/app/services/api/api.service' import { ErrorToastService } from '@start9labs/shared' import { StateService } from 'src/app/services/state.service' import { PasswordPage } from '../../modals/password/password.page' -import { ProdKeyModal } from '../../modals/prod-key-modal/prod-key-modal.page' @Component({ selector: 'app-recover', @@ -21,7 +14,6 @@ import { ProdKeyModal } from '../../modals/prod-key-modal/prod-key-modal.page' export class RecoverPage { loading = true mappedDrives: MappedDisk[] = [] - hasShownGuidAlert = false constructor( private readonly apiService: ApiService, @@ -29,8 +21,7 @@ export class RecoverPage { private readonly modalCtrl: ModalController, private readonly modalController: ModalController, private readonly alertCtrl: AlertController, - private readonly loadingCtrl: LoadingController, - private readonly errorToastService: ErrorToastService, + private readonly errToastService: ErrorToastService, private readonly stateService: StateService, ) {} @@ -44,10 +35,7 @@ export class RecoverPage { } driveClickable(mapped: MappedDisk) { - return ( - mapped.drive['embassy-os']?.full && - (this.stateService.hasProductKey || mapped.is02x) - ) + return mapped.drive['embassy-os']?.full } async getDrives() { @@ -89,50 +77,8 @@ export class RecoverPage { }) await alert.present() } - - const importableDrive = disks.find(d => !!d.guid) - if ( - !!importableDrive && - this.stateService.hasProductKey && - !this.hasShownGuidAlert - ) { - const alert = await this.alertCtrl.create({ - header: 'Embassy Data Drive Detected', - message: new IonicSafeString( - `${importableDrive.vendor || 'Unknown Vendor'} - ${ - importableDrive.model || 'Unknown Model' - } contains Embassy data. -

To use this drive and its data, select "USE DRIVE". This will complete the setup process. -

Important!

- If you are trying to restore from a backup or update from 0.2.x, DO NOT select "USE DRIVE". Instead, select "CANCEL" and follow instructions.`, - ), - buttons: [ - { - role: 'cancel', - text: 'Cancel', - }, - { - text: 'Use Drive', - handler: async () => { - const modal = await this.modalController.create({ - component: PasswordPage, - componentProps: { storageDrive: importableDrive }, - }) - modal.onDidDismiss().then(res => { - if (res.data && res.data.password) { - this.importDrive(importableDrive.guid!, res.data.password) - } - }) - await modal.present() - }, - }, - ], - }) - await alert.present() - this.hasShownGuidAlert = true - } } catch (e: any) { - this.errorToastService.present(e) + this.errToastService.present(e) } finally { this.loading = false } @@ -165,65 +111,20 @@ export class RecoverPage { if (!logicalname) return - if (this.stateService.hasProductKey) { - if (is02x) { - this.selectRecoverySource(logicalname) - } else { - const modal = await this.modalController.create({ - component: PasswordPage, - componentProps: { target }, - cssClass: 'alertlike-modal', - }) - modal.onDidDismiss().then(res => { - if (res.data?.password) { - this.selectRecoverySource(logicalname, res.data.password) - } - }) - await modal.present() - } - // if no product key, it means they are an upgrade kit user + if (is02x) { + this.selectRecoverySource(logicalname) } else { - if (!is02x) { - const alert = await this.alertCtrl.create({ - header: 'Error', - message: - 'In order to use this image, you must select a drive containing a valid 0.2.x Embassy.', - buttons: [ - { - role: 'cancel', - text: 'OK', - }, - ], - }) - await alert.present() - } else { - const modal = await this.modalController.create({ - component: ProdKeyModal, - componentProps: { target }, - cssClass: 'alertlike-modal', - }) - modal.onDidDismiss().then(res => { - if (res.data?.productKey) { - this.selectRecoverySource(logicalname) - } - }) - await modal.present() - } - } - } - - private async importDrive(guid: string, password: string) { - const loader = await this.loadingCtrl.create({ - message: 'Importing Drive', - }) - await loader.present() - try { - await this.stateService.importDrive(guid, password) - await this.navCtrl.navigateForward(`/success`) - } catch (e: any) { - this.errorToastService.present(e) - } finally { - loader.dismiss() + const modal = await this.modalController.create({ + component: PasswordPage, + componentProps: { target }, + cssClass: 'alertlike-modal', + }) + modal.onDidDismiss().then(res => { + if (res.data?.password) { + this.selectRecoverySource(logicalname, res.data.password) + } + }) + await modal.present() } } diff --git a/frontend/projects/setup-wizard/src/app/pages/success/success.page.html b/frontend/projects/setup-wizard/src/app/pages/success/success.page.html index 5fb046cb5..cecae06ab 100644 --- a/frontend/projects/setup-wizard/src/app/pages/success/success.page.html +++ b/frontend/projects/setup-wizard/src/app/pages/success/success.page.html @@ -9,174 +9,143 @@ name="checkmark-circle-outline" > Setup Complete + You have successully claimed your Embassy! +

- +

-

You can now safely unplug your backup drive.

- -

- You have successully claimed your Embassy! You can now access your - device using the methods below. + You can now safely unplug your backup drive. +

+

+ Access your Embassy using the methods below. You should + + download this page + + for your records.

-
- -

- Note: embassy.local was for setup purposes only, it will no - longer work. -

+
-
-

From Home (LAN)

- -
+

From Home (LAN)

-
-
-

- Visit the address below when you are conncted to the same WiFi - or Local Area Network (LAN) as your Embassy: -

+
+

+ Visit the address below when you are conncted to the same WiFi + or Local Area Network (LAN) as your Embassy: +

- - - {{ lanAddress }} - - - - - - -

- Important! - Your browser will warn you that the website is untrusted. You - can bypass this warning on most browsers. The warning will go - away after you - - download and trust - - your Embassy's Root Certificate Authority. -

- - - Download Root CA - - -
- -

-
- -
-

On The Go (Tor)

- -
+

+ Note: embassy.local was for setup purposes only, it will + no longer work. +

-
-
-

Visit the address below when you are away from home:

- - - - {{ torAddress }} - - - - - - -

- Important! - This address will only work from a - - Tor-enabled browser . -

-
- -
-
-
- -
- - Download this page + + {{ lanAddress }} + + + + + + + + + +

+ Important! + Your browser will warn you that the website is untrusted. You + can bypass this warning on most browsers. The warning will go + away after you + + follow the instructions + + + to downlaod and trust your Embassy's Root Certificate Authority. +

+ + + Download Root CA
-
+ +
+ + +

On The Go (Tor)

+ +
+

Visit the address below when you are away from home:

+ + + + {{ torAddress }} + + + + + + +

+ Important! + This address will only work from a + + Tor-enabled browser + . +

+
diff --git a/frontend/projects/setup-wizard/src/app/pages/success/success.page.scss b/frontend/projects/setup-wizard/src/app/pages/success/success.page.scss index b7afab096..8f57dd008 100644 --- a/frontend/projects/setup-wizard/src/app/pages/success/success.page.scss +++ b/frontend/projects/setup-wizard/src/app/pages/success/success.page.scss @@ -4,26 +4,12 @@ p { a { text-decoration: none; -} - -.toggle-label { - padding: 24px 0 8px 0; + font-weight: bold; cursor: pointer; - display: flex; - flex-direction: row; - justify-content: space-between; - - * { - display: inline-block; - vertical-align: middle; - } - - h2 { - font-weight: bold; - } - - ion-icon { - text-align: right; - font-size: 24px; - } +} + +.line { + margin-bottom: 48px; + padding-bottom: 48px; + border-bottom: solid 1px; } diff --git a/frontend/projects/setup-wizard/src/app/pages/success/success.page.ts b/frontend/projects/setup-wizard/src/app/pages/success/success.page.ts index d2f1418ed..bc0a16aba 100644 --- a/frontend/projects/setup-wizard/src/app/pages/success/success.page.ts +++ b/frontend/projects/setup-wizard/src/app/pages/success/success.page.ts @@ -16,8 +16,6 @@ import { StateService } from 'src/app/services/state.service' }) export class SuccessPage { @Output() onDownload = new EventEmitter() - torOpen = false - lanOpen = false constructor( @Inject(DOCUMENT) private readonly document: Document, @@ -69,14 +67,6 @@ export class SuccessPage { await toast.present() } - toggleTor() { - this.torOpen = !this.torOpen - } - - toggleLan() { - this.lanOpen = !this.lanOpen - } - installCert() { this.document.getElementById('install-cert')?.click() } diff --git a/frontend/projects/setup-wizard/src/app/services/api/api.service.ts b/frontend/projects/setup-wizard/src/app/services/api/api.service.ts index d64d1e398..31b110a44 100644 --- a/frontend/projects/setup-wizard/src/app/services/api/api.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/api/api.service.ts @@ -1,20 +1,19 @@ export abstract class ApiService { // unencrypted abstract getStatus(): Promise // setup.status + abstract getSecret(): Promise // setup.get-secret abstract getDrives(): Promise // setup.disk.list abstract set02XDrive(logicalname: string): Promise // setup.recovery.v2.set abstract getRecoveryStatus(): Promise // setup.recovery.status // encrypted abstract verifyCifs(cifs: CifsRecoverySource): Promise // setup.cifs.verify - abstract verifyProductKey(): Promise // echo - throws error if invalid abstract importDrive(importInfo: ImportDriveReq): Promise // setup.attach abstract setupEmbassy(setupInfo: SetupEmbassyReq): Promise // setup.execute abstract setupComplete(): Promise // setup.complete } export type GetStatusRes = { - 'product-key': boolean migrating: boolean } diff --git a/frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts b/frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts index 264767a00..6f4c3197d 100644 --- a/frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts @@ -1,5 +1,10 @@ import { Injectable } from '@angular/core' -import { HttpService } from '@start9labs/shared' +import { + HttpService, + isRpcError, + RpcError, + RPCOptions, +} from '@start9labs/shared' import { ApiService, CifsRecoverySource, @@ -13,43 +18,71 @@ import { SetupEmbassyRes, } from './api.service' import { RPCEncryptedService } from '../rpc-encrypted.service' +import * as jose from 'node-jose' @Injectable({ providedIn: 'root', }) -export class LiveApiService extends ApiService { +export class LiveApiService implements ApiService { constructor( private readonly unencrypted: HttpService, private readonly encrypted: RPCEncryptedService, - ) { - super() - } + ) {} // ** UNENCRYPTED ** async getStatus() { - return this.unencrypted.rpcRequest({ + return this.rpcRequest({ method: 'setup.status', params: {}, }) } + /** + * We want to update the secret, which means that we will call in clearnet the + * getSecret, and all the information is never in the clear, and only public + * information is sent across the network. We don't want to expose that we do + * this wil all public/private key, which means that there is no information loss + * through the network. + */ + async getSecret() { + const keystore = jose.JWK.createKeyStore() + const key = await keystore.generate('EC', 'P-256') + // const { privateKey, publicKey } = + + // jose.generateKeyPair('ECDH-ES', { + // extractable: true, + // }) + console.log({ publicKey: key.toJSON() }) + const response: string = await this.rpcRequest({ + method: 'setup.get-secret', + params: { pubkey: key.toJSON() }, + }) + + // const { plaintext } = await jose.compactDecrypt(response, privateKey) + const decrypted = await jose.JWE.createDecrypt(key).decrypt(response) + const decoded = new TextDecoder().decode(decrypted.plaintext) + console.log({ decoded }) + + return decoded + } + async getDrives() { - return this.unencrypted.rpcRequest({ + return this.rpcRequest({ method: 'setup.disk.list', params: {}, }) } async set02XDrive(logicalname: string) { - return this.unencrypted.rpcRequest({ + return this.rpcRequest({ method: 'setup.recovery.v2.set', params: { logicalname }, }) } async getRecoveryStatus() { - return this.unencrypted.rpcRequest({ + return this.rpcRequest({ method: 'setup.recovery.status', params: {}, }) @@ -65,13 +98,6 @@ export class LiveApiService extends ApiService { }) } - async verifyProductKey() { - return this.encrypted.rpcRequest({ - method: 'echo', - params: { message: 'hello' }, - }) - } - async importDrive(params: ImportDriveReq) { const res = await this.encrypted.rpcRequest({ method: 'setup.attach', @@ -113,6 +139,18 @@ export class LiveApiService extends ApiService { 'root-ca': btoa(res['root-ca']), } } + + private async rpcRequest(opts: RPCOptions): Promise { + const res = await this.unencrypted.rpcRequest(opts) + + const rpcRes = res.body + + if (isRpcError(rpcRes)) { + throw new RpcError(rpcRes.error) + } + + return rpcRes.result + } } function isCifsSource( diff --git a/frontend/projects/setup-wizard/src/app/services/api/mock-api.service.ts b/frontend/projects/setup-wizard/src/app/services/api/mock-api.service.ts index 0f7841cc5..25485fcf0 100644 --- a/frontend/projects/setup-wizard/src/app/services/api/mock-api.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/api/mock-api.service.ts @@ -12,21 +12,29 @@ let tries = 0 @Injectable({ providedIn: 'root', }) -export class MockApiService extends ApiService { - constructor() { - super() - } - +export class MockApiService implements ApiService { // ** UNENCRYPTED ** async getStatus() { await pauseFor(1000) return { - 'product-key': true, migrating: false, } } + async getSecret() { + await pauseFor(1000) + + const ascii = 'thisisasecret' + + const arr1 = [] + for (let n = 0, l = ascii.length; n < l; n++) { + var hex = Number(ascii.charCodeAt(n)).toString(16) + arr1.push(hex) + } + return arr1.join('') + } + async getDrives() { await pauseFor(1000) return { @@ -84,11 +92,6 @@ export class MockApiService extends ApiService { } } - async verifyProductKey() { - await pauseFor(1000) - return - } - async importDrive(params: ImportDriveReq) { await pauseFor(3000) return setupRes diff --git a/frontend/projects/setup-wizard/src/app/services/rpc-encrypted.service.ts b/frontend/projects/setup-wizard/src/app/services/rpc-encrypted.service.ts index afd7a6444..fc86b8462 100644 --- a/frontend/projects/setup-wizard/src/app/services/rpc-encrypted.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/rpc-encrypted.service.ts @@ -15,13 +15,13 @@ import { providedIn: 'root', }) export class RPCEncryptedService { - productKey?: string + secret?: string constructor(private readonly http: HttpService) {} async rpcRequest(opts: Omit): Promise { const encryptedBody = await AES_CTR.encryptPbkdf2( - this.productKey || '', + this.secret || '', encodeUtf8(JSON.stringify(opts)), ) @@ -36,7 +36,11 @@ export class RPCEncryptedService { 'Content-Type': 'application/json', }, }) - .then(body => AES_CTR.decryptPbkdf2(this.productKey || '', body)) + .then(res => AES_CTR.decryptPbkdf2(this.secret || '', res.body)) + .then(x => { + console.log(`Network: ${x}`) + return x + }) .then(res => JSON.parse(res)) .catch(e => { if (!e.status && !e.statusText) { diff --git a/frontend/projects/setup-wizard/src/app/services/state.service.ts b/frontend/projects/setup-wizard/src/app/services/state.service.ts index 1c79bdd8f..b009c61f7 100644 --- a/frontend/projects/setup-wizard/src/app/services/state.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/state.service.ts @@ -11,9 +11,6 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared' providedIn: 'root', }) export class StateService { - hasProductKey = false - isMigrating = false - polling = false embassyLoaded = false diff --git a/frontend/projects/setup-wizard/src/styles.scss b/frontend/projects/setup-wizard/src/styles.scss index 77e263ec4..ca97edbed 100644 --- a/frontend/projects/setup-wizard/src/styles.scss +++ b/frontend/projects/setup-wizard/src/styles.scss @@ -38,7 +38,7 @@ ion-content { ion-grid { padding-top: 32px; height: 100%; - max-width: 600px; + max-width: 640px; } ion-row { @@ -47,6 +47,8 @@ ion-row { ion-item { --color: var(--ion-color-light); + --highlight-color-valid: transparent; + --highlight-color-invalid: transparent; } ion-toolbar { @@ -61,13 +63,6 @@ ion-avatar { height: 27px; } -ion-item { - --highlight-color-valid: transparent; - --highlight-color-invalid: transparent; - - --border-radius: 4px; -} - ion-card-title { margin: 16px 0; font-family: 'Montserrat'; diff --git a/frontend/projects/shared/src/classes/rpc-error.ts b/frontend/projects/shared/src/classes/rpc-error.ts index 2c4c48450..4ffa042be 100644 --- a/frontend/projects/shared/src/classes/rpc-error.ts +++ b/frontend/projects/shared/src/classes/rpc-error.ts @@ -1,11 +1,10 @@ -import { RpcErrorDetails } from '../types/rpc-error-details' +import { RPCErrorDetails } from '../types/rpc.types' -export class RpcError { +export class RpcError { readonly code = this.error.code readonly message = this.getMessage() - readonly revision = this.getRevision() - constructor(private readonly error: RpcErrorDetails) {} + constructor(private readonly error: RPCErrorDetails) {} private getMessage(): string { if (typeof this.error.data === 'string') { @@ -16,10 +15,4 @@ export class RpcError { ? `${this.error.message}\n\n${this.error.data.details}` : this.error.message } - - private getRevision(): T | null { - return typeof this.error.data === 'string' - ? null - : this.error.data?.revision || null - } } diff --git a/frontend/projects/shared/src/components/markdown/markdown.component.ts b/frontend/projects/shared/src/components/markdown/markdown.component.ts index e5837aa89..d560f7537 100644 --- a/frontend/projects/shared/src/components/markdown/markdown.component.ts +++ b/frontend/projects/shared/src/components/markdown/markdown.component.ts @@ -14,17 +14,15 @@ export class MarkdownComponent { @Input() content!: string | Observable @Input() title!: string - private readonly data$ = defer(() => + readonly content$ = defer(() => isObservable(this.content) ? this.content : of(this.content), ).pipe(share()) - readonly error$ = this.data$.pipe( + readonly error$ = this.content$.pipe( ignoreElements(), catchError(e => of(getErrorMessage(e))), ) - readonly content$ = this.data$.pipe(catchError(() => of([]))) - constructor(private readonly modalCtrl: ModalController) {} async dismiss() { diff --git a/frontend/projects/shared/src/pipes/markdown/markdown.pipe.ts b/frontend/projects/shared/src/pipes/markdown/markdown.pipe.ts index 77b12485a..f2c024670 100644 --- a/frontend/projects/shared/src/pipes/markdown/markdown.pipe.ts +++ b/frontend/projects/shared/src/pipes/markdown/markdown.pipe.ts @@ -6,11 +6,22 @@ import * as DOMPurify from 'dompurify' name: 'markdown', }) export class MarkdownPipe implements PipeTransform { - transform(value: any): any { + transform(value: string): string { if (value && value.length > 0) { + // convert markdown to html const html = marked(value) + // sanitize html const sanitized = DOMPurify.sanitize(html) - return sanitized + // parse html to find all links + let parser = new DOMParser() + const doc = parser.parseFromString(sanitized, 'text/html') + const links = Array.from(doc.getElementsByTagName('a')) + // add target="_blank" to every link + links.forEach(link => { + link.setAttribute('target', '_blank') + }) + // return new html string + return doc.documentElement.innerHTML } return value } diff --git a/frontend/projects/shared/src/public-api.ts b/frontend/projects/shared/src/public-api.ts index c6df02a93..ece4ca414 100644 --- a/frontend/projects/shared/src/public-api.ts +++ b/frontend/projects/shared/src/public-api.ts @@ -40,12 +40,14 @@ export * from './services/error-toast.service' export * from './services/http.service' export * from './types/api' -export * from './types/rpc-error-details' +export * from './types/http.types' +export * from './types/rpc.types' export * from './types/url' export * from './types/workspace-config' export * from './util/copy-to-clipboard' export * from './util/get-pkg-id' export * from './util/misc.util' +export * from './util/rpc.util' export * from './util/to-local-iso-string' export * from './util/unused' diff --git a/frontend/projects/shared/src/services/http.service.ts b/frontend/projects/shared/src/services/http.service.ts index 9a43c7784..3357ed7ff 100644 --- a/frontend/projects/shared/src/services/http.service.ts +++ b/frontend/projects/shared/src/services/http.service.ts @@ -1,6 +1,14 @@ import { Inject, Injectable } from '@angular/core' -import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' -import { HttpError, RpcError, WorkspaceConfig } from '@start9labs/shared' +import { HttpClient } from '@angular/common/http' +import { HttpError } from '../classes/http-error' +import { + HttpAngularOptions, + HttpOptions, + LocalHttpResponse, + Method, +} from '../types/http.types' +import { RPCResponse, RPCOptions } from '../types/rpc.types' +import { WorkspaceConfig } from '../types/workspace-config' import { firstValueFrom, from, @@ -32,20 +40,21 @@ export class HttpService { this.fullUrl = `${protocol}//${hostname}:${port}` } - async rpcRequest(opts: RPCOptions): Promise { - const { method, params, timeout } = opts + async rpcRequest( + opts: RPCOptions, + ): Promise>> { + const { method, headers, params, timeout } = opts - const res = await this.httpRequest>({ + return this.httpRequest>({ method: Method.POST, url: this.relativeUrl, + headers, body: { method, params }, timeout, }) - if (isRpcError(res)) throw new RpcError(res.error) - return res.result } - async httpRequest(opts: HttpOptions): Promise { + async httpRequest(opts: HttpOptions): Promise> { let { method, url, headers, body, responseType, timeout } = opts url = opts.url.startsWith('/') ? this.fullUrl + url : url @@ -67,113 +76,21 @@ export class HttpService { responseType: responseType || 'json', } - let req: Observable<{ body: T }> + let req: Observable> if (method === Method.GET) { req = this.http.get(url, options as any) as any } else { req = this.http.post(url, body, options as any) as any } - return firstValueFrom(timeout ? withTimeout(req, timeout) : req) - .then(res => res.body) - .catch(e => { + return firstValueFrom(timeout ? withTimeout(req, timeout) : req).catch( + e => { throw new HttpError(e) - }) + }, + ) } } -// ** RPC types ** - -interface RPCBase { - jsonrpc: '2.0' - id: string -} - -export interface RPCRequest extends RPCBase { - method: string - params?: T -} - -export interface RPCSuccess extends RPCBase { - result: T -} - -export interface RPCError extends RPCBase { - error: { - code: number - message: string - data?: - | { - details: string - } - | string - } -} - -export type RPCResponse = RPCSuccess | RPCError - -export interface RPCOptions { - method: string - params: { - [param: string]: - | string - | number - | boolean - | object - | string[] - | number[] - | null - } - timeout?: number -} - -export function isRpcError( - arg: { error: Error } | { result: Result }, -): arg is { error: Error } { - return (arg as any).error !== undefined -} - -// ** HTTP types ** - -export enum Method { - GET = 'GET', - POST = 'POST', -} - -export interface HttpOptions { - method: Method - url: string - headers?: - | HttpHeaders - | { - [header: string]: string | string[] - } - params?: - | HttpParams - | { - [param: string]: string | string[] - } - responseType?: 'json' | 'text' | 'arrayBuffer' - body?: any - timeout?: number -} - -interface HttpAngularOptions { - observe: 'response' - withCredentials: true - headers?: - | HttpHeaders - | { - [header: string]: string | string[] - } - params?: - | HttpParams - | { - [param: string]: string | string[] - } - responseType?: 'json' | 'text' | 'arrayBuffer' -} - function hasParams( params?: HttpOptions['params'], ): params is Record { @@ -191,9 +108,3 @@ function withTimeout(req: Observable, timeout: number): Observable { ), ) } - -export interface RequestError { - code: number - message: string - details: string -} diff --git a/frontend/projects/shared/src/types/http.types.ts b/frontend/projects/shared/src/types/http.types.ts new file mode 100644 index 000000000..1ae2bcc50 --- /dev/null +++ b/frontend/projects/shared/src/types/http.types.ts @@ -0,0 +1,44 @@ +import { HttpHeaders, HttpResponse } from '@angular/common/http' + +export enum Method { + GET = 'GET', + POST = 'POST', +} + +export interface HttpOptions { + method: Method + url: string + headers?: { + [header: string]: string | string[] + } + params?: { + [param: string]: string | string[] + } + responseType?: 'json' | 'text' | 'arrayBuffer' + body?: any + timeout?: number +} + +export interface HttpAngularOptions { + observe: 'response' + withCredentials: true + headers?: + | HttpHeaders + | { + [header: string]: string | string[] + } + params?: { + [param: string]: string | string[] + } + responseType?: 'json' | 'text' | 'arrayBuffer' +} + +export interface LocalHttpResponse extends HttpResponse { + body: T +} + +export interface RequestError { + code: number + message: string + details: string +} diff --git a/frontend/projects/shared/src/types/rpc-error-details.ts b/frontend/projects/shared/src/types/rpc-error-details.ts deleted file mode 100644 index 5f1b0e973..000000000 --- a/frontend/projects/shared/src/types/rpc-error-details.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface RpcErrorDetails { - code: number - message: string - data?: - | { - details: string - revision?: T | null - } - | string -} diff --git a/frontend/projects/shared/src/types/rpc.types.ts b/frontend/projects/shared/src/types/rpc.types.ts new file mode 100644 index 000000000..e25c7eadf --- /dev/null +++ b/frontend/projects/shared/src/types/rpc.types.ts @@ -0,0 +1,55 @@ +// ** RPC types ** + +interface RPCBase { + jsonrpc: '2.0' + id: string +} + +export interface RPCRequest extends RPCBase { + method: string + params?: T +} + +export interface RPCSuccessRes extends RPCBase { + result: T +} + +export interface RPCErrorRes extends RPCBase { + error: RPCErrorDetails +} + +export interface RPCErrorDetails { + code: number + message: string + data?: + | { + details: string + } + | string +} + +export type RPCResponse = RPCSuccessRes | RPCErrorRes + +export interface RPCOptions { + method: string + headers?: { + [header: string]: string | string[] + } + params: { + [param: string]: + | string + | number + | boolean + | object + | string[] + | number[] + | null + } + timeout?: number +} + +export function isRpcError( + arg: { error: Error } | { result: Result }, +): arg is { error: Error } { + return (arg as any).error !== undefined +} diff --git a/frontend/projects/shared/src/util/rpc.util.ts b/frontend/projects/shared/src/util/rpc.util.ts new file mode 100644 index 000000000..ed922bc7c --- /dev/null +++ b/frontend/projects/shared/src/util/rpc.util.ts @@ -0,0 +1,11 @@ +import { RPCErrorDetails } from '../types/rpc.types' + +export function getRpcErrorMessage(error: RPCErrorDetails): string { + if (typeof error.data === 'string') { + return `${error.message}\n\n${error.data}` + } + + return error.data?.details + ? `${error.message}\n\n${error.data.details}` + : error.message +} diff --git a/frontend/projects/shared/styles/global.scss b/frontend/projects/shared/styles/global.scss index d854de84a..7fc729952 100644 --- a/frontend/projects/shared/styles/global.scss +++ b/frontend/projects/shared/styles/global.scss @@ -24,3 +24,6 @@ @import "~@ionic/angular/css/text-alignment.css"; @import "~@ionic/angular/css/text-transformation.css"; @import "~@ionic/angular/css/flex-utils.css"; + +/* Import swiper styles for slides */ +@import '~swiper/scss'; diff --git a/frontend/projects/shared/styles/shared.scss b/frontend/projects/shared/styles/shared.scss index defef4faa..2ae706306 100644 --- a/frontend/projects/shared/styles/shared.scss +++ b/frontend/projects/shared/styles/shared.scss @@ -18,6 +18,12 @@ ion-alert { } } +.swiper { + .swiper-slide { + display: unset; + } +} + ion-modal::part(content) { position: absolute; height: 90% !important; diff --git a/frontend/projects/ui/src/app/app.module.ts b/frontend/projects/ui/src/app/app.module.ts index 47afeec88..32ff52af0 100644 --- a/frontend/projects/ui/src/app/app.module.ts +++ b/frontend/projects/ui/src/app/app.module.ts @@ -34,7 +34,7 @@ import { ConnectionBarComponentModule } from './components/connection-bar/connec storeName: '_embassykv', dbKey: '_embassykey', name: '_embassystorage', - driverOrder: [Drivers.LocalStorage, Drivers.IndexedDB], + driverOrder: [Drivers.LocalStorage], }), MenuModule, PreloaderModule, diff --git a/frontend/projects/ui/src/app/app.providers.ts b/frontend/projects/ui/src/app/app.providers.ts index bf50889e5..251a71bd3 100644 --- a/frontend/projects/ui/src/app/app.providers.ts +++ b/frontend/projects/ui/src/app/app.providers.ts @@ -1,5 +1,4 @@ -import { Bootstrapper, DBCache } from 'patch-db-client' -import { APP_INITIALIZER, ErrorHandler, Provider } from '@angular/core' +import { APP_INITIALIZER, Provider } from '@angular/core' import { UntypedFormBuilder } from '@angular/forms' import { Router, RouteReuseStrategy } from '@angular/router' import { IonicRouteStrategy, IonNav } from '@ionic/angular' @@ -8,10 +7,8 @@ import { WorkspaceConfig } from '@start9labs/shared' import { ApiService } from './services/api/embassy-api.service' import { MockApiService } from './services/api/embassy-mock-api.service' import { LiveApiService } from './services/api/embassy-live-api.service' -import { BOOTSTRAPPER, PATCH_CACHE } from './services/patch-db/patch-db.factory' import { AuthService } from './services/auth.service' import { LocalStorageService } from './services/local-storage.service' -import { DataModel } from './services/patch-db/data-model' import { FilterPackagesPipe } from '../../../marketplace/src/pipes/filter-packages.pipe' const { useMocks } = require('../../../../config.json') as WorkspaceConfig @@ -30,14 +27,7 @@ export const APP_PROVIDERS: Provider[] = [ }, { provide: APP_INITIALIZER, - deps: [ - Storage, - AuthService, - LocalStorageService, - Router, - BOOTSTRAPPER, - PATCH_CACHE, - ], + deps: [Storage, AuthService, LocalStorageService, Router], useFactory: appInitializer, multi: true, }, @@ -48,19 +38,12 @@ export function appInitializer( auth: AuthService, localStorage: LocalStorageService, router: Router, - bootstrapper: Bootstrapper, - cache: DBCache, ): () => Promise { return async () => { await storage.create() await auth.init() await localStorage.init() - const localCache = await bootstrapper.init() - - cache.sequence = localCache.sequence - cache.data = localCache.data - router.initialNavigation() } } diff --git a/frontend/projects/ui/src/app/app/footer/footer.component.ts b/frontend/projects/ui/src/app/app/footer/footer.component.ts index f91fa909f..b7e18c08f 100644 --- a/frontend/projects/ui/src/app/app/footer/footer.component.ts +++ b/frontend/projects/ui/src/app/app/footer/footer.component.ts @@ -1,8 +1,8 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { heightCollapse } from '../../util/animations' -import { PatchDbService } from '../../services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { map } from 'rxjs/operators' -import { ServerInfo } from '../../services/patch-db/data-model' +import { DataModel, ServerInfo } from '../../services/patch-db/data-model' @Component({ selector: 'footer[appFooter]', @@ -24,7 +24,7 @@ export class FooterComponent { }, } - constructor(private readonly patch: PatchDbService) {} + constructor(private readonly patch: PatchDB) {} getProgress({ downloaded, diff --git a/frontend/projects/ui/src/app/app/menu/menu.component.html b/frontend/projects/ui/src/app/app/menu/menu.component.html index 3bfd04d58..c7dc61935 100644 --- a/frontend/projects/ui/src/app/app/menu/menu.component.html +++ b/frontend/projects/ui/src/app/app/menu/menu.component.html @@ -57,6 +57,6 @@ src="assets/img/icons/snek.png" [appSnekHighScore]="snekScore$ | async" /> - + diff --git a/frontend/projects/ui/src/app/app/menu/menu.component.ts b/frontend/projects/ui/src/app/app/menu/menu.component.ts index 0e0ec1f52..97c30ae60 100644 --- a/frontend/projects/ui/src/app/app/menu/menu.component.ts +++ b/frontend/projects/ui/src/app/app/menu/menu.component.ts @@ -1,11 +1,13 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' import { LocalStorageService } from '../../services/local-storage.service' import { EOSService } from '../../services/eos.service' -import { PatchDbService } from '../../services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { Observable } from 'rxjs' import { map } from 'rxjs/operators' import { AbstractMarketplaceService } from '@start9labs/marketplace' import { MarketplaceService } from 'src/app/services/marketplace.service' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { SplitPaneTracker } from 'src/app/services/split-pane.service' @Component({ selector: 'app-menu', @@ -57,11 +59,14 @@ export class MenuComponent { .getUpdates() .pipe(map(pkgs => pkgs.length)) + readonly sidebarOpen$ = this.splitPane.sidebarOpen$ + constructor( - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly localStorageService: LocalStorageService, private readonly eosService: EOSService, @Inject(AbstractMarketplaceService) private readonly marketplaceService: MarketplaceService, + private readonly splitPane: SplitPaneTracker, ) {} } diff --git a/frontend/projects/ui/src/app/app/preloader/preloader.component.html b/frontend/projects/ui/src/app/app/preloader/preloader.component.html index 4aef1e841..88408d6a0 100644 --- a/frontend/projects/ui/src/app/app/preloader/preloader.component.html +++ b/frontend/projects/ui/src/app/app/preloader/preloader.component.html @@ -4,9 +4,6 @@ - - Slide 1 - diff --git a/frontend/projects/ui/src/app/app/preloader/preloader.module.ts b/frontend/projects/ui/src/app/app/preloader/preloader.module.ts index 2ba1eff56..b1496e638 100644 --- a/frontend/projects/ui/src/app/app/preloader/preloader.module.ts +++ b/frontend/projects/ui/src/app/app/preloader/preloader.module.ts @@ -2,11 +2,10 @@ import { CommonModule } from '@angular/common' import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core' import { IonicModule } from '@ionic/angular' import { QrCodeModule } from 'ng-qrcode' -import { SwiperModule } from 'swiper/angular' import { PreloaderComponent } from './preloader.component' @NgModule({ - imports: [CommonModule, IonicModule, QrCodeModule, SwiperModule], + imports: [CommonModule, IonicModule, QrCodeModule], declarations: [PreloaderComponent], exports: [PreloaderComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], diff --git a/frontend/projects/ui/src/app/app/snek/snek.directive.ts b/frontend/projects/ui/src/app/app/snek/snek.directive.ts index 81b2f8361..d87a0898e 100644 --- a/frontend/projects/ui/src/app/app/snek/snek.directive.ts +++ b/frontend/projects/ui/src/app/app/snek/snek.directive.ts @@ -1,9 +1,7 @@ import { Directive, HostListener, Input } from '@angular/core' import { LoadingController, ModalController } from '@ionic/angular' import { ErrorToastService } from '@start9labs/shared' - import { SnakePage } from '../../modals/snake/snake.page' -import { PatchDbService } from '../../services/patch-db/patch-db.service' import { ApiService } from '../../services/api/embassy-api.service' @Directive({ diff --git a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.html b/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.html deleted file mode 100644 index b3fa9fe12..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.html +++ /dev/null @@ -1,4 +0,0 @@ -

- Warning -

-
diff --git a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.module.ts b/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.module.ts deleted file mode 100644 index efed93a4d..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { AlertComponent } from './alert.component' -import { IonicModule } from '@ionic/angular' -import { RouterModule } from '@angular/router' -import { MarkdownPipeModule } from '@start9labs/shared' - -@NgModule({ - declarations: [AlertComponent], - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild([]), - MarkdownPipeModule, - ], - exports: [AlertComponent], -}) -export class AlertComponentModule {} diff --git a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.ts b/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.ts deleted file mode 100644 index 7c403cc55..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, Input } from '@angular/core' -import { BaseSlide } from '../wizard-types' - -@Component({ - selector: 'alert', - templateUrl: './alert.component.html', - styleUrls: ['../app-wizard.component.scss'], -}) -export class AlertComponent implements BaseSlide { - @Input() - params!: { message: string } - - async load() {} - - loading = false -} diff --git a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.html b/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.html deleted file mode 100644 index 97266271c..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.html +++ /dev/null @@ -1,93 +0,0 @@ - - -
- {{ params.title }} -
- - {{ params.action | titlecase - }}: {{ params.version | displayEmver }} - -
- - - - - -
-
- - -
- - - - - - - - - -

- {{ error }} -

-
-
-
- - - - - - - Dismiss - - - - {{ - currentIndex < swiper.slides.length - 2 - ? 'Continue' - : params.submitBtn - }} - - - - - - diff --git a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.module.ts b/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.module.ts deleted file mode 100644 index 4de4e2dcb..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { AppWizardComponent } from './app-wizard.component' -import { IonicModule } from '@ionic/angular' -import { RouterModule } from '@angular/router' -import { EmverPipesModule } from '@start9labs/shared' -import { DependentsComponentModule } from './dependents/dependents.component.module' -import { CompleteComponentModule } from './complete/complete.component.module' -import { AlertComponentModule } from './alert/alert.component.module' -import { SwiperModule } from 'swiper/angular' - -@NgModule({ - declarations: [AppWizardComponent], - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild([]), - EmverPipesModule, - DependentsComponentModule, - CompleteComponentModule, - AlertComponentModule, - SwiperModule, - ], - exports: [AppWizardComponent], -}) -export class AppWizardComponentModule {} diff --git a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.scss b/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.scss deleted file mode 100644 index f88ae0ac1..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -.underline { - margin: 6px 0 8px 16px; - border-style: solid; - border-width: 0px 0px 1px 0px; - border-color: #404040; -} diff --git a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.ts b/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.ts deleted file mode 100644 index 8e2a8ff91..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { - Component, - Input, - QueryList, - ViewChild, - ViewChildren, -} from '@angular/core' -import { IonContent, ModalController } from '@ionic/angular' -import { CompleteComponent } from './complete/complete.component' -import { DependentsComponent } from './dependents/dependents.component' -import { AlertComponent } from './alert/alert.component' -import { WizardAction } from './wizard-types' -import SwiperCore, { Swiper } from 'swiper' -import { IonicSlides } from '@ionic/angular' -import { BaseSlide } from './wizard-types' - -SwiperCore.use([IonicSlides]) - -@Component({ - selector: 'app-wizard', - templateUrl: './app-wizard.component.html', - styleUrls: ['./app-wizard.component.scss'], -}) -export class AppWizardComponent { - @Input() - params!: { - action: WizardAction - title: string - slides: SlideDefinition[] - submitBtn: string - version?: string - } - - // content container so we can scroll to top between slide transitions - @ViewChild(IonContent) - content?: IonContent - - swiper?: Swiper - - //a slide component gives us hook into a slide. Allows us to call load when slide comes into view - @ViewChildren('components') - slideComponentsQL?: QueryList - - get slideComponents(): BaseSlide[] { - return this.slideComponentsQL?.toArray() || [] - } - - get currentSlide(): BaseSlide { - return this.slideComponents[this.currentIndex] - } - - get currentIndex(): number { - return this.swiper?.activeIndex || NaN - } - - initializing = true - error = '' - - constructor(private readonly modalController: ModalController) {} - - ionViewDidEnter() { - this.initializing = false - if (this.swiper) this.swiper.allowTouchMove = false - this.loadSlide() - } - - setSwiperInstance(swiper: any) { - this.swiper = swiper - } - - dismiss(role = 'cancelled') { - this.modalController.dismiss(null, role) - } - - async next() { - await this.content?.scrollToTop() - this.swiper?.slideNext(500) - } - - setError(e: any) { - this.error = e - } - - async loadSlide() { - this.currentSlide.load() - } -} - -export type SlideDefinition = - | { selector: 'alert'; params: AlertComponent['params'] } - | { selector: 'dependents'; params: DependentsComponent['params'] } - | { selector: 'complete'; params: CompleteComponent['params'] } - -export async function wizardModal( - modalController: ModalController, - params: AppWizardComponent['params'], -): Promise { - const modal = await modalController.create({ - backdropDismiss: false, - cssClass: 'wizard-modal', - component: AppWizardComponent, - componentProps: { params }, - }) - - await modal.present() - return modal.onDidDismiss().then(({ role }) => role === 'success') -} diff --git a/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.html b/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.html deleted file mode 100644 index 88bda51df..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
- -

{{ message }}

-
diff --git a/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.module.ts b/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.module.ts deleted file mode 100644 index a8cb7ba39..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { CompleteComponent } from './complete.component' -import { IonicModule } from '@ionic/angular' -import { RouterModule } from '@angular/router' - -@NgModule({ - declarations: [CompleteComponent], - imports: [CommonModule, IonicModule, RouterModule.forChild([])], - exports: [CompleteComponent], -}) -export class CompleteComponentModule {} diff --git a/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.ts b/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.ts deleted file mode 100644 index a5fdadb50..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core' -import { capitalizeFirstLetter } from '@start9labs/shared' -import { BaseSlide } from '../wizard-types' - -@Component({ - selector: 'complete', - templateUrl: './complete.component.html', - styleUrls: ['../app-wizard.component.scss'], -}) -export class CompleteComponent implements BaseSlide { - @Input() - params!: { - verb: string // loader verb: '*stopping* ...' - title: string - Fn: () => Promise - } - - @Output() onSuccess: EventEmitter = new EventEmitter() - @Output() onError: EventEmitter = new EventEmitter() - - message = '' - - loading = true - - async load() { - this.message = - capitalizeFirstLetter(this.params.verb || '') + ' ' + this.params.title - try { - await this.params.Fn() - this.onSuccess.emit() - } catch (e: any) { - this.onError.emit(`Error: ${e.message || e}`) - } - } -} diff --git a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.html b/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.html deleted file mode 100644 index 997a80595..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.html +++ /dev/null @@ -1,25 +0,0 @@ -
- -

Checking for installed services which depend on {{ params.title }}...

-
- - -

Warning

-

{{ warningMessage }}

- - - - - Affected Services - - - - - - - {{ pkgs[dep.key].manifest.title }} - - - - -
diff --git a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.module.ts b/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.module.ts deleted file mode 100644 index 9a1ae0efc..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { DependentsComponent } from './dependents.component' -import { IonicModule } from '@ionic/angular' -import { RouterModule } from '@angular/router' -import { SharedPipesModule } from '@start9labs/shared' - -@NgModule({ - declarations: [DependentsComponent], - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild([]), - SharedPipesModule, - ], - exports: [DependentsComponent], -}) -export class DependentsComponentModule {} diff --git a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.scss b/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.ts b/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.ts deleted file mode 100644 index 0ebc53dca..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core' -import { Breakages } from 'src/app/services/api/api.types' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { capitalizeFirstLetter, isEmptyObject } from '@start9labs/shared' -import { BaseSlide } from '../wizard-types' - -@Component({ - selector: 'dependents', - templateUrl: './dependents.component.html', - styleUrls: ['./dependents.component.scss', '../app-wizard.component.scss'], -}) -export class DependentsComponent implements BaseSlide { - @Input() - params!: { - title: string - verb: string // *Uninstalling* will cause problems... - Fn: () => Promise - } - - @Output() onSuccess: EventEmitter = new EventEmitter() - @Output() onError: EventEmitter = new EventEmitter() - - breakages?: Breakages - warningMessage = '' - - loading = true - - readonly pkgs$ = this.patch.watch$('package-data') - - constructor(private readonly patch: PatchDbService) {} - - async load() { - try { - this.breakages = await this.params.Fn() - if (this.breakages && !isEmptyObject(this.breakages)) { - this.warningMessage = - capitalizeFirstLetter(this.params.verb || '') + - ' ' + - this.params.title + - ' will prohibit the following services from functioning properly.' - } else { - this.onSuccess.emit() - } - } catch (e: any) { - this.onError.emit( - `Error fetching dependent service information: ${e.message || e}`, - ) - } finally { - this.loading = false - } - } -} diff --git a/frontend/projects/ui/src/app/components/app-wizard/wizard-defs.ts b/frontend/projects/ui/src/app/components/app-wizard/wizard-defs.ts deleted file mode 100644 index 2d433a22d..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/wizard-defs.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { Inject, Injectable } from '@angular/core' -import { exists } from '@start9labs/shared' -import { AbstractMarketplaceService } from '@start9labs/marketplace' -import { Manifest } from 'src/app/services/patch-db/data-model' -import { ApiService } from '../../services/api/embassy-api.service' -import { AppWizardComponent, SlideDefinition } from './app-wizard.component' -import { ConfigService } from 'src/app/services/config.service' -import { MarketplaceService } from 'src/app/services/marketplace.service' -import { firstValueFrom } from 'rxjs' - -@Injectable({ providedIn: 'root' }) -export class WizardDefs { - constructor( - private readonly embassyApi: ApiService, - private readonly config: ConfigService, - @Inject(AbstractMarketplaceService) - private readonly marketplaceService: MarketplaceService, - ) {} - - update(values: { - id: string - title: string - version: string - installAlert?: string - }): AppWizardComponent['params'] { - const { id, title, version, installAlert } = values - - const slides: Array = [ - installAlert - ? { - selector: 'alert', - params: { - message: installAlert, - }, - } - : undefined, - { - selector: 'complete', - params: { - verb: 'beginning update for', - title, - Fn: () => - firstValueFrom( - this.marketplaceService.installPackage({ - id, - 'version-spec': version ? `=${version}` : undefined, - }), - ), - }, - }, - ] - return { - action: 'update', - title, - version, - slides: slides.filter(exists), - submitBtn: 'Begin Update', - } - } - - downgrade(values: { - id: string - title: string - version: string - installAlert?: string - }): AppWizardComponent['params'] { - const { id, title, version, installAlert } = values - - const slides: Array = [ - installAlert - ? { - selector: 'alert', - params: { - message: installAlert, - }, - } - : undefined, - { - selector: 'complete', - params: { - verb: 'beginning downgrade for', - title, - Fn: () => - firstValueFrom( - this.marketplaceService.installPackage({ - id, - 'version-spec': version ? `=${version}` : undefined, - }), - ), - }, - }, - ] - - return { - action: 'downgrade', - title, - version, - slides: slides.filter(exists), - submitBtn: 'Begin Downgrade', - } - } - - uninstall(values: { - id: string - title: string - uninstallAlert?: string - }): AppWizardComponent['params'] { - const { id, title, uninstallAlert } = values - - const slides: SlideDefinition[] = [ - { - selector: 'alert', - params: { - message: uninstallAlert || defaultUninstallWarning(title), - }, - }, - { - selector: 'complete', - params: { - verb: 'uninstalling', - title, - Fn: () => this.embassyApi.uninstallPackage({ id }), - }, - }, - ] - - return { - action: 'uninstall', - title, - slides: slides.filter(exists), - submitBtn: 'Uninstall Anyway', - } - } - - stop(values: { id: string; title: string }): AppWizardComponent['params'] { - const { title, id } = values - - const slides: SlideDefinition[] = [ - { - selector: 'complete', - params: { - verb: 'stopping', - title, - Fn: () => this.embassyApi.stopPackage({ id }), - }, - }, - ] - - return { - action: 'stop', - title, - slides: slides.filter(exists), - submitBtn: 'Stop Anyway', - } - } - - configure(values: { - manifest: Manifest - config: object - }): AppWizardComponent['params'] { - const { manifest, config } = values - const { id, title } = manifest - - const slides: SlideDefinition[] = [ - { - selector: 'dependents', - params: { - verb: 'saving config for', - title, - Fn: () => this.embassyApi.drySetPackageConfig({ id, config }), - }, - }, - { - selector: 'complete', - params: { - verb: 'configuring', - title, - Fn: () => this.embassyApi.setPackageConfig({ id, config }), - }, - }, - ] - - return { - action: 'configure', - title, - slides: slides.filter(exists), - submitBtn: 'Configure Anyway', - } - } -} - -const defaultUninstallWarning = (serviceName: string) => - `Uninstalling ${serviceName} will result in the deletion of its data.` diff --git a/frontend/projects/ui/src/app/components/app-wizard/wizard-types.ts b/frontend/projects/ui/src/app/components/app-wizard/wizard-types.ts deleted file mode 100644 index 9f6cca859..000000000 --- a/frontend/projects/ui/src/app/components/app-wizard/wizard-types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type WizardAction = - | 'update' - | 'downgrade' - | 'uninstall' - | 'stop' - | 'configure' - -export interface BaseSlide { - load: () => Promise - loading: boolean -} diff --git a/frontend/projects/ui/src/app/components/badge-menu-button/badge-menu.component.ts b/frontend/projects/ui/src/app/components/badge-menu-button/badge-menu.component.ts index 1d00d438d..352049335 100644 --- a/frontend/projects/ui/src/app/components/badge-menu-button/badge-menu.component.ts +++ b/frontend/projects/ui/src/app/components/badge-menu-button/badge-menu.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { SplitPaneTracker } from 'src/app/services/split-pane.service' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'badge-menu-button', @@ -14,6 +15,6 @@ export class BadgeMenuComponent { constructor( private readonly splitPane: SplitPaneTracker, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} } diff --git a/frontend/projects/ui/src/app/components/connection-bar/connection-bar.component.html b/frontend/projects/ui/src/app/components/connection-bar/connection-bar.component.html index b7597dfa1..007d47ab5 100644 --- a/frontend/projects/ui/src/app/components/connection-bar/connection-bar.component.html +++ b/frontend/projects/ui/src/app/components/connection-bar/connection-bar.component.html @@ -8,7 +8,7 @@ slot="end" [name]="connection.icon" class="icon" - color="light" + [color]="connection.iconColor" >

{{ connection.message }}

= combineLatest([ this.connectionService.networkConnected$, @@ -25,29 +25,29 @@ export class ConnectionBarComponent { if (!network) return { message: 'No Internet', + color: 'danger', icon: 'cloud-offline-outline', - color: 'dark', + iconColor: 'dark', dots: false, } if (!websocket) return { message: 'Connecting', - icon: 'cloud-offline-outline', color: 'warning', + icon: 'cloud-offline-outline', + iconColor: 'light', dots: true, } return { message: 'Connected', - icon: 'cloud-done', color: 'success', + icon: 'cloud-done', + iconColor: 'light', dots: false, } }), ) - constructor( - private readonly connectionService: ConnectionService, - private readonly patch: PatchDbService, - ) {} + constructor(private readonly connectionService: ConnectionService) {} } diff --git a/frontend/projects/ui/src/app/components/form-object/form-object.component.html b/frontend/projects/ui/src/app/components/form-object/form-object.component.html index bfbf26852..ccbd81a20 100644 --- a/frontend/projects/ui/src/app/components/form-object/form-object.component.html +++ b/frontend/projects/ui/src/app/components/form-object/form-object.component.html @@ -214,15 +214,23 @@ >
+ @@ -366,6 +374,7 @@ { - if (control === primary) return + if (control === id) return this.formGroup.removeControl(control) }) @@ -118,7 +119,7 @@ export class FormObjectComponent { ) Object.keys(unionGroup.controls).forEach(control => { - if (control === primary) return + if (control === id) return this.formGroup.addControl(control, unionGroup.controls[control]) }) @@ -152,35 +153,6 @@ export class FormObjectComponent { this.presentAlertChangeWarning(key, spec, () => this.addListItem(key)) } - addListItem(key: string, markDirty = true, val?: string): void { - const arr = this.formGroup.get(key) as UntypedFormArray - if (markDirty) arr.markAsDirty() - const listSpec = this.objectSpec[key] as ValueSpecList - const newItem = this.formService.getListItem(listSpec, val) - - if (!newItem) return - - const index = arr.length - - newItem.markAllAsTouched() - arr.insert(index, newItem) - if (['object', 'union'].includes(listSpec.subtype)) { - const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)[ - 'display-as' - ] - this.objectListDisplay[key].push({ - height: '0px', - expanded: false, - displayAs: displayAs ? Mustache.render(displayAs, newItem.value) : '', - }) - } - - pauseFor(400).then(() => { - const element = document.getElementById(this.getElementId(key, index)) - element?.parentElement?.scrollIntoView({ behavior: 'smooth' }) - }) - } - toggleExpandObject(key: string) { this.objectDisplay[key].expanded = !this.objectDisplay[key].expanded this.objectDisplay[key].height = this.objectDisplay[key].expanded @@ -327,6 +299,36 @@ export class FormObjectComponent { await alert.present() } + private addListItem(key: string): void { + const arr = this.formGroup.get(key) as UntypedFormArray + const listSpec = this.objectSpec[key] as ValueSpecList + const newItem = this.formService.getListItem(listSpec, undefined)! + + const index = arr.length + arr.insert(index, newItem) + + if (['object', 'union'].includes(listSpec.subtype)) { + const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)[ + 'display-as' + ] + this.objectListDisplay[key].push({ + height: '0px', + expanded: false, + displayAs: displayAs ? Mustache.render(displayAs, newItem.value) : '', + }) + } + + this.onExpand.emit() + + pauseFor(400).then(() => { + const element = document.getElementById(this.getElementId(key, index)) + element?.parentElement?.scrollIntoView({ behavior: 'smooth' }) + }) + + arr.markAsDirty() + newItem.markAllAsTouched() + } + private deleteListItem(key: string, index: number, markDirty = true): void { if (this.objectListDisplay[key]) this.objectListDisplay[key][index].height = '0px' @@ -340,19 +342,25 @@ export class FormObjectComponent { } private updateEnumList(key: string, current: string[], updated: string[]) { - this.formGroup.get(key)?.markAsDirty() + const arr = this.formGroup.get(key) as FormArray for (let i = current.length - 1; i >= 0; i--) { if (!updated.includes(current[i])) { - this.deleteListItem(key, i, false) + arr.removeAt(i) } } + const listSpec = this.objectSpec[key] as ValueSpecList + updated.forEach(val => { if (!current.includes(val)) { - this.addListItem(key, false, val) + const newItem = this.formService.getListItem(listSpec, val)! + arr.insert(arr.length, newItem) } }) + + arr.markAsDirty() + arr.markAllAsTouched() } private getDocSize(key: string, index = 0): string { diff --git a/frontend/projects/ui/src/app/components/toast-container/notifications-toast/notifications-toast.service.ts b/frontend/projects/ui/src/app/components/toast-container/notifications-toast/notifications-toast.service.ts index b6eb55325..c9a6f30c6 100644 --- a/frontend/projects/ui/src/app/components/toast-container/notifications-toast/notifications-toast.service.ts +++ b/frontend/projects/ui/src/app/components/toast-container/notifications-toast/notifications-toast.service.ts @@ -2,7 +2,8 @@ import { Injectable } from '@angular/core' import { endWith, Observable } from 'rxjs' import { filter, map, pairwise } from 'rxjs/operators' import { exists } from '@start9labs/shared' -import { PatchDbService } from '../../../services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' +import { DataModel } from 'src/app/services/patch-db/data-model' @Injectable({ providedIn: 'root' }) export class NotificationsToastService extends Observable { @@ -15,7 +16,7 @@ export class NotificationsToastService extends Observable { endWith(false), ) - constructor(private readonly patch: PatchDbService) { + constructor(private readonly patch: PatchDB) { super(subscriber => this.stream$.subscribe(subscriber)) } } diff --git a/frontend/projects/ui/src/app/components/toast-container/refresh-alert/refresh-alert.service.ts b/frontend/projects/ui/src/app/components/toast-container/refresh-alert/refresh-alert.service.ts index 0855fc81f..460c00ff6 100644 --- a/frontend/projects/ui/src/app/components/toast-container/refresh-alert/refresh-alert.service.ts +++ b/frontend/projects/ui/src/app/components/toast-container/refresh-alert/refresh-alert.service.ts @@ -2,9 +2,9 @@ import { Injectable } from '@angular/core' import { endWith, Observable } from 'rxjs' import { map } from 'rxjs/operators' import { Emver } from '@start9labs/shared' - -import { PatchDbService } from '../../../services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { ConfigService } from '../../../services/config.service' +import { DataModel } from 'src/app/services/patch-db/data-model' // Watch for connection status @Injectable({ providedIn: 'root' }) @@ -15,7 +15,7 @@ export class RefreshAlertService extends Observable { ) constructor( - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly emver: Emver, private readonly config: ConfigService, ) { diff --git a/frontend/projects/ui/src/app/components/toast-container/update-toast/update-toast.service.ts b/frontend/projects/ui/src/app/components/toast-container/update-toast/update-toast.service.ts index ff8389b7f..5eb650869 100644 --- a/frontend/projects/ui/src/app/components/toast-container/update-toast/update-toast.service.ts +++ b/frontend/projects/ui/src/app/components/toast-container/update-toast/update-toast.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core' import { endWith, Observable } from 'rxjs' import { distinctUntilChanged, filter } from 'rxjs/operators' -import { PatchDbService } from '../../../services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' +import { DataModel } from 'src/app/services/patch-db/data-model' @Injectable({ providedIn: 'root' }) export class UpdateToastService extends Observable { @@ -9,7 +10,7 @@ export class UpdateToastService extends Observable { .watch$('server-info', 'status-info', 'updated') .pipe(distinctUntilChanged(), filter(Boolean), endWith(false)) - constructor(private readonly patch: PatchDbService) { + constructor(private readonly patch: PatchDB) { super(subscriber => this.stream$.subscribe(subscriber)) } } diff --git a/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts b/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts index 3e6fae6ad..c61a80699 100644 --- a/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts +++ b/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts @@ -14,8 +14,11 @@ import { } from '@start9labs/shared' import { DependentInfo } from 'src/app/types/dependent-info' import { ConfigSpec } from 'src/app/pkg-config/config-types' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { + DataModel, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' +import { PatchDB } from 'patch-db-client' import { UntypedFormGroup } from '@angular/forms' import { convertValuesRecursive, @@ -57,7 +60,7 @@ export class AppConfigPage { private readonly alertCtrl: AlertController, private readonly modalCtrl: ModalController, private readonly formService: FormService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} async ngOnInit() { diff --git a/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.ts b/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.ts index 6dedd66e4..d82710347 100644 --- a/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.ts +++ b/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.ts @@ -7,8 +7,9 @@ import { import { getErrorMessage } from '@start9labs/shared' import { BackupInfo } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { AppRecoverOption } from './to-options.pipe' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'app-recover-select', @@ -30,7 +31,7 @@ export class AppRecoverSelectPage { private readonly modalCtrl: ModalController, private readonly loadingCtrl: LoadingController, private readonly embassyApi: ApiService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} dismiss() { diff --git a/frontend/projects/ui/src/app/modals/backup-select/backup-select.page.ts b/frontend/projects/ui/src/app/modals/backup-select/backup-select.page.ts index d09b39534..be6ad7e4d 100644 --- a/frontend/projects/ui/src/app/modals/backup-select/backup-select.page.ts +++ b/frontend/projects/ui/src/app/modals/backup-select/backup-select.page.ts @@ -1,8 +1,8 @@ import { Component } from '@angular/core' import { ModalController } from '@ionic/angular' import { filter, map, take } from 'rxjs/operators' -import { PackageState } from 'src/app/services/patch-db/data-model' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { DataModel, PackageState } from 'src/app/services/patch-db/data-model' +import { PatchDB } from 'patch-db-client' @Component({ selector: 'backup-select', @@ -22,7 +22,7 @@ export class BackupSelectPage { constructor( private readonly modalCtrl: ModalController, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} ngOnInit() { diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts index 74d65a5a3..ce2c4048d 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts @@ -7,9 +7,10 @@ import { ModalController, NavController, } from '@ionic/angular' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { Action, + DataModel, PackageDataEntry, PackageMainStatus, } from 'src/app/services/patch-db/data-model' @@ -36,7 +37,7 @@ export class AppActionsPage { private readonly errToast: ErrorToastService, private readonly loadingCtrl: LoadingController, private readonly navCtrl: NavController, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} async handleAction( @@ -197,10 +198,10 @@ export class AppActionsPage { }) setTimeout(() => successModal.present(), 500) - return false + return true // needed to dismiss original modal/alert } catch (e: any) { this.errToast.present(e) - return false + return false // don't dismiss original modal/alert } finally { loader.dismiss() } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts index 7999d054b..cfe993d41 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts @@ -4,10 +4,11 @@ import { ModalController, ToastController } from '@ionic/angular' import { getPkgId, copyToClipboard } from '@start9labs/shared' import { getUiInterfaceKey } from 'src/app/services/config.service' import { + DataModel, InstalledPackageDataEntry, InterfaceDef, } from 'src/app/services/patch-db/data-model' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { QRComponent } from 'src/app/components/qr/qr.component' import { getPackage } from '../../../util/get-package-data' @@ -28,7 +29,7 @@ export class AppInterfacesPage { constructor( private readonly route: ActivatedRoute, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} async ngOnInit() { diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.page.ts index 86e4ef156..3a717e15d 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.page.ts @@ -1,6 +1,9 @@ import { Component } from '@angular/core' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { PatchDB } from 'patch-db-client' +import { + DataModel, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' import { Observable } from 'rxjs' import { filter, map, switchMap, take, takeUntil, tap } from 'rxjs/operators' import { isEmptyObject, exists, DestroyService } from '@start9labs/shared' @@ -22,7 +25,7 @@ export class AppListPage { constructor( private readonly api: ApiService, private readonly destroy$: DestroyService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} get empty(): boolean { diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/package-info.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/package-info.pipe.ts index a5fd72b9f..76712e4a3 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/package-info.pipe.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/package-info.pipe.ts @@ -1,15 +1,18 @@ import { Pipe, PipeTransform } from '@angular/core' import { Observable } from 'rxjs' import { filter, map, startWith } from 'rxjs/operators' -import { PackageDataEntry } from '../../../services/patch-db/data-model' +import { + DataModel, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' import { getPackageInfo, PkgInfo } from '../../../util/get-package-info' -import { PatchDbService } from '../../../services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' @Pipe({ name: 'packageInfo', }) export class PackageInfoPipe implements PipeTransform { - constructor(private readonly patch: PatchDbService) {} + constructor(private readonly patch: PatchDB) {} transform(pkg: PackageDataEntry): Observable { return this.patch diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts index 5e3cc89ca..722bca8d5 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts @@ -10,8 +10,11 @@ import { } from '@ionic/angular' import { PackageProperties } from 'src/app/util/properties.util' import { QRComponent } from 'src/app/components/qr/qr.component' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { PackageMainStatus } from 'src/app/services/patch-db/data-model' +import { PatchDB } from 'patch-db-client' +import { + DataModel, + PackageMainStatus, +} from 'src/app/services/patch-db/data-model' import { DestroyService, ErrorToastService, @@ -52,7 +55,7 @@ export class AppPropertiesPage { private readonly toastCtrl: ToastController, private readonly modalCtrl: ModalController, private readonly navCtrl: NavController, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly destroy$: DestroyService, ) {} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts index 0269ad56d..dfce4cadc 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts @@ -4,7 +4,6 @@ import { Routes, RouterModule } from '@angular/router' import { IonicModule } from '@ionic/angular' import { AppShowPage } from './app-show.page' import { EmverPipesModule } from '@start9labs/shared' -import { AppWizardComponentModule } from 'src/app/components/app-wizard/app-wizard.component.module' import { StatusComponentModule } from 'src/app/components/status/status.component.module' import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module' import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module' @@ -51,7 +50,6 @@ const routes: Routes = [ StatusComponentModule, IonicModule, RouterModule.forChild(routes), - AppWizardComponentModule, AppConfigPageModule, EmverPipesModule, LaunchablePipeModule, diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index a4f85a4ec..741275e83 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -1,7 +1,8 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' import { NavController } from '@ionic/angular' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { + DataModel, PackageDataEntry, PackageMainStatus, PackageState, @@ -62,7 +63,7 @@ export class AppShowPage { constructor( private readonly route: ActivatedRoute, private readonly navCtrl: NavController, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, @Inject(AbstractMarketplaceService) private readonly marketplaceService: MarketplaceService, ) {} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts index 49f5b8d68..2634b398f 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts @@ -8,6 +8,7 @@ import { removeTrailingSlash, } from '@start9labs/shared' import { + DataModel, PackageDataEntry, UIMarketplaceData, } from 'src/app/services/patch-db/data-model' @@ -16,7 +17,8 @@ import { ApiService } from 'src/app/services/api/embassy-api.service' import { from, map, Observable } from 'rxjs' import { Marketplace } from '@start9labs/marketplace' import { ActionMarketplaceComponent } from 'src/app/modals/action-marketplace/action-marketplace.component' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' + export interface Button { title: string description: string @@ -38,7 +40,7 @@ export class ToButtonsPipe implements PipeTransform { private readonly modalCtrl: ModalController, private readonly modalService: ModalService, private readonly apiService: ApiService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} transform( diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts index 8872e758f..4e8b19b3b 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts @@ -4,12 +4,13 @@ import { NavController } from '@ionic/angular' import { combineLatest, Observable } from 'rxjs' import { filter, map, startWith } from 'rxjs/operators' import { + DataModel, DependencyError, DependencyErrorType, PackageDataEntry, } from 'src/app/services/patch-db/data-model' import { DependentInfo } from 'src/app/types/dependent-info' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { ModalService } from 'src/app/services/modal.service' export interface DependencyInfo { @@ -27,7 +28,7 @@ export interface DependencyInfo { }) export class ToDependenciesPipe implements PipeTransform { constructor( - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly navCtrl: NavController, private readonly modalService: ModalService, ) {} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts index f6cbbe3e8..e8c34844a 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts @@ -1,19 +1,20 @@ import { Pipe, PipeTransform } from '@angular/core' import { + DataModel, HealthCheckResult, PackageDataEntry, PackageMainStatus, } from 'src/app/services/patch-db/data-model' import { exists, isEmptyObject } from '@start9labs/shared' import { filter, map, startWith } from 'rxjs/operators' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { Observable } from 'rxjs' @Pipe({ name: 'toHealthChecks', }) export class ToHealthChecksPipe implements PipeTransform { - constructor(private readonly patch: PatchDbService) {} + constructor(private readonly patch: PatchDB) {} transform( pkg: PackageDataEntry, diff --git a/frontend/projects/ui/src/app/pages/developer-routes/dev-config/dev-config.page.ts b/frontend/projects/ui/src/app/pages/developer-routes/dev-config/dev-config.page.ts index 39cdeb1c9..8a4a5ceec 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/dev-config/dev-config.page.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/dev-config/dev-config.page.ts @@ -5,9 +5,10 @@ import { debounce, ErrorToastService } from '@start9labs/shared' import * as yaml from 'js-yaml' import { filter, take } from 'rxjs/operators' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { getProjectId } from 'src/app/util/get-project-id' import { GenericFormPage } from '../../../modals/generic-form/generic-form.page' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'dev-config', @@ -24,12 +25,12 @@ export class DevConfigPage { private readonly route: ActivatedRoute, private readonly errToast: ErrorToastService, private readonly modalCtrl: ModalController, - private readonly patchDb: PatchDbService, + private readonly patch: PatchDB, private readonly api: ApiService, ) {} ngOnInit() { - this.patchDb + this.patch .watch$('ui', 'dev', this.projectId, 'config') .pipe(filter(Boolean), take(1)) .subscribe(config => { diff --git a/frontend/projects/ui/src/app/pages/developer-routes/dev-instructions/dev-instructions.page.ts b/frontend/projects/ui/src/app/pages/developer-routes/dev-instructions/dev-instructions.page.ts index fed1beb45..7686f6542 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/dev-instructions/dev-instructions.page.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/dev-instructions/dev-instructions.page.ts @@ -8,8 +8,9 @@ import { ErrorToastService, MarkdownComponent, } from '@start9labs/shared' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { getProjectId } from 'src/app/util/get-project-id' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'dev-instructions', @@ -26,12 +27,12 @@ export class DevInstructionsPage { private readonly route: ActivatedRoute, private readonly errToast: ErrorToastService, private readonly modalCtrl: ModalController, - private readonly patchDb: PatchDbService, + private readonly patch: PatchDB, private readonly api: ApiService, ) {} ngOnInit() { - this.patchDb + this.patch .watch$('ui', 'dev', this.projectId, 'instructions') .pipe(filter(Boolean), take(1)) .subscribe(config => { diff --git a/frontend/projects/ui/src/app/pages/developer-routes/dev-manifest/dev-manifest.page.ts b/frontend/projects/ui/src/app/pages/developer-routes/dev-manifest/dev-manifest.page.ts index cb76f263f..f039abbd8 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/dev-manifest/dev-manifest.page.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/dev-manifest/dev-manifest.page.ts @@ -2,8 +2,9 @@ import { Component } from '@angular/core' import { ActivatedRoute } from '@angular/router' import * as yaml from 'js-yaml' import { take } from 'rxjs/operators' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { getProjectId } from 'src/app/util/get-project-id' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'dev-manifest', @@ -17,11 +18,11 @@ export class DevManifestPage { constructor( private readonly route: ActivatedRoute, - private readonly patchDb: PatchDbService, + private readonly patch: PatchDB, ) {} ngOnInit() { - this.patchDb + this.patch .watch$('ui', 'dev', this.projectId) .pipe(take(1)) .subscribe(devData => { diff --git a/frontend/projects/ui/src/app/pages/developer-routes/developer-list/developer-list.page.ts b/frontend/projects/ui/src/app/pages/developer-routes/developer-list/developer-list.page.ts index 0b419fc51..896e0dc4e 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/developer-list/developer-list.page.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/developer-list/developer-list.page.ts @@ -10,12 +10,12 @@ import { GenericInputComponent, GenericInputOptions, } from 'src/app/modals/generic-input/generic-input.component' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ConfigSpec } from 'src/app/pkg-config/config-types' import * as yaml from 'js-yaml' import { v4 } from 'uuid' -import { DevData } from 'src/app/services/patch-db/data-model' +import { DataModel, DevData } from 'src/app/services/patch-db/data-model' import { DestroyService, ErrorToastService } from '@start9labs/shared' import { takeUntil } from 'rxjs/operators' @@ -35,7 +35,7 @@ export class DeveloperListPage { private readonly errToast: ErrorToastService, private readonly alertCtrl: AlertController, private readonly destroy$: DestroyService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly actionCtrl: ActionSheetController, ) {} diff --git a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.ts b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.ts index cad282a91..168933823 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.ts @@ -3,11 +3,11 @@ import { ActivatedRoute } from '@angular/router' import { LoadingController, ModalController } from '@ionic/angular' import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' import { BasicInfo, getBasicInfoSpec } from './form-info' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ErrorToastService } from '@start9labs/shared' import { getProjectId } from 'src/app/util/get-project-id' -import { DevProjectData } from 'src/app/services/patch-db/data-model' +import { DataModel, DevProjectData } from 'src/app/services/patch-db/data-model' @Component({ selector: 'developer-menu', @@ -25,7 +25,7 @@ export class DeveloperMenuPage { private readonly loadingCtrl: LoadingController, private readonly api: ApiService, private readonly errToast: ErrorToastService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} async openBasicInfoModal(data: DevProjectData) { diff --git a/frontend/projects/ui/src/app/pages/login/login.page.scss b/frontend/projects/ui/src/app/pages/login/login.page.scss index 58baa90a7..1a39af526 100644 --- a/frontend/projects/ui/src/app/pages/login/login.page.scss +++ b/frontend/projects/ui/src/app/pages/login/login.page.scss @@ -6,7 +6,6 @@ ion-card-title { } ion-item { - --border-radius: 6px; --border-style: solid; --border-width: 1px; --border-color: var(--ion-color-light); diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.ts index 41a4a7b95..d2ece24b7 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.ts +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.ts @@ -1,8 +1,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { map } from 'rxjs/operators' import { AbstractMarketplaceService } from '@start9labs/marketplace' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { ConnectionService } from 'src/app/services/connection.service' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'marketplace-list', @@ -23,7 +24,7 @@ export class MarketplaceListPage { .pipe(map(({ name }) => name)) constructor( - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly marketplaceService: AbstractMarketplaceService, private readonly connectionService: ConnectionService, ) {} diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts index d94406831..24f926673 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts @@ -11,6 +11,7 @@ import { } from '@start9labs/marketplace' import { Emver, ErrorToastService, isEmptyObject } from '@start9labs/shared' import { + DataModel, PackageDataEntry, PackageState, } from 'src/app/services/patch-db/data-model' @@ -19,7 +20,7 @@ import { MarketplaceService } from 'src/app/services/marketplace.service' import { hasCurrentDeps } from 'src/app/util/has-deps' import { ApiService } from 'src/app/services/api/embassy-api.service' import { Breakages } from 'src/app/services/api/api.types' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { getAllPackages } from 'src/app/util/get-package-data' import { firstValueFrom } from 'rxjs' @@ -49,7 +50,7 @@ export class MarketplaceShowControlsComponent { private readonly emver: Emver, private readonly errToast: ErrorToastService, private readonly embassyApi: ApiService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} get localVersion(): string { diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.module.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.module.ts index 685f0d77f..029fef1fb 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.module.ts +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.module.ts @@ -14,8 +14,6 @@ import { AdditionalModule, DependenciesModule, } from '@start9labs/marketplace' -import { AppWizardComponentModule } from 'src/app/components/app-wizard/app-wizard.component.module' - import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module' import { MarketplaceShowPage } from './marketplace-show.page' import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component' @@ -39,7 +37,6 @@ const routes: Routes = [ EmverPipesModule, MarkdownPipeModule, MarketplaceStatusModule, - AppWizardComponentModule, PackageModule, AboutModule, DependenciesModule, diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.ts index a52ef84c0..298c17af2 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.ts +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.ts @@ -5,10 +5,10 @@ import { MarketplacePkg, AbstractMarketplaceService, } from '@start9labs/marketplace' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { PatchDB } from 'patch-db-client' import { BehaviorSubject, Observable, of } from 'rxjs' import { catchError, filter, shareReplay, switchMap } from 'rxjs/operators' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'marketplace-show', @@ -40,7 +40,7 @@ export class MarketplaceShowPage { constructor( private readonly route: ActivatedRoute, private readonly errToast: ErrorToastService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly marketplaceService: AbstractMarketplaceService, ) {} diff --git a/frontend/projects/ui/src/app/pages/notifications/notifications.page.ts b/frontend/projects/ui/src/app/pages/notifications/notifications.page.ts index 4ea359297..0b9acdfba 100644 --- a/frontend/projects/ui/src/app/pages/notifications/notifications.page.ts +++ b/frontend/projects/ui/src/app/pages/notifications/notifications.page.ts @@ -13,7 +13,8 @@ import { import { ActivatedRoute } from '@angular/router' import { ErrorToastService } from '@start9labs/shared' import { BackupReportPage } from 'src/app/modals/backup-report/backup-report.page' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'notifications', @@ -36,7 +37,7 @@ export class NotificationsPage { private readonly modalCtrl: ModalController, private readonly errToast: ErrorToastService, private readonly route: ActivatedRoute, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} async ngOnInit() { diff --git a/frontend/projects/ui/src/app/pages/server-routes/lan/lan.page.ts b/frontend/projects/ui/src/app/pages/server-routes/lan/lan.page.ts index 15ea137b0..827a82455 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/lan/lan.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/lan/lan.page.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { ConfigService } from 'src/app/services/config.service' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'lan', @@ -14,7 +15,7 @@ export class LANPage { constructor( private readonly config: ConfigService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} installCert(): void { diff --git a/frontend/projects/ui/src/app/pages/server-routes/marketplaces/marketplaces.page.ts b/frontend/projects/ui/src/app/pages/server-routes/marketplaces/marketplaces.page.ts index d8edda5fe..534edb9f2 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/marketplaces/marketplaces.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/marketplaces/marketplaces.page.ts @@ -11,9 +11,12 @@ import { AbstractMarketplaceService } from '@start9labs/marketplace' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ValueSpecObject } from 'src/app/pkg-config/config-types' import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' -import { PatchDbService } from '../../../services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { v4 } from 'uuid' -import { UIMarketplaceData } from '../../../services/patch-db/data-model' +import { + DataModel, + UIMarketplaceData, +} from '../../../services/patch-db/data-model' import { ConfigService } from '../../../services/config.service' import { MarketplaceService } from 'src/app/services/marketplace.service' import { @@ -50,7 +53,7 @@ export class MarketplacesPage { @Inject(AbstractMarketplaceService) private readonly marketplaceService: MarketplaceService, private readonly config: ConfigService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly destroy$: DestroyService, private readonly alertCtrl: AlertController, ) {} diff --git a/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.ts b/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.ts index 4bfe716cb..56e022f23 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { LoadingController, ModalController, @@ -16,6 +16,7 @@ import { ServerNameInfo, ServerNameService, } from 'src/app/services/server-name.service' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'preferences', @@ -36,7 +37,7 @@ export class PreferencesPage { private readonly api: ApiService, private readonly toastCtrl: ToastController, private readonly localStorageService: LocalStorageService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly serverNameService: ServerNameService, readonly serverConfig: ServerConfigService, ) {} diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts b/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts index 875ffeb36..021e9fba0 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts @@ -4,9 +4,12 @@ import { Pipe, PipeTransform, } from '@angular/core' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { filter, take } from 'rxjs/operators' -import { PackageMainStatus } from 'src/app/services/patch-db/data-model' +import { + DataModel, + PackageMainStatus, +} from 'src/app/services/patch-db/data-model' import { Observable } from 'rxjs' @Component({ @@ -26,7 +29,7 @@ export class BackingUpComponent { PackageMainStatus = PackageMainStatus - constructor(private readonly patch: PatchDbService) {} + constructor(private readonly patch: PatchDB) {} } @Pipe({ @@ -44,5 +47,5 @@ export class PkgMainStatusPipe implements PipeTransform { ) } - constructor(private readonly patch: PatchDbService) {} + constructor(private readonly patch: PatchDB) {} } diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts index 33bd51bab..807c39b05 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts @@ -9,7 +9,7 @@ import { GenericInputComponent, GenericInputOptions, } from 'src/app/modals/generic-input/generic-input.component' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { skip, takeUntil } from 'rxjs/operators' import { MappedBackupTarget } from 'src/app/types/mapped-backup-target' import * as argon2 from '@start9labs/argon2' @@ -21,6 +21,7 @@ import { BackupSelectPage } from 'src/app/modals/backup-select/backup-select.pag import { EOSService } from 'src/app/services/eos.service' import { DestroyService } from '@start9labs/shared' import { getServerInfo } from 'src/app/util/get-server-info' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'server-backup', @@ -40,7 +41,7 @@ export class ServerBackupPage { private readonly navCtrl: NavController, private readonly destroy$: DestroyService, private readonly eosService: EOSService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} ngOnInit() { diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.module.ts b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.module.ts index 8f8086bc6..15fa48397 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.module.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.module.ts @@ -7,6 +7,7 @@ import { FormsModule } from '@angular/forms' import { TextSpinnerComponentModule } from '@start9labs/shared' import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module' import { OSUpdatePageModule } from 'src/app/modals/os-update/os-update.page.module' +import { BackupColorPipeModule } from 'src/app/pipes/backup-color/backup-color.module' const routes: Routes = [ { @@ -24,6 +25,7 @@ const routes: Routes = [ TextSpinnerComponentModule, BadgeMenuComponentModule, OSUpdatePageModule, + BackupColorPipeModule, ], declarations: [ServerShowPage], }) diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html index 8fac1c849..e231c9021 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html @@ -48,7 +48,7 @@

Last Backup: {{ server['last-backup'] ? (server['last-backup'] diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts index 26fe75efe..689f91f4d 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts @@ -7,7 +7,7 @@ import { } from '@ionic/angular' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ActivatedRoute } from '@angular/router' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { ServerNameService } from 'src/app/services/server-name.service' import { Observable, of } from 'rxjs' import { filter, take, tap } from 'rxjs/operators' @@ -17,6 +17,7 @@ import { LocalStorageService } from 'src/app/services/local-storage.service' import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page' import { getAllPackages } from '../../../util/get-package-data' import { AuthService } from 'src/app/services/auth.service' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'server-show', @@ -40,7 +41,7 @@ export class ServerShowPage { private readonly embassyApi: ApiService, private readonly navCtrl: NavController, private readonly route: ActivatedRoute, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly eosService: EOSService, private readonly localStorageService: LocalStorageService, private readonly serverNameService: ServerNameService, diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.ts index f77b8f7e1..316b8c4fc 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.ts @@ -1,8 +1,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { ToastController } from '@ionic/angular' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { ConfigService } from 'src/app/services/config.service' import { copyToClipboard } from '@start9labs/shared' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ selector: 'server-specs', @@ -15,7 +16,7 @@ export class ServerSpecsPage { constructor( private readonly toastCtrl: ToastController, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly config: ConfigService, ) {} diff --git a/frontend/projects/ui/src/app/pipes/backup-color/backup-color.module.ts b/frontend/projects/ui/src/app/pipes/backup-color/backup-color.module.ts new file mode 100644 index 000000000..3451791e1 --- /dev/null +++ b/frontend/projects/ui/src/app/pipes/backup-color/backup-color.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from '@angular/core' +import { BackupColorPipe } from './backup-color.pipe' + +@NgModule({ + declarations: [BackupColorPipe], + exports: [BackupColorPipe], +}) +export class BackupColorPipeModule {} diff --git a/frontend/projects/ui/src/app/pipes/backup-color/backup-color.pipe.ts b/frontend/projects/ui/src/app/pipes/backup-color/backup-color.pipe.ts new file mode 100644 index 000000000..461afa03e --- /dev/null +++ b/frontend/projects/ui/src/app/pipes/backup-color/backup-color.pipe.ts @@ -0,0 +1,23 @@ +import { Pipe, PipeTransform } from '@angular/core' + +@Pipe({ + name: 'backupColor', +}) +export class BackupColorPipe implements PipeTransform { + transform(lastBackup: string | null): 'success' | 'warning' | 'danger' { + if (!lastBackup) return 'danger' + + const currentDate = new Date().valueOf() + const backupDate = new Date(lastBackup).valueOf() + const diff = currentDate - backupDate + const week = 604800000 + + if (diff <= week) { + return 'success' + } else if (diff > week && diff <= week * 2) { + return 'warning' + } else { + return 'danger' + } + } +} diff --git a/frontend/projects/ui/src/app/services/api/api.fixures.ts b/frontend/projects/ui/src/app/services/api/api.fixures.ts index 0ff627db3..d1b8d8e54 100644 --- a/frontend/projects/ui/src/app/services/api/api.fixures.ts +++ b/frontend/projects/ui/src/app/services/api/api.fixures.ts @@ -679,7 +679,7 @@ export module Mock { manifest: { ...Mock.MockManifestBitcoind, 'release-notes': - 'For a complete list of changes, please visit https://bitcoincore.org/en/releases/0.21.0/

  • Taproot!
  • New RPCs
  • Experimental Descriptor Wallets
', + 'For a complete list of changes, please visit https://bitcoincore.org/en/releases/0.21.0/
Or in [markdown](https://bitcoincore.org/en/releases/0.21.0/)
  • Taproot!
  • New RPCs
  • Experimental Descriptor Wallets
', }, categories: ['bitcoin', 'cryptocurrency'], versions: ['0.19.0', '0.20.0', '0.21.0'], @@ -1469,6 +1469,14 @@ export module Mock { masked: false, copyable: true, }, + 'private-domain': { + name: 'Private Domain', + type: 'string', + description: 'the private address of the node', + nullable: false, + masked: true, + copyable: true, + }, }, }, }, @@ -1726,7 +1734,10 @@ export module Mock { rpcuser: '123', rulemakers: [], }, - 'bitcoin-node': undefined, + 'bitcoin-node': { + type: 'external', + 'public-domain': 'hello.com', + }, port: 20, rpcallowip: undefined, rpcauth: ['matt: 8273gr8qwoidm1uid91jeh8y23gdio1kskmwejkdnm'], diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index 6a6f0c2ef..e3bf51a4e 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -16,8 +16,8 @@ export module RR { export type GetDumpRes = Dump - export type SetDBValueReq = WithExpire<{ pointer: string; value: any }> // db.put.ui - export type SetDBValueRes = WithRevision + export type SetDBValueReq = { pointer: string; value: any } // db.put.ui + export type SetDBValueRes = null // auth @@ -44,8 +44,8 @@ export module RR { export type GetServerMetricsReq = {} // server.metrics export type GetServerMetricsRes = Metrics - export type UpdateServerReq = WithExpire<{ 'marketplace-url': string }> // server.update - export type UpdateServerRes = WithRevision<'updating' | 'no-updates'> + export type UpdateServerReq = { 'marketplace-url': string } // server.update + export type UpdateServerRes = 'updating' | 'no-updates' export type RestartServerReq = {} // server.restart export type RestartServerRes = null @@ -64,8 +64,8 @@ export module RR { sessions: { [hash: string]: Session } } - export type KillSessionsReq = WithExpire<{ ids: string[] }> // sessions.kill - export type KillSessionsRes = WithRevision + export type KillSessionsReq = { ids: string[] } // sessions.kill + export type KillSessionsRes = null // password @@ -74,11 +74,11 @@ export module RR { // notification - export type GetNotificationsReq = WithExpire<{ + export type GetNotificationsReq = { before?: number limit?: number - }> // notification.list - export type GetNotificationsRes = WithRevision[]> + } // notification.list + export type GetNotificationsRes = ServerNotification[] export type DeleteNotificationReq = { id: number } // notification.delete export type DeleteNotificationRes = null @@ -96,8 +96,8 @@ export module RR { ssids: { [ssid: string]: number } - connected?: string - country: string + connected: string | null + country: string | null ethernet: boolean 'available-wifi': AvailableWifi[] } @@ -151,14 +151,14 @@ export module RR { export type GetBackupInfoReq = { 'target-id': string; password: string } // backup.target.info export type GetBackupInfoRes = BackupInfo - export type CreateBackupReq = WithExpire<{ + export type CreateBackupReq = { // backup.create 'target-id': string 'package-ids': string[] 'old-password': string | null password: string - }> - export type CreateBackupRes = WithRevision + } + export type CreateBackupRes = null // package @@ -175,13 +175,13 @@ export module RR { export type GetPackageMetricsReq = { id: string } // package.metrics export type GetPackageMetricsRes = Metric - export type InstallPackageReq = WithExpire<{ + export type InstallPackageReq = { id: string 'version-spec'?: string 'version-priority'?: 'min' | 'max' 'marketplace-url': string - }> // package.install - export type InstallPackageRes = WithRevision + } // package.install + export type InstallPackageRes = null export type DryUpdatePackageReq = { id: string; version: string } // package.update.dry export type DryUpdatePackageRes = Breakages @@ -192,17 +192,17 @@ export module RR { export type DrySetPackageConfigReq = { id: string; config: object } // package.config.set.dry export type DrySetPackageConfigRes = Breakages - export type SetPackageConfigReq = WithExpire // package.config.set - export type SetPackageConfigRes = WithRevision + export type SetPackageConfigReq = DrySetPackageConfigReq // package.config.set + export type SetPackageConfigRes = null - export type RestorePackagesReq = WithExpire<{ + export type RestorePackagesReq = { // package.backup.restore ids: string[] 'target-id': string 'old-password': string | null password: string - }> - export type RestorePackagesRes = WithRevision + } + export type RestorePackagesRes = null export type ExecutePackageActionReq = { id: string @@ -211,20 +211,20 @@ export module RR { } // package.action export type ExecutePackageActionRes = ActionResponse - export type StartPackageReq = WithExpire<{ id: string }> // package.start - export type StartPackageRes = WithRevision + export type StartPackageReq = { id: string } // package.start + export type StartPackageRes = null - export type RestartPackageReq = WithExpire<{ id: string }> // package.restart - export type RestartPackageRes = WithRevision + export type RestartPackageReq = { id: string } // package.restart + export type RestartPackageRes = null - export type StopPackageReq = WithExpire<{ id: string }> // package.stop - export type StopPackageRes = WithRevision + export type StopPackageReq = { id: string } // package.stop + export type StopPackageRes = null - export type UninstallPackageReq = WithExpire<{ id: string }> // package.uninstall - export type UninstallPackageRes = WithRevision + export type UninstallPackageReq = { id: string } // package.uninstall + export type UninstallPackageRes = null export type DeleteRecoveredPackageReq = { id: string } // package.delete-recovered - export type DeleteRecoveredPackageRes = WithRevision + export type DeleteRecoveredPackageRes = null export type DryConfigureDependencyReq = { 'dependency-id': string @@ -268,9 +268,6 @@ export module RR { export type GetReleaseNotesRes = { [version: string]: string } } -export type WithExpire = { 'expire-id'?: string } & T -export type WithRevision = { response: T | null; revision?: Revision } - export interface MarketplaceEOS { version: string headline: string diff --git a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts index be836929e..34aefafb5 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts @@ -1,12 +1,12 @@ -import { Subject, Observable } from 'rxjs' -import { Update, Operation, Revision } from 'patch-db-client' +import { Observable, ReplaySubject } from 'rxjs' +import { Update } from 'patch-db-client' import { RR } from './api.types' import { DataModel } from 'src/app/services/patch-db/data-model' -import { Log, RequestError } from '@start9labs/shared' +import { Log } from '@start9labs/shared' import { WebSocketSubjectConfig } from 'rxjs/webSocket' export abstract class ApiService { - readonly sync$ = new Subject>() + readonly patchStream$ = new ReplaySubject[]>(1) // http @@ -18,15 +18,7 @@ export abstract class ApiService { // db - abstract getRevisions(since: number): Promise - - abstract getDump(): Promise - - protected abstract setDbValueRaw( - params: RR.SetDBValueReq, - ): Promise - setDbValue = (params: RR.SetDBValueReq) => - this.syncResponse(() => this.setDbValueRaw(params))() + abstract setDbValue(params: RR.SetDBValueReq): Promise // auth @@ -72,18 +64,7 @@ export abstract class ApiService { params: RR.GetPackageMetricsReq, ): Promise - protected abstract updateServerRaw( - params: RR.UpdateServerReq, - ): Promise - updateServer = (params: RR.UpdateServerReq) => - this.syncResponse(() => this.updateServerWrapper(params))() - async updateServerWrapper(params: RR.UpdateServerReq) { - const res = await this.updateServerRaw(params) - if (res.response === 'no-updates') { - throw new Error('Could not find a newer version of EmbassyOS') - } - return res - } + abstract updateServer(params: RR.UpdateServerReq): Promise abstract restartServer( params: RR.RestartServerReq, @@ -116,13 +97,9 @@ export abstract class ApiService { // notification - abstract getNotificationsRaw( + abstract getNotifications( params: RR.GetNotificationsReq, ): Promise - getNotifications = (params: RR.GetNotificationsReq) => - this.syncResponse(() => - this.getNotificationsRaw(params), - )() abstract deleteNotification( params: RR.DeleteNotificationReq, @@ -179,11 +156,7 @@ export abstract class ApiService { params: RR.GetBackupInfoReq, ): Promise - protected abstract createBackupRaw( - params: RR.CreateBackupReq, - ): Promise - createBackup = (params: RR.CreateBackupReq) => - this.syncResponse(() => this.createBackupRaw(params))() + abstract createBackup(params: RR.CreateBackupReq): Promise // package @@ -199,11 +172,9 @@ export abstract class ApiService { params: RR.FollowPackageLogsReq, ): Promise - protected abstract installPackageRaw( + abstract installPackage( params: RR.InstallPackageReq, ): Promise - installPackage = (params: RR.InstallPackageReq) => - this.syncResponse(() => this.installPackageRaw(params))() abstract dryUpdatePackage( params: RR.DryUpdatePackageReq, @@ -217,85 +188,39 @@ export abstract class ApiService { params: RR.DrySetPackageConfigReq, ): Promise - protected abstract setPackageConfigRaw( + abstract setPackageConfig( params: RR.SetPackageConfigReq, ): Promise - setPackageConfig = (params: RR.SetPackageConfigReq) => - this.syncResponse(() => this.setPackageConfigRaw(params))() - protected abstract restorePackagesRaw( + abstract restorePackages( params: RR.RestorePackagesReq, ): Promise - restorePackages = (params: RR.RestorePackagesReq) => - this.syncResponse(() => this.restorePackagesRaw(params))() abstract executePackageAction( params: RR.ExecutePackageActionReq, ): Promise - protected abstract startPackageRaw( - params: RR.StartPackageReq, - ): Promise - startPackage = (params: RR.StartPackageReq) => - this.syncResponse(() => this.startPackageRaw(params))() + abstract startPackage(params: RR.StartPackageReq): Promise - protected abstract restartPackageRaw( + abstract restartPackage( params: RR.RestartPackageReq, ): Promise - restartPackage = (params: RR.RestartPackageReq) => - this.syncResponse(() => this.restartPackageRaw(params))() - protected abstract stopPackageRaw( - params: RR.StopPackageReq, - ): Promise - stopPackage = (params: RR.StopPackageReq) => - this.syncResponse(() => this.stopPackageRaw(params))() + abstract stopPackage(params: RR.StopPackageReq): Promise - protected abstract uninstallPackageRaw( + abstract uninstallPackage( params: RR.UninstallPackageReq, ): Promise - uninstallPackage = (params: RR.UninstallPackageReq) => - this.syncResponse(() => this.uninstallPackageRaw(params))() abstract dryConfigureDependency( params: RR.DryConfigureDependencyReq, ): Promise - protected abstract deleteRecoveredPackageRaw( + abstract deleteRecoveredPackage( params: RR.UninstallPackageReq, ): Promise - deleteRecoveredPackage = (params: RR.UninstallPackageReq) => - this.syncResponse(() => this.deleteRecoveredPackageRaw(params))() abstract sideloadPackage( params: RR.SideloadPackageReq, ): Promise - - // Helper allowing quick decoration to sync the response patch and return the response contents. - // Pass in a tempUpdate function which returns a UpdateTemp corresponding to a temporary - // state change you'd like to enact prior to request and expired when request terminates. - private syncResponse< - T, - F extends (...args: any[]) => Promise<{ response: T; revision?: Revision }>, - >(f: F, temp?: Operation): (...args: Parameters) => Promise { - return (...a) => { - // let expireId = undefined - // if (temp) { - // expireId = uuid.v4() - // this.sync.next({ patch: [temp], expiredBy: expireId }) - // } - - return f(a) - .catch((e: UIRequestError) => { - if (e.revision) this.sync$.next(e.revision) - throw e - }) - .then(({ response, revision }) => { - if (revision) this.sync$.next(revision) - return response - }) - } - } } - -type UIRequestError = RequestError & { revision: Revision } diff --git a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts index 3e4847bfd..da0049144 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -1,9 +1,11 @@ import { Inject, Injectable } from '@angular/core' import { + HttpOptions, HttpService, + isRpcError, Log, Method, - RPCError, + RpcError, RPCOptions, } from '@start9labs/shared' import { ApiService } from './embassy-api.service' @@ -11,11 +13,11 @@ import { RR } from './api.types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { ConfigService } from '../config.service' import { webSocket, WebSocketSubjectConfig } from 'rxjs/webSocket' -import { Observable, timeout } from 'rxjs' +import { Observable } from 'rxjs' import { AuthService } from '../auth.service' import { DOCUMENT } from '@angular/common' import { DataModel } from '../patch-db/data-model' -import { Update } from 'patch-db-client' +import { PatchDB, Update } from 'patch-db-client' @Injectable() export class LiveApiService extends ApiService { @@ -24,13 +26,14 @@ export class LiveApiService extends ApiService { private readonly http: HttpService, private readonly config: ConfigService, private readonly auth: AuthService, + private readonly patch: PatchDB, ) { super() ; (window as any).rpcClient = this } async getStatic(url: string): Promise { - return this.http.httpRequest({ + return this.httpRequest({ method: Method.GET, url, responseType: 'text', @@ -38,7 +41,7 @@ export class LiveApiService extends ApiService { } async uploadPackage(guid: string, body: ArrayBuffer): Promise { - return this.http.httpRequest({ + return this.httpRequest({ method: Method.POST, body, url: `/rest/rpc/${guid}`, @@ -48,22 +51,14 @@ export class LiveApiService extends ApiService { // db - async getRevisions(since: number): Promise { - return this.rpcRequest({ method: 'db.revisions', params: { since } }) - } - - async getDump(): Promise { - return this.rpcRequest({ method: 'db.dump', params: {} }) - } - - async setDbValueRaw(params: RR.SetDBValueReq): Promise { + async setDbValue(params: RR.SetDBValueReq): Promise { return this.rpcRequest({ method: 'db.put.ui', params }) } // auth async login(params: RR.LoginReq): Promise { - return this.rpcRequest({ method: 'auth.login', params }) + return this.rpcRequest({ method: 'auth.login', params }, false) } async logout(params: RR.LogoutReq): Promise { @@ -81,7 +76,7 @@ export class LiveApiService extends ApiService { // server async echo(params: RR.EchoReq): Promise { - return this.rpcRequest({ method: 'echo', params }) + return this.rpcRequest({ method: 'echo', params }, false) } openPatchWebsocket$(): Observable> { @@ -94,7 +89,7 @@ export class LiveApiService extends ApiService { }, } - return this.openWebsocket(config).pipe(timeout({ first: 21000 })) + return this.openWebsocket(config) } openLogsWebsocket$(config: WebSocketSubjectConfig): Observable { @@ -131,10 +126,13 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'server.metrics', params }) } - async updateServerRaw( - params: RR.UpdateServerReq, - ): Promise { + async updateServer(params: RR.UpdateServerReq): Promise { return this.rpcRequest({ method: 'server.update', params }) + // const res = await this.updateServer(params) + // if (res.response === 'no-updates') { + // throw new Error('Could not find a newer version of EmbassyOS') + // } + // return res } async restartServer( @@ -182,7 +180,7 @@ export class LiveApiService extends ApiService { // notification - async getNotificationsRaw( + async getNotifications( params: RR.GetNotificationsReq, ): Promise { return this.rpcRequest({ method: 'notification.list', params }) @@ -277,9 +275,7 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'backup.target.info', params }) } - async createBackupRaw( - params: RR.CreateBackupReq, - ): Promise { + async createBackup(params: RR.CreateBackupReq): Promise { return this.rpcRequest({ method: 'backup.create', params }) } @@ -288,9 +284,9 @@ export class LiveApiService extends ApiService { async getPackageProperties( params: RR.GetPackagePropertiesReq, ): Promise['data']> { - return this.http - .rpcRequest({ method: 'package.properties', params }) - .then(parsePropertiesPermissive) + return this.rpcRequest({ method: 'package.properties', params }).then( + parsePropertiesPermissive, + ) } async getPackageLogs( @@ -311,7 +307,7 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.metrics', params }) } - async installPackageRaw( + async installPackage( params: RR.InstallPackageReq, ): Promise { return this.rpcRequest({ method: 'package.install', params }) @@ -335,13 +331,13 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.config.set.dry', params }) } - async setPackageConfigRaw( + async setPackageConfig( params: RR.SetPackageConfigReq, ): Promise { return this.rpcRequest({ method: 'package.config.set', params }) } - async restorePackagesRaw( + async restorePackages( params: RR.RestorePackagesReq, ): Promise { return this.rpcRequest({ method: 'package.backup.restore', params }) @@ -353,29 +349,27 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.action', params }) } - async startPackageRaw( - params: RR.StartPackageReq, - ): Promise { + async startPackage(params: RR.StartPackageReq): Promise { return this.rpcRequest({ method: 'package.start', params }) } - async restartPackageRaw( + async restartPackage( params: RR.RestartPackageReq, ): Promise { return this.rpcRequest({ method: 'package.restart', params }) } - async stopPackageRaw(params: RR.StopPackageReq): Promise { + async stopPackage(params: RR.StopPackageReq): Promise { return this.rpcRequest({ method: 'package.stop', params }) } - async deleteRecoveredPackageRaw( + async deleteRecoveredPackage( params: RR.DeleteRecoveredPackageReq, ): Promise { return this.rpcRequest({ method: 'package.delete-recovered', params }) } - async uninstallPackageRaw( + async uninstallPackage( params: RR.UninstallPackageReq, ): Promise { return this.rpcRequest({ method: 'package.uninstall', params }) @@ -409,13 +403,42 @@ export class LiveApiService extends ApiService { return webSocket(config) } - private async rpcRequest(options: RPCOptions): Promise { - return this.http.rpcRequest(options).catch(e => { - if ((e as RPCError).error.code === 34) { + private async rpcRequest( + options: RPCOptions, + addHeader = true, + ): Promise { + if (addHeader) { + options.headers = { + 'x-patch-sequence': String(this.patch.cache$.value.sequence), + ...(options.headers || {}), + } + } + + const res = await this.http.rpcRequest(options) + const encoded = res.headers.get('x-patch-updates') + + if (encoded) { + const updates: Update[] = JSON.parse( + decodeURIComponent(encoded), + ) + this.patchStream$.next(updates) + } + + const rpcRes = res.body + + if (isRpcError(rpcRes)) { + if (rpcRes.error.code === 34) { console.error('Unauthenticated, logging out') this.auth.setUnverified() } - throw e - }) + throw new RpcError(rpcRes.error) + } + + return rpcRes.result + } + + private async httpRequest(opts: HttpOptions): Promise { + const res = await this.http.httpRequest(opts) + return res.body } } diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 207e7b4f2..65b07df82 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { pauseFor, Log, LogsRes } from '@start9labs/shared' +import { pauseFor, Log } from '@start9labs/shared' import { ApiService } from './embassy-api.service' import { PatchOp, Update, Operation, RemoveOperation } from 'patch-db-client' import { @@ -11,11 +11,11 @@ import { PackageState, ServerStatus, } from 'src/app/services/patch-db/data-model' -import { CifsBackupTarget, RR, WithRevision } from './api.types' +import { CifsBackupTarget, RR } from './api.types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { Mock } from './api.fixures' import markdown from 'raw-loader!../../../../../../assets/markdown/md-sample.md' -import { BehaviorSubject, interval, map, Observable, tap } from 'rxjs' +import { BehaviorSubject, interval, map, Observable } from 'rxjs' import { LocalStorageBootstrap } from '../patch-db/local-storage-bootstrap' import { mockPatchData } from './mock-patch' import { WebSocketSubjectConfig } from 'rxjs/webSocket' @@ -32,10 +32,9 @@ const PROGRESS: InstallProgress = { @Injectable() export class MockApiService extends ApiService { - readonly mockPatch$ = new BehaviorSubject>({ + readonly mockWsSource$ = new BehaviorSubject>({ id: 1, value: mockPatchData, - expireId: null, }) private readonly revertTime = 2000 sequence = 0 @@ -56,20 +55,7 @@ export class MockApiService extends ApiService { // db - async getRevisions(since: number): Promise { - return this.getDump() - } - - async getDump(): Promise { - const cache = await this.bootstrapper.init() - return { - id: cache.sequence, - value: cache.data, - expireId: null, - } - } - - async setDbValueRaw(params: RR.SetDBValueReq): Promise { + async setDbValue(params: RR.SetDBValueReq): Promise { await pauseFor(2000) const patch = [ { @@ -87,7 +73,7 @@ export class MockApiService extends ApiService { await pauseFor(2000) setTimeout(() => { - this.mockPatch$.next({ id: 1, value: mockPatchData, expireId: null }) + this.mockWsSource$.next({ id: 1, value: mockPatchData }) }, 2000) return null @@ -105,7 +91,7 @@ export class MockApiService extends ApiService { async killSessions(params: RR.KillSessionsReq): Promise { await pauseFor(2000) - return { response: null } + return null } // server @@ -116,7 +102,7 @@ export class MockApiService extends ApiService { } openPatchWebsocket$(): Observable> { - return this.mockPatch$ + return this.mockWsSource$ } openLogsWebsocket$(config: WebSocketSubjectConfig): Observable { @@ -198,9 +184,7 @@ export class MockApiService extends ApiService { return Mock.getAppMetrics() } - async updateServerRaw( - params: RR.UpdateServerReq, - ): Promise { + async updateServer(params: RR.UpdateServerReq): Promise { await pauseFor(2000) const initialProgress = { size: 10000, @@ -289,7 +273,7 @@ export class MockApiService extends ApiService { // notification - async getNotificationsRaw( + async getNotifications( params: RR.GetNotificationsReq, ): Promise { await pauseFor(2000) @@ -418,9 +402,7 @@ export class MockApiService extends ApiService { return Mock.BackupInfo } - async createBackupRaw( - params: RR.CreateBackupReq, - ): Promise { + async createBackup(params: RR.CreateBackupReq): Promise { await pauseFor(2000) const path = '/server-info/status-info/backup-progress' const ids = params['package-ids'] @@ -436,17 +418,17 @@ export class MockApiService extends ApiService { value: PackageMainStatus.BackingUp, }, ] - this.updateMock(appPatch) + this.mockRevision(appPatch) await pauseFor(8000) - this.updateMock([ + this.mockRevision([ { ...appPatch[0], value: PackageMainStatus.Stopped, }, ]) - this.updateMock([ + this.mockRevision([ { op: PatchOp.REPLACE, path: `${path}/${id}/complete`, @@ -465,7 +447,7 @@ export class MockApiService extends ApiService { value: null, }, ] - this.updateMock(lastPatch) + this.mockRevision(lastPatch) }, 500) const originalPatch = [ @@ -525,7 +507,7 @@ export class MockApiService extends ApiService { } } - async installPackageRaw( + async installPackage( params: RR.InstallPackageReq, ): Promise { await pauseFor(2000) @@ -582,7 +564,7 @@ export class MockApiService extends ApiService { return {} } - async setPackageConfigRaw( + async setPackageConfig( params: RR.SetPackageConfigReq, ): Promise { await pauseFor(2000) @@ -596,7 +578,7 @@ export class MockApiService extends ApiService { return this.withRevision(patch) } - async restorePackagesRaw( + async restorePackages( params: RR.RestorePackagesReq, ): Promise { await pauseFor(2000) @@ -627,9 +609,7 @@ export class MockApiService extends ApiService { return Mock.ActionResponse } - async startPackageRaw( - params: RR.StartPackageReq, - ): Promise { + async startPackage(params: RR.StartPackageReq): Promise { const path = `/package-data/${params.id}/installed/status/main` await pauseFor(2000) @@ -647,7 +627,7 @@ export class MockApiService extends ApiService { value: new Date().toISOString(), }, ] - this.updateMock(patch2) + this.mockRevision(patch2) const patch3 = [ { @@ -663,7 +643,7 @@ export class MockApiService extends ApiService { }, }, ] - this.updateMock(patch3) + this.mockRevision(patch3) await pauseFor(2000) @@ -692,7 +672,7 @@ export class MockApiService extends ApiService { }, }, ] - this.updateMock(patch4) + this.mockRevision(patch4) }, 2000) const originalPatch = [ @@ -706,7 +686,7 @@ export class MockApiService extends ApiService { return this.withRevision(originalPatch) } - async restartPackageRaw( + async restartPackage( params: RR.RestartPackageReq, ): Promise { // first enact stop @@ -744,7 +724,7 @@ export class MockApiService extends ApiService { }, } as any, ] - this.updateMock(patch2) + this.mockRevision(patch2) }, this.revertTime) const patch = [ @@ -763,7 +743,7 @@ export class MockApiService extends ApiService { return this.withRevision(patch) } - async stopPackageRaw(params: RR.StopPackageReq): Promise { + async stopPackage(params: RR.StopPackageReq): Promise { await pauseFor(2000) const path = `/package-data/${params.id}/installed/status/main` @@ -775,7 +755,7 @@ export class MockApiService extends ApiService { value: PackageMainStatus.Stopped, }, ] - this.updateMock(patch2) + this.mockRevision(patch2) }, this.revertTime) const patch = [ @@ -794,7 +774,7 @@ export class MockApiService extends ApiService { return this.withRevision(patch) } - async uninstallPackageRaw( + async uninstallPackage( params: RR.UninstallPackageReq, ): Promise { await pauseFor(2000) @@ -806,7 +786,7 @@ export class MockApiService extends ApiService { path: `/package-data/${params.id}`, }, ] - this.updateMock(patch2) + this.mockRevision(patch2) }, this.revertTime) const patch = [ @@ -820,7 +800,7 @@ export class MockApiService extends ApiService { return this.withRevision(patch) } - async deleteRecoveredPackageRaw( + async deleteRecoveredPackage( params: RR.DeleteRecoveredPackageReq, ): Promise { await pauseFor(2000) @@ -878,7 +858,7 @@ export class MockApiService extends ApiService { value: { ...progress }, }, ] - this.updateMock(patch) + this.mockRevision(patch) } } @@ -898,7 +878,7 @@ export class MockApiService extends ApiService { path: `/recovered-packages/${id}`, }, ] - this.updateMock(patch2) + this.mockRevision(patch2) }, 1000) } @@ -914,7 +894,7 @@ export class MockApiService extends ApiService { value: downloaded, }, ] - this.updateMock(patch) + this.mockRevision(patch) } const patch2 = [ @@ -924,7 +904,7 @@ export class MockApiService extends ApiService { value: size, }, ] - this.updateMock(patch2) + this.mockRevision(patch2) setTimeout(async () => { const patch3: Operation[] = [ @@ -938,7 +918,7 @@ export class MockApiService extends ApiService { path: '/server-info/status-info/update-progress', }, ] - this.updateMock(patch3) + this.mockRevision(patch3) // quickly revert server to "running" for continued testing await pauseFor(100) const patch4 = [ @@ -948,7 +928,7 @@ export class MockApiService extends ApiService { value: ServerStatus.Running, }, ] - this.updateMock(patch4) + this.mockRevision(patch4) // set patch indicating update is complete await pauseFor(100) const patch6 = [ @@ -958,11 +938,11 @@ export class MockApiService extends ApiService { value: Mock.ServerUpdated, }, ] - this.updateMock(patch6) + this.mockRevision(patch6) }, 1000) } - private async updateMock(patch: Operation[]): Promise { + private async mockRevision(patch: Operation[]): Promise { if (!this.sequence) { const { sequence } = await this.bootstrapper.init() this.sequence = sequence @@ -970,26 +950,26 @@ export class MockApiService extends ApiService { const revision = { id: ++this.sequence, patch, - expireId: null, } - this.mockPatch$.next(revision) + this.mockWsSource$.next(revision) } private async withRevision( patch: Operation[], response: T | null = null, - ): Promise> { + ): Promise { if (!this.sequence) { const { sequence } = await this.bootstrapper.init() this.sequence = sequence } - const revision = { - id: ++this.sequence, - patch, - expireId: null, - } + this.patchStream$.next([ + { + id: ++this.sequence, + patch, + }, + ]) - return { response, revision } + return response as T } } diff --git a/frontend/projects/ui/src/app/services/api/mock-patch.ts b/frontend/projects/ui/src/app/services/api/mock-patch.ts index c0fa3ef0c..8fe87c35f 100644 --- a/frontend/projects/ui/src/app/services/api/mock-patch.ts +++ b/frontend/projects/ui/src/app/services/api/mock-patch.ts @@ -28,9 +28,10 @@ export const mockPatchData: DataModel = { 'server-info': { id: 'abcdefgh', version: '0.3.1.1', - 'last-backup': null, + 'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(), 'lan-address': 'https://embassy-abcdefgh.local', 'tor-address': 'http://myveryownspecialtoraddress.onion', + 'last-wifi-region': null, 'unread-notification-count': 4, // password is asdfasdf 'password-hash': diff --git a/frontend/projects/ui/src/app/services/auth.service.ts b/frontend/projects/ui/src/app/services/auth.service.ts index e3729c7b6..cdd28bcf3 100644 --- a/frontend/projects/ui/src/app/services/auth.service.ts +++ b/frontend/projects/ui/src/app/services/auth.service.ts @@ -1,6 +1,6 @@ import { Injectable, NgZone } from '@angular/core' import { ReplaySubject } from 'rxjs' -import { map } from 'rxjs/operators' +import { distinctUntilChanged, map } from 'rxjs/operators' import { Storage } from '@ionic/storage-angular' import { Router } from '@angular/router' @@ -17,6 +17,7 @@ export class AuthService { readonly isVerified$ = this.authState$.pipe( map(state => state === AuthState.VERIFIED), + distinctUntilChanged(), ) constructor( diff --git a/frontend/projects/ui/src/app/services/connection.service.ts b/frontend/projects/ui/src/app/services/connection.service.ts index 2f4967502..a45d5ec4c 100644 --- a/frontend/projects/ui/src/app/services/connection.service.ts +++ b/frontend/projects/ui/src/app/services/connection.service.ts @@ -17,6 +17,9 @@ export class ConnectionService { readonly websocketConnected$ = new ReplaySubject(1) readonly connected$ = combineLatest([ this.networkConnected$, - this.websocketConnected$, - ]).pipe(map(([network, websocket]) => network && websocket)) + this.websocketConnected$.pipe(distinctUntilChanged()), + ]).pipe( + map(([network, websocket]) => network && websocket), + distinctUntilChanged(), + ) } diff --git a/frontend/projects/ui/src/app/services/eos.service.ts b/frontend/projects/ui/src/app/services/eos.service.ts index 0e5887891..42a4ba965 100644 --- a/frontend/projects/ui/src/app/services/eos.service.ts +++ b/frontend/projects/ui/src/app/services/eos.service.ts @@ -5,8 +5,9 @@ import { distinctUntilChanged, filter, map } from 'rxjs/operators' import { MarketplaceEOS } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { getServerInfo } from 'src/app/util/get-server-info' +import { DataModel } from './patch-db/data-model' @Injectable({ providedIn: 'root', @@ -49,7 +50,7 @@ export class EOSService { constructor( private readonly api: ApiService, private readonly emver: Emver, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, ) {} async getEOS(): Promise { diff --git a/frontend/projects/ui/src/app/services/marketplace.service.ts b/frontend/projects/ui/src/app/services/marketplace.service.ts index c7b8fb0cd..5b199989e 100644 --- a/frontend/projects/ui/src/app/services/marketplace.service.ts +++ b/frontend/projects/ui/src/app/services/marketplace.service.ts @@ -12,10 +12,11 @@ import { RR } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ConfigService } from 'src/app/services/config.service' import { + DataModel, ServerInfo, UIMarketplaceData, } from 'src/app/services/patch-db/data-model' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { catchError, distinctUntilChanged, @@ -119,7 +120,7 @@ export class MarketplaceService extends AbstractMarketplaceService { constructor( private readonly api: ApiService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly config: ConfigService, private readonly errToast: ErrorToastService, private readonly emver: Emver, diff --git a/frontend/projects/ui/src/app/services/patch-data.service.ts b/frontend/projects/ui/src/app/services/patch-data.service.ts index 786a1ee6e..f11355dfb 100644 --- a/frontend/projects/ui/src/app/services/patch-data.service.ts +++ b/frontend/projects/ui/src/app/services/patch-data.service.ts @@ -3,7 +3,7 @@ import { ModalController } from '@ionic/angular' import { Observable } from 'rxjs' import { filter, share, switchMap, take, tap } from 'rxjs/operators' import { exists, isEmptyObject } from '@start9labs/shared' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { DataModel, UIData } from 'src/app/services/patch-db/data-model' import { EOSService } from 'src/app/services/eos.service' import { OSWelcomePage } from 'src/app/modals/os-welcome/os-welcome.page' @@ -33,7 +33,7 @@ export class PatchDataService extends Observable { ) constructor( - private readonly patch: PatchDbService, + private readonly patch: PatchDB, private readonly eosService: EOSService, private readonly config: ConfigService, private readonly modalCtrl: ModalController, diff --git a/frontend/projects/ui/src/app/services/patch-db/data-model.ts b/frontend/projects/ui/src/app/services/patch-db/data-model.ts index 037087aeb..ffff20bfd 100644 --- a/frontend/projects/ui/src/app/services/patch-db/data-model.ts +++ b/frontend/projects/ui/src/app/services/patch-db/data-model.ts @@ -52,6 +52,7 @@ export interface ServerInfo { 'last-backup': string | null 'lan-address': Url 'tor-address': Url + 'last-wifi-region': string | null 'unread-notification-count': number 'status-info': ServerStatusInfo 'eos-version-compat': string diff --git a/frontend/projects/ui/src/app/services/patch-db/patch-db.factory.ts b/frontend/projects/ui/src/app/services/patch-db/patch-db.factory.ts index 1c459e20e..73bd2b519 100644 --- a/frontend/projects/ui/src/app/services/patch-db/patch-db.factory.ts +++ b/frontend/projects/ui/src/app/services/patch-db/patch-db.factory.ts @@ -1,41 +1,45 @@ -import { InjectionToken } from '@angular/core' -import { catchError, switchMap, take, tap } from 'rxjs/operators' -import { Bootstrapper, DBCache, Update } from 'patch-db-client' +import { InjectionToken, Injector } from '@angular/core' +import { bufferTime, catchError, switchMap, take, tap } from 'rxjs/operators' +import { Update } from 'patch-db-client' import { DataModel } from './data-model' -import { EMPTY, from, interval, merge, Observable } from 'rxjs' +import { defer, EMPTY, from, interval, merge, Observable } from 'rxjs' import { AuthService } from '../auth.service' import { ConnectionService } from '../connection.service' import { ApiService } from '../api/embassy-api.service' -export const PATCH_SOURCE = new InjectionToken>>( +export const PATCH_SOURCE = new InjectionToken[]>>( '', ) -export const PATCH_CACHE = new InjectionToken>('', { - factory: () => ({} as any), -}) -export const BOOTSTRAPPER = new InjectionToken>('') export function sourceFactory( - api: ApiService, - authService: AuthService, - connectionService: ConnectionService, -): Observable> { - const websocket$ = api.openPatchWebsocket$().pipe( - catchError((_, watch$) => { - connectionService.websocketConnected$.next(false) + injector: Injector, +): Observable[]> { + // defer() needed to avoid circular dependency with ApiService, since PatchDB is needed there + return defer(() => { + const api = injector.get(ApiService) + const authService = injector.get(AuthService) + const connectionService = injector.get(ConnectionService) - return interval(4000).pipe( - switchMap(() => - from(api.echo({ message: 'ping' })).pipe(catchError(() => EMPTY)), - ), - take(1), - switchMap(() => watch$), - ) - }), - tap(() => connectionService.websocketConnected$.next(true)), - ) + const websocket$ = api.openPatchWebsocket$().pipe( + bufferTime(250), + catchError((_, watch$) => { + connectionService.websocketConnected$.next(false) - return authService.isVerified$.pipe( - switchMap(verified => (verified ? merge(websocket$, api.sync$) : EMPTY)), - ) + return interval(4000).pipe( + switchMap(() => + from(api.echo({ message: 'ping' })).pipe(catchError(() => EMPTY)), + ), + take(1), + switchMap(() => watch$), + ) + }), + tap(() => connectionService.websocketConnected$.next(true)), + ) + + return authService.isVerified$.pipe( + switchMap(verified => + verified ? merge(websocket$, api.patchStream$) : EMPTY, + ), + ) + }) } diff --git a/frontend/projects/ui/src/app/services/patch-db/patch-db.module.ts b/frontend/projects/ui/src/app/services/patch-db/patch-db.module.ts index 7e990d178..3c816e339 100644 --- a/frontend/projects/ui/src/app/services/patch-db/patch-db.module.ts +++ b/frontend/projects/ui/src/app/services/patch-db/patch-db.module.ts @@ -1,31 +1,18 @@ import { PatchDB } from 'patch-db-client' -import { NgModule } from '@angular/core' -import { - BOOTSTRAPPER, - PATCH_CACHE, - PATCH_SOURCE, - sourceFactory, -} from './patch-db.factory' -import { LocalStorageBootstrap } from './local-storage-bootstrap' -import { ApiService } from '../api/embassy-api.service' -import { AuthService } from '../auth.service' -import { ConnectionService } from '../connection.service' +import { Injector, NgModule } from '@angular/core' +import { PATCH_SOURCE, sourceFactory } from './patch-db.factory' // This module is purely for providers organization purposes @NgModule({ providers: [ - { - provide: BOOTSTRAPPER, - useExisting: LocalStorageBootstrap, - }, { provide: PATCH_SOURCE, - deps: [ApiService, AuthService, ConnectionService], + deps: [Injector], useFactory: sourceFactory, }, { provide: PatchDB, - deps: [PATCH_SOURCE, PATCH_CACHE], + deps: [PATCH_SOURCE], useClass: PatchDB, }, ], diff --git a/frontend/projects/ui/src/app/services/patch-db/patch-db.service.ts b/frontend/projects/ui/src/app/services/patch-db/patch-db.service.ts deleted file mode 100644 index f5e3f801a..000000000 --- a/frontend/projects/ui/src/app/services/patch-db/patch-db.service.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Inject, Injectable } from '@angular/core' -import { Bootstrapper, PatchDB, Store } from 'patch-db-client' -import { Observable, of, Subscription } from 'rxjs' -import { catchError, debounceTime, finalize, tap } from 'rxjs/operators' -import { DataModel } from './data-model' -import { BOOTSTRAPPER } from './patch-db.factory' - -@Injectable({ - providedIn: 'root', -}) -export class PatchDbService { - private sub?: Subscription - - constructor( - @Inject(BOOTSTRAPPER) - private readonly bootstrapper: Bootstrapper, - private readonly patchDb: PatchDB, - ) {} - - start(): void { - // Early return if already started - if (this.sub) { - return - } - - console.log('patchDB: STARTING') - this.sub = this.patchDb.cache$ - .pipe( - debounceTime(420), - tap(cache => { - this.bootstrapper.update(cache) - }), - ) - .subscribe() - } - - stop(): void { - // Early return if already stopped - if (!this.sub) { - return - } - - console.log('patchDB: STOPPING') - this.patchDb.store.reset() - this.sub.unsubscribe() - this.sub = undefined - } - - // prettier-ignore - watch$: Store['watch$'] = (...args: (string | number)[]): Observable => { - const argsString = '/' + args.join('/') - - console.log('patchDB: WATCHING ', argsString) - - return this.patchDb.store.watch$(...(args as [])).pipe( - tap(data => console.log('patchDB: NEW VALUE', argsString, data)), - catchError(e => { - console.error('patchDB: WATCH ERROR', e) - return of(e.message) - }), - finalize(() => console.log('patchDB: UNSUBSCRIBING', argsString)), - ) - } -} diff --git a/frontend/projects/ui/src/app/services/patch-monitor.service.ts b/frontend/projects/ui/src/app/services/patch-monitor.service.ts index aef9056e1..ac395798d 100644 --- a/frontend/projects/ui/src/app/services/patch-monitor.service.ts +++ b/frontend/projects/ui/src/app/services/patch-monitor.service.ts @@ -1,28 +1,31 @@ import { Injectable } from '@angular/core' import { Observable } from 'rxjs' -import { map } from 'rxjs/operators' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { tap } from 'rxjs/operators' +import { PatchDB } from 'patch-db-client' import { AuthService } from 'src/app/services/auth.service' +import { DataModel } from './patch-db/data-model' +import { LocalStorageBootstrap } from './patch-db/local-storage-bootstrap' // Start and stop PatchDb upon verification @Injectable({ providedIn: 'root', }) -export class PatchMonitorService extends Observable { +export class PatchMonitorService extends Observable { + // @TODO not happy with Observable private readonly stream$ = this.authService.isVerified$.pipe( - map(verified => { + tap(verified => { if (verified) { - this.patch.start() - return true + this.patch.start(this.bootstrapper) + } else { + this.patch.stop() } - this.patch.stop() - return false }), ) constructor( private readonly authService: AuthService, - private readonly patch: PatchDbService, + private readonly patch: PatchDB, + private readonly bootstrapper: LocalStorageBootstrap, ) { super(subscriber => this.stream$.subscribe(subscriber)) } diff --git a/frontend/projects/ui/src/app/services/server-name.service.ts b/frontend/projects/ui/src/app/services/server-name.service.ts index 81857b8e1..0fa15b367 100644 --- a/frontend/projects/ui/src/app/services/server-name.service.ts +++ b/frontend/projects/ui/src/app/services/server-name.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core' -import { PatchDbService } from './patch-db/patch-db.service' +import { PatchDB } from 'patch-db-client' import { combineLatest, filter, map, Observable } from 'rxjs' +import { DataModel } from './patch-db/data-model' export interface ServerNameInfo { current: string @@ -26,5 +27,5 @@ export class ServerNameService { }), ) - constructor(private readonly patch: PatchDbService) {} + constructor(private readonly patch: PatchDB) {} } diff --git a/frontend/projects/ui/src/app/util/get-marketplace.ts b/frontend/projects/ui/src/app/util/get-marketplace.ts index 1f78d83a9..8209e5338 100644 --- a/frontend/projects/ui/src/app/util/get-marketplace.ts +++ b/frontend/projects/ui/src/app/util/get-marketplace.ts @@ -1,9 +1,12 @@ -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { UIMarketplaceData } from 'src/app/services/patch-db/data-model' +import { PatchDB } from 'patch-db-client' +import { + DataModel, + UIMarketplaceData, +} from 'src/app/services/patch-db/data-model' import { filter, firstValueFrom } from 'rxjs' export function getMarketplace( - patch: PatchDbService, + patch: PatchDB, ): Promise { return firstValueFrom(patch.watch$('ui', 'marketplace').pipe(filter(Boolean))) } diff --git a/frontend/projects/ui/src/app/util/get-package-data.ts b/frontend/projects/ui/src/app/util/get-package-data.ts index 6bdc2abdc..7542ce024 100644 --- a/frontend/projects/ui/src/app/util/get-package-data.ts +++ b/frontend/projects/ui/src/app/util/get-package-data.ts @@ -1,16 +1,19 @@ -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { PatchDB } from 'patch-db-client' +import { + DataModel, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' import { filter, firstValueFrom } from 'rxjs' export function getPackage( - patch: PatchDbService, + patch: PatchDB, id: string, ): Promise { return firstValueFrom(patch.watch$('package-data', id)) } export function getAllPackages( - patch: PatchDbService, + patch: PatchDB, ): Promise> { return firstValueFrom(patch.watch$('package-data').pipe(filter(Boolean))) } diff --git a/frontend/projects/ui/src/app/util/get-server-info.ts b/frontend/projects/ui/src/app/util/get-server-info.ts index 58eef3ecf..d924f6012 100644 --- a/frontend/projects/ui/src/app/util/get-server-info.ts +++ b/frontend/projects/ui/src/app/util/get-server-info.ts @@ -1,7 +1,7 @@ -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { ServerInfo } from 'src/app/services/patch-db/data-model' +import { PatchDB } from 'patch-db-client' +import { DataModel, ServerInfo } from 'src/app/services/patch-db/data-model' import { filter, firstValueFrom } from 'rxjs' -export function getServerInfo(patch: PatchDbService): Promise { +export function getServerInfo(patch: PatchDB): Promise { return firstValueFrom(patch.watch$('server-info').pipe(filter(Boolean))) } diff --git a/frontend/projects/ui/src/styles.scss b/frontend/projects/ui/src/styles.scss index 5de41be67..e95b3115d 100644 --- a/frontend/projects/ui/src/styles.scss +++ b/frontend/projects/ui/src/styles.scss @@ -1,6 +1,3 @@ -@import '~swiper/scss'; -@import '~@ionic/angular/css/ionic-swiper'; - @font-face { font-family: 'Montserrat'; font-style: normal; @@ -61,12 +58,6 @@ $subheader-height: 48px; -.swiper { - .swiper-slide { - display: unset; - } -} - .btn-128 { min-width: 128px; } @@ -253,7 +244,7 @@ ion-item-divider { } ion-item { - border-radius: 6px; + --border-radius: 6px; --ripple-color: transparent; } @@ -266,12 +257,6 @@ ion-loading { z-index: 40000 !important; } -.swiper-pagination { - position: fixed; - bottom: 0px; - padding-bottom: 3px; -} - .rec-item { margin: 20px; border-style: solid; diff --git a/libs/Cargo.lock b/libs/Cargo.lock index 0810bc976..6f8d4f0b5 100644 --- a/libs/Cargo.lock +++ b/libs/Cargo.lock @@ -105,6 +105,17 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "barrage" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5951c75bdabb58753d140dd5802f12ff3a483cb2e16fb5276e111b94b19e87" +dependencies = [ + "concurrent-queue", + "event-listener", + "spin", +] + [[package]] name = "base64" version = "0.11.0" @@ -174,6 +185,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + [[package]] name = "cc" version = "1.0.73" @@ -213,6 +230,15 @@ dependencies = [ "tracing-error 0.2.0", ] +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -447,6 +473,12 @@ dependencies = [ "syn", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eyre" version = "0.6.8" @@ -1327,6 +1359,7 @@ name = "patch-db" version = "0.1.0" dependencies = [ "async-trait", + "barrage", "fd-lock-rs", "futures", "imbl", @@ -1897,6 +1930,12 @@ dependencies = [ "url", ] +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/patch-db b/patch-db index f4732b18a..74c01eb5d 160000 --- a/patch-db +++ b/patch-db @@ -1 +1 @@ -Subproject commit f4732b18a22f80556df778dc27933bac32d27f97 +Subproject commit 74c01eb5dbab375abfcf27b7ef9716d6d01c88b5 diff --git a/system-images/compat/Cargo.lock b/system-images/compat/Cargo.lock index a5db73869..cecfcbf7c 100644 --- a/system-images/compat/Cargo.lock +++ b/system-images/compat/Cargo.lock @@ -637,6 +637,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-queue" version = "0.3.5" @@ -960,6 +969,7 @@ dependencies = [ "indexmap", "isocountry", "itertools 0.10.3", + "josekit", "jsonpath_lib", "lazy_static", "libc", @@ -1161,6 +1171,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.10.12" @@ -1741,6 +1761,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "josekit" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee6af62ad98bdf699ad2ecc8323479a1fdc7aa5faa6043d93119d83f6c5fca8" +dependencies = [ + "anyhow", + "base64", + "flate2", + "once_cell", + "openssl", + "regex", + "serde", + "serde_json", + "thiserror", + "time 0.3.11", +] + [[package]] name = "js-sys" version = "0.3.57" @@ -2933,9 +2971,9 @@ dependencies = [ [[package]] name = "rpc-toolkit" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b40671737dff8e63a1294536eea6e186f3700930fe92d8b5c7dfd636d0df16" +checksum = "9d5bfeb75c188f3af65774d5fe92f97dac2cede5e313c643c7a1b82a8e53b0e6" dependencies = [ "clap 3.2.10", "futures", @@ -2955,9 +2993,9 @@ dependencies = [ [[package]] name = "rpc-toolkit-macro" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad6cd35336587eb0459cdc01f873ddd5a8a3480f97c5268a46a820e1b23b9aa" +checksum = "ecb48bdaace41cfbb514b3e541ae0fc1ac0fb8283498215ad8a3d22ca2ea5ae5" dependencies = [ "proc-macro2 1.0.39", "rpc-toolkit-macro-internals", @@ -2966,9 +3004,9 @@ dependencies = [ [[package]] name = "rpc-toolkit-macro-internals" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa57bace2d4fe20a2aeac1ef4e9f6f1574f07b72fde1e43c7bb55e963f1702e7" +checksum = "b2a9e2bae02a2beecad48d87255e51cab941d0c89a2bcee05a03a77803a0a282" dependencies = [ "proc-macro2 1.0.39", "quote 1.0.18", @@ -3849,7 +3887,9 @@ checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae" dependencies = [ "futures-util", "log", + "native-tls", "tokio", + "tokio-native-tls", "tungstenite", ] @@ -4100,6 +4140,7 @@ dependencies = [ "http", "httparse", "log", + "native-tls", "rand 0.8.5", "sha-1 0.10.0", "thiserror",