diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5db5d3b8..f92f6114 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,6 +26,7 @@ assignees: '' 4. See error **Error log** + **Additional context** diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 2a398f04..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "npm" - directory: "/src/frontend" - schedule: - interval: "daily" - - - package-ecosystem: "cargo" - directory: "/src/backend" - schedule: - interval: "daily" diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 00000000..0b55fc6b --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ], + "timezone": "America/Chicago", + "schedule": [ + "before 5:00am" + ], + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch", + "pin", + "digest" + ], + "automerge": true + } + ] +} \ No newline at end of file diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml deleted file mode 100644 index bcc9558e..00000000 --- a/.github/workflows/dependabot-auto-merge.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Dependabot auto-merge -on: pull_request - -permissions: - pull-requests: write - contents: write - -jobs: - dependabot: - runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' - steps: - - name: Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Approve PR - run: gh pr review --approve "${{ github.event.pull_request.html_url }}" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Enable auto-merge for non-major update PR - if: steps.metadata.outputs.update-type != 'version-update:semver-major' - run: gh pr merge --auto --squash "${{ github.event.pull_request.html_url }}" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/push-build.yml b/.github/workflows/push-build.yml index ab41a945..1c581260 100644 --- a/.github/workflows/push-build.yml +++ b/.github/workflows/push-build.yml @@ -1,105 +1,80 @@ name: Build -on: push +on: + push: + workflow_dispatch: + inputs: + debug_enabled: + description: 'Run the build with tmate debugging enabled' + required: false + default: false jobs: - build: - if: "!startsWith(github.event.head_commit.message, 'docs') && !startsWith(github.event.head_commit.message, 'style')" + fmt_clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Setup pnpm + run: corepack enable + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} - name: Lint (rustfmt) - working-directory: src/backend run: cargo fmt -- --check - - name: Install other Rust targets - run: | - rustup target add x86_64-unknown-linux-musl - rustup target add arm-unknown-linux-musleabihf - rustup target add armv7-unknown-linux-musleabihf - rustup target add aarch64-unknown-linux-musl - - name: Restore Rust cache - uses: Swatinem/rust-cache@v1 - with: - working-directory: src/backend - name: Lint (clippy) - working-directory: src/backend - run: | - mkdir dist - cargo clippy --all-targets --all-features -- -D warnings - - name: Install Rust dependencies - run: cargo install cross set-cargo-version - - name: Install Yarn Dependencies - working-directory: src/frontend - run: yarn install - - name: Change frontend version to commit hash - working-directory: src/frontend - run: npm version $(git describe --tags) --allow-same-version - - name: Change backend version to commit hash - run: set-cargo-version src/backend/Cargo.toml $(git describe --tags | cut -c 2-) - - name: Build frontend - working-directory: src/frontend - run: yarn build - - name: Compress frontend files - run: make compress - - name: Build x86_64 - working-directory: src/backend - run: cross build --release --target x86_64-unknown-linux-musl --features compression - - name: Delete build artifacts - run: | - rm -r src/backend/target/release/build/* - rm src/backend/target/release/deps/*.so - - name: Build armv6l - working-directory: src/backend - run: cross build --release --target arm-unknown-linux-musleabihf --features compression - - name: Build armv7l - working-directory: src/backend - run: cross build --release --target armv7-unknown-linux-musleabihf --features compression - - name: Build aarch64 - working-directory: src/backend - run: cross build --release --target aarch64-unknown-linux-musl --features compression - - uses: actions/upload-artifact@v2 - with: - name: dietpi-dashboard-x86_64 - path: src/backend/target/x86_64-unknown-linux-musl/release/dietpi-dashboard - - uses: actions/upload-artifact@v2 - with: - name: dietpi-dashboard-armv6l - path: src/backend/target/arm-unknown-linux-musleabihf/release/dietpi-dashboard - - uses: actions/upload-artifact@v2 + run: cargo clippy --all-targets --all-features -- -D warnings + + build: + runs-on: ubuntu-latest + strategy: + matrix: + job: + - { target: x86_64-unknown-linux-musl, pretty: x86_64, backend: false } + - { target: arm-unknown-linux-musleabihf, pretty: armv6l, backend: false } + - { target: armv7-unknown-linux-musleabihf, pretty: armv7l, backend: false } + - { target: aarch64-unknown-linux-musl, pretty: aarch64, backend: false } + - { target: x86_64-unknown-linux-musl, pretty: x86_64-backend, backend: true } + - { target: arm-unknown-linux-musleabihf, pretty: armv6l-backend, backend: true } + - { target: armv7-unknown-linux-musleabihf, pretty: armv7l-backend, backend: true } + - { target: aarch64-unknown-linux-musl, pretty: aarch64-backend, backend: true } + steps: + - uses: actions/checkout@v3 with: - name: dietpi-dashboard-armv7l - path: src/backend/target/armv7-unknown-linux-musleabihf/release/dietpi-dashboard - - uses: actions/upload-artifact@v2 + fetch-depth: 0 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + id: toolchain with: - name: dietpi-dashboard-aarch64 - path: src/backend/target/aarch64-unknown-linux-musl/release/dietpi-dashboard - - name: Build x86_64 (backend only) - working-directory: src/backend - run: cross build --release --target x86_64-unknown-linux-musl --no-default-features - - name: Build armv6l (backend only) - working-directory: src/backend - run: cross build --release --target arm-unknown-linux-musleabihf --no-default-features - - name: Build armv7l (backend only) - working-directory: src/backend - run: cross build --release --target armv7-unknown-linux-musleabihf --no-default-features - - name: Build aarch64 (backend only) - working-directory: src/backend - run: cross build --release --target aarch64-unknown-linux-musl --no-default-features - - uses: actions/upload-artifact@v2 + targets: ${{ matrix.job.target }} + components: clippy, rustfmt + - name: Setup Node.js environment + uses: actions/setup-node@v3 with: - name: dietpi-dashboard-x86_64-backend - path: src/backend/target/x86_64-unknown-linux-musl/release/dietpi-dashboard - - uses: actions/upload-artifact@v2 + node-version: 18 + - name: Enable pnpm + run: corepack enable + - uses: taiki-e/install-action@v2 with: - name: dietpi-dashboard-armv6l-backend - path: src/backend/target/arm-unknown-linux-musleabihf/release/dietpi-dashboard - - uses: actions/upload-artifact@v2 + tool: just,cross + - uses: Swatinem/rust-cache@v2 with: - name: dietpi-dashboard-armv7l-backend - path: src/backend/target/armv7-unknown-linux-musleabihf/release/dietpi-dashboard - - uses: actions/upload-artifact@v2 + key: ${{ matrix.job.pretty }}-${{ steps.toolchain.outputs.cachekey }} + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + - name: Install set-cargo-version + run: cargo install set-cargo-version + - name: Change frontend version to commit hash + working-directory: frontend + run: npm version $(git describe --tags) --allow-same-version + - name: Change backend version to commit hash + run: set-cargo-version Cargo.toml $(git describe --tags | cut -c 2-) + - name: Build DietPi-Dashboard + run: | + just ci ${{ matrix.job.target }} ${{ matrix.job.backend }} + - name: Upload binary + uses: actions/upload-artifact@v3 with: - name: dietpi-dashboard-aarch64-backend - path: src/backend/target/aarch64-unknown-linux-musl/release/dietpi-dashboard - + name: dietpi-dashboard-${{ matrix.job.pretty }} + path: target/${{ matrix.job.target }}/release/dietpi-dashboard diff --git a/.gitignore b/.gitignore index 7c6c8530..65a8f9f9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,13 +5,11 @@ dist/ dietpi-dashboard .DS_Store target/ -src/backend/.cargo/ +.cargo/ -# Yarn -.yarn/* -!.yarn/cache -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions +frontend/.yarn/* +!frontend/.yarn/cache +!frontend/.yarn/patches +!frontend/.yarn/plugins +!frontend/.yarn/releases +!frontend/.yarn/versions diff --git a/.versionrc b/.versionrc index dee7609c..2fbace9d 100644 --- a/.versionrc +++ b/.versionrc @@ -1,17 +1,17 @@ { "packageFiles": [ { - "filename": "src/frontend/package.json", + "filename": "frontend/package.json", "type": "json" } ], "bumpFiles": [ { - "filename": "src/frontend/package.json", + "filename": "frontend/package.json", "type": "json" }, { - "filename": "src/backend/Cargo.toml", + "filename": "Cargo.toml", "updater": "standard-version-toml.js" } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 93a4ed56..689c7541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [0.6.1](https://github.com/ravenclaw900/DietPi-Dashboard/compare/v0.6.0...v0.6.1) (2022-08-15) + + +### Features + +* **backend:** make warp listen on both IPv4 and IPv6 ([#216](https://github.com/ravenclaw900/DietPi-Dashboard/issues/216)) ([8bcf1b2](https://github.com/ravenclaw900/DietPi-Dashboard/commit/8bcf1b25ed62930f2164e84f122dbc852f103242)) +* **backend:** store files in binary as compressed ([#263](https://github.com/ravenclaw900/DietPi-Dashboard/issues/263)) ([cd80f5a](https://github.com/ravenclaw900/DietPi-Dashboard/commit/cd80f5a6b01856bbe6f361199ad07d1140e1f62d)) +* **dashboard:** add CPU temperature ([#232](https://github.com/ravenclaw900/DietPi-Dashboard/issues/232)) ([6567d10](https://github.com/ravenclaw900/DietPi-Dashboard/commit/6567d10b2f88aca09c8292d85f479a397c3a5a1e)) + + +### Bug Fixes + +* **backend:** add error handling ([#247](https://github.com/ravenclaw900/DietPi-Dashboard/issues/247)) ([1acbd83](https://github.com/ravenclaw900/DietPi-Dashboard/commit/1acbd8303997517769249dc235585b4e7b7cf954)) +* **backend:** quit socket_handler if there's a websocket error ([#312](https://github.com/ravenclaw900/DietPi-Dashboard/issues/312)) ([5bb4142](https://github.com/ravenclaw900/DietPi-Dashboard/commit/5bb41421f98170754c7f42ac512ba7ef258a6394)) +* **backend:** remove useless `take` call when getting software ([711952e](https://github.com/ravenclaw900/DietPi-Dashboard/commit/711952e97260d0da7ae23d9f690673260265d94c)) +* **backend:** replace blocking functions with async functions ([#270](https://github.com/ravenclaw900/DietPi-Dashboard/issues/270)) ([aa6e69f](https://github.com/ravenclaw900/DietPi-Dashboard/commit/aa6e69fcddb78af1a3b25bc9a08f8249447ca406)) +* **dashboard:** fix error about cpu temp when changing nodes ([495fe5e](https://github.com/ravenclaw900/DietPi-Dashboard/commit/495fe5e48113613f957ba47693c32cf6d9b840f0)) +* **dashboard:** fix typescript errors ([0c1934c](https://github.com/ravenclaw900/DietPi-Dashboard/commit/0c1934c2ccb88eed2c2286c6fac63c106e892488)) +* **deps:** update rust crate serde to 1.0.140 ([#309](https://github.com/ravenclaw900/DietPi-Dashboard/issues/309)) ([0dc7565](https://github.com/ravenclaw900/DietPi-Dashboard/commit/0dc756501283f23d04f4947c0b61fd4b25f30fb5)) +* **deps:** update rust crate tracing-subscriber to 0.3.15 ([#310](https://github.com/ravenclaw900/DietPi-Dashboard/issues/310)) ([79e8be4](https://github.com/ravenclaw900/DietPi-Dashboard/commit/79e8be473e97a2ad7bef2d12be8aa677d678ba57)) +* **filebrowser:** fix "couldn't get parent of path" error ([7817b65](https://github.com/ravenclaw900/DietPi-Dashboard/commit/7817b655db767ff1f4cf9b91df253c389069287e)) +* **filebrowser:** give file editor black background on dark mode without syntax highlighting ([07be869](https://github.com/ravenclaw900/DietPi-Dashboard/commit/07be869c97418e1c1a7fa42081438d6a8ac05d7f)), closes [#218](https://github.com/ravenclaw900/DietPi-Dashboard/issues/218) +* **frontend:** make websocket reconnect ([#325](https://github.com/ravenclaw900/DietPi-Dashboard/issues/325)) ([0db5fb0](https://github.com/ravenclaw900/DietPi-Dashboard/commit/0db5fb0a842e126a12a0f484c43fe58e819776d1)) +* **services:** fix dashboard sometimes crashing when reloading services page ([657f5e9](https://github.com/ravenclaw900/DietPi-Dashboard/commit/657f5e964b55b482dd99402069d8df9239bd0d97)) + ## [0.6.0](https://github.com/ravenclaw900/DietPi-Dashboard/compare/v0.5.0...v0.6.0) (2022-04-08) diff --git a/src/backend/Cargo.lock b/Cargo.lock similarity index 63% rename from src/backend/Cargo.lock rename to Cargo.lock index f0ae64f0..4ddac7ca 100644 --- a/src/backend/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "atomic" @@ -18,27 +24,22 @@ dependencies = [ ] [[package]] -name = "atty" -version = "0.2.14" +name = "autocfg" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "autocfg" -version = "1.0.1" +name = "base64" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.13.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "bitflags" @@ -48,18 +49,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" @@ -69,15 +70,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.71" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -85,22 +86,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -116,12 +106,21 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.9" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff1f980957787286a554052d03c7aee98d99cc32e09f6d45f0a814133c87978" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", - "once_cell", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", ] [[package]] @@ -157,37 +156,43 @@ dependencies = [ [[package]] name = "dietpi-dashboard" -version = "0.6.0" +version = "0.6.1" dependencies = [ "anyhow", "figment", "futures", + "getrandom", + "hex", + "hyper", "if-addrs", - "include_dir", - "infer", "jsonwebtoken", - "log", + "mime_guess", "once_cell", "psutil", "pty-process", "ring", + "rustls-pemfile", "serde", "serde_json", - "simple_logger", "tempfile", "tokio", + "tokio-rustls", + "tokio-tungstenite", + "tracing", + "tracing-subscriber", + "vite-embed", "walkdir", - "warp", "zip", ] [[package]] name = "digest" -version = "0.9.0" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "generic-array", + "block-buffer", + "crypto-common", ] [[package]] @@ -201,9 +206,9 @@ dependencies = [ [[package]] name = "figment" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" dependencies = [ "atomic", "pear", @@ -213,6 +218,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -221,19 +236,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "futures" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" dependencies = [ "futures-channel", "futures-core", @@ -246,9 +260,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" dependencies = [ "futures-core", "futures-sink", @@ -256,15 +270,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" dependencies = [ "futures-core", "futures-task", @@ -273,15 +287,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" dependencies = [ "proc-macro2", "quote", @@ -290,21 +304,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" dependencies = [ "futures-channel", "futures-core", @@ -320,9 +334,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -330,9 +344,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -341,85 +355,41 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "h2" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "headers" -version = "0.3.5" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c4eb0471fcb85846d8b0690695ef354f9afb11cb03cac2e1d7c9253351afb0" -dependencies = [ - "base64", - "bitflags", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha-1", -] +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] -name = "headers-core" -version = "0.2.0" +name = "hermit-abi" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ - "http", + "libc", ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 0.4.8", + "itoa", ] [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -428,32 +398,31 @@ dependencies = [ [[package]] name = "httparse" -version = "1.5.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.14" +version = "0.14.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", "http", "http-body", "httparse", "httpdate", - "itoa 0.4.8", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -464,60 +433,24 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "if-addrs" -version = "0.7.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" +checksum = "2cfc4a06638d2fd0dda83b01126fefd38ef9f04f54d2fc717a938df68b83a68d" dependencies = [ "libc", - "winapi", + "windows-sys", ] -[[package]] -name = "include_dir" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "482a2e29200b7eed25d7fdbd14423326760b7f6658d21a4cf12d55a50713c69f" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d551dc625a699489a6903cd41dd91aef674a5126f3d28799a316d14e7b15fcf5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "indexmap" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "infer" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e035cede526e0b21d5adffc9fa0eb4ef5d6026fe9c5b0bfe8084b9472b587a55" - [[package]] name = "inlinable_string" version = "0.1.15" @@ -535,32 +468,32 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "jsonwebtoken" -version = "8.1.1" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64", + "base64 0.21.0", "ring", "serde", "serde_json", @@ -574,9 +507,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.112" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "log" @@ -597,22 +530,25 @@ dependencies = [ ] [[package]] -name = "matches" -version = "0.1.9" +name = "memchr" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] -name = "memchr" -version = "2.3.4" +name = "memoffset" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] @@ -625,88 +561,93 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mime_guess" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" dependencies = [ "mime", "unicase", ] [[package]] -name = "mio" -version = "0.7.14" +name = "miniz_oxide" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", + "adler", ] [[package]] -name = "miow" -version = "0.3.7" +name = "mio" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ - "winapi", + "libc", + "log", + "wasi", + "windows-sys", ] [[package]] name = "nix" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ "bitflags", "cc", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] -name = "ntapi" -version = "0.3.6" +name = "nix" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "winapi", + "bitflags", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", + "static_assertions", ] [[package]] -name = "num_cpus" -version = "1.13.1" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "hermit-abi", - "libc", + "overload", + "winapi", ] [[package]] -name = "num_threads" -version = "0.1.5" +name = "num_cpus" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ + "hermit-abi", "libc", ] [[package]] name = "once_cell" -version = "1.12.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pear" @@ -733,35 +674,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.8" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -777,17 +698,17 @@ checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -814,7 +735,7 @@ dependencies = [ "derive_more", "glob", "mach", - "nix", + "nix 0.23.2", "num_cpus", "once_cell", "platforms", @@ -824,33 +745,33 @@ dependencies = [ [[package]] name = "pty-process" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c0159357669a990d96531d7095fd9d5eaa9f59d3d7d522f65b438dc8d1c109" +checksum = "d52ebddb2c1f610f98c2af1c6c6560adeb83ceafff2b785b068e0ee00c80c1a2" dependencies = [ "libc", - "nix", + "nix 0.26.2", + "tokio", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] @@ -865,22 +786,13 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "redox_syscall" version = "0.2.11" @@ -916,22 +828,30 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ - "base64", "log", "ring", "sct", "webpki", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "same-file" @@ -942,17 +862,11 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "sct" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", @@ -960,18 +874,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.137" +version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d" dependencies = [ "proc-macro2", "quote", @@ -980,72 +894,64 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ - "itoa 1.0.1", + "itoa", "ryu", "serde", ] [[package]] -name = "serde_urlencoded" -version = "0.7.0" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ - "form_urlencoded", - "itoa 0.4.8", - "ryu", - "serde", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] -name = "sha-1" -version = "0.9.8" +name = "sharded-slab" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "block-buffer", - "cfg-if", - "cpufeatures", - "digest", - "opaque-debug", + "lazy_static", ] [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] -name = "simple_logger" -version = "2.1.0" +name = "slab" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75a9723083573ace81ad0cdfc50b858aa3c366c48636edb4109d73122a0c0ea" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ - "atty", - "colored", - "log", - "winapi", + "autocfg", ] [[package]] -name = "slab" -version = "0.4.5" +name = "smallvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -1057,15 +963,21 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" -version = "1.0.92" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1084,79 +996,88 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" -version = "0.3.7" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "itoa 1.0.1", - "libc", - "num_threads", - "time-macros", + "serde", + "time-core", ] [[package]] -name = "time-macros" -version = "0.2.3" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "tinyvec" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.16.1" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", - "once_cell", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -1165,109 +1086,120 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.22.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", "tokio", "webpki", ] -[[package]] -name = "tokio-stream" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-tungstenite" -version = "0.15.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "pin-project", "tokio", "tungstenite", ] -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", - "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.14.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", "httparse", "log", "rand", - "sha-1", + "sha1", "thiserror", "url", "utf-8", @@ -1275,15 +1207,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.14.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "uncased" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" dependencies = [ "version_check", ] @@ -1305,40 +1237,51 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c" + +[[package]] +name = "unicode-ident" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "ureq" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" +dependencies = [ + "base64 0.13.1", + "log", + "once_cell", + "url", +] + [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -1348,20 +1291,44 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vite-embed" +version = "0.1.0" +source = "git+https://github.com/ravenclaw900/vite-embed#6bacdac0c32bee82d9eb1ebcaf66eeb22e026596" +dependencies = [ + "ureq", + "vite-embed-macro", +] + +[[package]] +name = "vite-embed-macro" +version = "0.1.0" +source = "git+https://github.com/ravenclaw900/vite-embed#6bacdac0c32bee82d9eb1ebcaf66eeb22e026596" +dependencies = [ + "flate2", + "json", + "quote", +] [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -1375,47 +1342,17 @@ dependencies = [ "try-lock", ] -[[package]] -name = "warp" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "headers", - "http", - "hyper", - "log", - "mime", - "mime_guess", - "percent-encoding", - "pin-project", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls", - "tokio-stream", - "tokio-tungstenite", - "tokio-util", - "tower-service", - "tracing", -] - [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1423,13 +1360,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1438,9 +1375,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1448,9 +1385,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -1461,15 +1398,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -1477,9 +1414,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", @@ -1516,17 +1453,83 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "yansi" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zip" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d" +checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" dependencies = [ "byteorder", "crc32fast", diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..95afdba8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "dietpi-dashboard" +version = "0.6.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1", features = [ + "rt", + "macros", + "time", + "sync", + "fs", + "process", +] } +futures = "0.3.27" +pty-process = { version = "0.3.0", features = ["async"] } +psutil = "3.2.2" +ring = "0.16.20" +figment = { version = "0.10", features = ["toml", "env"] } +if-addrs = "0.10.1" +zip = { version = "0.6.4", default-features = false, features = ["time"] } +walkdir = "2.3.3" +jsonwebtoken = { version = "8.3.0", default-features = false } +serde = { version = "1.0.156", features = ["derive"] } +anyhow = "1.0.69" +once_cell = "1.17.1" +serde_json = "1.0.94" +tracing = { version = "0.1.37", features = ["release_max_level_debug"] } +tracing-subscriber = "0.3.16" +hyper = { version = "0.14.25", features = ["server", "http1", "tcp"] } +tokio-tungstenite = "0.18.0" +tokio-rustls = "0.23.4" +rustls-pemfile = "1.0.2" +getrandom = "0.2.8" +hex = "0.4.3" +vite-embed = { git = "https://github.com/ravenclaw900/vite-embed", version = "0.1.0", features = ["prod"], optional = true } +mime_guess = { version = "2.0.4", default-features = false } + +[features] +default = ["frontend"] +frontend = ["dep:vite-embed"] +dev = ["frontend", "vite-embed?/dev"] + +[profile.release] +lto = "fat" +panic = "abort" +codegen-units = 1 +strip = true + +[profile.release.package.serde] +opt-level = 3 + +[profile.release.package.psutil] +opt-level = 3 + +[profile.release.package.zip] +opt-level = 3 + +[profile.release.package."*"] +opt-level = "s" diff --git a/Justfile b/Justfile new file mode 100644 index 00000000..15182cff --- /dev/null +++ b/Justfile @@ -0,0 +1,35 @@ +alias release := backend-release + +frontend: + #!/bin/bash -eux + cd frontend/ + pnpm install + pnpm build + +backend target="x86_64-unknown-linux-gnu": frontend + cargo build --target {{target}} + +backend-release target="x86_64-unknown-linux-gnu": frontend + cargo build --target {{target}} --release + +backend-only target="x86_64-unknown-linux-gnu": + cargo build --target {{target}} --release --no-default-features + +ci target backend-only: + #!/bin/bash -eux + if {{backend-only}}; then + cross build --target {{target}} --release --no-default-features + else + just frontend + cross build --target {{target}} --release + fi + +dev: + #!/bin/bash -eux + cd frontend/ + pnpm install + pnpm dev & + cd .. + cargo run --quiet --features dev & + trap 'kill $(jobs -pr)' EXIT + wait \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 1ab598ea..00000000 --- a/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -build: yarn rustbuild - -rustbuild: distcopy fmt -ifdef TARGET - cd src/backend; cargo build --target $(TARGET) - mv src/backend/target/$(TARGET)/debug/dietpi-dashboard . -else - cd src/backend; cargo build - mv src/backend/target/debug/dietpi-dashboard . -endif - -release: yarn compress -ifdef TARGET - cd src/backend; cargo build --target $(TARGET) --release --features compression - mv src/backend/target/$(TARGET)/release/dietpi-dashboard . -else - cd src/backend; cargo build --release --features compression - mv src/backend/target/release/dietpi-dashboard . -endif - - rm -r src/backend/dist - -# There may be a better, more 'make'y way of doing this, but find works for now -compress: distcopy - find src/backend/dist ! -name '*.png' -type f -exec gzip -9 {} \; -exec mv {}.gz {} \; - -yarn: - cd src/frontend; yarn build - -ifdef TARGET - rm -f src/backend/target/$(TARGET)/debug/deps/dietpi_dashboard-* -else - rm -f src/backend/target/debug/deps/dietpi_dashboard-* -endif - -fmt: - cd src/backend; cargo fmt -ifdef TARGET - cd src/backend; cargo clippy --target $(TARGET) -else - cd src/backend; cargo clippy -endif - -distcopy: - rm -rf src/backend/dist - cp -r src/frontend/dist src/backend diff --git a/README.md b/README.md index a154bd87..5897478b 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ To install, use one of the [precompiled releases](#release), [nightly builds](#n #### Release: ```sh -curl -fL "$(curl -sSf 'https://api.github.com/repos/ravenclaw900/dietpi-dashboard/releases/latest' | mawk -F\" "/\"browser_download_url\": \".*dietpi-dashboard-$G_HW_ARCH_NAME\"/{print \$4}")" -o dietpi-dashboard # Download latest binary for current architecture -chmod +x dietpi-dashboard # Make binary exectuable +curl -fL "$(curl -sSf 'https://api.github.com/repos/ravenclaw900/DietPi-Dashboard/releases/latest' | mawk -F\" "/\"browser_download_url\": \".*dietpi-dashboard-$G_HW_ARCH_NAME\"/{print \$4}")" -o dietpi-dashboard # Download latest binary for current architecture +chmod +x dietpi-dashboard # Make binary executable ./dietpi-dashboard # Run binary ``` @@ -19,7 +19,7 @@ chmod +x dietpi-dashboard # Make binary exectuable curl -fL "https://nightly.link/ravenclaw900/DietPi-Dashboard/workflows/push-build/main/dietpi-dashboard-$G_HW_ARCH_NAME.zip" -o dietpi-dashboard.zip # Download latest nightly build for current architecture unzip dietpi-dashboard.zip # Unzip binary rm dietpi-dashboard.zip # Remove archive -chmod +x dietpi-dashboard # Make binary exectuable +chmod +x dietpi-dashboard # Make binary executable ./dietpi-dashboard # Run binary ``` @@ -28,47 +28,23 @@ chmod +x dietpi-dashboard # Make binary exectuable #### Prereq: ```sh -dietpi-software install 9 16 17 # Install Node.js (webpage), Build-Essential (make and gcc), and Git (git clone), respectively -npm install -g yarn # Install Yarn package manager, for node dependencies +dietpi-software install 9 16 17 # Install Node.js (webpage), Build-Essential (gcc), and Git (git clone), respectively +corepack enable # Enable pnpm package manager, for node dependencies curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Install Rust (backend) -source ~/.cargo/env # Update PATH +source ~/.cargo/env # Update $PATH +cargo install just # Install just command runner, for build file ``` #### Compiling: ```sh -rm -rf DietPi-Dashboard # Remove possibly pre-downloaded repository git clone https://github.com/ravenclaw900/DietPi-Dashboard # Download source code -cd DietPi-Dashboard/src/frontend # Change directories -yarn # Install dependencies -cd ../.. # Change directories -make # Compile binary for your platform -./dietpi-dashboard # Run binary -``` - -### Compiling for all targets (release) -#### Prereq: - -Normal compilation prereq (see above) -```sh -rustup target add aarch64-unknown-linux-gnu arm-unknown-linux-gnueabihf armv7-unknown-linux-gnueabihf x86_64-unknown-linux-gnu # Add Rust standard libraries -apt install gcc-aarch64-linux-gnu libc-dev-arm64-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-x86-64-linux-gnu libc6-dev-amd64-cross # Install cross-compiling toolchains -mkdir /opt/rpi ; git clone https://github.com/raspberrypi/tools /opt/rpi # Install Raspberry Pi cross-compiling toolchain -apt install upx # Install UPX (for compressing binaries) +cd DietPi-Dashboard # Change directories +cargo build --release # Compile binary for your platform +./target/release/dietpi-dashboard # Run binary ``` -You also need to [compile](https://github.com/upx/upx/blob/devel/README.SRC) UPX, to compress the ARMv6/7 binaries. -#### Compiling: - -```sh -rm -rf DietPi-Dashboard # Remove possibly pre-downloaded repository -git clone https://github.com/ravenclaw900/DietPi-Dashboard # Download source code -cd DietPi-Dashboard/src/frontend # Change directories -yarn # Install dependencies -cd ../.. # Change directories -make build # Compile binaries for all platforms -``` -Binaries will then be available in the `build` directory. +Note that there will be a difference between self-compiled binaries and the nightly/release builds. The nightly/release builds are statically linked with the musl libc implementation, while self-compiled binaries will be dynamically linked with glibc. This should not affect functionality in any way. ### Open dashboard: `http://:5252` diff --git a/config.toml b/config.toml index e350b18c..954c9cae 100644 --- a/config.toml +++ b/config.toml @@ -42,5 +42,5 @@ # Preferred temperature unit # - Options: fahrenheit, celsius -# - Default: celsius -#temp_unit = celsius +# - Default: "celsius" +#temp_unit = "celsius" diff --git a/src/frontend/index.html b/frontend/index.html similarity index 59% rename from src/frontend/index.html rename to frontend/index.html index dd745742..1ce2e4d5 100644 --- a/src/frontend/index.html +++ b/frontend/index.html @@ -3,14 +3,15 @@ - + DietPi Dashboard +
- + \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..245774f8 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,44 @@ +{ + "name": "dietpi-dashboard", + "version": "0.6.1", + "type": "module", + "license": "GPL-3.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "check": "svelte-check" + }, + "devDependencies": { + "@fortawesome/free-solid-svg-icons": "^6.2.1", + "@sveltejs/vite-plugin-svelte": "^2.0.2", + "@tsconfig/svelte": "^3.0.0", + "@types/autosize": "^4.0.1", + "@types/microlight": "^0.0.0", + "@types/semver-compare-multi": "^1.0.0", + "postcss": "^8.4.21", + "svelte": "^3.55.1", + "svelte-check": "^3.0.3", + "svelte-fa": "^3.0.3", + "svelte-language-server": "^0.15.3", + "svelte-preprocess": "^5.0.1", + "svelte-routing": "^1.6.0", + "tslib": "^2.5.0", + "typescript": "^4.9.4", + "vite": "^4.0.4", + "vite-plugin-replace": "^0.1.1", + "vite-plugin-windicss": "^1.8.10", + "windicss": "^3.5.6" + }, + "dependencies": { + "microlight": "^0.0.7", + "pretty-bytes": "^6.0.0", + "pretty-ms": "^8.0.0", + "semver-compare-multi": "^1.0.3", + "uplot": "^1.6.24", + "xterm": "^5.1.0", + "xterm-addon-attach": "^0.8.0", + "xterm-addon-fit": "^0.7.0" + }, + "packageManager": "pnpm@7.29.3" +} \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 00000000..d6758115 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,1280 @@ +lockfileVersion: 5.4 + +specifiers: + '@fortawesome/free-solid-svg-icons': ^6.2.1 + '@sveltejs/vite-plugin-svelte': ^2.0.2 + '@tsconfig/svelte': ^3.0.0 + '@types/autosize': ^4.0.1 + '@types/microlight': ^0.0.0 + '@types/semver-compare-multi': ^1.0.0 + microlight: ^0.0.7 + postcss: ^8.4.21 + pretty-bytes: ^6.0.0 + pretty-ms: ^8.0.0 + semver-compare-multi: ^1.0.3 + svelte: ^3.55.1 + svelte-check: ^3.0.3 + svelte-fa: ^3.0.3 + svelte-language-server: ^0.15.3 + svelte-preprocess: ^5.0.1 + svelte-routing: ^1.6.0 + tslib: ^2.5.0 + typescript: ^4.9.4 + uplot: ^1.6.24 + vite: ^4.0.4 + vite-plugin-replace: ^0.1.1 + vite-plugin-windicss: ^1.8.10 + windicss: ^3.5.6 + xterm: ^5.1.0 + xterm-addon-attach: ^0.8.0 + xterm-addon-fit: ^0.7.0 + +dependencies: + microlight: 0.0.7 + pretty-bytes: 6.0.0 + pretty-ms: 8.0.0 + semver-compare-multi: 1.0.3 + uplot: 1.6.24 + xterm: 5.1.0 + xterm-addon-attach: 0.8.0_xterm@5.1.0 + xterm-addon-fit: 0.7.0_xterm@5.1.0 + +devDependencies: + '@fortawesome/free-solid-svg-icons': 6.2.1 + '@sveltejs/vite-plugin-svelte': 2.0.2_svelte@3.55.1+vite@4.0.4 + '@tsconfig/svelte': 3.0.0 + '@types/autosize': 4.0.1 + '@types/microlight': 0.0.0 + '@types/semver-compare-multi': 1.0.0 + postcss: 8.4.21 + svelte: 3.55.1 + svelte-check: 3.0.3_pehl75e5jsy22vp33udjja4soi + svelte-fa: 3.0.3 + svelte-language-server: 0.15.3_postcss@8.4.21 + svelte-preprocess: 5.0.1_oifjsu5oar4lzyaoppb2hxramu + svelte-routing: 1.6.0_atrrhq7vg4ekua4nnyrpuardle + tslib: 2.5.0 + typescript: 4.9.4 + vite: 4.0.4 + vite-plugin-replace: 0.1.1_vite@4.0.4 + vite-plugin-windicss: 1.8.10_vite@4.0.4 + windicss: 3.5.6 + +packages: + + /@antfu/utils/0.7.2: + resolution: {integrity: sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g==} + dev: true + + /@emmetio/abbreviation/2.2.3: + resolution: {integrity: sha512-87pltuCPt99aL+y9xS6GPZ+Wmmyhll2WXH73gG/xpGcQ84DRnptBsI2r0BeIQ0EB/SQTOe2ANPqFqj3Rj5FOGA==} + dependencies: + '@emmetio/scanner': 1.0.0 + dev: true + + /@emmetio/css-abbreviation/2.1.4: + resolution: {integrity: sha512-qk9L60Y+uRtM5CPbB0y+QNl/1XKE09mSO+AhhSauIfr2YOx/ta3NJw2d8RtCFxgzHeRqFRr8jgyzThbu+MZ4Uw==} + dependencies: + '@emmetio/scanner': 1.0.0 + dev: true + + /@emmetio/scanner/1.0.0: + resolution: {integrity: sha512-8HqW8EVqjnCmWXVpqAOZf+EGESdkR27odcMMMGefgKXtar00SoYNSryGv//TELI4T3QFsECo78p+0lmalk/CFA==} + dev: true + + /@esbuild/android-arm/0.16.17: + resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.16.17: + resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.16.17: + resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.16.17: + resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.16.17: + resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.16.17: + resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.16.17: + resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.16.17: + resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.16.17: + resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.16.17: + resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64/0.16.17: + resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.16.17: + resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.16.17: + resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.16.17: + resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.16.17: + resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.16.17: + resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.16.17: + resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.16.17: + resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.16.17: + resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.16.17: + resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.16.17: + resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.16.17: + resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@fortawesome/fontawesome-common-types/6.2.1: + resolution: {integrity: sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ==} + engines: {node: '>=6'} + requiresBuild: true + dev: true + + /@fortawesome/free-solid-svg-icons/6.2.1: + resolution: {integrity: sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + '@fortawesome/fontawesome-common-types': 6.2.1 + dev: true + + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: true + + /@jridgewell/trace-mapping/0.3.17: + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@sveltejs/vite-plugin-svelte/2.0.2_svelte@3.55.1+vite@4.0.4: + resolution: {integrity: sha512-xCEan0/NNpQuL0l5aS42FjwQ6wwskdxC3pW1OeFtEKNZwRg7Evro9lac9HesGP6TdFsTv2xMes5ASQVKbCacxg==} + engines: {node: ^14.18.0 || >= 16} + peerDependencies: + svelte: ^3.54.0 + vite: ^4.0.0 + dependencies: + debug: 4.3.4 + deepmerge: 4.2.2 + kleur: 4.1.5 + magic-string: 0.27.0 + svelte: 3.55.1 + svelte-hmr: 0.15.1_svelte@3.55.1 + vite: 4.0.4 + vitefu: 0.2.4_vite@4.0.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@tsconfig/svelte/3.0.0: + resolution: {integrity: sha512-pYrtLtOwku/7r1i9AMONsJMVYAtk3hzOfiGNekhtq5tYBGA7unMve8RvUclKLMT3PrihvJqUmzsRGh0RP84hKg==} + dev: true + + /@types/autosize/4.0.1: + resolution: {integrity: sha512-iPJT/FCaSO79G6j+9n6gmFc5nhxZ1gDrA2UAvb5FslJ6FJQZnDfbBU0qp5vpp0Cbjj7+gOyjuWZ7RrXvRuETaA==} + dev: true + + /@types/microlight/0.0.0: + resolution: {integrity: sha512-PhPnbZkunal+hvIwXKct4KimoQIwiYvBx9yQRBR+kC302420pGRYGZEnLtY1seR/bjlV7bUUs7na4Z5tu3060w==} + dev: true + + /@types/node/18.11.18: + resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} + dev: true + + /@types/pug/2.0.6: + resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} + dev: true + + /@types/sass/1.43.1: + resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} + dependencies: + '@types/node': 18.11.18 + dev: true + + /@types/semver-compare-multi/1.0.0: + resolution: {integrity: sha512-Kq3ctX7IkkD3dh3GAKDv55bSgLVpj0eeCsxJPYMMhZDLGiteS3/I/GxsdsQ4IGToPCaSLf1RuGmsSGFUShZ9iw==} + dev: true + + /@vscode/emmet-helper/2.8.6: + resolution: {integrity: sha512-IIB8jbiKy37zN8bAIHx59YmnIelY78CGHtThnibD/d3tQOKRY83bYVi9blwmZVUZh6l9nfkYH3tvReaiNxY9EQ==} + dependencies: + emmet: 2.3.6 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.8 + vscode-languageserver-types: 3.17.2 + vscode-uri: 2.1.2 + dev: true + + /@vscode/l10n/0.0.11: + resolution: {integrity: sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==} + dev: true + + /@windicss/config/1.8.10: + resolution: {integrity: sha512-O9SsC110b1Ik3YYa4Ck/0TWuCo7YFfA9KDrwD5sAeqscT5COIGK1HszdCT3oh0MJFej2wNrvpfyW9h6yQaW6PA==} + dependencies: + debug: 4.3.4 + jiti: 1.16.2 + windicss: 3.5.6 + transitivePeerDependencies: + - supports-color + dev: true + + /@windicss/plugin-utils/1.8.10: + resolution: {integrity: sha512-Phqk5OW1w+Mv+ry6t7BzAeDq3aMhbI94gR49j9vQCufFfDGCHndhhjtMK0sBv+NPJUsIAIh6qayb1iwBCXUGrw==} + dependencies: + '@antfu/utils': 0.7.2 + '@windicss/config': 1.8.10 + debug: 4.3.4 + fast-glob: 3.2.12 + magic-string: 0.27.0 + micromatch: 4.0.5 + windicss: 3.5.6 + transitivePeerDependencies: + - supports-color + dev: true + + /anymatch/3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /buffer-crc32/0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /dedent-js/1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + dev: true + + /deepmerge/4.2.2: + resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + engines: {node: '>=0.10.0'} + dev: true + + /detect-indent/6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /emmet/2.3.6: + resolution: {integrity: sha512-pLS4PBPDdxuUAmw7Me7+TcHbykTsBKN/S9XJbUOMFQrNv9MoshzyMFK/R57JBm94/6HSL4vHnDeEmxlC82NQ4A==} + dependencies: + '@emmetio/abbreviation': 2.2.3 + '@emmetio/css-abbreviation': 2.1.4 + dev: true + + /es6-promise/3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + dev: true + + /esbuild/0.16.17: + resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.16.17 + '@esbuild/android-arm64': 0.16.17 + '@esbuild/android-x64': 0.16.17 + '@esbuild/darwin-arm64': 0.16.17 + '@esbuild/darwin-x64': 0.16.17 + '@esbuild/freebsd-arm64': 0.16.17 + '@esbuild/freebsd-x64': 0.16.17 + '@esbuild/linux-arm': 0.16.17 + '@esbuild/linux-arm64': 0.16.17 + '@esbuild/linux-ia32': 0.16.17 + '@esbuild/linux-loong64': 0.16.17 + '@esbuild/linux-mips64el': 0.16.17 + '@esbuild/linux-ppc64': 0.16.17 + '@esbuild/linux-riscv64': 0.16.17 + '@esbuild/linux-s390x': 0.16.17 + '@esbuild/linux-x64': 0.16.17 + '@esbuild/netbsd-x64': 0.16.17 + '@esbuild/openbsd-x64': 0.16.17 + '@esbuild/sunos-x64': 0.16.17 + '@esbuild/win32-arm64': 0.16.17 + '@esbuild/win32-ia32': 0.16.17 + '@esbuild/win32-x64': 0.16.17 + dev: true + + /estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + + /fast-glob/3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fastq/1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-core-module/2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /jiti/1.16.2: + resolution: {integrity: sha512-OKBOVWmU3FxDt/UH4zSwiKPuc1nihFZiOD722FuJlngvLz2glX1v2/TJIgoA4+mrpnXxHV6dSAoCvPcYQtoG5A==} + hasBin: true + dev: true + + /jsonc-parser/2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + dev: true + + /kleur/4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /kolorist/1.6.0: + resolution: {integrity: sha512-dLkz37Ab97HWMx9KTes3Tbi3D1ln9fCAy2zr2YVExJasDRPGRaKcoE4fycWNtnCAJfjFqe0cnY+f8KT2JePEXQ==} + dev: true + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true + + /lower-case/2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.5.0 + dev: true + + /magic-string/0.27.0: + resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /microlight/0.0.7: + resolution: {integrity: sha512-kigwsJYoy4mAMkGZpS839/KZ5WWQQm4TzD+eIjR5leS5H0j+EhExvK0Z2Or2ewkBR/t7/AHHhxRyeXi1kurG0g==} + dev: false + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /min-indent/1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimist/1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: true + + /mkdirp/0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.7 + dev: true + + /mri/1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /no-case/3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.5.0 + dev: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-ms/3.0.0: + resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} + engines: {node: '>=12'} + dev: false + + /pascal-case/3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + dependencies: + no-case: 3.0.4 + tslib: 2.5.0 + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /postcss/8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /prettier-plugin-svelte/2.9.0_4h247imu46srxjbpsfob5j3nnq: + resolution: {integrity: sha512-3doBi5NO4IVgaNPtwewvrgPpqAcvNv0NwJNflr76PIGgi9nf1oguQV1Hpdm9TI2ALIQVn/9iIwLpBO5UcD2Jiw==} + peerDependencies: + prettier: ^1.16.4 || ^2.0.0 + svelte: ^3.2.0 + dependencies: + prettier: 2.8.1 + svelte: 3.55.1 + dev: true + + /prettier/2.8.1: + resolution: {integrity: sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /pretty-bytes/6.0.0: + resolution: {integrity: sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==} + engines: {node: ^14.13.1 || >=16.0.0} + dev: false + + /pretty-ms/8.0.0: + resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} + engines: {node: '>=14.16'} + dependencies: + parse-ms: 3.0.0 + dev: false + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf/2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup/3.11.0: + resolution: {integrity: sha512-+uWPPkpWQ2H3Qi7sNBcRfhhHJyUNgBYhG4wKe5wuGRj2m55kpo+0p5jubKNBjQODyPe6tSBE3tNpdDwEisQvAQ==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /sade/1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 + dev: true + + /sander/0.5.1: + resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} + dependencies: + es6-promise: 3.3.1 + graceful-fs: 4.2.10 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: true + + /semver-compare-multi/1.0.3: + resolution: {integrity: sha512-D6TUU36WXt52qOPXfWZj7Jlq+yTvpFu5+QL23czUiKxwpCIF+CSx3IyP8lWeLOOm17zkw/DQ8kjjAGns4bOxxg==} + dev: false + + /sorcery/0.11.0: + resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} + hasBin: true + dependencies: + '@jridgewell/sourcemap-codec': 1.4.14 + buffer-crc32: 0.2.13 + minimist: 1.2.7 + sander: 0.5.1 + dev: true + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /strip-indent/3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /svelte-check/3.0.3_pehl75e5jsy22vp33udjja4soi: + resolution: {integrity: sha512-ByBFXo3bfHRGIsYEasHkdMhLkNleVfszX/Ns1oip58tPJlKdo5Ssr8kgVIuo5oq00hss8AIcdesuy0Xt0BcTvg==} + hasBin: true + peerDependencies: + svelte: ^3.55.0 + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + chokidar: 3.5.3 + fast-glob: 3.2.12 + import-fresh: 3.3.0 + picocolors: 1.0.0 + sade: 1.8.1 + svelte: 3.55.1 + svelte-preprocess: 5.0.1_oifjsu5oar4lzyaoppb2hxramu + typescript: 4.9.4 + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + dev: true + + /svelte-fa/3.0.3: + resolution: {integrity: sha512-GIikJjcVCD+5Y/x9hZc2R4gvuA0gVftacuWu1a+zVQWSFjFYZ+hhU825x+QNs2slsppfrgmFiUyU9Sz9gj4Rdw==} + dev: true + + /svelte-hmr/0.15.1_svelte@3.55.1: + resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} + engines: {node: ^12.20 || ^14.13.1 || >= 16} + peerDependencies: + svelte: '>=3.19.0' + dependencies: + svelte: 3.55.1 + dev: true + + /svelte-language-server/0.15.3_postcss@8.4.21: + resolution: {integrity: sha512-HwPvDZGMCSBkdRD0cfEBTPfy5F3qxqcYNIyMrD0zUH9FoWy7gpEpUZVbCTZ7d5QQVst9rgNdGhzfd+O/s+3b8g==} + engines: {node: '>= 12.0.0'} + hasBin: true + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + '@vscode/emmet-helper': 2.8.6 + chokidar: 3.5.3 + estree-walker: 2.0.2 + fast-glob: 3.2.12 + lodash: 4.17.21 + prettier: 2.8.1 + prettier-plugin-svelte: 2.9.0_4h247imu46srxjbpsfob5j3nnq + svelte: 3.55.1 + svelte-preprocess: 5.0.1_oifjsu5oar4lzyaoppb2hxramu + svelte2tsx: 0.6.0_atrrhq7vg4ekua4nnyrpuardle + typescript: 4.9.4 + vscode-css-languageservice: 6.2.3 + vscode-html-languageservice: 5.0.4 + vscode-languageserver: 8.0.2 + vscode-languageserver-protocol: 3.17.2 + vscode-languageserver-types: 3.17.2 + vscode-uri: 3.0.7 + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + dev: true + + /svelte-preprocess/5.0.1_oifjsu5oar4lzyaoppb2hxramu: + resolution: {integrity: sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==} + engines: {node: '>= 14.10.0'} + requiresBuild: true + peerDependencies: + '@babel/core': ^7.10.2 + coffeescript: ^2.5.1 + less: ^3.11.3 || ^4.0.0 + postcss: ^7 || ^8 + postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 + pug: ^3.0.0 + sass: ^1.26.8 + stylus: ^0.55.0 + sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 + svelte: ^3.23.0 + typescript: ^3.9.5 || ^4.0.0 + peerDependenciesMeta: + '@babel/core': + optional: true + coffeescript: + optional: true + less: + optional: true + postcss: + optional: true + postcss-load-config: + optional: true + pug: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + typescript: + optional: true + dependencies: + '@types/pug': 2.0.6 + '@types/sass': 1.43.1 + detect-indent: 6.1.0 + magic-string: 0.27.0 + postcss: 8.4.21 + sorcery: 0.11.0 + strip-indent: 3.0.0 + svelte: 3.55.1 + typescript: 4.9.4 + dev: true + + /svelte-routing/1.6.0_atrrhq7vg4ekua4nnyrpuardle: + resolution: {integrity: sha512-+DbrSGttLA6lan7oWFz1MjyGabdn3tPRqn8Osyc471ut2UgCrzM5x1qViNMc2gahOP6fKbKK1aNtZMJEQP2vHQ==} + peerDependencies: + svelte: ^3.20.x + dependencies: + svelte: 3.55.1 + svelte2tsx: 0.1.193_atrrhq7vg4ekua4nnyrpuardle + transitivePeerDependencies: + - typescript + dev: true + + /svelte/3.55.1: + resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} + engines: {node: '>= 8'} + dev: true + + /svelte2tsx/0.1.193_atrrhq7vg4ekua4nnyrpuardle: + resolution: {integrity: sha512-vzy4YQNYDnoqp2iZPnJy7kpPAY6y121L0HKrSBjU/IWW7DQ6T7RMJed2VVHFmVYm0zAGYMDl9urPc6R4DDUyhg==} + peerDependencies: + svelte: ^3.24 + typescript: ^4.1.2 + dependencies: + dedent-js: 1.0.1 + pascal-case: 3.1.2 + svelte: 3.55.1 + typescript: 4.9.4 + dev: true + + /svelte2tsx/0.6.0_atrrhq7vg4ekua4nnyrpuardle: + resolution: {integrity: sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==} + peerDependencies: + svelte: ^3.55 + typescript: ^4.9.4 + dependencies: + dedent-js: 1.0.1 + pascal-case: 3.1.2 + svelte: 3.55.1 + typescript: 4.9.4 + dev: true + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /tslib/2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + dev: true + + /typescript/4.9.4: + resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /uplot/1.6.24: + resolution: {integrity: sha512-WpH2BsrFrqxkMu+4XBvc0eCDsRBhzoq9crttYeSI0bfxpzR5YoSVzZXOKFVWcVC7sp/aDXrdDPbDZGCtck2PVg==} + dev: false + + /vite-plugin-replace/0.1.1_vite@4.0.4: + resolution: {integrity: sha512-v+okl3JNt2pf1jDYijw+WPVt6h9FWa/atTi+qnSFBqmKThLTDhlesx0r3bh+oFPmxRJmis5tNx9HtN6lGFoqWg==} + peerDependencies: + vite: ^2 + dependencies: + vite: 4.0.4 + dev: true + + /vite-plugin-windicss/1.8.10_vite@4.0.4: + resolution: {integrity: sha512-scywsuzo46lcTBohspmF0WiwhWEte6p+OUVrX4yr7VMRvLHMHVfLtJReyD5pppjijG7YOwVsZn7XBWWZtF658Q==} + peerDependencies: + vite: ^2.0.1 || ^3.0.0 || ^4.0.0 + dependencies: + '@windicss/plugin-utils': 1.8.10 + debug: 4.3.4 + kolorist: 1.6.0 + vite: 4.0.4 + windicss: 3.5.6 + transitivePeerDependencies: + - supports-color + dev: true + + /vite/4.0.4: + resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.16.17 + postcss: 8.4.21 + resolve: 1.22.1 + rollup: 3.11.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /vitefu/0.2.4_vite@4.0.4: + resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 4.0.4 + dev: true + + /vscode-css-languageservice/6.2.3: + resolution: {integrity: sha512-EAyhyIVHpEaf+GjtI+tVe7SekdoANfG0aubnspsQwak3Qkimn/97FpAufNyXk636ngW05pjNKAR9zyTCzo6avQ==} + dependencies: + '@vscode/l10n': 0.0.11 + vscode-languageserver-textdocument: 1.0.8 + vscode-languageserver-types: 3.17.2 + vscode-uri: 3.0.7 + dev: true + + /vscode-html-languageservice/5.0.4: + resolution: {integrity: sha512-tvrySfpglu4B2rQgWGVO/IL+skvU7kBkQotRlxA7ocSyRXOZUd6GA13XHkxo8LPe07KWjeoBlN1aVGqdfTK4xA==} + dependencies: + '@vscode/l10n': 0.0.11 + vscode-languageserver-textdocument: 1.0.8 + vscode-languageserver-types: 3.17.2 + vscode-uri: 3.0.7 + dev: true + + /vscode-jsonrpc/8.0.2: + resolution: {integrity: sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==} + engines: {node: '>=14.0.0'} + dev: true + + /vscode-languageserver-protocol/3.17.2: + resolution: {integrity: sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg==} + dependencies: + vscode-jsonrpc: 8.0.2 + vscode-languageserver-types: 3.17.2 + dev: true + + /vscode-languageserver-textdocument/1.0.8: + resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==} + dev: true + + /vscode-languageserver-types/3.17.2: + resolution: {integrity: sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==} + dev: true + + /vscode-languageserver/8.0.2: + resolution: {integrity: sha512-bpEt2ggPxKzsAOZlXmCJ50bV7VrxwCS5BI4+egUmure/oI/t4OlFzi/YNtVvY24A2UDOZAgwFGgnZPwqSJubkA==} + hasBin: true + dependencies: + vscode-languageserver-protocol: 3.17.2 + dev: true + + /vscode-uri/2.1.2: + resolution: {integrity: sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==} + dev: true + + /vscode-uri/3.0.7: + resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==} + dev: true + + /windicss/3.5.6: + resolution: {integrity: sha512-P1mzPEjgFMZLX0ZqfFht4fhV/FX8DTG7ERG1fBLiWvd34pTLVReS5CVsewKn9PApSgXnVfPWwvq+qUsRwpnwFA==} + engines: {node: '>= 12'} + hasBin: true + dev: true + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /xterm-addon-attach/0.8.0_xterm@5.1.0: + resolution: {integrity: sha512-k8N5boSYn6rMJTTNCgFpiSTZ26qnYJf3v/nJJYexNO2sdAHDN3m1ivVQWVZ8CHJKKnZQw1rc44YP2NtgalWHfQ==} + peerDependencies: + xterm: ^5.0.0 + dependencies: + xterm: 5.1.0 + dev: false + + /xterm-addon-fit/0.7.0_xterm@5.1.0: + resolution: {integrity: sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==} + peerDependencies: + xterm: ^5.0.0 + dependencies: + xterm: 5.1.0 + dev: false + + /xterm/5.1.0: + resolution: {integrity: sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ==} + dev: false diff --git a/src/frontend/public/favicon.png b/frontend/public/favicon.png similarity index 100% rename from src/frontend/public/favicon.png rename to frontend/public/favicon.png diff --git a/src/frontend/src/App.svelte b/frontend/src/App.svelte similarity index 79% rename from src/frontend/src/App.svelte rename to frontend/src/App.svelte index 5c3ddab5..b57de322 100755 --- a/src/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -8,6 +8,7 @@ import Terminal from "./pages/Terminal.svelte"; import NavbarLink from "./components/NavbarLink.svelte"; import Fa from "svelte-fa"; + import FaLayers from "svelte-fa/src/fa-layers.svelte"; import { faTachometerAlt, faMicrochip, @@ -20,8 +21,8 @@ faSun, faMoon, faEnvelope, - faEnvelopeOpenText, faCog, + faCircle, } from "@fortawesome/free-solid-svg-icons"; import Management from "./pages/Management.svelte"; import FileBrowser from "./pages/FileBrowser.svelte"; @@ -32,7 +33,7 @@ import type { socketData } from "./types"; let socket: WebSocket; - let socketData: Partial = {}; + let socketData: socketData; let nodes: string[] = []; let shown = false; let darkMode = false; @@ -43,6 +44,7 @@ let settingsShown = false; let passwordMessage = false; let notify = false; + let reopenSocket = true; let menu = window.innerWidth > 768; let dpUpdate = ""; let tempUnit: "fahrenheit" | "celsius"; @@ -54,7 +56,7 @@ let updateAvailable = ""; let node = `${window.location.hostname}:${window.location.port}`; - $: node && ((shown = false), connectSocket(node)); + $: node && (((shown = false), (reopenSocket = false)), connectSocket(node)); $: notify = dpUpdate != "" || cmp(frontendVersion, backendVersion) != 0 || @@ -103,7 +105,7 @@ const socketMessageListener = (e: MessageEvent) => { socketData = JSON.parse(e.data); - if (socketData.update != undefined) { + if (socketData.dataKind == "GLOBAL") { dpUpdate = socketData.update; login = socketData.login; if (socketData.nodes) { @@ -120,6 +122,7 @@ } else { // Or use stored token token = obj[node]; + socket.send(JSON.stringify({ token })); pollServer(window.location.pathname); } } else { @@ -133,7 +136,7 @@ updateCheck(); } } - if (socketData.error == true) { + if (socketData.dataKind == "REAUTH") { loginDialog = true; } if (navPage) { @@ -143,32 +146,30 @@ } }; const socketOpenListener = () => { - console.log("Connected"); + console.info("Connected"); shown = true; + loginDialog = false; }; const socketErrorListener = (e: ErrorEvent) => { console.error(e); - connectSocket(node); }; - const socketCloseListener = () => { - console.log("Disconnected"); + const socketCloseListener = (e: CloseEvent) => { + console.info("Disconnected, reconnecting:", reopenSocket); + if (reopenSocket) { + setTimeout(() => connectSocket(node), 1000); + } else { + reopenSocket = true; + } }; function pollServer(page: string) { if (page != "/terminal") { // Terminal doesn't work if sent - let json: string; - if (login) { - json = JSON.stringify({ - page, - token, - }); - } else { - json = JSON.stringify({ + socket.send( + JSON.stringify({ page, - }); - } - socket.send(json); + }) + ); } } @@ -187,47 +188,43 @@ body: password, }; fetch(`${window.location.protocol}//${node}/login/`, options).then( - (response) => + (response) => { + password = ""; + if (response.status == 401) { + passwordMessage = true; + setTimeout(() => (passwordMessage = false), 2000); + return; + } response.text().then((body) => { - password = ""; - if (body == "Unauthorized") { - passwordMessage = true; - setTimeout(() => (passwordMessage = false), 2000); - } else { - token = body; - let obj = - localStorage.getItem("tokens") == null - ? {} - : JSON.parse(localStorage.getItem("tokens")); - obj[node] = body; - localStorage.setItem("tokens", JSON.stringify(obj)); - loginDialog = false; - pollServer(window.location.pathname); - } - }) + token = body; + let obj = + localStorage.getItem("tokens") == null + ? {} + : JSON.parse(localStorage.getItem("tokens")); + obj[node] = body; + localStorage.setItem("tokens", JSON.stringify(obj)); + loginDialog = false; + socket.send(JSON.stringify({ token })); + pollServer(window.location.pathname); + }); + } ); } function socketSend(cmd: string, args: string[]) { - let json; - if (login) { - json = JSON.stringify({ - cmd, - args, - token, - }); - } else { - json = JSON.stringify({ + socket.send( + JSON.stringify({ cmd, args, - }); - } - socket.send(json); + }) + ); } function connectSocket(url: string) { if (socket) { socket.close(); + } else { + reopenSocket = true; } let proto = window.location.protocol == "https:" ? "wss" : "ws"; socket = new WebSocket(`${proto}://${url}/ws`); @@ -338,10 +335,21 @@ class="cursor-pointer" on:click={() => (notificationsShown = !notificationsShown)} - > + >{#if notify} + + + + + {:else} + + {/if} {#if nodes.length != 0} @@ -417,32 +425,44 @@ ? ' children:blur-2 children:filter' : ''}" > - {#if shown} + {#if shown && socketData != undefined} - - - + {#if socketData.dataKind == "PROCESS"} + + {/if} + {#if socketData.dataKind == "STATISTIC"} + + {/if} + {#if socketData.dataKind == "SOFTWARE"} + + {/if} - - - + {#if socketData.dataKind == "MANAGEMENT"} + + {/if} + {#if socketData.dataKind == "BROWSER"} + + {/if} + {#if socketData.dataKind == "SERVICE"} + + {/if}

Page not found

{:else} diff --git a/src/frontend/src/assets/dietpi.png b/frontend/src/assets/dietpi.png similarity index 100% rename from src/frontend/src/assets/dietpi.png rename to frontend/src/assets/dietpi.png diff --git a/src/frontend/src/assets/github-mark.svg b/frontend/src/assets/github-mark.svg similarity index 100% rename from src/frontend/src/assets/github-mark.svg rename to frontend/src/assets/github-mark.svg diff --git a/src/frontend/src/components/Card.svelte b/frontend/src/components/Card.svelte similarity index 100% rename from src/frontend/src/components/Card.svelte rename to frontend/src/components/Card.svelte diff --git a/src/frontend/src/components/NavbarLink.svelte b/frontend/src/components/NavbarLink.svelte similarity index 64% rename from src/frontend/src/components/NavbarLink.svelte rename to frontend/src/components/NavbarLink.svelte index 80d4c5a1..8997542d 100644 --- a/src/frontend/src/components/NavbarLink.svelte +++ b/frontend/src/components/NavbarLink.svelte @@ -1,6 +1,7 @@ -
void; - export let socketData: Partial; + export let socketData: browserPage; export let node: string; export let login: boolean; @@ -61,7 +62,11 @@ const fileSocket = new WebSocket( `${ window.location.protocol == "https:" ? "wss" : "ws" - }://${node}/ws/file` + }://${node}/ws/file${ + login + ? `?token=${JSON.parse(localStorage.getItem("tokens"))[node]}` + : "" + }` ); fileSocket.onmessage = (e: MessageEvent) => { if (typeof e.data == "string") { @@ -124,22 +129,13 @@ } function fileSend(path: string, cmd: string, arg: string) { - let json; - if (login) { - json = JSON.stringify({ + fileSocket.send( + JSON.stringify({ cmd, path, arg, - token: JSON.parse(localStorage.getItem("tokens"))[node], - }); - } else { - json = JSON.stringify({ - cmd, - path, - arg, - }); - } - fileSocket.send(json); + }) + ); } function rename(oldname: string, newname: string) { @@ -194,6 +190,8 @@ return faFileArchive; case "text": return faFileAlt; + case "notafile": + return faCube; default: return faFile; } @@ -356,7 +354,7 @@ currentPath = contents.path; break; case "text": - if (contents.subtype == "large") { + if (contents.size > 2 * 1000 * 1000) { if ( confirm( "Can't view files above 2MB, would you like to download instead?" @@ -376,6 +374,9 @@ } currentPath = contents.path; break; + case "notafile": + alert("Cannot download special files"); + break; default: if ( confirm( @@ -510,7 +511,7 @@ }} /> - {#if selPath.path != ""} + {#if selPath.path != "" && selPath.maintype != "notafile"} ; + export let socketData: statisticsPage; export let darkMode: boolean; export let tempUnit: "fahrenheit" | "celsius"; @@ -37,45 +37,23 @@ let data: uPlot.AlignedData = [[], [], [], [], [], [], [], []]; - $: socketData.cpu != undefined && - (cpuAnimate.set(socketData.cpu), - ramAnimate.set(socketData.ram.percent), - swapAnimate.set(socketData.swap.percent), - diskAnimate.set(socketData.disk.percent)); + $: cpuAnimate.set(socketData.cpu); + $: ramAnimate.set(socketData.ram.percent); + $: swapAnimate.set(socketData.swap.percent); + $: diskAnimate.set(socketData.disk.percent); - $: socketData.ram && - ((ramData = [ - prettyBytes(socketData.ram.used, { binary: true }), - prettyBytes(socketData.ram.total, { binary: true }), - ]), - (swapData = [ - prettyBytes(socketData.swap.used, { binary: true }), - prettyBytes(socketData.swap.total, { binary: true }), - ]), - (diskData = [ - prettyBytes(socketData.disk.used), - prettyBytes(socketData.disk.total), - ])); - - function getSize() { - if (portrait) { - return { - width: Math.max( - (window.innerWidth / 100) * 70, - document.getElementById("chart").getBoundingClientRect() - .width - 20 - ), - height: (window.innerHeight / 100) * 50, - }; - } else { - return { - height: (window.innerHeight / 100) * 70, - width: - document.getElementById("chart").getBoundingClientRect() - .width - 20, - }; - } - } + $: ramData = [ + prettyBytes(socketData.ram.used, { binary: true }), + prettyBytes(socketData.ram.total, { binary: true }), + ]; + $: swapData = [ + prettyBytes(socketData.swap.used, { binary: true }), + prettyBytes(socketData.swap.total, { binary: true }), + ]; + $: diskData = [ + prettyBytes(socketData.disk.used), + prettyBytes(socketData.disk.total), + ]; function getTempMsg(temp: number) { if (temp >= 70) { @@ -107,11 +85,25 @@ } } + function resizeUplot(uplot: uPlot, entry: Element) { + uplot.setSize({ + width: Math.min( + entry.clientWidth - 10, + (window.innerWidth / 100) * (portrait ? 70 : 50) + ), + height: Math.min( + entry.clientHeight - 20, + (window.innerHeight / 100) * (portrait ? 50 : 70) + ), + }); + } + let uplot: uPlot; onMount(() => { let opts: uPlot.Options = { - ...getSize(), + width: 100, + height: 100, series: [ {}, { @@ -213,23 +205,27 @@ uplot = new uPlot(opts, data, chart); - if (socketData.swap != undefined && socketData.swap.total == 0) { + if (socketData.swap.total == 0) { uplot.setSeries(3, { show: false }); } + + let observer = new ResizeObserver((entries, _) => + resizeUplot(uplot, entries[0].target) + ); + + observer.observe(document.getElementById("chart")); }); let handle1 = setInterval(() => { let dataPush = data as number[][]; - if (socketData.ram.used != undefined) { - dataPush[0].push(Math.round(Date.now() / 1000)); - dataPush[1].push(socketData.cpu); - dataPush[2].push(socketData.ram.used / 1000000); - dataPush[3].push(socketData.swap.used / 1000000); - dataPush[4].push(socketData.disk.used / 1000000); - dataPush[5].push(socketData.network.sent / 1000000); - dataPush[6].push(socketData.network.received / 1000000); - } - if (socketData.temp != undefined && socketData.temp.available) { + dataPush[0].push(Math.round(Date.now() / 1000)); + dataPush[1].push(socketData.cpu); + dataPush[2].push(socketData.ram.used / 1000000); + dataPush[3].push(socketData.swap.used / 1000000); + dataPush[4].push(socketData.disk.used / 1000000); + dataPush[5].push(socketData.network.sent / 1000000); + dataPush[6].push(socketData.network.received / 1000000); + if (socketData.temp.available) { if (uplot.series[7] == undefined) { uplot.addSeries({ spanGaps: false, @@ -259,7 +255,6 @@ { portrait = window.innerHeight > window.innerWidth; - uplot.setSize(getSize()); }} /> @@ -271,15 +266,13 @@
- {#if ramData != undefined} - {#if socketData.temp.available} -
- - {socketData.temp.celsius}ºC/{socketData.temp - .fahrenheit}ºF: {getTempMsg(socketData.temp.celsius)} -
- {/if} + {#if socketData.temp.available} +
+ + {socketData.temp.celsius}ºC/{socketData.temp + .fahrenheit}ºF: {getTempMsg(socketData.temp.celsius)} +
CPU:{socketData.cpu}/100%
diff --git a/src/frontend/src/pages/Management.svelte b/frontend/src/pages/Management.svelte similarity index 94% rename from src/frontend/src/pages/Management.svelte rename to frontend/src/pages/Management.svelte index e06a2ced..ccdaa8b7 100644 --- a/src/frontend/src/pages/Management.svelte +++ b/frontend/src/pages/Management.svelte @@ -3,20 +3,19 @@ import prettyMilliseconds from "pretty-ms"; import { fade } from "svelte/transition"; - import type { socketData } from "../types"; + import type { managementPage } from "../types"; export let socketSend: (cmd: string, args: string[]) => void; - export let socketData: Partial; + export let socketData: managementPage; let uptime: string; let dialog = false; let msg = ""; - $: socketData.uptime && - ((uptime = prettyMilliseconds(socketData.uptime * 60000, { - verbose: true, - })), - (dialog = false)); + $: (uptime = prettyMilliseconds(socketData.uptime * 60000, { + verbose: true, + })), + (dialog = false); function sendData(data: string) { socketSend(data, []); diff --git a/src/frontend/src/pages/Process.svelte b/frontend/src/pages/Process.svelte similarity index 57% rename from src/frontend/src/pages/Process.svelte rename to frontend/src/pages/Process.svelte index befea1e4..409ef6b4 100644 --- a/src/frontend/src/pages/Process.svelte +++ b/frontend/src/pages/Process.svelte @@ -11,9 +11,9 @@ } from "@fortawesome/free-solid-svg-icons"; import prettyBytes from "pretty-bytes"; - import type { socketData } from "../types"; + import type { processPage } from "../types"; - export let socketData: Partial; + export let socketData: processPage; export let socketSend: (cmd: string, args: string[]) => void; let reverse = false; @@ -28,11 +28,13 @@ let ramIcon = faSort; let statusIcon = faSort; - $: cpuSort && socketData.processes && sortCPU(reverse); - $: pidSort && socketData.processes && sortPid(reverse); - $: nameSort && socketData.processes && sortName(reverse); - $: ramSort && socketData.processes && sortRAM(reverse); - $: statusSort && socketData.processes && sortStatus(reverse); + $: cpuSort && sortCPU(reverse); + $: pidSort && sortPid(reverse); + $: nameSort && sortName(reverse); + $: ramSort && sortRAM(reverse); + $: statusSort && sortStatus(reverse); + + // TODO: DRY much? function sortCPU(reverse: boolean) { socketData.processes.sort((a, b) => { @@ -215,124 +217,120 @@
- {#if socketData.processes} - - - - - - + + + + + + + + + {#each socketData.processes as process} + + + + + + - - - - {#each socketData.processes as process} - - - - - - - - - {/each} -
PIDNameStatusCPU Usage
PIDNameStatusCPU UsageRAM UsageActions
{process.pid}{process.name}{process.status}{process.cpu}%{prettyBytes(process.ram, { + binary: true, + maximumFractionDigits: 0, + })}RAM UsageActions
{process.pid}{process.name}{process.status}{process.cpu}%{prettyBytes(process.ram, { - binary: true, - maximumFractionDigits: 0, - })} - {#if process.name != "dietpi-dashboar"} + + {#if process.name != "dietpi-dashboar"} + + socketSend("terminate", [ + process.pid.toString(), + ])} + title="Terminate" + > + + socketSend("kill", [process.pid.toString()])} + title="Kill" + > + {#if process.status != "stopped"} - socketSend("terminate", [ + socketSend("suspend", [ process.pid.toString(), ])} - title="Terminate" + title="Suspend" > + {:else} - socketSend("kill", [ + socketSend("resume", [ process.pid.toString(), ])} - title="Kill" + title="Resume" > - {#if process.status != "stopped"} - - socketSend("suspend", [ - process.pid.toString(), - ])} - title="Suspend" - > - {:else} - - socketSend("resume", [ - process.pid.toString(), - ])} - title="Resume" - > - {/if} {/if} -
- {/if} + {/if} + + + {/each} +
diff --git a/frontend/src/pages/Service.svelte b/frontend/src/pages/Service.svelte new file mode 100644 index 00000000..a5762f18 --- /dev/null +++ b/frontend/src/pages/Service.svelte @@ -0,0 +1,76 @@ + + +
+ + + + + + + + + {#each socketData.services as service} + + + + + + + + {/each} +
NameStatusError LogStart TimeActions
{service.name}{service.status} + {#if service.log != ""} +
+ Show log + {@html service.log} +
+ {/if}
{service.start} + {#if service.status == "inactive" || service.status == "failed"} + socketSend("start", [service.name])} + title="Start" + > + {:else} + socketSend("stop", [service.name])} + title="Stop" + > + socketSend("restart", [service.name])} + title="Restart" + > + {/if}
+
diff --git a/src/frontend/src/pages/Software.svelte b/frontend/src/pages/Software.svelte similarity index 50% rename from src/frontend/src/pages/Software.svelte rename to frontend/src/pages/Software.svelte index cd20561e..8ef0f3d7 100644 --- a/src/frontend/src/pages/Software.svelte +++ b/frontend/src/pages/Software.svelte @@ -1,9 +1,9 @@
- {#if socketData.uninstalled} -
- - -
- + + - - - - - - - - - {#each socketData[installTable ? "installed" : "uninstalled"] as software} - {#if software.id != -1} - +
ID{installTable ? "Uninstall" : "Install"}NameDescriptionDependenciesDocumentation link
+ + + + + + + + + {#each socketData[installTable ? "installed" : "uninstalled"] as software} + {#if software.id != -1} + + + - - - - - - + + + - - {/if} - {/each} -
ID{installTable ? "Uninstall" : "Install"}NameDescriptionDependenciesDocumentation link
{software.id} + (installTemp[software.id] = + !installTemp[software.id])} + bind:checked={installTemp[software.id]} + disabled={running} + />{software.id} - (installTemp[software.id] = - !installTemp[software.id])} - bind:checked={installTemp[software.id]} - disabled={running} - />{software.name}{software.description}{software.dependencies} - {#if software.docs.substring(0, 5) == "https"} - - {software.docs} - - {:else} + {software.name}{software.description}{software.dependencies} + {#if software.docs.substring(0, 5) == "https"} + {software.docs} - {/if} -
- {/if} + + {:else} + {software.docs} + {/if} + + + {/if} + {/each} +
diff --git a/src/frontend/src/pages/Terminal.svelte b/frontend/src/pages/Terminal.svelte similarity index 85% rename from src/frontend/src/pages/Terminal.svelte rename to frontend/src/pages/Terminal.svelte index 1e137413..8d342c93 100644 --- a/src/frontend/src/pages/Terminal.svelte +++ b/frontend/src/pages/Terminal.svelte @@ -11,12 +11,16 @@ let termDiv: HTMLDivElement; let proto = window.location.protocol == "https:" ? "wss" : "ws"; - let socket = new WebSocket(`${proto}://${node}/ws/term`); + let socket = new WebSocket( + `${proto}://${node}/ws/term${token ? `?token=${token}` : ""}` + ); $: token, node, (socket.onopen = () => {}), - ((socket = new WebSocket(`${proto}://${node}/ws/term`)), + ((socket = new WebSocket( + `${proto}://${node}/ws/term${token ? `?token=${token}` : ""}` + )), (socket.onopen = socketOpen)); const fitAddon = new FitAddon(); @@ -36,9 +40,6 @@ }; let socketOpen = () => { - if (token) { - socket.send(`token${token}`); - } termDiv.replaceChildren(); const attachAddon = new AttachAddon(socket); terminal.loadAddon(attachAddon); diff --git a/frontend/src/types.ts b/frontend/src/types.ts new file mode 100644 index 00000000..8a1c7b7c --- /dev/null +++ b/frontend/src/types.ts @@ -0,0 +1,132 @@ +type socketData = + | statisticsPage + | softwarePage + | processPage + | servicesPage + | browserPage + | managementPage + | globalSettings + | reauthenticate; + +interface statisticsPage { + dataKind: "STATISTIC"; + cpu: number; + ram: usage; + swap: usage; + disk: usage; + network: net; + temp: temp; +} + +interface softwarePage { + dataKind: "SOFTWARE"; + uninstalled: softwareItem[]; + installed: softwareItem[]; + response: string; +} + +interface processPage { + dataKind: "PROCESS"; + processes: processItem[]; +} + +interface servicesPage { + dataKind: "SERVICE"; + services: serviceItem[]; +} + +interface browserPage { + dataKind: "BROWSER"; + contents: browserItem[]; + textdata: string; +} + +interface managementPage { + dataKind: "MANAGEMENT"; + hostname: string; + uptime: number; + arch: string; + kernel: string; + dp_version: string; + packages: number; + upgrades: number; + nic: string; + ip: string; +} + +interface globalSettings { + dataKind: "GLOBAL"; + update: string; + login: boolean; + nodes: string[]; + version: string; + update_check: boolean; + temp_unit: "fahrenheit" | "celsius"; +} + +interface reauthenticate { + dataKind: "REAUTH"; + reauth: true; +} + +interface softwareItem { + id: number; + name: string; + description: string; + dependencies: string; + docs: string; +} + +interface processItem { + pid: number; + name: string; + cpu: number; + ram: number; + status: string; +} + +interface serviceItem { + name: string; + status: string; + log: string; + start: string; +} + +interface browserItem { + name: string; + path: string; + prettytype: string; + maintype: string; + subtype: string; + size: number; +} + +interface usage { + used: number; + total: number; + percent: number; +} + +interface net { + sent: number; + received: number; +} + +interface temp { + available: boolean; + celsius: number; + fahrenheit: number; +} + +// 'browserItem' required for selected path in file browser +export type { + socketData, + statisticsPage, + softwarePage, + processPage, + servicesPage, + browserPage, + managementPage, + globalSettings, + browserItem, +}; diff --git a/src/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts similarity index 100% rename from src/frontend/src/vite-env.d.ts rename to frontend/src/vite-env.d.ts diff --git a/src/frontend/svelte.config.js b/frontend/svelte.config.js similarity index 100% rename from src/frontend/svelte.config.js rename to frontend/svelte.config.js diff --git a/src/frontend/tsconfig.json b/frontend/tsconfig.json similarity index 92% rename from src/frontend/tsconfig.json rename to frontend/tsconfig.json index b504ae08..508c82af 100644 --- a/src/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/svelte/tsconfig.json", + "extends": "@tsconfig/svelte", "compilerOptions": { "target": "esnext", "useDefineForClassFields": true, diff --git a/src/frontend/vite.config.js b/frontend/vite.config.js similarity index 93% rename from src/frontend/vite.config.js rename to frontend/vite.config.js index 8c24f742..4a6104ec 100644 --- a/src/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -19,7 +19,9 @@ export default defineConfig(({ mode }) => { ] })], build: { + manifest: true, rollupOptions: { + input: "src/main.ts", output: { manualChunks: { xterm: ['xterm', 'xterm-addon-attach', 'xterm-addon-fit'] diff --git a/src/frontend/windi.config.ts b/frontend/windi.config.ts similarity index 100% rename from src/frontend/windi.config.ts rename to frontend/windi.config.ts diff --git a/src/backend/Cargo.toml b/src/backend/Cargo.toml deleted file mode 100644 index 11670881..00000000 --- a/src/backend/Cargo.toml +++ /dev/null @@ -1,53 +0,0 @@ -[package] -name = "dietpi-dashboard" -version = "0.6.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -warp = {version = "0.3.2", default-features = false, features = ["websocket", "tls"]} -tokio = { version = "1", features = ["rt", "macros", "time", "sync", "fs", "process"] } -simple_logger = { version = "2.1.0", default-features = false, features = ["colors"] } -log = "0.4.17" -include_dir = {version = "0.7.2", optional = true} -futures = "0.3.21" -pty-process = "0.2.0" -psutil = "3.2.2" -infer = { version = "0.8.1", default-features = false } -ring = "0.16.20" -figment = { version = "0.10", features = ["toml", "env"] } -if-addrs = "0.7.0" -zip = { version = "0.6.2", default-features = false, features = ["time"] } -walkdir = "2.3.2" -jsonwebtoken = { version = "8.1.1", default-features = false } -serde = { version = "1.0.137", features = ["derive"] } -anyhow = "1.0.58" -once_cell = "1.12.0" -serde_json = "1.0.81" - -[dev-dependencies] -tempfile = "3.3.0" - -[features] -default = ["frontend"] -frontend = ["include_dir"] -compression = ["frontend"] - -[profile.release] -lto = "fat" -panic = "abort" -codegen-units = 1 -strip = true - -[profile.release.package.serde] -opt-level = 3 - -[profile.release.package.psutil] -opt-level = 3 - -[profile.release.package.zip] -opt-level = 3 - -[profile.release.package."*"] -opt-level = "s" diff --git a/src/backend/src/main.rs b/src/backend/src/main.rs deleted file mode 100644 index 31744d5d..00000000 --- a/src/backend/src/main.rs +++ /dev/null @@ -1,233 +0,0 @@ -#![warn(clippy::pedantic)] -#![allow(clippy::too_many_lines)] -use crate::shared::CONFIG; -use anyhow::Context; -use ring::digest; -use simple_logger::SimpleLogger; -use std::net::IpAddr; -use warp::Filter; -#[cfg(feature = "frontend")] -use warp::{http::header, Reply}; - -mod config; -mod page_handlers; -mod shared; -mod socket_handlers; -mod systemdata; - -#[tokio::main(flavor = "current_thread")] -async fn main() -> anyhow::Result<()> { - #[cfg(feature = "frontend")] - const DIR: include_dir::Dir = include_dir::include_dir!("dist"); - - SimpleLogger::new() - .with_level(CONFIG.log_level) - .env() - .init() - .context("Couldn't init logger")?; - - #[cfg(feature = "frontend")] - let mut headers = header::HeaderMap::new(); - #[cfg(feature = "frontend")] - { - headers.insert( - header::X_CONTENT_TYPE_OPTIONS, - header::HeaderValue::from_static("nosniff"), - ); - headers.insert( - header::X_FRAME_OPTIONS, - header::HeaderValue::from_static("sameorigin"), - ); - headers.insert("X-Robots-Tag", header::HeaderValue::from_static("none")); - headers.insert( - "X-Permitted-Cross-Domain_Policies", - header::HeaderValue::from_static("none"), - ); - headers.insert( - header::REFERRER_POLICY, - header::HeaderValue::from_static("no-referrer"), - ); - headers.insert("Content-Security-Policy", header::HeaderValue::from_static("default-src 'self'; font-src 'self'; img-src 'self' blob:; script-src 'self'; style-src 'unsafe-inline' 'self'; connect-src * ws:;")); - #[cfg(feature = "compression")] - headers.insert( - header::CONTENT_ENCODING, - header::HeaderValue::from_static("gzip"), - ); - } - - #[cfg(feature = "frontend")] - let favicon_route = warp::path("favicon.png").map(|| { - warp::reply::with_header( - handle_error!( - DIR.get_file("favicon.png").context("Couldn't get favicon"), - return warp::reply::with_status( - "Couldn't get favicon", - warp::http::StatusCode::INTERNAL_SERVER_ERROR - ) - .into_response() - ) - .contents(), - "content-type", - "image/png", - ) - .into_response() - }); - - #[cfg(feature = "frontend")] - let assets_route = warp::path("assets") - .and(warp::path::param()) - .map(|path: String| { - let ext = path.rsplit('.').next().unwrap_or("plain"); - #[allow(unused_mut)] - // Mute warning, variable is mut because it's used with the compression feature - let mut reply = warp::reply::with_header( - match DIR.get_file(format!("assets/{}", path)) { - Some(file) => file.contents(), - None => { - log::info!("Couldn't get asset {}", path); - &[] - } - }, - header::CONTENT_TYPE, - if ext == "js" { - "text/javascript".to_string() - } else if ext == "svg" { - "image/svg+xml".to_string() - } else if ext == "png" { - "image/png".to_string() - } else { - format!("text/{}", ext) - }, - ) - .into_response(); - - #[cfg(feature = "compression")] - if ext != "png" { - reply.headers_mut().insert( - header::CONTENT_ENCODING, - header::HeaderValue::from_static("gzip"), - ); - }; - - reply - }); - - let login_route = warp::path("login") - .and(warp::post()) - .and(warp::body::bytes()) - .map(|pass: warp::hyper::body::Bytes| { - let token: String; - if CONFIG.pass { - let shasum = digest::digest(&digest::SHA512, &pass) - .as_ref() - .iter() - .map(|b| format!("{:02x}", b)) - .collect::(); - if shasum == CONFIG.hash { - let timestamp = jsonwebtoken::get_current_timestamp(); - - let claims = crate::shared::JWTClaims { - iss: "DietPi Dashboard".to_string(), - iat: timestamp, - exp: timestamp + CONFIG.expiry, - }; - - token = handle_error!( - jsonwebtoken::encode( - &jsonwebtoken::Header::default(), - &claims, - &jsonwebtoken::EncodingKey::from_secret(CONFIG.secret.as_ref()), - ) - .context("Error creating login token"), - return warp::reply::with_status( - "Error creating login token".to_string(), - warp::http::StatusCode::INTERNAL_SERVER_ERROR, - ) - ); - - return warp::reply::with_status(token, warp::http::StatusCode::OK); - } - return warp::reply::with_status( - "Unauthorized".to_string(), - warp::http::StatusCode::UNAUTHORIZED, - ); - } - warp::reply::with_status("No login needed".to_string(), warp::http::StatusCode::OK) - }) - .with(warp::reply::with::header( - "Access-Control-Allow-Origin", - "*", - )); - - let terminal_route = warp::path("ws") - .and(warp::path("term")) - .and(warp::ws()) - .map(|ws: warp::ws::Ws| ws.on_upgrade(socket_handlers::term_handler)); - - let socket_route = warp::path("ws") - .and(warp::ws()) - .map(|ws: warp::ws::Ws| ws.on_upgrade(socket_handlers::socket_handler)); - - let file_route = warp::path("ws") - .and(warp::path("file")) - .and(warp::ws()) - .map(|ws: warp::ws::Ws| ws.on_upgrade(socket_handlers::file_handler)); - - #[cfg(feature = "frontend")] - let main_route = warp::any() - .map(|| { - let file = handle_error!( - DIR.get_file("index.html") - .context("Couldn't get main HTML file"), - return warp::reply::with_status( - "Couldn't get main HTML file", - warp::http::StatusCode::INTERNAL_SERVER_ERROR - ) - .into_response() - ) - .contents(); - warp::reply::html(file).into_response() - }) - .with(warp::reply::with::headers(headers)); - - #[cfg(feature = "frontend")] - let page_routes = favicon_route.or(assets_route).or(main_route); - - let socket_routes = terminal_route.or(file_route).or(socket_route); - - let routes = socket_routes - .or(login_route) - .with(warp::log::custom(|info| { - log::info!("Request to {}", info.path()); - log::debug!( - "by {}, using {} {:?}, with response of HTTP code {:?}", - info.remote_addr() - .unwrap_or_else(|| std::net::SocketAddr::from(( - std::net::Ipv4Addr::UNSPECIFIED, - 0 - ))) - .ip(), - info.user_agent().unwrap_or("unknown"), - info.version(), - info.status() - ); - })); - - #[cfg(feature = "frontend")] - let routes = routes.or(page_routes); - - let addr = IpAddr::from([0; 8]); - - if CONFIG.tls { - warp::serve(routes) - .tls() - .cert_path(&CONFIG.cert) - .key_path(&CONFIG.key) - .run((addr, CONFIG.port)) - .await; - } else { - warp::serve(routes).run((addr, CONFIG.port)).await; - } - - Ok(()) -} diff --git a/src/backend/src/page_handlers.rs b/src/backend/src/page_handlers.rs deleted file mode 100644 index 1fd9028e..00000000 --- a/src/backend/src/page_handlers.rs +++ /dev/null @@ -1,328 +0,0 @@ -use anyhow::Context; -use futures::stream::SplitSink; -use futures::SinkExt; -use std::time::Duration; -use tokio::process::Command; -use tokio::sync::mpsc::Receiver; -use tokio::time::sleep; -use warp::ws::Message; - -use crate::{handle_error, json_msg, shared, systemdata}; - -type SocketSend = SplitSink; -type RecvChannel = Receiver>; - -fn main_handler_getter( - cpu_collector: &mut psutil::cpu::CpuPercentCollector, - net_collector: &mut psutil::network::NetIoCountersCollector, - prev_data: &mut shared::NetData, -) -> anyhow::Result { - Ok(shared::SysData { - cpu: systemdata::cpu(cpu_collector)?, - ram: systemdata::ram()?, - swap: systemdata::swap()?, - disk: systemdata::disk()?, - network: systemdata::network(net_collector, prev_data)?, - temp: systemdata::temp(), - }) -} - -pub async fn main_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) { - let mut cpu_collector = handle_error!( - psutil::cpu::CpuPercentCollector::new().context("Couldn't init cpu collector"), - return - ); - - let mut net_collector = psutil::network::NetIoCountersCollector::default(); - let mut prev_data = match net_collector.net_io_counters() { - Ok(counters) => shared::NetData { - received: counters.bytes_recv(), - sent: counters.bytes_sent(), - }, - Err(_) => shared::NetData { - received: u64::MAX, - sent: u64::MAX, - }, - }; - - loop { - tokio::select! { - biased; - data = data_recv.recv() => match data { - Some(Some(_)) => {}, - _ => break, - }, - res = socket_send - .send(json_msg!(&handle_error!(main_handler_getter(&mut cpu_collector, &mut net_collector, &mut prev_data), shared::SysData::default()), continue)) - => { - sleep(Duration::from_secs(1)).await; - if res.is_err() { - break - } - }, - } - } -} - -fn process_handler_helper(data: &shared::Request) -> anyhow::Result<()> { - let process = psutil::process::Process::new( - data.args[0] - .parse::() - .with_context(|| format!("Invalid pid {}", data.args[0]))?, - ) - .with_context(|| format!("Couldn't make process from pid {}", data.args[0]))?; - log::info!( - "{}ing process {}", - data.cmd.trim_end_matches('e'), - process.pid() - ); - match data.cmd.as_str() { - "terminate" => process.terminate(), - "kill" => process.kill(), - "suspend" => process.suspend(), - "resume" => process.resume(), - _ => (Ok(())), - } - .with_context(|| format!("Couldn't {} process {}", data.cmd, process.pid()))?; - Ok(()) -} - -pub async fn process_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) { - loop { - tokio::select! { - biased; - data = data_recv.recv() => match data { - Some(Some(data)) => handle_error!(process_handler_helper(&data)), - _ => break, - }, - res = socket_send - .send(json_msg!( - &shared::ProcessList { - processes: handle_error!(systemdata::processes().await, Vec::new()), - }, continue - )) => { - sleep(Duration::from_secs(1)).await; - if res.is_err() { - break - } - }, - } - } -} - -pub async fn software_handler_helper( - data: &shared::Request, -) -> anyhow::Result { - // We don't just want to run dietpi-software without args - anyhow::ensure!(!data.args.is_empty(), "Empty dietpi-software args"); - - let mut cmd = tokio::process::Command::new("/boot/dietpi/dietpi-software"); - let mut arg_list = vec![data.cmd.as_str()]; - for element in &data.args { - arg_list.push(element.as_str()); - } - log::info!("{}ing software with ID(s) {:?}", data.cmd, data.args); - let out = std::string::String::from_utf8( - cmd.args(arg_list) - .output() - .await - .context("Couldn't get DietPi-Software output")? - .stdout, - ) - .context("Invalid DietPi-Software output")? - .replace('', ""); - - let software = systemdata::dpsoftware().await?; - Ok(shared::DPSoftwareList { - uninstalled: software.0, - installed: software.1, - response: out, - }) -} - -pub async fn software_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) { - let software = handle_error!(systemdata::dpsoftware().await, (Vec::new(), Vec::new())); - if socket_send - .send(json_msg!( - &shared::DPSoftwareList { - uninstalled: software.0, - installed: software.1, - response: String::new(), - }, - return - )) - .await - .is_err() - { - return; - } - while let Some(Some(data)) = data_recv.recv().await { - let out = handle_error!( - software_handler_helper(&data).await, - shared::DPSoftwareList::default() - ); - if socket_send.send(json_msg!(&out, continue)).await.is_err() { - break; - } - } -} - -pub async fn management_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) { - if socket_send - .send(json_msg!( - &handle_error!(systemdata::host().await, shared::HostData::default()), - return - )) - .await - .is_err() - { - return; - } - while let Some(Some(data)) = data_recv.recv().await { - // Don't care about the Ok value, so remove it to make the type checker happy - handle_error!(Command::new(&data.cmd) - .spawn() - .map(|_| ()) - .with_context(|| format!("Couldn't spawn command {}", &data.cmd))); - } -} - -pub async fn service_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) { - if socket_send - .send(json_msg!( - &shared::ServiceList { - services: handle_error!(systemdata::services().await, Vec::new()), - }, - return - )) - .await - .is_err() - { - return; - } - while let Some(Some(data)) = data_recv.recv().await { - handle_error!(Command::new("systemctl") - .args([&data.cmd, data.args[0].as_str()]) - .spawn() - .map(|_| ()) // Don't care about the Ok value, so remove it to make the type checker happy - .with_context(|| format!("Couldn't {} service {}", &data.cmd, &data.args[0]))); - if socket_send - .send(json_msg!( - &shared::ServiceList { - services: handle_error!(systemdata::services().await, Vec::new()), - }, - continue - )) - .await - .is_err() - { - break; - } - } -} - -async fn browser_refresh(path: &std::path::Path) -> anyhow::Result { - let dir_path = path - .parent() - .with_context(|| format!("Couldn't get parent of path {}", path.display()))?; - - Ok(shared::BrowserList { - contents: systemdata::browser_dir(std::path::Path::new(dir_path)).await?, - }) -} - -async fn browser_handler_helper(data: &shared::Request) -> anyhow::Result { - use tokio::fs; - - match data.cmd.as_str() { - "cd" => { - return Ok(shared::BrowserList { - contents: systemdata::browser_dir(std::path::Path::new(&data.args[0])).await?, - }); - } - "copy" => { - let mut num = 2; - while std::path::Path::new(&format!("{} {}", &data.args[0], num)).exists() { - num += 1; - } - fs::copy(&data.args[0], format!("{} {}", &data.args[0], num)) - .await - .with_context(|| { - format!("Couldn't copy file {0} to {0} {1}", &data.args[0], num) - })?; - } - "rm" => { - fs::remove_file(&data.args[0]) - .await - .with_context(|| format!("Couldn't delete file at {}", &data.args[0]))?; - } - "rmdir" => { - fs::remove_dir_all(&data.args[0]) - .await - .with_context(|| format!("Couldn't delete directory at {}", &data.args[0]))?; - } - "mkdir" => { - fs::create_dir(&data.args[0]) - .await - .with_context(|| format!("Couldn't create directory at {}", &data.args[0]))?; - } - "mkfile" => { - fs::write(&data.args[0], "") - .await - .with_context(|| format!("Couldn't create file at {}", &data.args[0]))?; - } - "rename" => { - fs::rename(&data.args[0], &data.args[1]) - .await - .with_context(|| { - format!( - "Couldn't rename file {} to {}", - &data.args[0], &data.args[1] - ) - })?; - } - _ => {} - } - - browser_refresh(std::path::Path::new(&data.args[0])).await -} - -pub async fn browser_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) { - // Get initial listing of $HOME - if socket_send - .send(json_msg!( - &shared::BrowserList { - contents: handle_error!( - systemdata::browser_dir(std::path::Path::new( - &std::env::var_os("HOME").unwrap_or_else(|| "/root".into()), - )) - .await, - Vec::new() - ), - }, - return - )) - .await - .is_err() - { - return; - } - - 'outer: while let Some(Some(mut data)) = data_recv.recv().await { - loop { - tokio::select! { - res = browser_handler_helper(&data) => { - let list = handle_error!(res, shared::BrowserList::default()); - if socket_send.send(json_msg!(&list, continue)).await.is_err() { - break 'outer; - } - break; - }, - recv = data_recv.recv() => match recv { - Some(Some(data_tmp)) => data = data_tmp, - _ => break 'outer, - }, - } - } - } -} diff --git a/src/backend/src/socket_handlers.rs b/src/backend/src/socket_handlers.rs deleted file mode 100644 index 43f9e1d7..00000000 --- a/src/backend/src/socket_handlers.rs +++ /dev/null @@ -1,471 +0,0 @@ -use anyhow::Context; -use futures::{SinkExt, StreamExt}; -use pty_process::Command; -use std::io::{Read, Write}; -use std::sync::Arc; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::sync::mpsc; -use warp::ws::Message; - -use crate::{handle_error, page_handlers, shared, systemdata, CONFIG}; - -fn validate_token(token: &str) -> bool { - let mut validator = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256); - validator.set_issuer(&["DietPi Dashboard"]); - validator.set_required_spec_claims(&["exp", "iat"]); - if jsonwebtoken::decode::( - token, - &jsonwebtoken::DecodingKey::from_secret(CONFIG.secret.as_bytes()), - &validator, - ) - .is_err() - { - return false; - } - true -} - -pub async fn socket_handler(socket: warp::ws::WebSocket) { - let (mut socket_send, mut socket_recv) = socket.split(); - let (data_send, mut data_recv) = mpsc::channel(1); - tokio::task::spawn(async move { - let mut first_message = true; - let mut req: shared::Request; - while let Some(Ok(data)) = socket_recv.next().await { - if data.is_close() { - break; - } - let data_str = handle_error!( - data.to_str().map_err(|_| anyhow::anyhow!( - "Couldn't convert received data {:?} to text", - data - )), - continue - ); - req = handle_error!( - serde_json::from_str(data_str) - .with_context(|| format!("Couldn't parse JSON {}", data_str)), - continue - ); - if CONFIG.pass && !validate_token(&req.token) { - if !first_message { - if let Err(err) = data_send.send(None).await { - log::error!("Internal error: couldn't initiate login: {}", err); - break; - } - } - handle_error!(data_send - .send(Some(shared::Request { - page: "/login".to_string(), - token: String::new(), - cmd: String::new(), - args: Vec::new(), - })) - .await - .context("Internal error: couldn't send login request")); - continue; - } - if req.cmd.is_empty() { - // Quit out of handler - if first_message { - first_message = false; - } else if let Err(err) = data_send.send(None).await { - log::error!("Internal error: couldn't change page: {}", err); - break; - } - } - // Send new page/data - if let Err(err) = data_send.send(Some(req.clone())).await { - // Manual error handling here, to use log::error - log::error!("Internal error: couldn't send request: {}", err); - break; - } - } - }); - // Send global message (shown on all pages) - if socket_send - .send(crate::json_msg!(&systemdata::global().await, return)) - .await - .is_err() - { - return; - } - while let Some(Some(message)) = data_recv.recv().await { - match message.page.as_str() { - "/" => page_handlers::main_handler(&mut socket_send, &mut data_recv).await, - "/process" => { - page_handlers::process_handler(&mut socket_send, &mut data_recv).await; - } - "/software" => { - page_handlers::software_handler(&mut socket_send, &mut data_recv).await; - } - "/management" => { - page_handlers::management_handler(&mut socket_send, &mut data_recv).await; - } - "/service" => { - page_handlers::service_handler(&mut socket_send, &mut data_recv).await; - } - "/browser" => { - page_handlers::browser_handler(&mut socket_send, &mut data_recv).await; - } - "/login" => { - // Internal poll, see other thread - if socket_send - .send(crate::json_msg!( - &shared::TokenError { error: true }, - continue - )) - .await - .is_err() - { - break; - } - } - _ => { - log::debug!("Got page {}, not handling", message.page); - } - } - } -} - -#[derive(serde::Deserialize)] -struct TTYSize { - cols: u16, - rows: u16, -} - -pub async fn term_handler(socket: warp::ws::WebSocket) { - let (mut socket_send, mut socket_recv) = socket.split(); - - if crate::CONFIG.pass { - if let Some(Ok(token)) = socket_recv.next().await { - // Stop from panicking, return from function with invalid token instead - let token = token.to_str().unwrap_or(""); - if token.get(..5) == Some("token") { - if !validate_token(&token[5..]) { - return; - } - } else { - return; - } - } - } - - // We use std::process::Command here because it lets us read and write without a mutable reference (even though it should require one?) - let mut pre_cmd = std::process::Command::new("/bin/login"); - pre_cmd.env("TERM", "xterm"); - - let mut cmd = Arc::new(handle_error!( - if crate::CONFIG.terminal_user == "manual" { - &mut pre_cmd - } else { - pre_cmd.args(&["-f", &crate::CONFIG.terminal_user]) - } - .spawn_pty(None) - .context("Couldn't spawn pty"), - return - )); - let mut cmd_read = Arc::clone(&cmd); - - tokio::join!( - async { - loop { - // Don't care about partial reads, it's in a loop - #[allow(clippy::unused_io_amount)] - let result = handle_error!( - tokio::task::spawn_blocking(move || { - let mut data = [0; 256]; - let res = cmd_read.pty().read(&mut data); - (res, data, cmd_read) - }) - .await - .context("Couldn't spawn tokio reader thread"), - break - ); - cmd_read = result.2; - if result.0.is_ok() { - if socket_send - .send(Message::binary( - result.1.split(|num| *num == 0).next().unwrap_or(&result.1), - )) // Should never be None, but return data just in case - .await - .is_err() - { - break; - } - } else { - break; - } - } - }, - async { - loop { - match socket_recv.next().await { - Some(Ok(data)) => { - if data.is_text() && data.to_str().unwrap().get(..4) == Some("size") { - let data_str = data.to_str().unwrap(); - let json: TTYSize = handle_error!( - serde_json::from_str(&data_str[4..]).with_context(|| format!( - "Couldn't deserialize pty size from {}", - &data_str - )), - continue - ); - handle_error!(cmd - .resize_pty(&pty_process::Size::new(json.rows, json.cols)) - .context("Couldn't resize pty")); - } else if cmd.pty().write_all(data.as_bytes()).is_err() { - break; - } - } - None | Some(Err(_)) => { - // Stop bash by writing "exit", since it won't respond to a SIGTERM - let _write = cmd.pty().write_all("exit\n".as_bytes()); - break; - } - } - } - } - ); - - // Reap PID, unwrap is safe because all references will have been dropped - handle_error!( - Arc::get_mut(&mut cmd) - .unwrap() - .wait() - .context("Couldn't close terminal"), - return - ); - - log::info!("Closed terminal"); -} - -async fn create_zip_file(req: &shared::FileRequest) -> anyhow::Result> { - let src_path = tokio::fs::canonicalize(&req.path) - .await - .with_context(|| format!("Invalid source path {}", &req.path))?; - if src_path.is_dir() { - let mut zip_file = zip::ZipWriter::new(std::io::Cursor::new(Vec::new())); - for entry in walkdir::WalkDir::new(&src_path) { - let entry = entry.context("Couldn't get data for recursive entry")?; - let path = entry.path(); - // 'unwrap' is safe here because path is canonicalized - let name = std::path::Path::new(&src_path.file_name().unwrap()).join( - // Here too, because the path should always be a child - path.strip_prefix(&src_path).unwrap(), - ); - let name = name.to_string_lossy().to_string(); - if path.is_file() { - let mut file_buf = Vec::new(); - zip_file - .start_file(&name, zip::write::FileOptions::default()) - .with_context(|| format!("Couldn't add file {} to zip", &name))?; - let mut file = handle_error!( - tokio::fs::File::open(path) - .await - .with_context(|| format!("Couldn't open file {}, skipping", &name)), - continue - ); - handle_error!( - file.read_to_end(&mut file_buf) - .await - .with_context(|| format!("Couldn't read file {}, skipping", &name)), - continue - ); - let tup = tokio::task::spawn_blocking( - move || -> (zip::ZipWriter>>, Vec, anyhow::Result<()>) { - if let Err(err) = zip_file.write_all(&file_buf) { - return (zip_file, file_buf, Err(err.into())); - } - (zip_file, file_buf, Ok(())) - }, - ) - .await - .context("Couldn't spawn zip task")?; - zip_file = tup.0; - file_buf = tup.1; - handle_error!(tup - .2 - .with_context(|| format!("Couldn't write file {} into zip, skipping", name))); - file_buf.clear(); - } else if !name.is_empty() { - zip_file - .add_directory(&name, zip::write::FileOptions::default()) - .with_context(|| format!("Couldn't add directory {} to zip", name))?; - } - } - return Ok(zip_file - .finish() - .context("Couldn't finish writing to zip file")? - .into_inner()); - } - tokio::fs::read(&src_path) - .await - .with_context(|| format!("Couldn't read file {}", src_path.display())) -} - -// Not the most elegant solution, but it works -enum FileHandlerHelperReturns { - String(String), - SizeBuf(shared::FileSize, Vec), - StreamUpload(usize, tokio::fs::File), -} - -async fn file_handler_helper( - req: &shared::FileRequest, -) -> anyhow::Result> { - match req.cmd.as_str() { - "open" => { - return Ok(Some(FileHandlerHelperReturns::String( - tokio::fs::read_to_string(&req.path) - .await - .with_context(|| format!("Couldn't read file {}", &req.path))?, - ))) - } - // Technically works for both files and directories - "dl" => { - let buf = create_zip_file(req).await?; - #[allow( - clippy::cast_lossless, - clippy::cast_sign_loss, - clippy::cast_precision_loss, - clippy::cast_possible_truncation - )] - let size = (buf.len() as f64 / f64::from(1000 * 1000)).ceil() as usize; - return Ok(Some(FileHandlerHelperReturns::SizeBuf( - shared::FileSize { size }, - buf, - ))); - } - "up" => { - let file = tokio::fs::File::create(&req.path) - .await - .with_context(|| format!("Couldn't create file at {}", &req.path))?; - return Ok(Some(FileHandlerHelperReturns::StreamUpload( - req.arg.parse::().context("Invalid max size")?, - file, - ))); - } - "save" => tokio::fs::write(&req.path, &req.arg) - .await - .with_context(|| format!("Couldn't save file {}", &req.path))?, - _ => {} - } - Ok(None) -} - -fn get_file_req(data: &warp::ws::Message) -> anyhow::Result { - let data_str = data - .to_str() - .map_err(|_| anyhow::anyhow!("Couldn't convert received data {:?} to text", data))?; - let req = serde_json::from_str(data_str) - .with_context(|| format!("Couldn't parse JSON from {}", data_str))?; - Ok(req) -} - -pub async fn file_handler(socket: warp::ws::WebSocket) { - let (mut socket_send, mut socket_recv) = socket.split(); - let mut req: shared::FileRequest; - - 'outer: while let Some(Ok(data)) = socket_recv.next().await { - if data.is_close() { - break; - } - - req = handle_error!(get_file_req(&data), continue); - - if CONFIG.pass && !validate_token(&req.token) { - continue; - } - - loop { - tokio::select! { - result = file_handler_helper(&req) => { - match handle_error!(result, continue) { - Some(FileHandlerHelperReturns::String(file)) => { - if socket_send.send(Message::text(file)).await.is_err() { - break 'outer; - } - } - Some(FileHandlerHelperReturns::SizeBuf(size, buf)) => { - if socket_send - .send(crate::json_msg!(&size, continue)) - .await - .is_err() - { - break 'outer; - } - for i in buf.chunks(1000 * 1000) { - if socket_send.feed(Message::binary(i)).await.is_err() { - break 'outer; - } - } - if socket_send.flush().await.is_err() { - break 'outer; - } - } - Some(FileHandlerHelperReturns::StreamUpload(size, mut file)) => { - while let Some(Ok(msg)) = (&mut socket_recv).take(size).next().await { - handle_error!(file.write_all(msg.as_bytes()).await.with_context(|| { - format!("Couldn't write to file {}, stopping upload", &req.path) - }), continue 'outer); - } - } - None => {} - } - break; - }, - recv = socket_recv.next() => match recv { - Some(Ok(req_tmp)) => req = handle_error!(get_file_req(&req_tmp), continue 'outer), - _ => break 'outer, - }, - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use nanoserde::SerJson; - use shared::FileRequest; - use std::io::Write; - use tempfile::NamedTempFile; - use warp::Filter; - - #[tokio::test] - async fn file_socket_test() { - let file_route = warp::ws().map(|ws: warp::ws::Ws| ws.on_upgrade(file_handler)); - - let (mut file, path) = NamedTempFile::new().unwrap().into_parts(); - let path = path.to_str().unwrap().to_string(); - file.write_all(b"dietpi dashboard, hello file!").unwrap(); - let mut socket = warp::test::ws().handshake(file_route).await.unwrap(); - socket - .send_text(SerJson::serialize_json(&FileRequest { - cmd: "open".to_string(), - path: path.clone(), - token: String::new(), - arg: String::new(), - })) - .await; - assert_eq!( - socket.recv().await.unwrap().to_str().unwrap(), - "dietpi dashboard, hello file!" - ); - socket - .send_text(SerJson::serialize_json(&FileRequest { - cmd: "save".to_string(), - path: path.clone(), - token: String::new(), - arg: "dietpi dashboard, writing test".to_string(), - })) - .await; - // Wait for writing to complete - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - - // Yes, .read_to_string() on the file should work, but it doesn't for some reason - let contents = std::fs::read_to_string(path).unwrap(); - assert_eq!(contents, "dietpi dashboard, writing test"); - } -} diff --git a/src/backend/src/config.rs b/src/config.rs similarity index 78% rename from src/backend/src/config.rs rename to src/config.rs index 471dda2d..ce6dc197 100644 --- a/src/backend/src/config.rs +++ b/src/config.rs @@ -3,25 +3,11 @@ use figment::{ providers::{Env, Format, Serialized, Toml}, Figment, }; -use log::LevelFilter; use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize)] -#[serde(remote = "LevelFilter")] -#[serde(rename_all = "lowercase")] -enum LevelFilterDef { - Off, - Error, - Warn, - Info, - Debug, - Trace, -} - #[derive(Deserialize, Serialize)] pub struct Config { - #[serde(with = "LevelFilterDef")] - pub log_level: log::LevelFilter, + pub log_level: String, pub port: u16, @@ -45,9 +31,9 @@ pub struct Config { } impl Default for Config { - fn default() -> Config { - Config { - log_level: LevelFilter::Info, + fn default() -> Self { + Self { + log_level: "info".to_string(), port: 5252, diff --git a/src/frontend/microlight.d.ts b/src/frontend/microlight.d.ts deleted file mode 100644 index 2351682c..00000000 --- a/src/frontend/microlight.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'microlight'; \ No newline at end of file diff --git a/src/frontend/package.json b/src/frontend/package.json deleted file mode 100644 index 07463dcf..00000000 --- a/src/frontend/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "dietpi-dashboard", - "version": "0.6.0", - "type": "module", - "license": "GPL-3.0", - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "check": "svelte-check --tsconfig ./tsconfig.json" - }, - "devDependencies": { - "@fortawesome/free-solid-svg-icons": "^6.1.1", - "@sveltejs/vite-plugin-svelte": "^1.0.0-next.49", - "@tsconfig/svelte": "^3.0.0", - "@types/autosize": "^4.0.1", - "svelte": "^3.48.0", - "svelte-check": "^2.7.2", - "svelte-fa": "^3.0.2", - "svelte-preprocess": "^4.10.7", - "svelte-routing": "^1.6.0", - "tslib": "^2.4.0", - "typescript": "^4.7.4", - "vite": "^2.9.12", - "vite-plugin-replace": "^0.1.1", - "vite-plugin-windicss": "^1.8.4", - "windicss": "^3.5.5" - }, - "dependencies": { - "microlight": "^0.0.7", - "pretty-bytes": "^6.0.0", - "pretty-ms": "^8.0.0", - "semver-compare-multi": "^1.0.3", - "uplot": "^1.6.21", - "xterm": "^4.18.0", - "xterm-addon-attach": "^0.6.0", - "xterm-addon-fit": "^0.5.0" - } -} diff --git a/src/frontend/semver-compare-multi.d.ts b/src/frontend/semver-compare-multi.d.ts deleted file mode 100644 index d5d7fd84..00000000 --- a/src/frontend/semver-compare-multi.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'semver-compare-multi'; \ No newline at end of file diff --git a/src/frontend/src/pages/Service.svelte b/src/frontend/src/pages/Service.svelte deleted file mode 100644 index e084cab6..00000000 --- a/src/frontend/src/pages/Service.svelte +++ /dev/null @@ -1,80 +0,0 @@ - - -
- {#if socketData.services} - - - - - - - - - {#each socketData.services as service} - - - - - - - - {/each} -
NameStatusError LogStart TimeActions
{service.name}{service.status} - {#if service.log != ""} -
- Show log - {@html service.log} -
- {/if}
{service.start} - {#if service.status == "inactive" || service.status == "failed"} - - socketSend("start", [service.name])} - title="Start" - > - {:else} - - socketSend("stop", [service.name])} - title="Stop" - > - socketSend("restart", [service.name])} - title="Restart" - > - {/if}
- {/if} -
diff --git a/src/frontend/src/types.ts b/src/frontend/src/types.ts deleted file mode 100644 index 3d4ed4ab..00000000 --- a/src/frontend/src/types.ts +++ /dev/null @@ -1,90 +0,0 @@ -interface socketData { - // Statistics page - cpu: number; - ram: usage; - swap: usage; - disk: usage; - network: net; - temp: temp; - // Software page - uninstalled: software[]; - installed: software[]; - response: string; - // Process page - processes: processes[]; - // Services page - services: services[]; - // File browser page - contents: browser[]; - textdata: string; - // Management page - hostname: string; - uptime: number; - arch: string; - kernel: string; - dp_version: string; - packages: number; - upgrades: number; - nic: string; - ip: string; - // Global - update: string; - login: boolean; - error: boolean; - nodes: string[]; - version: string; - update_check: boolean; - temp_unit: "fahrenheit" | "celsius"; -} - -interface software { - id: number; - name: string; - description: string; - dependencies: string; - docs: string; -} - -interface processes { - pid: number; - name: string; - cpu: number; - ram: number; - status: string; -} - -interface services { - name: string; - status: string; - log: string; - start: string; -} - -interface browser { - name: string; - path: string; - prettytype: string; - maintype: string; - subtype: string; - size: number; -} - -interface usage { - used: number; - total: number; - percent: number; -} - -interface net { - sent: number; - received: number; -} - -interface temp { - available: boolean; - celsius: number; - fahrenheit: number; -} - -// 'browser' required for selected path in file browser -export type { socketData, browser }; \ No newline at end of file diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock deleted file mode 100644 index 27669a7e..00000000 --- a/src/frontend/yarn.lock +++ /dev/null @@ -1,890 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@antfu/utils@^0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.5.1.tgz#7eb6764878adb715daff20019e5a15fd63d93342" - integrity sha512-8Afo0+xvYe1K8Wm4xHTymfTkpzy36aaqDvhXIayUwl+mecMG9Xzl3XjXa6swG6Bk8FBeQ646RyvmsYt6+2Be9g== - -"@fortawesome/fontawesome-common-types@6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.1.tgz#7dc996042d21fc1ae850e3173b5c67b0549f9105" - integrity sha512-wVn5WJPirFTnzN6tR95abCx+ocH+3IFLXAgyavnf9hUmN0CfWoDjPT/BAWsUVwSlYYVBeCLJxaqi7ZGe4uSjBA== - -"@fortawesome/free-solid-svg-icons@^6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.1.1.tgz#3369e673f8fe8be2fba30b1ec274d47490a830a6" - integrity sha512-0/5exxavOhI/D4Ovm2r3vxNojGZioPwmFrKg0ZUH69Q68uFhFPs6+dhAToh6VEQBntxPRYPuT5Cg1tpNa9JUPg== - dependencies: - "@fortawesome/fontawesome-common-types" "6.1.1" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" - integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.13" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" - integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== - -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" - integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@rollup/pluginutils@^4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" - integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== - dependencies: - estree-walker "^2.0.1" - picomatch "^2.2.2" - -"@sveltejs/vite-plugin-svelte@^1.0.0-next.49": - version "1.0.0-next.49" - resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.49.tgz#44cc00a19c6c23002516b66c5ab90ee66720df57" - integrity sha512-AKh0Ka8EDgidnxWUs8Hh2iZLZovkETkefO99XxZ4sW4WGJ7VFeBx5kH/NIIGlaNHLcrIvK3CK0HkZwC3Cici0A== - dependencies: - "@rollup/pluginutils" "^4.2.1" - debug "^4.3.4" - deepmerge "^4.2.2" - kleur "^4.1.4" - magic-string "^0.26.2" - svelte-hmr "^0.14.12" - -"@tsconfig/svelte@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@tsconfig/svelte/-/svelte-3.0.0.tgz#b06e059209f04c414de0069f2f0e2796d979fc6f" - integrity sha512-pYrtLtOwku/7r1i9AMONsJMVYAtk3hzOfiGNekhtq5tYBGA7unMve8RvUclKLMT3PrihvJqUmzsRGh0RP84hKg== - -"@types/autosize@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/autosize/-/autosize-4.0.1.tgz#999a7c305b96766248044ebaac1a0299961f3b61" - integrity sha512-iPJT/FCaSO79G6j+9n6gmFc5nhxZ1gDrA2UAvb5FslJ6FJQZnDfbBU0qp5vpp0Cbjj7+gOyjuWZ7RrXvRuETaA== - -"@types/node@*": - version "17.0.16" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.16.tgz#e3733f46797b9df9e853ca9f719c8a6f7b84cd26" - integrity sha512-ydLaGVfQOQ6hI1xK2A5nVh8bl0OGoIfYMxPWHqqYe9bTkWCfqiVvZoh2I/QF2sNSkZzZyROBoTefIEI+PB6iIA== - -"@types/pug@^2.0.4": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.6.tgz#f830323c88172e66826d0bde413498b61054b5a6" - integrity sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg== - -"@types/sass@^1.16.0": - version "1.43.1" - resolved "https://registry.yarnpkg.com/@types/sass/-/sass-1.43.1.tgz#86bb0168e9e881d7dade6eba16c9ed6d25dc2f68" - integrity sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g== - dependencies: - "@types/node" "*" - -"@windicss/config@1.8.4": - version "1.8.4" - resolved "https://registry.yarnpkg.com/@windicss/config/-/config-1.8.4.tgz#090c7f48cb86b6cba1bd97742a2f012852026d3c" - integrity sha512-i4fFGFfZoRess6WMkauykHC3PFd9xKYVx7lSuLfMK7sgo6x3+l4dY42GbsWMHyLqH1sTMfyt1LgfXSIKYJozSA== - dependencies: - debug "^4.3.4" - jiti "^1.13.0" - windicss "^3.5.1" - -"@windicss/plugin-utils@1.8.4": - version "1.8.4" - resolved "https://registry.yarnpkg.com/@windicss/plugin-utils/-/plugin-utils-1.8.4.tgz#fba74b6eb29276e24b9d09931bc22808fd7230d7" - integrity sha512-DqJVwAfzlgd8nYSNlmhXOey32pI8UwH7QiOWdFS/AR2O/q9oLDGHDn97Its/kZdfoyhi8ylwZNP2Pk0H7cihhQ== - dependencies: - "@antfu/utils" "^0.5.1" - "@windicss/config" "1.8.4" - debug "^4.3.4" - fast-glob "^3.2.11" - magic-string "^0.26.1" - micromatch "^4.0.5" - windicss "^3.5.1" - -anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -buffer-crc32@^0.2.5: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -chokidar@^3.4.1: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -dedent-js@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dedent-js/-/dedent-js-1.0.1.tgz#bee5fb7c9e727d85dffa24590d10ec1ab1255305" - integrity sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU= - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -detect-indent@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" - integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== - -es6-promise@^3.1.2: - version "3.3.1" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" - integrity sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM= - -esbuild-android-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.29.tgz#c0960c84c9b832bade20831515e89d32549d4769" - integrity sha512-tJuaN33SVZyiHxRaVTo1pwW+rn3qetJX/SRuc/83rrKYtyZG0XfsQ1ao1nEudIt9w37ZSNXR236xEfm2C43sbw== - -esbuild-android-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.29.tgz#8eceb3abe5abde5489d6a5cbe6a7c1044f71115f" - integrity sha512-D74dCv6yYnMTlofVy1JKiLM5JdVSQd60/rQfJSDP9qvRAI0laPXIG/IXY1RG6jobmFMUfL38PbFnCqyI/6fPXg== - -esbuild-darwin-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.29.tgz#26f3f14102310ecb8f2d9351c5b7a47a60d2cc8a" - integrity sha512-+CJaRvfTkzs9t+CjGa0Oa28WoXa7EeLutQhxus+fFcu0MHhsBhlmeWHac3Cc/Sf/xPi1b2ccDFfzGYJCfV0RrA== - -esbuild-darwin-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.29.tgz#6d2d89dfd937992649239711ed5b86e51b13bd89" - integrity sha512-5Wgz/+zK+8X2ZW7vIbwoZ613Vfr4A8HmIs1XdzRmdC1kG0n5EG5fvKk/jUxhNlrYPx1gSY7XadQ3l4xAManPSw== - -esbuild-freebsd-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.29.tgz#2cb41a0765d0040f0838280a213c81bbe62d6394" - integrity sha512-VTfS7Bm9QA12JK1YXF8+WyYOfvD7WMpbArtDj6bGJ5Sy5xp01c/q70Arkn596aGcGj0TvQRplaaCIrfBG1Wdtg== - -esbuild-freebsd-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.29.tgz#e1b79fbb63eaeff324cf05519efa7ff12ce4586a" - integrity sha512-WP5L4ejwLWWvd3Fo2J5mlXvG3zQHaw5N1KxFGnUc4+2ZFZknP0ST63i0IQhpJLgEJwnQpXv2uZlU1iWZjFqEIg== - -esbuild-linux-32@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.29.tgz#a4a5a0b165b15081bc3227986e10dd4943edb7d6" - integrity sha512-4myeOvFmQBWdI2U1dEBe2DCSpaZyjdQtmjUY11Zu2eQg4ynqLb8Y5mNjNU9UN063aVsCYYfbs8jbken/PjyidA== - -esbuild-linux-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.29.tgz#4c450088c84f8bfd22c51d116f59416864b85481" - integrity sha512-iaEuLhssReAKE7HMwxwFJFn7D/EXEs43fFy5CJeA4DGmU6JHh0qVJD2p/UP46DvUXLRKXsXw0i+kv5TdJ1w5pg== - -esbuild-linux-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.29.tgz#d1a23993b26cb1f63f740329b2fc09218e498bd1" - integrity sha512-KYf7s8wDfUy+kjKymW3twyGT14OABjGHRkm9gPJ0z4BuvqljfOOUbq9qT3JYFnZJHOgkr29atT//hcdD0Pi7Mw== - -esbuild-linux-arm@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.29.tgz#a7e2fea558525eab812b1fe27d7a2659cd1bb723" - integrity sha512-OXa9D9QL1hwrAnYYAHt/cXAuSCmoSqYfTW/0CEY0LgJNyTxJKtqc5mlwjAZAvgyjmha0auS/sQ0bXfGf2wAokQ== - -esbuild-linux-mips64le@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.29.tgz#e708c527f0785574e400828cdbed3d9b17b5ddff" - integrity sha512-05jPtWQMsZ1aMGfHOvnR5KrTvigPbU35BtuItSSWLI2sJu5VrM8Pr9Owym4wPvA4153DFcOJ1EPN/2ujcDt54g== - -esbuild-linux-ppc64le@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.29.tgz#0137d1b38beae36a57176ef45e90740e734df502" - integrity sha512-FYhBqn4Ir9xG+f6B5VIQVbRuM4S6qwy29dDNYFPoxLRnwTEKToIYIUESN1qHyUmIbfO0YB4phG2JDV2JDN9Kgw== - -esbuild-linux-riscv64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.29.tgz#a2f73235347a58029dcacf0fb91c9eb8bebc8abb" - integrity sha512-eqZMqPehkb4nZcffnuOpXJQdGURGd6GXQ4ZsDHSWyIUaA+V4FpMBe+5zMPtXRD2N4BtyzVvnBko6K8IWWr36ew== - -esbuild-linux-s390x@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.29.tgz#0f7310ff1daec463ead9b9e26b7aa083a9f9f1ee" - integrity sha512-o7EYajF1rC/4ho7kpSG3gENVx0o2SsHm7cJ5fvewWB/TEczWU7teDgusGSujxCYcMottE3zqa423VTglNTYhjg== - -esbuild-netbsd-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.29.tgz#ba9a0d9cb8aed73b684825126927f75d4fe44ff9" - integrity sha512-/esN6tb6OBSot6+JxgeOZeBk6P8V/WdR3GKBFeFpSqhgw4wx7xWUqPrdx4XNpBVO7X4Ipw9SAqgBrWHlXfddww== - -esbuild-openbsd-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.29.tgz#36dbe2c32d899106791b5f3af73f359213f71b8a" - integrity sha512-jUTdDzhEKrD0pLpjmk0UxwlfNJNg/D50vdwhrVcW/D26Vg0hVbthMfb19PJMatzclbK7cmgk1Nu0eNS+abzoHw== - -esbuild-sunos-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.29.tgz#e5f857c121441ec63bf9b399a2131409a7d344e5" - integrity sha512-EfhQN/XO+TBHTbkxwsxwA7EfiTHFe+MNDfxcf0nj97moCppD9JHPq48MLtOaDcuvrTYOcrMdJVeqmmeQ7doTcg== - -esbuild-windows-32@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.29.tgz#9c2f1ab071a828f3901d1d79d205982a74bdda6e" - integrity sha512-uoyb0YAJ6uWH4PYuYjfGNjvgLlb5t6b3zIaGmpWPOjgpr1Nb3SJtQiK4YCPGhONgfg2v6DcJgSbOteuKXhwqAw== - -esbuild-windows-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.29.tgz#85fbce7c2492521896451b98d649a7db93e52667" - integrity sha512-X9cW/Wl95QjsH8WUyr3NqbmfdU72jCp71cH3pwPvI4CgBM2IeOUDdbt6oIGljPu2bf5eGDIo8K3Y3vvXCCTd8A== - -esbuild-windows-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.29.tgz#0aa7a9a1bc43a63350bcf574d94b639176f065b5" - integrity sha512-+O/PI+68fbUZPpl3eXhqGHTGK7DjLcexNnyJqtLZXOFwoAjaXlS5UBCvVcR3o2va+AqZTj8o6URaz8D2K+yfQQ== - -esbuild@^0.14.27: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.29.tgz#24ad09c0674cbcb4aa2fe761485524eb1f6b1419" - integrity sha512-SQS8cO8xFEqevYlrHt6exIhK853Me4nZ4aMW6ieysInLa0FMAL+AKs87HYNRtR2YWRcEIqoXAHh+Ytt5/66qpg== - optionalDependencies: - esbuild-android-64 "0.14.29" - esbuild-android-arm64 "0.14.29" - esbuild-darwin-64 "0.14.29" - esbuild-darwin-arm64 "0.14.29" - esbuild-freebsd-64 "0.14.29" - esbuild-freebsd-arm64 "0.14.29" - esbuild-linux-32 "0.14.29" - esbuild-linux-64 "0.14.29" - esbuild-linux-arm "0.14.29" - esbuild-linux-arm64 "0.14.29" - esbuild-linux-mips64le "0.14.29" - esbuild-linux-ppc64le "0.14.29" - esbuild-linux-riscv64 "0.14.29" - esbuild-linux-s390x "0.14.29" - esbuild-netbsd-64 "0.14.29" - esbuild-openbsd-64 "0.14.29" - esbuild-sunos-64 "0.14.29" - esbuild-windows-32 "0.14.29" - esbuild-windows-64 "0.14.29" - esbuild-windows-arm64 "0.14.29" - -estree-walker@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" - integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - -fast-glob@^3.2.11, fast-glob@^3.2.7: - version "3.2.11" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" - integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== - dependencies: - reusify "^1.0.4" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.3: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -graceful-fs@^4.1.3: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" - integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== - dependencies: - has "^1.0.3" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -jiti@^1.13.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.13.0.tgz#3cdfc4e651ca0cca4c62ed5e47747b5841d41a8e" - integrity sha512-/n9mNxZj/HDSrincJ6RP+L+yXbpnB8FybySBa+IjIaoH9FIxBbrbRT5XUbe8R7zuVM2AQqNMNDDqz0bzx3znOQ== - -kleur@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d" - integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA== - -kolorist@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.5.1.tgz#c3d66dc4fabde4f6b7faa6efda84c00491f9e52b" - integrity sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ== - -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - -magic-string@^0.25.7: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== - dependencies: - sourcemap-codec "^1.4.4" - -magic-string@^0.26.1, magic-string@^0.26.2: - version "0.26.2" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.2.tgz#5331700e4158cd6befda738bb6b0c7b93c0d4432" - integrity sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A== - dependencies: - sourcemap-codec "^1.4.8" - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -microlight@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/microlight/-/microlight-0.0.7.tgz#f3d7eb460acc11317bb32388b29930d54ba47890" - integrity sha1-89frRgrMETF7syOIspkw1UukeJA= - -micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - -micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -minimatch@^3.0.4: - version "3.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3" - integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -mkdirp@^0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mri@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" - integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -nanoid@^3.3.3: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== - -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-ms@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-3.0.0.tgz#3ea24a934913345fcc3656deda72df921da3a70e" - integrity sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw== - -pascal-case@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -postcss@^8.4.13: - version "8.4.13" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" - integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== - dependencies: - nanoid "^3.3.3" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -pretty-bytes@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.0.0.tgz#928be2ad1f51a2e336add8ba764739f9776a8140" - integrity sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg== - -pretty-ms@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-8.0.0.tgz#a35563b2a02df01e595538f86d7de54ca23194a3" - integrity sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q== - dependencies: - parse-ms "^3.0.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve@^1.22.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" - integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== - dependencies: - is-core-module "^2.8.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^2.5.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rollup@^2.59.0: - version "2.67.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.67.1.tgz#4402665706fa00f321d446ce45f880e02cf54f01" - integrity sha512-1Sbcs4OuW+aD+hhqpIRl+RqooIpF6uQcfzU/QSI7vGkwADY6cM4iLsBGRM2CGLXDTDN5y/yShohFmnKegSPWzg== - optionalDependencies: - fsevents "~2.3.2" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -sade@^1.7.4: - version "1.8.1" - resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" - integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== - dependencies: - mri "^1.1.0" - -sander@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/sander/-/sander-0.5.1.tgz#741e245e231f07cafb6fdf0f133adfa216a502ad" - integrity sha1-dB4kXiMfB8r7b98PEzrfohalAq0= - dependencies: - es6-promise "^3.1.2" - graceful-fs "^4.1.3" - mkdirp "^0.5.1" - rimraf "^2.5.2" - -semver-compare-multi@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/semver-compare-multi/-/semver-compare-multi-1.0.3.tgz#3e1695b4bd8c4adbd01ac1c46fa08466d369dd48" - integrity sha1-PhaVtL2MStvQGsHEb6CEZtNp3Ug= - -sorcery@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.10.0.tgz#8ae90ad7d7cb05fc59f1ab0c637845d5c15a52b7" - integrity sha1-iukK19fLBfxZ8asMY3hF1cFaUrc= - dependencies: - buffer-crc32 "^0.2.5" - minimist "^1.2.0" - sander "^0.5.0" - sourcemap-codec "^1.3.0" - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -sourcemap-codec@^1.3.0, sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svelte-check@^2.7.2: - version "2.7.2" - resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-2.7.2.tgz#0e31e4d3d766ca41f38812cfb1c3257654c93e34" - integrity sha512-TuVX4YtXHbRM8sVuK5Jk+mKWdm3f0d6hvAC6qCTp8yUszGZewpEBCo2V5fRWZCiz+0J4OCiDHOS+DFMxv39rJA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.9" - chokidar "^3.4.1" - fast-glob "^3.2.7" - import-fresh "^3.2.1" - picocolors "^1.0.0" - sade "^1.7.4" - svelte-preprocess "^4.0.0" - typescript "*" - -svelte-fa@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/svelte-fa/-/svelte-fa-3.0.2.tgz#9bd41de42eb647b1c764028650c66f11c0caecca" - integrity sha512-cmoAJIcaLB5QvY1pyhy6biPk0+BOpGRljO56ftopaT6PkVBRp6GHxDcvgxNh6HN1+DJpES7UvTY4mhTDOEN/MQ== - -svelte-hmr@^0.14.12: - version "0.14.12" - resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.12.tgz#a127aec02f1896500b10148b2d4d21ddde39973f" - integrity sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w== - -svelte-preprocess@^4.0.0, svelte-preprocess@^4.10.7: - version "4.10.7" - resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.10.7.tgz#3626de472f51ffe20c9bc71eff5a3da66797c362" - integrity sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw== - dependencies: - "@types/pug" "^2.0.4" - "@types/sass" "^1.16.0" - detect-indent "^6.0.0" - magic-string "^0.25.7" - sorcery "^0.10.0" - strip-indent "^3.0.0" - -svelte-routing@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/svelte-routing/-/svelte-routing-1.6.0.tgz#cdfed6b665d4a851f3d7e532e16bfbb318ce5294" - integrity sha512-+DbrSGttLA6lan7oWFz1MjyGabdn3tPRqn8Osyc471ut2UgCrzM5x1qViNMc2gahOP6fKbKK1aNtZMJEQP2vHQ== - dependencies: - svelte2tsx "^0.1.157" - -svelte2tsx@^0.1.157: - version "0.1.193" - resolved "https://registry.yarnpkg.com/svelte2tsx/-/svelte2tsx-0.1.193.tgz#16fe594898ef455e4f715ac317d219c9c757656b" - integrity sha512-vzy4YQNYDnoqp2iZPnJy7kpPAY6y121L0HKrSBjU/IWW7DQ6T7RMJed2VVHFmVYm0zAGYMDl9urPc6R4DDUyhg== - dependencies: - dedent-js "^1.0.1" - pascal-case "^3.1.1" - -svelte@^3.48.0: - version "3.48.0" - resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.48.0.tgz#f98c866d45e155bad8e1e88f15f9c03cd28753d3" - integrity sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -tslib@^2.0.3, tslib@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - -typescript@*, typescript@^4.7.4: - version "4.7.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" - integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== - -uplot@^1.6.21: - version "1.6.21" - resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.21.tgz#39030f64dccf3c777ce2cc04742d46111ceec7f8" - integrity sha512-Xd7m2GSHyzhz+mP9CzYTalzp9WDlHzTorNY1yDvg6E+wiCwMIIY+MLfNveObyp3qVMH4kP8mOhRX4k4S9YLHRQ== - -vite-plugin-replace@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/vite-plugin-replace/-/vite-plugin-replace-0.1.1.tgz#d9fdd04ac98d4d2552ef9ff3d6c2768e825c8562" - integrity sha512-v+okl3JNt2pf1jDYijw+WPVt6h9FWa/atTi+qnSFBqmKThLTDhlesx0r3bh+oFPmxRJmis5tNx9HtN6lGFoqWg== - -vite-plugin-windicss@^1.8.4: - version "1.8.4" - resolved "https://registry.yarnpkg.com/vite-plugin-windicss/-/vite-plugin-windicss-1.8.4.tgz#98430706691d54d6b9bf43ac0e3c74dd26b10664" - integrity sha512-LSZAO8BZn3x406GRbYX5t5ONXXJVdqiQtN1qrznLA/Dy5/NzZVhfcrL6N1qEYYO7HsCDT4pLAjTzObvDnM9Y8A== - dependencies: - "@windicss/plugin-utils" "1.8.4" - debug "^4.3.4" - kolorist "^1.5.1" - windicss "^3.5.1" - -vite@^2.9.12: - version "2.9.12" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.12.tgz#b1d636b0a8ac636afe9d83e3792d4895509a941b" - integrity sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew== - dependencies: - esbuild "^0.14.27" - postcss "^8.4.13" - resolve "^1.22.0" - rollup "^2.59.0" - optionalDependencies: - fsevents "~2.3.2" - -windicss@^3.5.1, windicss@^3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/windicss/-/windicss-3.5.5.tgz#43d6071c79f18898f15671fdda41e88fa7c6366e" - integrity sha512-Mnbb9DT+74cw3UxcVimbwCw5tKxITNvOBwTFMFdUv9f5KoZensestA4vKUM8tw74QAwyjE5bqH0QQufgFQwj0Q== - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xterm-addon-attach@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.6.0.tgz#220c23addd62ab88c9914e2d4c06f7407e44680e" - integrity sha512-Mo8r3HTjI/EZfczVCwRU6jh438B4WLXxdFO86OB7bx0jGhwh2GdF4ifx/rP+OB+Cb2vmLhhVIZ00/7x3YSP3dg== - -xterm-addon-fit@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596" - integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ== - -xterm@^4.18.0: - version "4.18.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1" - integrity sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ== diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..f998533d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,106 @@ +#![warn( + clippy::pedantic, + clippy::unwrap_used, + rust_2018_idioms, + clippy::nursery +)] +#![allow(clippy::too_many_lines)] +use crate::shared::CONFIG; +use anyhow::Context; +use hyper::service::{make_service_fn, service_fn}; +use std::{net::IpAddr, str::FromStr}; + +mod config; +mod page_handlers; +mod routes; +mod shared; +mod socket_handlers; +mod systemdata; +mod tls; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { + tracing::subscriber::set_global_default( + tracing_subscriber::FmtSubscriber::builder() + .with_max_level( + tracing_subscriber::filter::LevelFilter::from_str(&CONFIG.log_level) + .context("Couldn't parse log level")?, + ) + .with_timer(tracing_subscriber::fmt::time::uptime()) + .finish(), + ) + .context("Couldn't init logger")?; + + let addr = std::net::SocketAddr::from((IpAddr::from([0; 8]), CONFIG.port)); + + let tcp = tokio::net::TcpListener::bind(&addr) + .await + .with_context(|| format!("Couldn't bind to {}", &addr))?; + + let make_svc = make_service_fn(|conn: &tls::TlsOrTcpConnection| { + let remote_addr = conn.remote_addr(); + async move { + Ok::<_, std::convert::Infallible>(service_fn(move |req| async move { + let span = tracing::info_span!("request", %remote_addr); + span.in_scope(|| { + tracing::info!("Request to {}", req.uri().path()); + tracing::debug!( + "using {:?}", + req.headers() + .get(hyper::header::USER_AGENT) + .map_or("unknown", |x| x.to_str().unwrap_or("unknown")) + ); + }); + routes::router(req, span).await + })) + } + }); + + let mut acceptor = if CONFIG.tls { + let tls_cfg = { + let certs = rustls_pemfile::certs(&mut std::io::BufReader::new( + std::fs::File::open(&CONFIG.cert).context("Couldn't open cert file")?, + )) + .context("Couldn't read certs")? + .into_iter() + .map(tokio_rustls::rustls::Certificate) + .collect(); + + let key = match rustls_pemfile::read_one(&mut std::io::BufReader::new( + std::fs::File::open(&CONFIG.key).context("Couldn't open cert file")?, + )) + .context("Couldn't read key")? + .context("No private key")? + { + rustls_pemfile::Item::PKCS8Key(vec) | rustls_pemfile::Item::RSAKey(vec) => { + tokio_rustls::rustls::PrivateKey(vec) + } + _ => anyhow::bail!("No PKCS8 or RSA formatted private key"), + }; + + let mut cfg = tokio_rustls::rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certs, key) + .context("Couldn't build TLS config")?; + cfg.alpn_protocols = vec![b"http/1.1".to_vec()]; + std::sync::Arc::new(cfg) + }; + + tls::HyperTlsOrTcpAcceptor::new(tcp, Some(tokio_rustls::TlsAcceptor::from(tls_cfg))) + } else { + tls::HyperTlsOrTcpAcceptor::new(tcp, None) + }; + + // Ignore result, because it will never be an error + loop { + let _res = hyper::server::Server::builder(&mut acceptor) + .serve(make_svc) + .await + .context("Server error") + .or_else(|e| { + tracing::warn!("{:?}", e); + anyhow::Ok(()) + }); + } +} diff --git a/src/page_handlers.rs b/src/page_handlers.rs new file mode 100644 index 00000000..dc813d56 --- /dev/null +++ b/src/page_handlers.rs @@ -0,0 +1,370 @@ +use anyhow::Context; +use std::time::Duration; +use tokio::process::Command; +use tokio::sync::mpsc::Receiver; +use tokio::time::sleep; +use tracing::instrument; + +use crate::{ + handle_error, + shared::{self, RequestTypes, SocketSend}, + systemdata, +}; + +type RecvChannel = Receiver>; + +#[instrument(level = "debug", skip_all)] +fn main_handler_getter( + cpu_collector: &mut psutil::cpu::CpuPercentCollector, + net_collector: &mut psutil::network::NetIoCountersCollector, + prev_data: &mut shared::NetData, +) -> anyhow::Result { + Ok(shared::SysData { + cpu: systemdata::cpu(cpu_collector)?, + ram: systemdata::ram()?, + swap: systemdata::swap()?, + disk: systemdata::disk()?, + network: systemdata::network(net_collector, prev_data)?, + temp: systemdata::temp(), + }) +} + +// Return true if error was related to websocket, false otherwise +#[instrument(skip_all)] +pub async fn main_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) -> bool { + let mut cpu_collector = handle_error!( + psutil::cpu::CpuPercentCollector::new().context("Couldn't init cpu collector"), + return false + ); + + let mut net_collector = psutil::network::NetIoCountersCollector::default(); + let mut prev_data = net_collector.net_io_counters().map_or_else( + |_err| { + tracing::debug!("Couldn't get original network counter data, starting with u64::MAX"); + shared::NetData { + received: u64::MAX, + sent: u64::MAX, + } + }, + |counters| shared::NetData { + received: counters.bytes_recv(), + sent: counters.bytes_sent(), + }, + ); + + loop { + tokio::select! { + biased; + data = data_recv.recv() => if let Some(Some(_)) = data {} else { return false }, + res = socket_send + .send(shared::BackendData::Statistic(handle_error!(main_handler_getter(&mut cpu_collector, &mut net_collector, &mut prev_data), shared::SysData::default()))) + => { + sleep(Duration::from_secs(1)).await; + if res.is_err() { + tracing::debug!("Socket send failed, returning"); + return true; + } + }, + } + } +} + +#[instrument(level = "debug", skip_all)] +fn process_handler_helper(cmd: &str, arg: Option<&str>) -> anyhow::Result<()> { + if let Some(arg) = arg { + let process = psutil::process::Process::new( + arg.parse::() + .with_context(|| format!("Invalid pid {arg}"))?, + ) + .with_context(|| format!("Couldn't make process from pid {arg}"))?; + tracing::info!("{}ing process {}", cmd.trim_end_matches('e'), process.pid()); + match cmd { + "terminate" => process.terminate(), + "kill" => process.kill(), + "suspend" => process.suspend(), + "resume" => process.resume(), + _ => Ok(()), + } + .with_context(|| format!("Couldn't {cmd} process {}", process.pid()))?; + Ok(()) + } else { + Err(anyhow::anyhow!("No argument")) + } +} + +#[instrument(skip_all)] +pub async fn process_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) -> bool { + loop { + tokio::select! { + biased; + data = data_recv.recv() => match data { + Some(Some(RequestTypes::Cmd { cmd, args: Some(args) })) => handle_error!(process_handler_helper(&cmd, args.get(0).map(String::as_str))), + Some(Some(_)) => {} + _ => return false, + }, + res = socket_send + .send(shared::BackendData::Process(shared::ProcessList { + processes: handle_error!(systemdata::processes().await, Vec::new()), + } + )) => { + sleep(Duration::from_secs(1)).await; + if res.is_err() { + tracing::debug!("Socket send failed, returning"); + return true; + } + }, + } + } +} + +#[instrument(level = "debug", skip_all)] +pub async fn software_handler_helper( + cmd: &str, + args: &[String], +) -> anyhow::Result { + // We don't just want to run dietpi-software without args + anyhow::ensure!(!args.is_empty(), "Empty DietPi-Software args"); + + let mut software_cmd = tokio::process::Command::new("/boot/dietpi/dietpi-software"); + let mut arg_list = vec![cmd]; + for element in args { + arg_list.push(element.as_str()); + } + tracing::info!("{}ing software with ID(s) {:?}", cmd, args); + let out = shared::remove_color_codes( + std::str::from_utf8( + &software_cmd + .args(arg_list) + .output() + .await + .context("Couldn't get DietPi-Software output")? + .stdout, + ) + .context("Invalid DietPi-Software output")?, + ); + + let software = systemdata::dpsoftware().await?; + Ok(shared::DPSoftwareList { + uninstalled: software.0, + installed: software.1, + response: out, + }) +} + +#[instrument(skip_all)] +pub async fn software_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) -> bool { + let software = handle_error!(systemdata::dpsoftware().await, (Vec::new(), Vec::new())); + if socket_send + .send(shared::BackendData::Software(shared::DPSoftwareList { + uninstalled: software.0, + installed: software.1, + response: String::new(), + })) + .await + .is_err() + { + tracing::debug!("Socket send failed, returning"); + return true; + } + while let Some(Some(data)) = data_recv.recv().await { + if let RequestTypes::Cmd { + cmd, + args: Some(args), + } = data + { + if socket_send + .send(shared::BackendData::Software(handle_error!( + software_handler_helper(&cmd, &args).await, + shared::DPSoftwareList::default() + ))) + .await + .is_err() + { + tracing::debug!("Socket send failed, returning"); + return true; + } + } + } + false +} + +#[instrument(skip_all)] +pub async fn management_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) -> bool { + if socket_send + .send(shared::BackendData::Management(handle_error!( + systemdata::host().await, + shared::HostData::default() + ))) + .await + .is_err() + { + tracing::debug!("Socket send failed, returning"); + return true; + } + while let Some(Some(data)) = data_recv.recv().await { + if let RequestTypes::Cmd { cmd, args: _ } = data { + tracing::info!("Running command {}", &cmd); + // Don't care about the Ok value, so remove it to make the type checker happy + handle_error!(Command::new(&cmd) + .spawn() + .map(|_| ()) + .with_context(|| format!("Couldn't spawn command {}", &cmd))); + } + } + false +} + +#[instrument(skip_all)] +pub async fn service_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) -> bool { + if socket_send + .send(shared::BackendData::Service(shared::ServiceList { + services: handle_error!(systemdata::services().await, Vec::new()), + })) + .await + .is_err() + { + tracing::debug!("Socket send failed, returning"); + return true; + } + while let Some(Some(data)) = data_recv.recv().await { + if let RequestTypes::Cmd { + cmd, + args: Some(args), + } = data + { + if let Some(arg) = args.get(0) { + handle_error!(Command::new("systemctl") + .args([&cmd, arg]) + .spawn() + .map(|_| ()) // Don't care about the Ok value, so remove it to make the type checker happy + .with_context(|| format!("Couldn't {} service {arg}", &cmd))); + if socket_send + .send(shared::BackendData::Service(shared::ServiceList { + services: handle_error!(systemdata::services().await, Vec::new()), + })) + .await + .is_err() + { + tracing::debug!("Socket send failed, returning"); + return true; + } + } + } + } + false +} + +async fn browser_refresh(path: &std::path::Path) -> anyhow::Result { + let dir_path = path + .parent() + .with_context(|| format!("Couldn't get parent of path {}", path.display()))?; + + Ok(shared::BrowserList { + contents: systemdata::browser_dir(std::path::Path::new(dir_path)).await?, + }) +} + +#[instrument(level = "debug", skip_all)] +async fn browser_handler_helper(cmd: &str, args: &[String]) -> anyhow::Result { + use tokio::fs; + + tracing::debug!("Command is {}", cmd); + + if let Some(arg) = args.get(0) { + match cmd { + "cd" => { + return Ok(shared::BrowserList { + contents: systemdata::browser_dir(std::path::Path::new(arg)).await?, + }); + } + "copy" => { + let mut num = 2; + while std::path::Path::new(&format!("{arg} {num}")).exists() { + num += 1; + } + fs::copy(arg, format!("{arg} {num}")) + .await + .with_context(|| format!("Couldn't copy file {arg} to {arg} {num}"))?; + } + "rm" => { + fs::remove_file(arg) + .await + .with_context(|| format!("Couldn't delete file at {arg}"))?; + } + "rmdir" => { + fs::remove_dir_all(arg) + .await + .with_context(|| format!("Couldn't delete directory at {arg}"))?; + } + "mkdir" => { + fs::create_dir(arg) + .await + .with_context(|| format!("Couldn't create directory at {arg}"))?; + } + "mkfile" => { + fs::write(arg, "") + .await + .with_context(|| format!("Couldn't create file at {arg}"))?; + } + "rename" => { + if let Some(arg1) = args.get(1) { + fs::rename(arg, arg1) + .await + .with_context(|| format!("Couldn't rename file {arg} to {arg1}"))?; + } else { + return Err(anyhow::anyhow!("No second argument")); + } + } + _ => tracing::debug!("Got command {}, not handling", cmd), + } + browser_refresh(std::path::Path::new(arg)).await + } else { + Err(anyhow::anyhow!("No argument")) + } +} + +#[instrument(skip_all)] +pub async fn browser_handler(socket_send: &mut SocketSend, data_recv: &mut RecvChannel) -> bool { + // Get initial listing of $HOME + if socket_send + .send(shared::BackendData::Browser(shared::BrowserList { + contents: handle_error!( + systemdata::browser_dir(std::path::Path::new( + &std::env::var_os("HOME").unwrap_or_else(|| "/root".into()), + )) + .await, + Vec::new() + ), + })) + .await + .is_err() + { + tracing::debug!("Socket send failed, returning"); + return true; + } + + 'outer: while let Some(Some(mut data)) = data_recv.recv().await { + loop { + if let RequestTypes::Cmd { + cmd, + args: Some(args), + } = &data + { + tokio::select! { + res = browser_handler_helper(cmd, args) => { + if socket_send.send(shared::BackendData::Browser(handle_error!(res, shared::BrowserList::default()))).await.is_err() { + tracing::debug!("Socket send failed, returning"); + return true; + } + break; + }, + recv = data_recv.recv() => match recv { + Some(Some(data_tmp)) => data = data_tmp, + _ => break 'outer, + }, + } + } + } + } + false +} diff --git a/src/routes.rs b/src/routes.rs new file mode 100644 index 00000000..e5acd5cf --- /dev/null +++ b/src/routes.rs @@ -0,0 +1,361 @@ +use crate::handle_error; +use crate::shared::CONFIG; +use anyhow::Context; +use futures::Future; +use hyper::http::{header, HeaderValue}; +use hyper::{Body, Method, Request, Response, StatusCode}; +use ring::digest; +use tracing::Instrument; + +#[cfg(feature = "dev")] +vite_embed::generate_vite_html_dev!("$CARGO_MANIFEST_DIR/frontend/index.html", "src/main.ts"); + +#[cfg(feature = "dev")] +async fn frontend_proxy(path: &str) -> anyhow::Result> { + let path = path.to_string(); + let vite_resp = tokio::task::spawn_blocking(move || (vite_embed::vite_proxy_dev(&path), path)) + .await + .expect("Failed to spawn blocking call to frontend"); + + match vite_resp { + (Ok(body), _) => Ok(Response::new(body.into())), + (Err(vite_embed::RequestError::Status(code, _)), _) => { + Ok(Response::builder().status(code).body(Body::empty())?) + } + (_, path) => Err(anyhow::anyhow!( + "Failed to connect to dev server while getting {}", + path + )), + } +} + +#[cfg(all(feature = "frontend", not(feature = "dev")))] +vite_embed::generate_vite_prod!( + "$CARGO_MANIFEST_DIR/frontend/dist/manifest.json", + "src/main.ts", + "$CARGO_MANIFEST_DIR/frontend/index.html" +); + +#[cfg(all(feature = "frontend", not(feature = "dev")))] +fn main_route() -> Response { + // index.html is guaranteed to exist, compilation would fail if it didn't + #[allow(clippy::unwrap_used)] + let mut reply = Response::new(vite_prod("/index.html").unwrap().0.into()); + let headers = reply.headers_mut(); + + headers.insert( + header::X_CONTENT_TYPE_OPTIONS, + header::HeaderValue::from_static("nosniff"), + ); + headers.insert( + header::X_FRAME_OPTIONS, + header::HeaderValue::from_static("sameorigin"), + ); + headers.insert("X-Robots-Tag", header::HeaderValue::from_static("none")); + headers.insert( + "X-Permitted-Cross-Domain-Policies", + header::HeaderValue::from_static("none"), + ); + headers.insert( + header::REFERRER_POLICY, + header::HeaderValue::from_static("no-referrer"), + ); + headers.insert("Content-Security-Policy", header::HeaderValue::from_static("default-src 'self'; style-src 'unsafe-inline' 'self'; connect-src * ws:; object-src 'none';")); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html"), + ); + headers.insert( + header::CONTENT_ENCODING, + header::HeaderValue::from_static("gzip"), + ); + + reply +} + +#[cfg(all(feature = "frontend", not(feature = "dev")))] +fn asset_route(path: &str) -> Response { + let mut reply = Response::new(Body::empty()); + let Some((data, compressed)) = vite_prod(path) else { + *reply.status_mut() = StatusCode::NOT_FOUND; + *reply.body_mut() = "Asset not found".into(); + return reply; + }; + + if compressed { + reply.headers_mut().insert( + header::CONTENT_ENCODING, + header::HeaderValue::from_static("gzip"), + ); + } + + let mime_type = match path.rsplit_once('.').unwrap_or(("plain", "plain")).1 { + "js" => HeaderValue::from_static("text/javascript"), + "svg" => HeaderValue::from_static("image/svg+xml"), + "png" => HeaderValue::from_static("image/png"), + "css" => HeaderValue::from_static("text/css"), + // There should be no other file types + _ => unreachable!(), + }; + + reply.headers_mut().insert(header::CONTENT_TYPE, mime_type); + + *reply.body_mut() = data.into(); + + reply +} + +#[tracing::instrument(skip_all)] +pub async fn login_route(mut req: Request) -> anyhow::Result> { + let token: String; + let mut response = Response::new(Body::empty()); + response.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + HeaderValue::from_static("*"), + ); + if CONFIG.pass { + let shasum = hex::encode( + digest::digest( + &digest::SHA512, + &hyper::body::to_bytes(req.body_mut()).await?, + ) + .as_ref(), + ); + if shasum == CONFIG.hash { + let timestamp = jsonwebtoken::get_current_timestamp(); + + let fingerprint = match crate::shared::get_fingerprint(&req) { + Ok(Some(cookie)) => cookie, + Err(err) => { + tracing::warn!("{:#}", err); + *response.status_mut() = StatusCode::BAD_REQUEST; + *response.body_mut() = "Invalid fingerprint token".into(); + return Ok(response); + } + Ok(None) => { + let mut buf = [0u8; 32].to_vec(); + handle_error!( + getrandom::getrandom(&mut buf) + .context("Couldn't generate random fingerprint token"), + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body("Couldn't generate random fingerprint token".into())?) + ); + response.headers_mut().insert( + hyper::header::SET_COOKIE, + hyper::header::HeaderValue::from_str(&format!( + "FINGERPRINT={}; Path=/; HttpOnly", + hex::encode(&buf) + )) + .context("Couldn't set fingerprint token")?, + ); + hex::encode(digest::digest(&digest::SHA256, &buf).as_ref()) + } + }; + + let claims = crate::shared::JWTClaims { + iss: "DietPi Dashboard".to_string(), + iat: timestamp, + exp: timestamp + CONFIG.expiry, + fingerprint, + }; + + token = handle_error!( + jsonwebtoken::encode( + &jsonwebtoken::Header::default(), + &claims, + &jsonwebtoken::EncodingKey::from_secret(CONFIG.secret.as_ref()), + ) + .context("Error creating login token"), + return Ok({ + *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + *response.body_mut() = "Couldn't create login token".into(); + response + }) + ); + + *response.body_mut() = token.into(); + + return Ok(response); + } + *response.status_mut() = StatusCode::UNAUTHORIZED; + *response.body_mut() = "Invalid password".into(); + return Ok(response); + } + *response.status_mut() = StatusCode::NO_CONTENT; + *response.body_mut() = "No login needed".into(); + Ok(response) +} + +pub fn websocket( + mut req: Request, + func: F, + span: tracing::Span, + token: String, +) -> anyhow::Result> +where + O: Future + std::marker::Send, + F: Fn( + tokio_tungstenite::WebSocketStream, + Option, + String, + ) -> O + + std::marker::Send + + std::marker::Sync + + 'static, +{ + let key = req + .headers() + .get(header::SEC_WEBSOCKET_KEY) + .context("Failed to read key from headers")?; + + let Ok(cookie) = crate::shared::get_fingerprint(&req) else { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::empty())?); + }; + + if !(req + .headers() + .get(header::CONNECTION) + .and_then(|x| x.to_str().ok()) + .map_or(false, |x| x.contains("Upgrade")) + && req + .headers() + .get(header::UPGRADE) + .and_then(|x| x.to_str().ok()) + .map_or(false, |x| x.contains("websocket")) + && req.headers().get(header::SEC_WEBSOCKET_VERSION) + == Some(&HeaderValue::from_static("13"))) + { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::empty())?); + } + + let resp = Response::builder() + .status(StatusCode::SWITCHING_PROTOCOLS) + .header(header::CONNECTION, "upgrade") + .header(header::UPGRADE, "websocket") + .header( + "Sec-WebSocket-Accept", + &tokio_tungstenite::tungstenite::handshake::derive_accept_key(key.as_bytes()), + ) + .body(Body::from("switching to websocket protocol")); + + tokio::spawn(async move { + match hyper::upgrade::on(&mut req).await { + Ok(upgraded) => { + let ws = tokio_tungstenite::WebSocketStream::from_raw_socket( + upgraded, + tokio_tungstenite::tungstenite::protocol::Role::Server, + None, + ) + .await; + func(ws, cookie, token).instrument(span).await; + } + Err(e) => eprintln!("upgrade error: {e}"), + } + }); + Ok(resp?) +} + +pub async fn router(req: Request, span: tracing::Span) -> anyhow::Result> { + let mut response = Response::new(Body::empty()); + + match ( + req.method(), + req.uri().path(), + // Make a String to avoid lifetime errors + req.uri().query().map(str::to_string), + ) { + #[cfg(all(feature = "frontend", not(feature = "dev")))] + (&Method::GET, "/favicon.png", _) => { + let _guard = span.enter(); + response = asset_route("/favicon.png"); + } + #[cfg(all(feature = "frontend", not(feature = "dev")))] + (&Method::GET, path, _) if path.starts_with("/assets") => { + let _guard = span.enter(); + response = asset_route(req.uri().path()); + } + (&Method::GET, "/ws", _) => { + response = websocket( + req, + crate::socket_handlers::socket_handler, + span, + String::new(), + )?; + } + (&Method::GET, "/ws/term", Some(token)) if crate::CONFIG.pass => { + let token = crate::shared::get_token_from_list(&token, ['=', ';'], "token"); + if let Some(token) = token { + response = websocket( + req, + crate::socket_handlers::term_handler, + span, + token.to_string(), + )?; + } else { + *response.status_mut() = StatusCode::UNAUTHORIZED; + *response.body_mut() = "No token".into(); + return Ok(response); + } + } + (&Method::GET, "/ws/file", Some(token)) if crate::CONFIG.pass => { + let token = crate::shared::get_token_from_list(&token, ['=', ';'], "token"); + if let Some(token) = token { + response = websocket( + req, + crate::socket_handlers::file_handler, + span, + token.to_string(), + )?; + } else { + *response.status_mut() = StatusCode::UNAUTHORIZED; + *response.body_mut() = "No token".into(); + return Ok(response); + } + } + (&Method::GET, "/ws/term", _) if !crate::CONFIG.pass => { + response = websocket( + req, + crate::socket_handlers::term_handler, + span, + String::new(), + )?; + } + (&Method::GET, "/ws/file", _) if !crate::CONFIG.pass => { + response = websocket( + req, + crate::socket_handlers::file_handler, + span, + String::new(), + )?; + } + (&Method::POST, "/login", _) => { + response = login_route(req).instrument(span).await?; + } + #[cfg(feature = "dev")] + (&Method::GET, "/", _) => { + let _guard = span.enter(); + response = Response::new(vite_html_dev().into()); + } + #[cfg(feature = "dev")] + (&Method::GET | &Method::POST, _, _) => { + let _guard = span.enter(); + response = frontend_proxy(req.uri().path()).await?; + } + #[cfg(all(feature = "frontend", not(feature = "dev")))] + (&Method::GET, _, _) => { + let _guard = span.enter(); + response = main_route(); + } + _ => { + *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED; + *response.body_mut() = "Method not allowed".into(); + } + } + + Ok(response) +} diff --git a/src/backend/src/shared.rs b/src/shared.rs similarity index 55% rename from src/backend/src/shared.rs rename to src/shared.rs index 2c39ca7c..7d71b881 100644 --- a/src/backend/src/shared.rs +++ b/src/shared.rs @@ -1,3 +1,5 @@ +use anyhow::Context; +use futures::SinkExt; use serde::{Deserialize, Serialize}; pub static CONFIG: once_cell::sync::Lazy = @@ -10,18 +12,87 @@ macro_rules! handle_error { match $e { Ok(val) => val, Err(err) => { - log::warn!("{:#}", err); + tracing::warn!("{:#}", err); $($handler)? } } }; } -#[macro_export] -macro_rules! json_msg { - ($e: expr, $handler:expr) => { - Message::text(handle_error!(serde_json::to_string($e).context("Couldn't serialize json"), $handler)) +pub fn remove_color_codes(s: &str) -> String { + s.replace('\u{1b}', "") + .replace("[33m", "") + .replace("[90m", "") + .replace("[0m", "") + .replace("[32m", "") + .replace("[31m", "") + .replace("[38;5;154m", "") + .replace("[J", "") +} + +pub fn get_token_from_list<'a>( + token_list: &'a str, + pat: [char; 2], + token_name: &str, +) -> Option<&'a str> { + let mut iter = token_list.split(pat).map(str::trim); + if !iter.any(|x| x == token_name) { + return None; + } + if let Some(next) = iter.next() { + return Some(next); + } + None +} + +pub fn get_fingerprint(req: &hyper::Request) -> anyhow::Result> { + let cookie = if let Some(cookie) = req.headers().get(hyper::header::COOKIE) { + cookie.to_str().context("Invalid cookie list")? + } else { + return Ok(None); }; + let fingerprint_option = get_token_from_list(cookie, ['=', ';'], "FINGERPRINT"); + Ok(Some(hex::encode(ring::digest::digest( + &ring::digest::SHA256, + &hex::decode(if let Some(fingerprint) = fingerprint_option { + fingerprint + } else { + return Ok(None); + }) + .context("Invalid fingerprint token")?, + )))) +} + +pub struct SocketSend( + pub futures::stream::SplitSink< + tokio_tungstenite::WebSocketStream, + tokio_tungstenite::tungstenite::Message, + >, +); + +impl SocketSend { + pub async fn send(&mut self, value: BackendData) -> anyhow::Result<()> { + Ok(self + .0 + .send(tokio_tungstenite::tungstenite::Message::Text( + serde_json::to_string(&value).context("Couldn't serialize JSON")?, + )) + .await?) + } +} + +#[derive(Serialize)] +#[serde(rename_all = "UPPERCASE")] +#[serde(tag = "dataKind")] +pub enum BackendData { + Statistic(SysData), + Process(ProcessList), + Software(DPSoftwareList), + Management(HostData), + Service(ServiceList), + Global(GlobalData), + Browser(BrowserList), + Reauth { reauth: bool }, } #[derive(Serialize, Default)] @@ -41,22 +112,25 @@ pub struct UsageData { pub percent: f32, } -#[derive(Serialize, Default)] +#[derive(Serialize, Default, Debug)] pub struct NetData { pub sent: u64, pub received: u64, } -#[derive(Debug, Clone, Deserialize)] -pub struct Request { - #[serde(default)] - pub page: String, - #[serde(default)] - pub cmd: String, - #[serde(default)] - pub args: Vec, - #[serde(default)] - pub token: String, +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub enum RequestTypes { + Page { + page: String, + }, + Cmd { + cmd: String, + args: Option>, + }, + Token { + token: String, + }, } #[derive(Serialize)] @@ -130,8 +204,8 @@ pub struct GlobalData { pub struct BrowserData { pub path: String, pub name: String, - pub subtype: &'static str, - pub maintype: &'static str, + pub subtype: String, + pub maintype: String, pub prettytype: String, pub size: u64, } @@ -146,15 +220,13 @@ pub struct TokenError { pub error: bool, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct FileRequest { #[serde(default)] pub cmd: String, #[serde(default)] pub path: String, #[serde(default)] - pub token: String, - #[serde(default)] pub arg: String, } @@ -173,6 +245,7 @@ pub struct JWTClaims { pub iss: String, pub exp: u64, pub iat: u64, + pub fingerprint: String, } #[derive(Serialize, Default)] diff --git a/src/socket_handlers.rs b/src/socket_handlers.rs new file mode 100644 index 00000000..290f2402 --- /dev/null +++ b/src/socket_handlers.rs @@ -0,0 +1,490 @@ +use anyhow::Context; +use futures::{SinkExt, StreamExt}; +use std::io::Write; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::sync::mpsc; +use tokio_tungstenite::tungstenite::Message; +use tracing::{instrument, Instrument}; + +use crate::{handle_error, page_handlers, shared, systemdata, CONFIG}; + +enum TokenState { + InvalidToken, + ValidToken, + NoFingerprint, +} + +impl TokenState { + const fn as_bool(&self) -> bool { + if matches!(self, Self::ValidToken) { + return true; + } + false + } +} + +#[instrument(level = "debug", skip_all)] +fn validate_token(token: &str, fingerprint: Option<&str>) -> TokenState { + let mut validator = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256); + validator.set_issuer(&["DietPi Dashboard"]); + validator.set_required_spec_claims(&["exp", "iat"]); + if let Ok(claims) = jsonwebtoken::decode::( + token, + &jsonwebtoken::DecodingKey::from_secret(CONFIG.secret.as_bytes()), + &validator, + ) { + if let Some(fingerprint) = fingerprint { + if claims.claims.fingerprint != fingerprint { + return TokenState::InvalidToken; + } + } else { + return TokenState::NoFingerprint; + } + } else { + tracing::debug!("Invalid token"); + return TokenState::InvalidToken; + } + TokenState::ValidToken +} + +#[instrument(skip_all)] +pub async fn socket_handler( + socket: tokio_tungstenite::WebSocketStream, + fingerprint: Option, + _token: String, +) { + let (socket_send, mut socket_recv) = socket.split(); + let (data_send, mut data_recv) = mpsc::channel(1); + tokio::task::spawn(async move { + let mut first_message = true; + let mut req: shared::RequestTypes; + let mut token = String::new(); + while let Some(Ok(data)) = socket_recv.next().await { + if data.is_close() { + break; + } + let Message::Text(data_str) = data else { + continue; + }; + req = handle_error!( + serde_json::from_str::(&data_str) + .with_context(|| format!("Couldn't parse JSON {data_str}")), + continue + ); + // Don't print token + if let shared::RequestTypes::Token { .. } = req { + tracing::debug!("Got token message"); + } else { + tracing::debug!("Got request {:?}", req); + } + if CONFIG.pass { + if let shared::RequestTypes::Token { token: req_token } = req { + token = req_token; + continue; + } + } + if CONFIG.pass { + let validation = validate_token(&token, fingerprint.as_deref()); + if !validation.as_bool() { + if !first_message { + tracing::debug!("Requesting login"); + if let Err(err) = data_send.send(None).await { + tracing::error!("Internal error: couldn't initiate login: {}", err); + break; + } + } + handle_error!(data_send + .send(Some(shared::RequestTypes::Page { + page: "/login".to_string() + })) + .await + .context("Internal error: couldn't send login request")); + } + match validation { + TokenState::InvalidToken => continue, + TokenState::NoFingerprint => return, + TokenState::ValidToken => {} + } + } + if let shared::RequestTypes::Page { .. } = req { + // Quit out of handler + if first_message { + tracing::debug!("First message, not sending quit"); + first_message = false; + } else if let Err(err) = data_send.send(None).await { + tracing::error!("Internal error: couldn't change page: {}", err); + break; + } + } + // Send new page/data + if let Err(err) = data_send.send(Some(req)).await { + // Manual error handling here, to use tracing::error + tracing::error!("Internal error: couldn't send request: {}", err); + break; + } + } + }); + let mut socket_send = shared::SocketSend(socket_send); + // Send global message (shown on all pages) + if socket_send + .send(shared::BackendData::Global(systemdata::global().await)) + .await + .is_err() + { + return; + } + while let Some(Some(message)) = data_recv.recv().await { + if let shared::RequestTypes::Page { page } = message { + if match page.as_str() { + "/" => page_handlers::main_handler(&mut socket_send, &mut data_recv).await, + "/process" => { + page_handlers::process_handler(&mut socket_send, &mut data_recv).await + } + "/software" => { + page_handlers::software_handler(&mut socket_send, &mut data_recv).await + } + "/management" => { + page_handlers::management_handler(&mut socket_send, &mut data_recv).await + } + "/service" => { + page_handlers::service_handler(&mut socket_send, &mut data_recv).await + } + "/browser" => { + page_handlers::browser_handler(&mut socket_send, &mut data_recv).await + } + "/login" => { + tracing::debug!("Sending login message"); + // Internal poll, see other thread + if socket_send + .send(shared::BackendData::Reauth { reauth: true }) + .await + .is_err() + { + break; + } + false + } + _ => { + tracing::debug!("Got page {}, not handling", page); + false + } + } { + break; + } + } + } +} + +#[derive(serde::Deserialize, Debug)] +struct TTYSize { + cols: u16, + rows: u16, +} + +#[instrument(skip_all)] +pub async fn term_handler( + socket: tokio_tungstenite::WebSocketStream, + fingerprint: Option, + token: String, +) { + let (mut socket_send, mut socket_recv) = socket.split(); + + if crate::CONFIG.pass && !validate_token(&token, fingerprint.as_deref()).as_bool() { + return; + } + + let pty = handle_error!( + pty_process::Pty::new().context("Couldn't spawn pty"), + return + ); + + let mut cmd = pty_process::Command::new("/bin/login"); + cmd.env("TERM", "xterm"); + + if crate::CONFIG.terminal_user != "manual" { + cmd.args(["-f", &crate::CONFIG.terminal_user]); + } + + let pts = handle_error!(pty.pts().context("Couldn't spawn pts"), return); + + let mut child = handle_error!( + cmd.spawn(&pts).context("Couldn't spawn command onto pts"), + return + ); + + let (mut pty_read, mut pty_write) = pty.into_split(); + + tokio::join!( + async { + loop { + let mut data = [0; 256]; + let read_res = pty_read.read(&mut data).await; + if let Ok(num_read) = read_res { + if socket_send + .send(Message::binary(&data[..num_read])) + .await + .is_err() + { + tracing::debug!("Socket closed, breaking"); + break; + } + } else { + tracing::debug!("Terminal closed, breaking"); + break; + } + } + } + .instrument(tracing::debug_span!("term_reader")), + async { + loop { + if let Some(Ok(data)) = socket_recv.next().await { + match data { + Message::Text(data_str) => { + if data_str.get(..4) == Some("size") { + let json: TTYSize = handle_error!( + serde_json::from_str(&data_str[4..]).with_context(|| format!( + "Couldn't deserialize pty size from {}", + &data_str + )), + continue + ); + tracing::debug!("Got size message {:?}", json); + handle_error!(pty_write + .resize(pty_process::Size::new(json.rows, json.cols)) + .context("Couldn't resize pty")); + } else if pty_write.write_all(data_str.as_bytes()).await.is_err() { + tracing::debug!("Terminal closed, breaking"); + break; + } + } + Message::Binary(data_bin) => { + if pty_write.write_all(&data_bin).await.is_err() { + tracing::debug!("Terminal closed, breaking"); + break; + } + } + _ => {} + } + } else { + tracing::debug!("Exiting terminal"); + // Stop bash by writing "exit", since it won't respond to a SIGTERM + let _write = pty_write.write_all(b"exit\n"); + break; + } + } + } + .instrument(tracing::debug_span!("term_writer")) + ); + + // Reap PID, unwrap is safe because all references will have been dropped + handle_error!( + child.wait().await.context("Couldn't close terminal"), + return + ); + + tracing::info!("Closed terminal"); +} + +#[instrument(level = "debug", skip_all)] +async fn create_zip_file(req: &shared::FileRequest) -> anyhow::Result> { + let src_path = tokio::fs::canonicalize(&req.path) + .await + .with_context(|| format!("Invalid source path {}", &req.path))?; + if src_path.is_dir() { + tracing::debug!("Source path is directory, recursively walking through"); + let mut zip_file = zip::ZipWriter::new(std::io::Cursor::new(Vec::new())); + for entry in walkdir::WalkDir::new(&src_path) { + let entry = entry.context("Couldn't get data for recursive entry")?; + let path = entry.path(); + let name = + std::path::Path::new(&src_path.file_name().context("Couldn't get file name")?) + .join( + // Here too, because the path should always be a child + path.strip_prefix(&src_path) + .context("Path isn't a child of source")?, + ); + let name = name.to_string_lossy().to_string(); + if path.is_file() { + tracing::debug!("Adding file {} to zip", &name); + let mut file_buf = Vec::new(); + zip_file + .start_file(&name, zip::write::FileOptions::default()) + .with_context(|| format!("Couldn't add file {} to zip", &name))?; + let mut file = handle_error!( + tokio::fs::File::open(path) + .await + .with_context(|| format!("Couldn't open file {}, skipping", &name)), + continue + ); + handle_error!( + file.read_to_end(&mut file_buf) + .await + .with_context(|| format!("Couldn't read file {}, skipping", &name)), + continue + ); + let tup = tokio::task::spawn_blocking( + move || -> (zip::ZipWriter>>, Vec, anyhow::Result<()>) { + if let Err(err) = zip_file.write_all(&file_buf) { + return (zip_file, file_buf, Err(err.into())); + } + (zip_file, file_buf, Ok(())) + }, + ) + .await + .context("Couldn't spawn zip task")?; + zip_file = tup.0; + file_buf = tup.1; + handle_error!(tup + .2 + .with_context(|| format!("Couldn't write file {name} into zip, skipping"))); + file_buf.clear(); + } else if !name.is_empty() { + tracing::debug!("Adding directory {} to zip", &name); + zip_file + .add_directory(&name, zip::write::FileOptions::default()) + .with_context(|| format!("Couldn't add directory {name} to zip"))?; + } + } + return Ok(zip_file + .finish() + .context("Couldn't finish writing to zip file")? + .into_inner()); + } + tracing::debug!("Source path is file, returning file data"); + tokio::fs::read(&src_path) + .await + .with_context(|| format!("Couldn't read file {}", src_path.display())) +} + +// Not the most elegant solution, but it works +enum FileHandlerHelperReturns { + String(String), + SizeBuf(shared::FileSize, Vec), + StreamUpload(usize, tokio::fs::File), +} + +#[instrument(level = "debug", skip_all)] +async fn file_handler_helper( + req: &shared::FileRequest, +) -> anyhow::Result> { + tracing::debug!("Command is {}", &req.cmd); + match req.cmd.as_str() { + "open" => { + return Ok(Some(FileHandlerHelperReturns::String( + tokio::fs::read_to_string(&req.path) + .await + .with_context(|| format!("Couldn't read file {}", &req.path))?, + ))) + } + // Technically works for both files and directories + "dl" => { + let buf = create_zip_file(req).await?; + #[allow( + clippy::cast_lossless, + clippy::cast_sign_loss, + clippy::cast_precision_loss, + clippy::cast_possible_truncation + )] + let size = (buf.len() as f64 / f64::from(1000 * 1000)).ceil() as usize; + return Ok(Some(FileHandlerHelperReturns::SizeBuf( + shared::FileSize { size }, + buf, + ))); + } + "up" => { + let file = tokio::fs::File::create(&req.path) + .await + .with_context(|| format!("Couldn't create file at {}", &req.path))?; + return Ok(Some(FileHandlerHelperReturns::StreamUpload( + req.arg.parse::().context("Invalid max size")?, + file, + ))); + } + "save" => tokio::fs::write(&req.path, &req.arg) + .await + .with_context(|| format!("Couldn't save file {}", &req.path))?, + _ => tracing::debug!("Got command {}, not handling", &req.cmd), + } + Ok(None) +} + +fn get_file_req(data: &Message) -> anyhow::Result { + if let Message::Text(data_str) = data { + let req = serde_json::from_str(data_str) + .with_context(|| format!("Couldn't parse JSON from {data_str}"))?; + Ok(req) + } else { + Err(anyhow::anyhow!( + "Couldn't convert received data {:?} to text", + data + )) + } +} + +#[instrument(skip_all)] +pub async fn file_handler( + socket: tokio_tungstenite::WebSocketStream, + fingerprint: Option, + token: String, +) { + let (mut socket_send, mut socket_recv) = socket.split(); + let mut req: shared::FileRequest; + + 'outer: while let Some(Ok(data)) = socket_recv.next().await { + if data.is_close() { + break; + } + + req = handle_error!(get_file_req(&data), continue); + + tracing::debug!("Got file request {:?}", req); + + if CONFIG.pass && !validate_token(&token, fingerprint.as_deref()).as_bool() { + return; + } + + loop { + tokio::select! { + result = file_handler_helper(&req) => { + match handle_error!(result, continue) { + Some(FileHandlerHelperReturns::String(file)) => { + if socket_send.send(Message::text(file)).await.is_err() { + break 'outer; + } + } + Some(FileHandlerHelperReturns::SizeBuf(size, buf)) => { + if socket_send + .send(Message::text(handle_error!(serde_json::to_string(&size).context("Couldn't serialize json"), continue))) + .await + .is_err() + { + break 'outer; + } + for i in buf.chunks(1000 * 1000) { + if socket_send.feed(Message::binary(i)).await.is_err() { + break 'outer; + } + } + if socket_send.flush().await.is_err() { + break 'outer; + } + } + Some(FileHandlerHelperReturns::StreamUpload(size, mut file)) => { + while let Some(Ok(Message::Binary(msg))) = (&mut socket_recv).take(size).next().await { + handle_error!(file.write_all(&msg).await.with_context(|| { + format!("Couldn't write to file {}, stopping upload", &req.path) + }), continue 'outer); + } + } + None => {} + } + break; + }, + recv = socket_recv.next() => match recv { + Some(Ok(req_tmp)) => req = handle_error!(get_file_req(&req_tmp), continue 'outer), + _ => break 'outer, + }, + } + } + } +} diff --git a/src/backend/src/systemdata.rs b/src/systemdata.rs similarity index 71% rename from src/backend/src/systemdata.rs rename to src/systemdata.rs index e5a41788..2be1e830 100644 --- a/src/backend/src/systemdata.rs +++ b/src/systemdata.rs @@ -5,6 +5,7 @@ use std::time::Duration; use tokio::fs; use tokio::process::Command; use tokio::time::sleep; +use tracing::instrument; use crate::shared; @@ -12,6 +13,7 @@ fn round_percent(unrounded: f32) -> f32 { (unrounded * 100.0).round() / 100.0 } +#[instrument(skip_all)] pub fn cpu(collector: &mut cpu::CpuPercentCollector) -> anyhow::Result { Ok(round_percent( collector @@ -20,6 +22,7 @@ pub fn cpu(collector: &mut cpu::CpuPercentCollector) -> anyhow::Result { )) } +#[instrument] pub fn ram() -> anyhow::Result { let ram = memory::virtual_memory().context("Couldn't get memory data")?; @@ -30,6 +33,7 @@ pub fn ram() -> anyhow::Result { }) } +#[instrument] pub fn swap() -> anyhow::Result { let swap = memory::swap_memory().context("Couldn't get swap data")?; @@ -40,6 +44,7 @@ pub fn swap() -> anyhow::Result { }) } +#[instrument] pub fn disk() -> anyhow::Result { let disk = disk::disk_usage("/").context("Couldn't get disk usage data")?; @@ -50,6 +55,7 @@ pub fn disk() -> anyhow::Result { }) } +#[instrument(skip(collector))] pub fn network( collector: &mut network::NetIoCountersCollector, prev_data: &mut shared::NetData, @@ -92,26 +98,22 @@ fn get_process_data(process: &mut psutil::process::Process) -> anyhow::Result anyhow::Result> { let mut processes = process::processes().context("Couldn't get list of processes")?; let mut process_list = Vec::new(); process_list.reserve(processes.len()); - for element in &mut processes { - match element.as_mut() { - Ok(unwrapped_el) => match unwrapped_el.cpu_percent() { - Ok(_) => (), - Err(_) => continue, - }, - Err(_) => continue, + for process in processes.iter_mut().flatten() { + // Required to get cpu times before actual measurement + if process.cpu_percent().is_err() { + continue; } } sleep(Duration::from_millis(500)).await; for mut element in processes.into_iter().flatten() { // Errors shouldn't return, just skip the process - let process = if let Ok(process) = get_process_data(&mut element) { - process - } else { + let Ok(process) = get_process_data(&mut element) else { continue; }; @@ -140,121 +142,47 @@ pub async fn processes() -> anyhow::Result> { Ok(process_list) } +#[instrument] // Return on error here, trust that DietPi-Software should work and if something goes wrong that it's bad pub async fn dpsoftware( ) -> anyhow::Result<(Vec, Vec)> { - let free_out = Command::new("/boot/dietpi/dietpi-software") - .arg("free") - .output() - .await - .context("Couldn't get DietPi-Software free list")? - .stdout; - anyhow::ensure!(!free_out.is_empty(), "DietPi-Software not running as root"); - let free = from_utf8(&free_out) - .context("Invalid DietPi-Software free list")? - .lines() - .nth(4) - .context("DietPi-Software free list is too short")? - .trim_start_matches("Free software ID(s): "); - let free_list = if &free[..4] == "None" { - Vec::new() - } else { - // Should be no negative software IDs, so ignore parsing errors by returning one - free.split(' ') - .map(|id| id.parse::().unwrap_or(-1)) - .collect() - }; - let out = Command::new("/boot/dietpi/dietpi-software") - .arg("list") + .args(["list", "--machine-readable"]) .output() .await .context("Couldn't get DietPi-Software software list")? .stdout; + anyhow::ensure!(!out.is_empty(), "DietPi-Software not running as root"); let out_list = from_utf8(&out) .context("Invalid DietPi-Software software list")? .lines() .collect::>(); let mut installed_list = Vec::new(); let mut uninstalled_list = Vec::new(); - let mut index = 0_i16; - uninstalled_list.reserve( - out_list - .len() - .checked_sub(4) // Database messages - .context("DietPi-Software software list is too short")?, - ); + uninstalled_list.reserve(out_list.len()); // First 4 skipped lines are the database messages - 'software: for element in out_list.iter().skip(4) { - // Skip if in free list - if free_list.contains(&(index as i16)) { - index += 1; - } + 'software: for element in out_list { let mut software = shared::DPSoftwareData::default(); let mut installed = false; for (in1, el1) in element.split('|').enumerate() { match in1 { 0 => { - software.id = el1 - .trim() - .trim_start_matches("\u{001b}[32m") - .trim_start_matches("ID ") - .parse::() - .with_context(|| { - format!( - "Invalid software ID {}", - el1.trim() - .trim_start_matches("\u{001b}[32m") - .trim_start_matches("ID ") - ) - })?; - } - 1 => { - installed = el1 - .trim() - .trim_start_matches('=') - .parse::() - .with_context(|| { - format!( - "Invalid installed specifier {} for id {}", - el1.trim().trim_start_matches('='), - software.id - ) - })? - > 0; - } - 2 => { - let mut name_desc = el1.trim().split(':'); - software.name = name_desc - .next() - .with_context(|| { - format!("Couldn't get software name for id {}", software.id) - })? - .to_string(); - software.description = name_desc - .next() - .with_context(|| { - format!("Couldn't get software description for id {}", software.id) - })? - .trim_start_matches("\u{001b}[0m \u{001b}[90m") - .trim_end_matches("\u{001b}[0m") - .to_string(); - } - 3 => { - // Annoying that here is the only place that software can be detected as disabled or not, and not before if el1.contains("DISABLED") { - index += 1; continue 'software; } - software.dependencies = el1.trim().to_string(); + software.id = el1 + .parse::() + .with_context(|| format!("Invalid software ID {el1}"))?; } - 4 => { - software.docs = el1 - .trim() - .trim_start_matches("\u{001b}[90m") - .trim_end_matches("\u{001b}[0m") - .to_string(); + 1 => { + installed = el1.parse::().with_context(|| { + format!("Invalid installed specifier {el1} for id {}", software.id) + })? > 0; } + 2 => software.name = el1.to_string(), + 3 => software.description = el1.to_string(), + 4 => software.dependencies = el1.replace(',', ", "), + 5 => software.docs = el1.to_string(), _ => {} } } @@ -263,18 +191,18 @@ pub async fn dpsoftware( } else { uninstalled_list.push(software); } - index += 1; } Ok((uninstalled_list, installed_list)) } +#[instrument] pub async fn host() -> anyhow::Result { let info = host::info(); let uptime = host::uptime().context("Couldn't get uptime")?.as_secs() / 60; let dp_file = fs::read_to_string("/boot/dietpi/.version") .await .context("Couldn't get DietPi version")?; - let dp_version: Vec<&str> = dp_file.split(&['=', '\n'][..]).collect(); + let dp_version: Vec<&str> = dp_file.split(['=', '\n']).collect(); // Much faster than 'apt list --installed' // Count number of newlines let installed_pkgs = Command::new("dpkg") @@ -299,14 +227,7 @@ pub async fn host() -> anyhow::Result { arch = "armv7l"; } let addrs = &if_addrs::get_if_addrs().context("Couldn't get IP addresses")?; - // Start with first address (probably loopback), and loop to try to get an actual one - let mut addr = &addrs[0]; - for i in addrs { - if !i.is_loopback() { - addr = i; - break; - } - } + let addr = addrs.iter().find(|x| !x.is_loopback()).unwrap_or(&addrs[0]); Ok(shared::HostData { hostname: info.hostname().to_string(), uptime, @@ -320,6 +241,7 @@ pub async fn host() -> anyhow::Result { }) } +#[instrument] // Also assume DietPi-Services output is good, and return on error pub async fn services() -> anyhow::Result> { let services = &mut Command::new("/boot/dietpi/dietpi-services") @@ -336,7 +258,7 @@ pub async fn services() -> anyhow::Result> { let services_str = from_utf8(&services.stdout).context("Invalid service list")?; let mut services_list = Vec::new(); // Split on 3 different tokens - for element in services_str + for element in shared::remove_color_codes(services_str) .replace("[FAILED] DietPi-Services | \u{25cf} ", "dpdashboardtemp") .replace("[ INFO ] DietPi-Services | ", "dpdashboardtemp") .replace("[ OK ] DietPi-Services | ", "dpdashboardtemp") @@ -358,17 +280,14 @@ pub async fn services() -> anyhow::Result> { .to_string(); } // Every line after 9 (before is data that's useless to us) should be service error log, format with HTML breaks - 9.. => service.log.push_str(format!("{}
", el1).as_str()), + 9.. => service.log.push_str(format!("{el1}
").as_str()), _ => (), } } } else { - let (el1, el2) = match element.split_once('\t') { - Some(els) => els, - None => continue, - }; - service.name = el1.trim().to_string(); - match el2.split_once(" since ") { + let Some(els) = element.split_once('\t') else { continue }; + service.name = els.0.trim().to_string(); + match els.1.split_once(" since ") { Some(statusdate) => { service.status = match statusdate.0.trim() { "active (running)" | "active (exited)" => "active", @@ -385,6 +304,7 @@ pub async fn services() -> anyhow::Result> { Ok(services_list) } +#[instrument] pub async fn global() -> shared::GlobalData { use crate::CONFIG; @@ -402,12 +322,41 @@ pub async fn global() -> shared::GlobalData { } } +fn uppercase_first_letter(word: &str) -> (char, &str) { + // There will always be at least one letter in the mime type + #[allow(clippy::unwrap_used)] + let first_letter = word.chars().next().unwrap().to_ascii_uppercase(); + (first_letter, &word[1..]) +} + +#[instrument] pub async fn browser_dir(path: &std::path::Path) -> anyhow::Result> { let mut dir = fs::read_dir(path) .await .with_context(|| format!("Couldn't read path {}", path.display()))?; + let mut file_list = Vec::new(); + while let Ok(Some(file)) = dir.next_entry().await { + tracing::debug!( + "Got {} with type {:?}", + file.path().display(), + file.file_type() + .await + .map(|x| { + if x.is_file() { + "file" + } else if x.is_dir() { + "directory" + } else if x.is_symlink() { + "symlink" + } else { + "something else" + } + }) + .unwrap_or("unknown") + ); + // Resolve all symlinks let path = fs::canonicalize(file.path()) .await @@ -415,59 +364,50 @@ pub async fn browser_dir(path: &std::path::Path) -> anyhow::Result 2 * 1000 * 1000 { - subtype = "large"; - } else { - subtype = "plain"; - } - maintype = "text"; - prettytype = "Plain Text File".to_string(); } + } else { + maintype = "notafile".to_string(); + subtype = "notafile".to_string(); + prettytype = "Special File".to_string(); } + file_list.push(shared::BrowserData { path: crate::handle_error!( file.path() @@ -491,10 +431,19 @@ pub async fn browser_dir(path: &std::path::Path) -> anyhow::Result shared::CPUTemp { - match &sensors::temperatures().get(0) { + let temps = sensors::temperatures(); + match &temps.get( + // Prefer 'coretemp' sensor for Intel CPUs, otherwise fallback to first in list + temps + .iter() + .filter_map(|x| x.as_ref().ok()) + .position(|x| x.unit() == "coretemp") + .unwrap_or(0), + ) { Some(Ok(temp)) => { let temp = temp.current(); shared::CPUTemp { @@ -503,7 +452,7 @@ pub fn temp() -> shared::CPUTemp { fahrenheit: temp.fahrenheit().round() as i16, } } - None | Some(Err(_)) => shared::CPUTemp { + _ => shared::CPUTemp { available: false, celsius: 0, fahrenheit: 0, diff --git a/src/tls.rs b/src/tls.rs new file mode 100644 index 00000000..08bef028 --- /dev/null +++ b/src/tls.rs @@ -0,0 +1,142 @@ +use anyhow::Context; +use futures::FutureExt; +use std::task::Poll; + +#[allow(clippy::module_name_repetitions)] +pub enum TlsOrTcpConnection { + Plain(tokio::net::TcpStream, std::net::SocketAddr), + Tls( + Box>, + std::net::SocketAddr, + ), +} + +pub struct HyperTlsOrTcpAcceptor { + listener: tokio::net::TcpListener, + acceptor: Option, + accept_future: Option>, + remote_addr: Option, +} + +impl HyperTlsOrTcpAcceptor { + pub const fn new( + listener: tokio::net::TcpListener, + acceptor: Option, + ) -> Self { + Self { + listener, + acceptor, + accept_future: None, + remote_addr: None, + } + } +} + +impl TlsOrTcpConnection { + pub const fn remote_addr(&self) -> std::net::SocketAddr { + match self { + Self::Plain(_, remote_addr) | Self::Tls(_, remote_addr) => *remote_addr, + } + } +} + +impl tokio::io::AsyncRead for TlsOrTcpConnection { + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + match self.get_mut() { + Self::Plain(tcp, _) => std::pin::Pin::new(tcp).poll_read(cx, buf), + Self::Tls(tls, _) => std::pin::Pin::new(tls).poll_read(cx, buf), + } + } +} + +impl tokio::io::AsyncWrite for TlsOrTcpConnection { + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + match self.get_mut() { + Self::Plain(tcp, _) => std::pin::Pin::new(tcp).poll_write(cx, buf), + Self::Tls(tls, _) => std::pin::Pin::new(tls).poll_write(cx, buf), + } + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + match self.get_mut() { + Self::Plain(tcp, _) => std::pin::Pin::new(tcp).poll_flush(cx), + Self::Tls(tls, _) => std::pin::Pin::new(tls).poll_flush(cx), + } + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + match self.get_mut() { + Self::Plain(tcp, _) => std::pin::Pin::new(tcp).poll_shutdown(cx), + Self::Tls(tls, _) => std::pin::Pin::new(tls).poll_shutdown(cx), + } + } +} + +impl hyper::server::accept::Accept for &mut HyperTlsOrTcpAcceptor { + type Conn = TlsOrTcpConnection; + type Error = anyhow::Error; + + fn poll_accept( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll>> { + if self.accept_future.is_none() { + match self.listener.poll_accept(cx) { + Poll::Ready(stream) => match stream { + Ok(stream) => { + if let Some(acceptor) = &self.acceptor { + self.accept_future = Some(acceptor.accept(stream.0)); + self.remote_addr = Some(stream.1); + } else { + return Poll::Ready(Some(Ok(TlsOrTcpConnection::Plain( + stream.0, stream.1, + )))); + } + } + Err(err) => { + return Poll::Ready(Some(Err(err).context("Couldn't make TCP connection"))); + } + }, + Poll::Pending => return Poll::Pending, + } + } + if let Some(accept_future) = &mut self.accept_future { + match accept_future.poll_unpin(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(tls) => { + self.accept_future = None; + let tls = match tls { + Ok(tls) => tls, + Err(err) => { + return Poll::Ready(Some( + Err(err).context("Couldn't encrypt TCP connection"), + )); + } + }; + let remote_addr = self.remote_addr.take().unwrap_or_else(|| { + std::net::SocketAddr::from((std::net::Ipv4Addr::UNSPECIFIED, 0)) + }); + return Poll::Ready(Some(Ok(TlsOrTcpConnection::Tls( + Box::new(tls), + remote_addr, + )))); + } + } + } + Poll::Pending + } +}