├── .envrc ├── .github └── workflows │ ├── ci-nix.yml │ ├── image.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Justfile ├── LICENSE ├── README.md ├── api.hurl ├── flake.lock ├── flake.nix ├── local.hurl ├── screenshots ├── 1.png ├── 2.png └── 3.png ├── src └── main.rs ├── statics ├── box.svg ├── boxes.svg ├── github.svg ├── logo.svg ├── move-up-right.svg ├── radio.svg ├── reset.css └── tag.svg └── ui ├── .gitignore ├── default.nix ├── elm-srcs.nix ├── elm.json ├── index.html ├── registry.dat └── src └── Main.elm /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/ci-nix.yml: -------------------------------------------------------------------------------- 1 | name: Nix CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: DeterminateSystems/nix-installer-action@main 14 | - uses: DeterminateSystems/magic-nix-cache-action@main 15 | 16 | - name: Run Flake Check 17 | run: nix flake check 18 | 19 | - name: Run a Build 20 | run: nix build .#default 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/image.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Image 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**[0-9]+.[0-9]+.[0-9]+*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-24.04 11 | 12 | permissions: 13 | contents: read 14 | id-token: write 15 | 16 | env: 17 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: DeterminateSystems/nix-installer-action@main 22 | - uses: DeterminateSystems/magic-nix-cache-action@main 23 | 24 | - name: Deploy Docker Image 25 | run: nix run .#deploy 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by cargo-dist: https://opensource.axo.dev/cargo-dist/ 2 | # 3 | # Copyright 2022-2024, axodotdev 4 | # SPDX-License-Identifier: MIT or Apache-2.0 5 | # 6 | # CI that: 7 | # 8 | # * checks for a Git Tag that looks like a release 9 | # * builds artifacts with cargo-dist (archives, installers, hashes) 10 | # * uploads those artifacts to temporary workflow zip 11 | # * on success, uploads the artifacts to a GitHub Release 12 | # 13 | # Note that the GitHub Release will be created with a generated 14 | # title/body based on your changelogs. 15 | 16 | name: Release 17 | permissions: 18 | "contents": "write" 19 | 20 | # This task will run whenever you push a git tag that looks like a version 21 | # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. 22 | # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where 23 | # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION 24 | # must be a Cargo-style SemVer Version (must have at least major.minor.patch). 25 | # 26 | # If PACKAGE_NAME is specified, then the announcement will be for that 27 | # package (erroring out if it doesn't have the given version or isn't cargo-dist-able). 28 | # 29 | # If PACKAGE_NAME isn't specified, then the announcement will be for all 30 | # (cargo-dist-able) packages in the workspace with that version (this mode is 31 | # intended for workspaces with only one dist-able package, or with all dist-able 32 | # packages versioned/released in lockstep). 33 | # 34 | # If you push multiple tags at once, separate instances of this workflow will 35 | # spin up, creating an independent announcement for each one. However, GitHub 36 | # will hard limit this to 3 tags per commit, as it will assume more tags is a 37 | # mistake. 38 | # 39 | # If there's a prerelease-style suffix to the version, then the release(s) 40 | # will be marked as a prerelease. 41 | on: 42 | pull_request: 43 | push: 44 | tags: 45 | - '**[0-9]+.[0-9]+.[0-9]+*' 46 | 47 | jobs: 48 | # Run 'cargo dist plan' (or host) to determine what tasks we need to do 49 | plan: 50 | runs-on: "ubuntu-20.04" 51 | outputs: 52 | val: ${{ steps.plan.outputs.manifest }} 53 | tag: ${{ !github.event.pull_request && github.ref_name || '' }} 54 | tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} 55 | publishing: ${{ !github.event.pull_request }} 56 | env: 57 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | steps: 59 | - uses: actions/checkout@v4 60 | with: 61 | submodules: recursive 62 | - name: Install cargo-dist 63 | # we specify bash to get pipefail; it guards against the `curl` command 64 | # failing. otherwise `sh` won't catch that `curl` returned non-0 65 | shell: bash 66 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.22.1/cargo-dist-installer.sh | sh" 67 | - name: Cache cargo-dist 68 | uses: actions/upload-artifact@v4 69 | with: 70 | name: cargo-dist-cache 71 | path: ~/.cargo/bin/cargo-dist 72 | # sure would be cool if github gave us proper conditionals... 73 | # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible 74 | # functionality based on whether this is a pull_request, and whether it's from a fork. 75 | # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* 76 | # but also really annoying to build CI around when it needs secrets to work right.) 77 | - id: plan 78 | run: | 79 | cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json 80 | echo "cargo dist ran successfully" 81 | cat plan-dist-manifest.json 82 | echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" 83 | - name: "Upload dist-manifest.json" 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: artifacts-plan-dist-manifest 87 | path: plan-dist-manifest.json 88 | 89 | # Build and packages all the platform-specific things 90 | build-local-artifacts: 91 | name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) 92 | # Let the initial task tell us to not run (currently very blunt) 93 | needs: 94 | - plan 95 | if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} 96 | strategy: 97 | fail-fast: false 98 | # Target platforms/runners are computed by cargo-dist in create-release. 99 | # Each member of the matrix has the following arguments: 100 | # 101 | # - runner: the github runner 102 | # - dist-args: cli flags to pass to cargo dist 103 | # - install-dist: expression to run to install cargo-dist on the runner 104 | # 105 | # Typically there will be: 106 | # - 1 "global" task that builds universal installers 107 | # - N "local" tasks that build each platform's binaries and platform-specific installers 108 | matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} 109 | runs-on: ${{ matrix.runner }} 110 | env: 111 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 112 | BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json 113 | steps: 114 | - name: enable windows longpaths 115 | run: | 116 | git config --global core.longpaths true 117 | - uses: actions/checkout@v4 118 | with: 119 | submodules: recursive 120 | - name: Install cargo-dist 121 | run: ${{ matrix.install_dist }} 122 | # Get the dist-manifest 123 | - name: Fetch local artifacts 124 | uses: actions/download-artifact@v4 125 | with: 126 | pattern: artifacts-* 127 | path: target/distrib/ 128 | merge-multiple: true 129 | 130 | - name: Install dependencies 131 | run: | 132 | ${{ matrix.packages_install }} 133 | 134 | - name: Set up Node.js 135 | uses: actions/setup-node@v4 136 | with: 137 | node-version: '20' 138 | - name: Install Elm 139 | run: npm install -g elm 140 | - name: Build Elm UI 141 | run: cd ui; elm make src/Main.elm --optimize --output Main.js 142 | 143 | - name: Build artifacts 144 | run: | 145 | # Actually do builds and make zips and whatnot 146 | cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json 147 | echo "cargo dist ran successfully" 148 | - id: cargo-dist 149 | name: Post-build 150 | # We force bash here just because github makes it really hard to get values up 151 | # to "real" actions without writing to env-vars, and writing to env-vars has 152 | # inconsistent syntax between shell and powershell. 153 | shell: bash 154 | run: | 155 | # Parse out what we just built and upload it to scratch storage 156 | echo "paths<> "$GITHUB_OUTPUT" 157 | jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" 158 | echo "EOF" >> "$GITHUB_OUTPUT" 159 | 160 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 161 | - name: "Upload artifacts" 162 | uses: actions/upload-artifact@v4 163 | with: 164 | name: artifacts-build-local-${{ join(matrix.targets, '_') }} 165 | path: | 166 | ${{ steps.cargo-dist.outputs.paths }} 167 | ${{ env.BUILD_MANIFEST_NAME }} 168 | 169 | # Build and package all the platform-agnostic(ish) things 170 | build-global-artifacts: 171 | needs: 172 | - plan 173 | - build-local-artifacts 174 | runs-on: "ubuntu-20.04" 175 | env: 176 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 177 | BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json 178 | steps: 179 | - uses: actions/checkout@v4 180 | with: 181 | submodules: recursive 182 | - name: Install cached cargo-dist 183 | uses: actions/download-artifact@v4 184 | with: 185 | name: cargo-dist-cache 186 | path: ~/.cargo/bin/ 187 | - run: chmod +x ~/.cargo/bin/cargo-dist 188 | # Get all the local artifacts for the global tasks to use (for e.g. checksums) 189 | - name: Fetch local artifacts 190 | uses: actions/download-artifact@v4 191 | with: 192 | pattern: artifacts-* 193 | path: target/distrib/ 194 | merge-multiple: true 195 | - id: cargo-dist 196 | shell: bash 197 | run: | 198 | cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json 199 | echo "cargo dist ran successfully" 200 | 201 | # Parse out what we just built and upload it to scratch storage 202 | echo "paths<> "$GITHUB_OUTPUT" 203 | jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" 204 | echo "EOF" >> "$GITHUB_OUTPUT" 205 | 206 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 207 | - name: "Upload artifacts" 208 | uses: actions/upload-artifact@v4 209 | with: 210 | name: artifacts-build-global 211 | path: | 212 | ${{ steps.cargo-dist.outputs.paths }} 213 | ${{ env.BUILD_MANIFEST_NAME }} 214 | # Determines if we should publish/announce 215 | host: 216 | needs: 217 | - plan 218 | - build-local-artifacts 219 | - build-global-artifacts 220 | # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) 221 | if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} 222 | env: 223 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 224 | runs-on: "ubuntu-20.04" 225 | outputs: 226 | val: ${{ steps.host.outputs.manifest }} 227 | steps: 228 | - uses: actions/checkout@v4 229 | with: 230 | submodules: recursive 231 | - name: Install cached cargo-dist 232 | uses: actions/download-artifact@v4 233 | with: 234 | name: cargo-dist-cache 235 | path: ~/.cargo/bin/ 236 | - run: chmod +x ~/.cargo/bin/cargo-dist 237 | # Fetch artifacts from scratch-storage 238 | - name: Fetch artifacts 239 | uses: actions/download-artifact@v4 240 | with: 241 | pattern: artifacts-* 242 | path: target/distrib/ 243 | merge-multiple: true 244 | - id: host 245 | shell: bash 246 | run: | 247 | cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json 248 | echo "artifacts uploaded and released successfully" 249 | cat dist-manifest.json 250 | echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" 251 | - name: "Upload dist-manifest.json" 252 | uses: actions/upload-artifact@v4 253 | with: 254 | # Overwrite the previous copy 255 | name: artifacts-dist-manifest 256 | path: dist-manifest.json 257 | # Create a GitHub Release while uploading all files to it 258 | - name: "Download GitHub Artifacts" 259 | uses: actions/download-artifact@v4 260 | with: 261 | pattern: artifacts-* 262 | path: artifacts 263 | merge-multiple: true 264 | - name: Cleanup 265 | run: | 266 | # Remove the granular manifests 267 | rm -f artifacts/*-dist-manifest.json 268 | - name: Create GitHub Release 269 | env: 270 | PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" 271 | ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" 272 | ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" 273 | RELEASE_COMMIT: "${{ github.sha }}" 274 | run: | 275 | # Write and read notes from a file to avoid quoting breaking things 276 | echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt 277 | 278 | gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* 279 | 280 | announce: 281 | needs: 282 | - plan 283 | - host 284 | # use "always() && ..." to allow us to wait for all publish jobs while 285 | # still allowing individual publish jobs to skip themselves (for prereleases). 286 | # "host" however must run to completion, no skipping allowed! 287 | if: ${{ always() && needs.host.result == 'success' }} 288 | runs-on: "ubuntu-20.04" 289 | env: 290 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 291 | steps: 292 | - uses: actions/checkout@v4 293 | with: 294 | submodules: recursive 295 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | vars.env 3 | /target 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys 0.59.0", 77 | ] 78 | 79 | [[package]] 80 | name = "autocfg" 81 | version = "1.4.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 84 | 85 | [[package]] 86 | name = "backtrace" 87 | version = "0.3.71" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 90 | dependencies = [ 91 | "addr2line", 92 | "cc", 93 | "cfg-if", 94 | "libc", 95 | "miniz_oxide", 96 | "object", 97 | "rustc-demangle", 98 | ] 99 | 100 | [[package]] 101 | name = "base64" 102 | version = "0.21.7" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 105 | 106 | [[package]] 107 | name = "base64" 108 | version = "0.22.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 111 | 112 | [[package]] 113 | name = "bitflags" 114 | version = "2.9.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 117 | 118 | [[package]] 119 | name = "block-buffer" 120 | version = "0.10.4" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 123 | dependencies = [ 124 | "generic-array", 125 | ] 126 | 127 | [[package]] 128 | name = "bumpalo" 129 | version = "3.17.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 132 | 133 | [[package]] 134 | name = "byteorder" 135 | version = "1.5.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 138 | 139 | [[package]] 140 | name = "bytes" 141 | version = "1.10.1" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 144 | 145 | [[package]] 146 | name = "cc" 147 | version = "1.2.16" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" 150 | dependencies = [ 151 | "shlex", 152 | ] 153 | 154 | [[package]] 155 | name = "cfg-if" 156 | version = "1.0.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 159 | 160 | [[package]] 161 | name = "cfg_aliases" 162 | version = "0.2.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 165 | 166 | [[package]] 167 | name = "clap" 168 | version = "4.5.32" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" 171 | dependencies = [ 172 | "clap_builder", 173 | "clap_derive", 174 | ] 175 | 176 | [[package]] 177 | name = "clap_builder" 178 | version = "4.5.32" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" 181 | dependencies = [ 182 | "anstream", 183 | "anstyle", 184 | "clap_lex", 185 | "strsim", 186 | ] 187 | 188 | [[package]] 189 | name = "clap_derive" 190 | version = "4.5.32" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 193 | dependencies = [ 194 | "heck", 195 | "proc-macro2", 196 | "quote", 197 | "syn", 198 | ] 199 | 200 | [[package]] 201 | name = "clap_lex" 202 | version = "0.7.4" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 205 | 206 | [[package]] 207 | name = "color-eyre" 208 | version = "0.6.3" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 211 | dependencies = [ 212 | "backtrace", 213 | "color-spantrace", 214 | "eyre", 215 | "indenter", 216 | "once_cell", 217 | "owo-colors", 218 | "tracing-error", 219 | ] 220 | 221 | [[package]] 222 | name = "color-spantrace" 223 | version = "0.2.1" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 226 | dependencies = [ 227 | "once_cell", 228 | "owo-colors", 229 | "tracing-core", 230 | "tracing-error", 231 | ] 232 | 233 | [[package]] 234 | name = "colorchoice" 235 | version = "1.0.3" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 238 | 239 | [[package]] 240 | name = "cpufeatures" 241 | version = "0.2.17" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 244 | dependencies = [ 245 | "libc", 246 | ] 247 | 248 | [[package]] 249 | name = "crypto-common" 250 | version = "0.1.6" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 253 | dependencies = [ 254 | "generic-array", 255 | "typenum", 256 | ] 257 | 258 | [[package]] 259 | name = "data-encoding" 260 | version = "2.8.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" 263 | 264 | [[package]] 265 | name = "digest" 266 | version = "0.10.7" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 269 | dependencies = [ 270 | "block-buffer", 271 | "crypto-common", 272 | ] 273 | 274 | [[package]] 275 | name = "displaydoc" 276 | version = "0.2.5" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 279 | dependencies = [ 280 | "proc-macro2", 281 | "quote", 282 | "syn", 283 | ] 284 | 285 | [[package]] 286 | name = "encoding_rs" 287 | version = "0.8.35" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 290 | dependencies = [ 291 | "cfg-if", 292 | ] 293 | 294 | [[package]] 295 | name = "equivalent" 296 | version = "1.0.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 299 | 300 | [[package]] 301 | name = "eyre" 302 | version = "0.6.12" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 305 | dependencies = [ 306 | "indenter", 307 | "once_cell", 308 | ] 309 | 310 | [[package]] 311 | name = "fnv" 312 | version = "1.0.7" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 315 | 316 | [[package]] 317 | name = "form_urlencoded" 318 | version = "1.2.1" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 321 | dependencies = [ 322 | "percent-encoding", 323 | ] 324 | 325 | [[package]] 326 | name = "futures-channel" 327 | version = "0.3.31" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 330 | dependencies = [ 331 | "futures-core", 332 | "futures-sink", 333 | ] 334 | 335 | [[package]] 336 | name = "futures-core" 337 | version = "0.3.31" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 340 | 341 | [[package]] 342 | name = "futures-sink" 343 | version = "0.3.31" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 346 | 347 | [[package]] 348 | name = "futures-task" 349 | version = "0.3.31" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 352 | 353 | [[package]] 354 | name = "futures-util" 355 | version = "0.3.31" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 358 | dependencies = [ 359 | "futures-core", 360 | "futures-sink", 361 | "futures-task", 362 | "pin-project-lite", 363 | "pin-utils", 364 | "slab", 365 | ] 366 | 367 | [[package]] 368 | name = "generic-array" 369 | version = "0.14.7" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 372 | dependencies = [ 373 | "typenum", 374 | "version_check", 375 | ] 376 | 377 | [[package]] 378 | name = "getrandom" 379 | version = "0.2.15" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 382 | dependencies = [ 383 | "cfg-if", 384 | "js-sys", 385 | "libc", 386 | "wasi", 387 | "wasm-bindgen", 388 | ] 389 | 390 | [[package]] 391 | name = "gimli" 392 | version = "0.28.1" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 395 | 396 | [[package]] 397 | name = "h2" 398 | version = "0.3.26" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 401 | dependencies = [ 402 | "bytes", 403 | "fnv", 404 | "futures-core", 405 | "futures-sink", 406 | "futures-util", 407 | "http 0.2.12", 408 | "indexmap", 409 | "slab", 410 | "tokio", 411 | "tokio-util", 412 | "tracing", 413 | ] 414 | 415 | [[package]] 416 | name = "hashbrown" 417 | version = "0.15.2" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 420 | 421 | [[package]] 422 | name = "headers" 423 | version = "0.3.9" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" 426 | dependencies = [ 427 | "base64 0.21.7", 428 | "bytes", 429 | "headers-core", 430 | "http 0.2.12", 431 | "httpdate", 432 | "mime", 433 | "sha1", 434 | ] 435 | 436 | [[package]] 437 | name = "headers-core" 438 | version = "0.2.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" 441 | dependencies = [ 442 | "http 0.2.12", 443 | ] 444 | 445 | [[package]] 446 | name = "heck" 447 | version = "0.5.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 450 | 451 | [[package]] 452 | name = "http" 453 | version = "0.2.12" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 456 | dependencies = [ 457 | "bytes", 458 | "fnv", 459 | "itoa", 460 | ] 461 | 462 | [[package]] 463 | name = "http" 464 | version = "1.3.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 467 | dependencies = [ 468 | "bytes", 469 | "fnv", 470 | "itoa", 471 | ] 472 | 473 | [[package]] 474 | name = "http-body" 475 | version = "0.4.6" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 478 | dependencies = [ 479 | "bytes", 480 | "http 0.2.12", 481 | "pin-project-lite", 482 | ] 483 | 484 | [[package]] 485 | name = "http-body" 486 | version = "1.0.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 489 | dependencies = [ 490 | "bytes", 491 | "http 1.3.1", 492 | ] 493 | 494 | [[package]] 495 | name = "http-body-util" 496 | version = "0.1.3" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 499 | dependencies = [ 500 | "bytes", 501 | "futures-core", 502 | "http 1.3.1", 503 | "http-body 1.0.1", 504 | "pin-project-lite", 505 | ] 506 | 507 | [[package]] 508 | name = "httparse" 509 | version = "1.10.1" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 512 | 513 | [[package]] 514 | name = "httpdate" 515 | version = "1.0.3" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 518 | 519 | [[package]] 520 | name = "hyper" 521 | version = "0.14.32" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 524 | dependencies = [ 525 | "bytes", 526 | "futures-channel", 527 | "futures-core", 528 | "futures-util", 529 | "h2", 530 | "http 0.2.12", 531 | "http-body 0.4.6", 532 | "httparse", 533 | "httpdate", 534 | "itoa", 535 | "pin-project-lite", 536 | "socket2", 537 | "tokio", 538 | "tower-service", 539 | "tracing", 540 | "want", 541 | ] 542 | 543 | [[package]] 544 | name = "hyper" 545 | version = "1.6.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 548 | dependencies = [ 549 | "bytes", 550 | "futures-channel", 551 | "futures-util", 552 | "http 1.3.1", 553 | "http-body 1.0.1", 554 | "httparse", 555 | "itoa", 556 | "pin-project-lite", 557 | "smallvec", 558 | "tokio", 559 | "want", 560 | ] 561 | 562 | [[package]] 563 | name = "hyper-rustls" 564 | version = "0.27.5" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" 567 | dependencies = [ 568 | "futures-util", 569 | "http 1.3.1", 570 | "hyper 1.6.0", 571 | "hyper-util", 572 | "rustls", 573 | "rustls-pki-types", 574 | "tokio", 575 | "tokio-rustls", 576 | "tower-service", 577 | "webpki-roots", 578 | ] 579 | 580 | [[package]] 581 | name = "hyper-util" 582 | version = "0.1.10" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 585 | dependencies = [ 586 | "bytes", 587 | "futures-channel", 588 | "futures-util", 589 | "http 1.3.1", 590 | "http-body 1.0.1", 591 | "hyper 1.6.0", 592 | "pin-project-lite", 593 | "socket2", 594 | "tokio", 595 | "tower-service", 596 | "tracing", 597 | ] 598 | 599 | [[package]] 600 | name = "icu_collections" 601 | version = "1.5.0" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 604 | dependencies = [ 605 | "displaydoc", 606 | "yoke", 607 | "zerofrom", 608 | "zerovec", 609 | ] 610 | 611 | [[package]] 612 | name = "icu_locid" 613 | version = "1.5.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 616 | dependencies = [ 617 | "displaydoc", 618 | "litemap", 619 | "tinystr", 620 | "writeable", 621 | "zerovec", 622 | ] 623 | 624 | [[package]] 625 | name = "icu_locid_transform" 626 | version = "1.5.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 629 | dependencies = [ 630 | "displaydoc", 631 | "icu_locid", 632 | "icu_locid_transform_data", 633 | "icu_provider", 634 | "tinystr", 635 | "zerovec", 636 | ] 637 | 638 | [[package]] 639 | name = "icu_locid_transform_data" 640 | version = "1.5.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 643 | 644 | [[package]] 645 | name = "icu_normalizer" 646 | version = "1.5.0" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 649 | dependencies = [ 650 | "displaydoc", 651 | "icu_collections", 652 | "icu_normalizer_data", 653 | "icu_properties", 654 | "icu_provider", 655 | "smallvec", 656 | "utf16_iter", 657 | "utf8_iter", 658 | "write16", 659 | "zerovec", 660 | ] 661 | 662 | [[package]] 663 | name = "icu_normalizer_data" 664 | version = "1.5.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 667 | 668 | [[package]] 669 | name = "icu_properties" 670 | version = "1.5.1" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 673 | dependencies = [ 674 | "displaydoc", 675 | "icu_collections", 676 | "icu_locid_transform", 677 | "icu_properties_data", 678 | "icu_provider", 679 | "tinystr", 680 | "zerovec", 681 | ] 682 | 683 | [[package]] 684 | name = "icu_properties_data" 685 | version = "1.5.0" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 688 | 689 | [[package]] 690 | name = "icu_provider" 691 | version = "1.5.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 694 | dependencies = [ 695 | "displaydoc", 696 | "icu_locid", 697 | "icu_provider_macros", 698 | "stable_deref_trait", 699 | "tinystr", 700 | "writeable", 701 | "yoke", 702 | "zerofrom", 703 | "zerovec", 704 | ] 705 | 706 | [[package]] 707 | name = "icu_provider_macros" 708 | version = "1.5.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 711 | dependencies = [ 712 | "proc-macro2", 713 | "quote", 714 | "syn", 715 | ] 716 | 717 | [[package]] 718 | name = "idna" 719 | version = "1.0.3" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 722 | dependencies = [ 723 | "idna_adapter", 724 | "smallvec", 725 | "utf8_iter", 726 | ] 727 | 728 | [[package]] 729 | name = "idna_adapter" 730 | version = "1.2.0" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 733 | dependencies = [ 734 | "icu_normalizer", 735 | "icu_properties", 736 | ] 737 | 738 | [[package]] 739 | name = "include_dir" 740 | version = "0.7.4" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" 743 | dependencies = [ 744 | "include_dir_macros", 745 | ] 746 | 747 | [[package]] 748 | name = "include_dir_macros" 749 | version = "0.7.4" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" 752 | dependencies = [ 753 | "proc-macro2", 754 | "quote", 755 | ] 756 | 757 | [[package]] 758 | name = "indenter" 759 | version = "0.3.3" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 762 | 763 | [[package]] 764 | name = "indexmap" 765 | version = "2.8.0" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" 768 | dependencies = [ 769 | "equivalent", 770 | "hashbrown", 771 | ] 772 | 773 | [[package]] 774 | name = "ipnet" 775 | version = "2.11.0" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 778 | 779 | [[package]] 780 | name = "is_terminal_polyfill" 781 | version = "1.70.1" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 784 | 785 | [[package]] 786 | name = "itoa" 787 | version = "1.0.15" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 790 | 791 | [[package]] 792 | name = "js-sys" 793 | version = "0.3.77" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 796 | dependencies = [ 797 | "once_cell", 798 | "wasm-bindgen", 799 | ] 800 | 801 | [[package]] 802 | name = "lazy_static" 803 | version = "1.5.0" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 806 | 807 | [[package]] 808 | name = "libc" 809 | version = "0.2.171" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 812 | 813 | [[package]] 814 | name = "litemap" 815 | version = "0.7.5" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 818 | 819 | [[package]] 820 | name = "lock_api" 821 | version = "0.4.12" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 824 | dependencies = [ 825 | "autocfg", 826 | "scopeguard", 827 | ] 828 | 829 | [[package]] 830 | name = "log" 831 | version = "0.4.26" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 834 | 835 | [[package]] 836 | name = "matchers" 837 | version = "0.1.0" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 840 | dependencies = [ 841 | "regex-automata 0.1.10", 842 | ] 843 | 844 | [[package]] 845 | name = "memchr" 846 | version = "2.7.4" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 849 | 850 | [[package]] 851 | name = "mime" 852 | version = "0.3.17" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 855 | 856 | [[package]] 857 | name = "mime_guess" 858 | version = "2.0.5" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 861 | dependencies = [ 862 | "mime", 863 | "unicase", 864 | ] 865 | 866 | [[package]] 867 | name = "miniz_oxide" 868 | version = "0.7.4" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 871 | dependencies = [ 872 | "adler", 873 | ] 874 | 875 | [[package]] 876 | name = "mio" 877 | version = "1.0.3" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 880 | dependencies = [ 881 | "libc", 882 | "wasi", 883 | "windows-sys 0.52.0", 884 | ] 885 | 886 | [[package]] 887 | name = "multer" 888 | version = "2.1.0" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" 891 | dependencies = [ 892 | "bytes", 893 | "encoding_rs", 894 | "futures-util", 895 | "http 0.2.12", 896 | "httparse", 897 | "log", 898 | "memchr", 899 | "mime", 900 | "spin", 901 | "version_check", 902 | ] 903 | 904 | [[package]] 905 | name = "nu-ansi-term" 906 | version = "0.46.0" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 909 | dependencies = [ 910 | "overload", 911 | "winapi", 912 | ] 913 | 914 | [[package]] 915 | name = "object" 916 | version = "0.32.2" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 919 | dependencies = [ 920 | "memchr", 921 | ] 922 | 923 | [[package]] 924 | name = "octopod" 925 | version = "0.1.3" 926 | dependencies = [ 927 | "clap", 928 | "color-eyre", 929 | "include_dir", 930 | "reqwest", 931 | "serde_json", 932 | "tokio", 933 | "tracing", 934 | "tracing-subscriber", 935 | "warp", 936 | ] 937 | 938 | [[package]] 939 | name = "once_cell" 940 | version = "1.21.1" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" 943 | 944 | [[package]] 945 | name = "overload" 946 | version = "0.1.1" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 949 | 950 | [[package]] 951 | name = "owo-colors" 952 | version = "3.5.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 955 | 956 | [[package]] 957 | name = "parking_lot" 958 | version = "0.12.3" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 961 | dependencies = [ 962 | "lock_api", 963 | "parking_lot_core", 964 | ] 965 | 966 | [[package]] 967 | name = "parking_lot_core" 968 | version = "0.9.10" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 971 | dependencies = [ 972 | "cfg-if", 973 | "libc", 974 | "redox_syscall", 975 | "smallvec", 976 | "windows-targets 0.52.6", 977 | ] 978 | 979 | [[package]] 980 | name = "percent-encoding" 981 | version = "2.3.1" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 984 | 985 | [[package]] 986 | name = "pin-project" 987 | version = "1.1.10" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 990 | dependencies = [ 991 | "pin-project-internal", 992 | ] 993 | 994 | [[package]] 995 | name = "pin-project-internal" 996 | version = "1.1.10" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 999 | dependencies = [ 1000 | "proc-macro2", 1001 | "quote", 1002 | "syn", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "pin-project-lite" 1007 | version = "0.2.16" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1010 | 1011 | [[package]] 1012 | name = "pin-utils" 1013 | version = "0.1.0" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1016 | 1017 | [[package]] 1018 | name = "ppv-lite86" 1019 | version = "0.2.21" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1022 | dependencies = [ 1023 | "zerocopy", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "proc-macro2" 1028 | version = "1.0.94" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 1031 | dependencies = [ 1032 | "unicode-ident", 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "quinn" 1037 | version = "0.11.6" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" 1040 | dependencies = [ 1041 | "bytes", 1042 | "pin-project-lite", 1043 | "quinn-proto", 1044 | "quinn-udp", 1045 | "rustc-hash", 1046 | "rustls", 1047 | "socket2", 1048 | "thiserror 2.0.12", 1049 | "tokio", 1050 | "tracing", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "quinn-proto" 1055 | version = "0.11.9" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" 1058 | dependencies = [ 1059 | "bytes", 1060 | "getrandom", 1061 | "rand", 1062 | "ring", 1063 | "rustc-hash", 1064 | "rustls", 1065 | "rustls-pki-types", 1066 | "slab", 1067 | "thiserror 2.0.12", 1068 | "tinyvec", 1069 | "tracing", 1070 | "web-time", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "quinn-udp" 1075 | version = "0.5.10" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" 1078 | dependencies = [ 1079 | "cfg_aliases", 1080 | "libc", 1081 | "once_cell", 1082 | "socket2", 1083 | "tracing", 1084 | "windows-sys 0.59.0", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "quote" 1089 | version = "1.0.40" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1092 | dependencies = [ 1093 | "proc-macro2", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "rand" 1098 | version = "0.8.5" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1101 | dependencies = [ 1102 | "libc", 1103 | "rand_chacha", 1104 | "rand_core", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "rand_chacha" 1109 | version = "0.3.1" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1112 | dependencies = [ 1113 | "ppv-lite86", 1114 | "rand_core", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "rand_core" 1119 | version = "0.6.4" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1122 | dependencies = [ 1123 | "getrandom", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "redox_syscall" 1128 | version = "0.5.10" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" 1131 | dependencies = [ 1132 | "bitflags", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "regex" 1137 | version = "1.11.1" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1140 | dependencies = [ 1141 | "aho-corasick", 1142 | "memchr", 1143 | "regex-automata 0.4.9", 1144 | "regex-syntax 0.8.5", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "regex-automata" 1149 | version = "0.1.10" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1152 | dependencies = [ 1153 | "regex-syntax 0.6.29", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "regex-automata" 1158 | version = "0.4.9" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1161 | dependencies = [ 1162 | "aho-corasick", 1163 | "memchr", 1164 | "regex-syntax 0.8.5", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "regex-syntax" 1169 | version = "0.6.29" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 1172 | 1173 | [[package]] 1174 | name = "regex-syntax" 1175 | version = "0.8.5" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1178 | 1179 | [[package]] 1180 | name = "reqwest" 1181 | version = "0.12.14" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254" 1184 | dependencies = [ 1185 | "base64 0.22.1", 1186 | "bytes", 1187 | "futures-core", 1188 | "futures-util", 1189 | "http 1.3.1", 1190 | "http-body 1.0.1", 1191 | "http-body-util", 1192 | "hyper 1.6.0", 1193 | "hyper-rustls", 1194 | "hyper-util", 1195 | "ipnet", 1196 | "js-sys", 1197 | "log", 1198 | "mime", 1199 | "once_cell", 1200 | "percent-encoding", 1201 | "pin-project-lite", 1202 | "quinn", 1203 | "rustls", 1204 | "rustls-pemfile", 1205 | "rustls-pki-types", 1206 | "serde", 1207 | "serde_json", 1208 | "serde_urlencoded", 1209 | "sync_wrapper", 1210 | "tokio", 1211 | "tokio-rustls", 1212 | "tower", 1213 | "tower-service", 1214 | "url", 1215 | "wasm-bindgen", 1216 | "wasm-bindgen-futures", 1217 | "web-sys", 1218 | "webpki-roots", 1219 | "windows-registry", 1220 | ] 1221 | 1222 | [[package]] 1223 | name = "ring" 1224 | version = "0.17.14" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1227 | dependencies = [ 1228 | "cc", 1229 | "cfg-if", 1230 | "getrandom", 1231 | "libc", 1232 | "untrusted", 1233 | "windows-sys 0.52.0", 1234 | ] 1235 | 1236 | [[package]] 1237 | name = "rustc-demangle" 1238 | version = "0.1.24" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1241 | 1242 | [[package]] 1243 | name = "rustc-hash" 1244 | version = "2.1.1" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 1247 | 1248 | [[package]] 1249 | name = "rustls" 1250 | version = "0.23.23" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" 1253 | dependencies = [ 1254 | "once_cell", 1255 | "ring", 1256 | "rustls-pki-types", 1257 | "rustls-webpki", 1258 | "subtle", 1259 | "zeroize", 1260 | ] 1261 | 1262 | [[package]] 1263 | name = "rustls-pemfile" 1264 | version = "2.2.0" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1267 | dependencies = [ 1268 | "rustls-pki-types", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "rustls-pki-types" 1273 | version = "1.11.0" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1276 | dependencies = [ 1277 | "web-time", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "rustls-webpki" 1282 | version = "0.102.8" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1285 | dependencies = [ 1286 | "ring", 1287 | "rustls-pki-types", 1288 | "untrusted", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "rustversion" 1293 | version = "1.0.20" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1296 | 1297 | [[package]] 1298 | name = "ryu" 1299 | version = "1.0.20" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1302 | 1303 | [[package]] 1304 | name = "scoped-tls" 1305 | version = "1.0.1" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1308 | 1309 | [[package]] 1310 | name = "scopeguard" 1311 | version = "1.2.0" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1314 | 1315 | [[package]] 1316 | name = "serde" 1317 | version = "1.0.219" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1320 | dependencies = [ 1321 | "serde_derive", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "serde_derive" 1326 | version = "1.0.219" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1329 | dependencies = [ 1330 | "proc-macro2", 1331 | "quote", 1332 | "syn", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "serde_json" 1337 | version = "1.0.140" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1340 | dependencies = [ 1341 | "itoa", 1342 | "memchr", 1343 | "ryu", 1344 | "serde", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "serde_urlencoded" 1349 | version = "0.7.1" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1352 | dependencies = [ 1353 | "form_urlencoded", 1354 | "itoa", 1355 | "ryu", 1356 | "serde", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "sha1" 1361 | version = "0.10.6" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1364 | dependencies = [ 1365 | "cfg-if", 1366 | "cpufeatures", 1367 | "digest", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "sharded-slab" 1372 | version = "0.1.7" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1375 | dependencies = [ 1376 | "lazy_static", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "shlex" 1381 | version = "1.3.0" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1384 | 1385 | [[package]] 1386 | name = "signal-hook-registry" 1387 | version = "1.4.2" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1390 | dependencies = [ 1391 | "libc", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "slab" 1396 | version = "0.4.9" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1399 | dependencies = [ 1400 | "autocfg", 1401 | ] 1402 | 1403 | [[package]] 1404 | name = "smallvec" 1405 | version = "1.14.0" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 1408 | 1409 | [[package]] 1410 | name = "socket2" 1411 | version = "0.5.8" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1414 | dependencies = [ 1415 | "libc", 1416 | "windows-sys 0.52.0", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "spin" 1421 | version = "0.9.8" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1424 | 1425 | [[package]] 1426 | name = "stable_deref_trait" 1427 | version = "1.2.0" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1430 | 1431 | [[package]] 1432 | name = "strsim" 1433 | version = "0.11.1" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1436 | 1437 | [[package]] 1438 | name = "subtle" 1439 | version = "2.6.1" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1442 | 1443 | [[package]] 1444 | name = "syn" 1445 | version = "2.0.100" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 1448 | dependencies = [ 1449 | "proc-macro2", 1450 | "quote", 1451 | "unicode-ident", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "sync_wrapper" 1456 | version = "1.0.2" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1459 | dependencies = [ 1460 | "futures-core", 1461 | ] 1462 | 1463 | [[package]] 1464 | name = "synstructure" 1465 | version = "0.13.1" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1468 | dependencies = [ 1469 | "proc-macro2", 1470 | "quote", 1471 | "syn", 1472 | ] 1473 | 1474 | [[package]] 1475 | name = "thiserror" 1476 | version = "1.0.69" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1479 | dependencies = [ 1480 | "thiserror-impl 1.0.69", 1481 | ] 1482 | 1483 | [[package]] 1484 | name = "thiserror" 1485 | version = "2.0.12" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1488 | dependencies = [ 1489 | "thiserror-impl 2.0.12", 1490 | ] 1491 | 1492 | [[package]] 1493 | name = "thiserror-impl" 1494 | version = "1.0.69" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1497 | dependencies = [ 1498 | "proc-macro2", 1499 | "quote", 1500 | "syn", 1501 | ] 1502 | 1503 | [[package]] 1504 | name = "thiserror-impl" 1505 | version = "2.0.12" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1508 | dependencies = [ 1509 | "proc-macro2", 1510 | "quote", 1511 | "syn", 1512 | ] 1513 | 1514 | [[package]] 1515 | name = "thread_local" 1516 | version = "1.1.8" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1519 | dependencies = [ 1520 | "cfg-if", 1521 | "once_cell", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "tinystr" 1526 | version = "0.7.6" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1529 | dependencies = [ 1530 | "displaydoc", 1531 | "zerovec", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "tinyvec" 1536 | version = "1.9.0" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 1539 | dependencies = [ 1540 | "tinyvec_macros", 1541 | ] 1542 | 1543 | [[package]] 1544 | name = "tinyvec_macros" 1545 | version = "0.1.1" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1548 | 1549 | [[package]] 1550 | name = "tokio" 1551 | version = "1.44.2" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" 1554 | dependencies = [ 1555 | "backtrace", 1556 | "bytes", 1557 | "libc", 1558 | "mio", 1559 | "parking_lot", 1560 | "pin-project-lite", 1561 | "signal-hook-registry", 1562 | "socket2", 1563 | "tokio-macros", 1564 | "windows-sys 0.52.0", 1565 | ] 1566 | 1567 | [[package]] 1568 | name = "tokio-macros" 1569 | version = "2.5.0" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1572 | dependencies = [ 1573 | "proc-macro2", 1574 | "quote", 1575 | "syn", 1576 | ] 1577 | 1578 | [[package]] 1579 | name = "tokio-rustls" 1580 | version = "0.26.2" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 1583 | dependencies = [ 1584 | "rustls", 1585 | "tokio", 1586 | ] 1587 | 1588 | [[package]] 1589 | name = "tokio-tungstenite" 1590 | version = "0.21.0" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" 1593 | dependencies = [ 1594 | "futures-util", 1595 | "log", 1596 | "tokio", 1597 | "tungstenite", 1598 | ] 1599 | 1600 | [[package]] 1601 | name = "tokio-util" 1602 | version = "0.7.14" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" 1605 | dependencies = [ 1606 | "bytes", 1607 | "futures-core", 1608 | "futures-sink", 1609 | "pin-project-lite", 1610 | "tokio", 1611 | ] 1612 | 1613 | [[package]] 1614 | name = "tower" 1615 | version = "0.5.2" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1618 | dependencies = [ 1619 | "futures-core", 1620 | "futures-util", 1621 | "pin-project-lite", 1622 | "sync_wrapper", 1623 | "tokio", 1624 | "tower-layer", 1625 | "tower-service", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "tower-layer" 1630 | version = "0.3.3" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1633 | 1634 | [[package]] 1635 | name = "tower-service" 1636 | version = "0.3.3" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1639 | 1640 | [[package]] 1641 | name = "tracing" 1642 | version = "0.1.41" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1645 | dependencies = [ 1646 | "log", 1647 | "pin-project-lite", 1648 | "tracing-attributes", 1649 | "tracing-core", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "tracing-attributes" 1654 | version = "0.1.28" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 1657 | dependencies = [ 1658 | "proc-macro2", 1659 | "quote", 1660 | "syn", 1661 | ] 1662 | 1663 | [[package]] 1664 | name = "tracing-core" 1665 | version = "0.1.33" 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" 1667 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1668 | dependencies = [ 1669 | "once_cell", 1670 | "valuable", 1671 | ] 1672 | 1673 | [[package]] 1674 | name = "tracing-error" 1675 | version = "0.2.1" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 1678 | dependencies = [ 1679 | "tracing", 1680 | "tracing-subscriber", 1681 | ] 1682 | 1683 | [[package]] 1684 | name = "tracing-log" 1685 | version = "0.2.0" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1688 | dependencies = [ 1689 | "log", 1690 | "once_cell", 1691 | "tracing-core", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "tracing-subscriber" 1696 | version = "0.3.19" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1699 | dependencies = [ 1700 | "matchers", 1701 | "nu-ansi-term", 1702 | "once_cell", 1703 | "regex", 1704 | "sharded-slab", 1705 | "smallvec", 1706 | "thread_local", 1707 | "tracing", 1708 | "tracing-core", 1709 | "tracing-log", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "try-lock" 1714 | version = "0.2.5" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1717 | 1718 | [[package]] 1719 | name = "tungstenite" 1720 | version = "0.21.0" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" 1723 | dependencies = [ 1724 | "byteorder", 1725 | "bytes", 1726 | "data-encoding", 1727 | "http 1.3.1", 1728 | "httparse", 1729 | "log", 1730 | "rand", 1731 | "sha1", 1732 | "thiserror 1.0.69", 1733 | "url", 1734 | "utf-8", 1735 | ] 1736 | 1737 | [[package]] 1738 | name = "typenum" 1739 | version = "1.18.0" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 1742 | 1743 | [[package]] 1744 | name = "unicase" 1745 | version = "2.8.1" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 1748 | 1749 | [[package]] 1750 | name = "unicode-ident" 1751 | version = "1.0.18" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1754 | 1755 | [[package]] 1756 | name = "untrusted" 1757 | version = "0.9.0" 1758 | source = "registry+https://github.com/rust-lang/crates.io-index" 1759 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1760 | 1761 | [[package]] 1762 | name = "url" 1763 | version = "2.5.4" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1766 | dependencies = [ 1767 | "form_urlencoded", 1768 | "idna", 1769 | "percent-encoding", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "utf-8" 1774 | version = "0.7.6" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1777 | 1778 | [[package]] 1779 | name = "utf16_iter" 1780 | version = "1.0.5" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1783 | 1784 | [[package]] 1785 | name = "utf8_iter" 1786 | version = "1.0.4" 1787 | source = "registry+https://github.com/rust-lang/crates.io-index" 1788 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1789 | 1790 | [[package]] 1791 | name = "utf8parse" 1792 | version = "0.2.2" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1795 | 1796 | [[package]] 1797 | name = "valuable" 1798 | version = "0.1.1" 1799 | source = "registry+https://github.com/rust-lang/crates.io-index" 1800 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1801 | 1802 | [[package]] 1803 | name = "version_check" 1804 | version = "0.9.5" 1805 | source = "registry+https://github.com/rust-lang/crates.io-index" 1806 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1807 | 1808 | [[package]] 1809 | name = "want" 1810 | version = "0.3.1" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1813 | dependencies = [ 1814 | "try-lock", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "warp" 1819 | version = "0.3.7" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" 1822 | dependencies = [ 1823 | "bytes", 1824 | "futures-channel", 1825 | "futures-util", 1826 | "headers", 1827 | "http 0.2.12", 1828 | "hyper 0.14.32", 1829 | "log", 1830 | "mime", 1831 | "mime_guess", 1832 | "multer", 1833 | "percent-encoding", 1834 | "pin-project", 1835 | "scoped-tls", 1836 | "serde", 1837 | "serde_json", 1838 | "serde_urlencoded", 1839 | "tokio", 1840 | "tokio-tungstenite", 1841 | "tokio-util", 1842 | "tower-service", 1843 | "tracing", 1844 | ] 1845 | 1846 | [[package]] 1847 | name = "wasi" 1848 | version = "0.11.0+wasi-snapshot-preview1" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1851 | 1852 | [[package]] 1853 | name = "wasm-bindgen" 1854 | version = "0.2.100" 1855 | source = "registry+https://github.com/rust-lang/crates.io-index" 1856 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1857 | dependencies = [ 1858 | "cfg-if", 1859 | "once_cell", 1860 | "rustversion", 1861 | "wasm-bindgen-macro", 1862 | ] 1863 | 1864 | [[package]] 1865 | name = "wasm-bindgen-backend" 1866 | version = "0.2.100" 1867 | source = "registry+https://github.com/rust-lang/crates.io-index" 1868 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1869 | dependencies = [ 1870 | "bumpalo", 1871 | "log", 1872 | "proc-macro2", 1873 | "quote", 1874 | "syn", 1875 | "wasm-bindgen-shared", 1876 | ] 1877 | 1878 | [[package]] 1879 | name = "wasm-bindgen-futures" 1880 | version = "0.4.50" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 1883 | dependencies = [ 1884 | "cfg-if", 1885 | "js-sys", 1886 | "once_cell", 1887 | "wasm-bindgen", 1888 | "web-sys", 1889 | ] 1890 | 1891 | [[package]] 1892 | name = "wasm-bindgen-macro" 1893 | version = "0.2.100" 1894 | source = "registry+https://github.com/rust-lang/crates.io-index" 1895 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1896 | dependencies = [ 1897 | "quote", 1898 | "wasm-bindgen-macro-support", 1899 | ] 1900 | 1901 | [[package]] 1902 | name = "wasm-bindgen-macro-support" 1903 | version = "0.2.100" 1904 | source = "registry+https://github.com/rust-lang/crates.io-index" 1905 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1906 | dependencies = [ 1907 | "proc-macro2", 1908 | "quote", 1909 | "syn", 1910 | "wasm-bindgen-backend", 1911 | "wasm-bindgen-shared", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "wasm-bindgen-shared" 1916 | version = "0.2.100" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1919 | dependencies = [ 1920 | "unicode-ident", 1921 | ] 1922 | 1923 | [[package]] 1924 | name = "web-sys" 1925 | version = "0.3.77" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 1928 | dependencies = [ 1929 | "js-sys", 1930 | "wasm-bindgen", 1931 | ] 1932 | 1933 | [[package]] 1934 | name = "web-time" 1935 | version = "1.1.0" 1936 | source = "registry+https://github.com/rust-lang/crates.io-index" 1937 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 1938 | dependencies = [ 1939 | "js-sys", 1940 | "wasm-bindgen", 1941 | ] 1942 | 1943 | [[package]] 1944 | name = "webpki-roots" 1945 | version = "0.26.8" 1946 | source = "registry+https://github.com/rust-lang/crates.io-index" 1947 | checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" 1948 | dependencies = [ 1949 | "rustls-pki-types", 1950 | ] 1951 | 1952 | [[package]] 1953 | name = "winapi" 1954 | version = "0.3.9" 1955 | source = "registry+https://github.com/rust-lang/crates.io-index" 1956 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1957 | dependencies = [ 1958 | "winapi-i686-pc-windows-gnu", 1959 | "winapi-x86_64-pc-windows-gnu", 1960 | ] 1961 | 1962 | [[package]] 1963 | name = "winapi-i686-pc-windows-gnu" 1964 | version = "0.4.0" 1965 | source = "registry+https://github.com/rust-lang/crates.io-index" 1966 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1967 | 1968 | [[package]] 1969 | name = "winapi-x86_64-pc-windows-gnu" 1970 | version = "0.4.0" 1971 | source = "registry+https://github.com/rust-lang/crates.io-index" 1972 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1973 | 1974 | [[package]] 1975 | name = "windows-link" 1976 | version = "0.1.0" 1977 | source = "registry+https://github.com/rust-lang/crates.io-index" 1978 | checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" 1979 | 1980 | [[package]] 1981 | name = "windows-registry" 1982 | version = "0.4.0" 1983 | source = "registry+https://github.com/rust-lang/crates.io-index" 1984 | checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" 1985 | dependencies = [ 1986 | "windows-result", 1987 | "windows-strings", 1988 | "windows-targets 0.53.0", 1989 | ] 1990 | 1991 | [[package]] 1992 | name = "windows-result" 1993 | version = "0.3.1" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" 1996 | dependencies = [ 1997 | "windows-link", 1998 | ] 1999 | 2000 | [[package]] 2001 | name = "windows-strings" 2002 | version = "0.3.1" 2003 | source = "registry+https://github.com/rust-lang/crates.io-index" 2004 | checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" 2005 | dependencies = [ 2006 | "windows-link", 2007 | ] 2008 | 2009 | [[package]] 2010 | name = "windows-sys" 2011 | version = "0.52.0" 2012 | source = "registry+https://github.com/rust-lang/crates.io-index" 2013 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2014 | dependencies = [ 2015 | "windows-targets 0.52.6", 2016 | ] 2017 | 2018 | [[package]] 2019 | name = "windows-sys" 2020 | version = "0.59.0" 2021 | source = "registry+https://github.com/rust-lang/crates.io-index" 2022 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2023 | dependencies = [ 2024 | "windows-targets 0.52.6", 2025 | ] 2026 | 2027 | [[package]] 2028 | name = "windows-targets" 2029 | version = "0.52.6" 2030 | source = "registry+https://github.com/rust-lang/crates.io-index" 2031 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2032 | dependencies = [ 2033 | "windows_aarch64_gnullvm 0.52.6", 2034 | "windows_aarch64_msvc 0.52.6", 2035 | "windows_i686_gnu 0.52.6", 2036 | "windows_i686_gnullvm 0.52.6", 2037 | "windows_i686_msvc 0.52.6", 2038 | "windows_x86_64_gnu 0.52.6", 2039 | "windows_x86_64_gnullvm 0.52.6", 2040 | "windows_x86_64_msvc 0.52.6", 2041 | ] 2042 | 2043 | [[package]] 2044 | name = "windows-targets" 2045 | version = "0.53.0" 2046 | source = "registry+https://github.com/rust-lang/crates.io-index" 2047 | checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" 2048 | dependencies = [ 2049 | "windows_aarch64_gnullvm 0.53.0", 2050 | "windows_aarch64_msvc 0.53.0", 2051 | "windows_i686_gnu 0.53.0", 2052 | "windows_i686_gnullvm 0.53.0", 2053 | "windows_i686_msvc 0.53.0", 2054 | "windows_x86_64_gnu 0.53.0", 2055 | "windows_x86_64_gnullvm 0.53.0", 2056 | "windows_x86_64_msvc 0.53.0", 2057 | ] 2058 | 2059 | [[package]] 2060 | name = "windows_aarch64_gnullvm" 2061 | version = "0.52.6" 2062 | source = "registry+https://github.com/rust-lang/crates.io-index" 2063 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2064 | 2065 | [[package]] 2066 | name = "windows_aarch64_gnullvm" 2067 | version = "0.53.0" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 2070 | 2071 | [[package]] 2072 | name = "windows_aarch64_msvc" 2073 | version = "0.52.6" 2074 | source = "registry+https://github.com/rust-lang/crates.io-index" 2075 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2076 | 2077 | [[package]] 2078 | name = "windows_aarch64_msvc" 2079 | version = "0.53.0" 2080 | source = "registry+https://github.com/rust-lang/crates.io-index" 2081 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 2082 | 2083 | [[package]] 2084 | name = "windows_i686_gnu" 2085 | version = "0.52.6" 2086 | source = "registry+https://github.com/rust-lang/crates.io-index" 2087 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2088 | 2089 | [[package]] 2090 | name = "windows_i686_gnu" 2091 | version = "0.53.0" 2092 | source = "registry+https://github.com/rust-lang/crates.io-index" 2093 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 2094 | 2095 | [[package]] 2096 | name = "windows_i686_gnullvm" 2097 | version = "0.52.6" 2098 | source = "registry+https://github.com/rust-lang/crates.io-index" 2099 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2100 | 2101 | [[package]] 2102 | name = "windows_i686_gnullvm" 2103 | version = "0.53.0" 2104 | source = "registry+https://github.com/rust-lang/crates.io-index" 2105 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 2106 | 2107 | [[package]] 2108 | name = "windows_i686_msvc" 2109 | version = "0.52.6" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2112 | 2113 | [[package]] 2114 | name = "windows_i686_msvc" 2115 | version = "0.53.0" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 2118 | 2119 | [[package]] 2120 | name = "windows_x86_64_gnu" 2121 | version = "0.52.6" 2122 | source = "registry+https://github.com/rust-lang/crates.io-index" 2123 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2124 | 2125 | [[package]] 2126 | name = "windows_x86_64_gnu" 2127 | version = "0.53.0" 2128 | source = "registry+https://github.com/rust-lang/crates.io-index" 2129 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 2130 | 2131 | [[package]] 2132 | name = "windows_x86_64_gnullvm" 2133 | version = "0.52.6" 2134 | source = "registry+https://github.com/rust-lang/crates.io-index" 2135 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2136 | 2137 | [[package]] 2138 | name = "windows_x86_64_gnullvm" 2139 | version = "0.53.0" 2140 | source = "registry+https://github.com/rust-lang/crates.io-index" 2141 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 2142 | 2143 | [[package]] 2144 | name = "windows_x86_64_msvc" 2145 | version = "0.52.6" 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" 2147 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2148 | 2149 | [[package]] 2150 | name = "windows_x86_64_msvc" 2151 | version = "0.53.0" 2152 | source = "registry+https://github.com/rust-lang/crates.io-index" 2153 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 2154 | 2155 | [[package]] 2156 | name = "write16" 2157 | version = "1.0.0" 2158 | source = "registry+https://github.com/rust-lang/crates.io-index" 2159 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2160 | 2161 | [[package]] 2162 | name = "writeable" 2163 | version = "0.5.5" 2164 | source = "registry+https://github.com/rust-lang/crates.io-index" 2165 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2166 | 2167 | [[package]] 2168 | name = "yoke" 2169 | version = "0.7.5" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2172 | dependencies = [ 2173 | "serde", 2174 | "stable_deref_trait", 2175 | "yoke-derive", 2176 | "zerofrom", 2177 | ] 2178 | 2179 | [[package]] 2180 | name = "yoke-derive" 2181 | version = "0.7.5" 2182 | source = "registry+https://github.com/rust-lang/crates.io-index" 2183 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2184 | dependencies = [ 2185 | "proc-macro2", 2186 | "quote", 2187 | "syn", 2188 | "synstructure", 2189 | ] 2190 | 2191 | [[package]] 2192 | name = "zerocopy" 2193 | version = "0.8.23" 2194 | source = "registry+https://github.com/rust-lang/crates.io-index" 2195 | checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" 2196 | dependencies = [ 2197 | "zerocopy-derive", 2198 | ] 2199 | 2200 | [[package]] 2201 | name = "zerocopy-derive" 2202 | version = "0.8.23" 2203 | source = "registry+https://github.com/rust-lang/crates.io-index" 2204 | checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" 2205 | dependencies = [ 2206 | "proc-macro2", 2207 | "quote", 2208 | "syn", 2209 | ] 2210 | 2211 | [[package]] 2212 | name = "zerofrom" 2213 | version = "0.1.6" 2214 | source = "registry+https://github.com/rust-lang/crates.io-index" 2215 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2216 | dependencies = [ 2217 | "zerofrom-derive", 2218 | ] 2219 | 2220 | [[package]] 2221 | name = "zerofrom-derive" 2222 | version = "0.1.6" 2223 | source = "registry+https://github.com/rust-lang/crates.io-index" 2224 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2225 | dependencies = [ 2226 | "proc-macro2", 2227 | "quote", 2228 | "syn", 2229 | "synstructure", 2230 | ] 2231 | 2232 | [[package]] 2233 | name = "zeroize" 2234 | version = "1.8.1" 2235 | source = "registry+https://github.com/rust-lang/crates.io-index" 2236 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2237 | 2238 | [[package]] 2239 | name = "zerovec" 2240 | version = "0.10.4" 2241 | source = "registry+https://github.com/rust-lang/crates.io-index" 2242 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2243 | dependencies = [ 2244 | "yoke", 2245 | "zerofrom", 2246 | "zerovec-derive", 2247 | ] 2248 | 2249 | [[package]] 2250 | name = "zerovec-derive" 2251 | version = "0.10.3" 2252 | source = "registry+https://github.com/rust-lang/crates.io-index" 2253 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2254 | dependencies = [ 2255 | "proc-macro2", 2256 | "quote", 2257 | "syn", 2258 | ] 2259 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "octopod" 3 | version = "0.1.3" 4 | edition = "2021" 5 | description = "A UI for Docker Registries" 6 | repository = "https://github.com/frectonz/octopod" 7 | 8 | [dependencies] 9 | clap = { version = "4.5.19", features = ["derive", "env"] } 10 | color-eyre = "0.6.3" 11 | include_dir = "0.7.4" 12 | reqwest = { version = "0.12.8", features = ["json", "rustls-tls"], default-features = false } 13 | serde_json = "1.0.128" 14 | tokio = { version = "1.44.2", features = ["full"] } 15 | tracing = "0.1.40" 16 | tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } 17 | warp = "0.3.7" 18 | 19 | # The profile that 'cargo dist' will build with 20 | [profile.dist] 21 | inherits = "release" 22 | lto = "thin" 23 | 24 | # Config for 'cargo dist' 25 | [workspace.metadata.dist] 26 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 27 | cargo-dist-version = "0.22.1" 28 | # CI backends to support 29 | ci = "github" 30 | # The installers to generate for each app 31 | installers = ["shell", "powershell"] 32 | # Target platforms to build apps for (Rust target-triple syntax) 33 | targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"] 34 | # Path that installers should place binaries in 35 | install-path = "CARGO_HOME" 36 | # Skip checking whether the specified configuration files are up to date 37 | allow-dirty = ["ci"] 38 | # Whether to install an updater program 39 | install-updater = true 40 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | default: 2 | @just --choose 3 | 4 | api: 5 | hurl --variables-file vars.env --test api.hurl 6 | 7 | api-proxy: 8 | hurl --variable address=http://localhost:3030/api --test local.hurl 9 | 10 | run: 11 | cd ui; elm make src/Main.elm --debug --output Main.js 12 | cargo run 13 | 14 | dev: 15 | watchexec -r just run 16 | 17 | fmt: 18 | cd ui; elm-format . --yes 19 | cargo fmt 20 | nix fmt 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 frectonz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Octopod 2 | 3 | A UI for Docker Registries 4 | 5 | ``` 6 | A UI for Docker Registries 7 | 8 | Usage: octopod [OPTIONS] --registry-url 9 | 10 | Options: 11 | --address
12 | The address to bind to [env: ADDRESS=] [default: 127.0.0.1:3030] 13 | --registry-url 14 | Registry URL to connect to. Example [http://127.0.0.1:3030] [env: REGISTRY_URL=] 15 | --registry-credentials 16 | Registry username and password separated with a colon. Example [username:password] [env: REGISTRY_CREDENTIALS=] 17 | -h, --help 18 | Print help 19 | -V, --version 20 | Print version 21 | ``` 22 | 23 | ## Run it with Docker 24 | 25 | ``` 26 | docker run -p 3030:3030 \ 27 | -e ADDRESS="0.0.0.0:3030" \ 28 | -e REGISTRY_URL="" \ 29 | -e REGISTRY_CREDENTIALS=":" \ 30 | frectonz/octopod 31 | ``` 32 | 33 | ## Install 34 | 35 | ### Linux and MacOS 36 | 37 | ``` 38 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/frectonz/octopod/releases/download/0.1.3/octopod-installer.sh | sh 39 | ``` 40 | 41 | ### Windows 42 | 43 | ``` 44 | powershell -ExecutionPolicy ByPass -c "irm https://github.com/frectonz/octopod/releases/download/0.1.3/octopod-installer.ps1 | iex" 45 | ``` 46 | 47 | ### Nix 48 | 49 | ``` 50 | nix run github:frectonz/octopod 51 | ``` 52 | 53 | ## Screenshots 54 | 55 | ### Repositories Page 56 | ![repositories](./screenshots/1.png) 57 | 58 | ### Tags Page 59 | ![tags](./screenshots/2.png) 60 | 61 | ### Image Page 62 | ![image](./screenshots/3.png) 63 | -------------------------------------------------------------------------------- /api.hurl: -------------------------------------------------------------------------------- 1 | # INITIAL CHECK 2 | GET {{address}}/v2/ 3 | [Options] 4 | user: {{auth}} 5 | 6 | HTTP 200 7 | Content-Type: application/json 8 | 9 | {} 10 | 11 | 12 | # GET IMAGES CATALOG 13 | GET {{address}}/v2/_catalog 14 | [Options] 15 | user: {{auth}} 16 | 17 | HTTP 200 18 | Content-Type: application/json 19 | 20 | [Asserts] 21 | jsonpath "$.repositories" count > 0 22 | 23 | [Captures] 24 | image_name: jsonpath "$.repositories[0]" 25 | 26 | 27 | # GET IMAGE TAGS 28 | GET {{address}}/v2/{{image_name}}/tags/list 29 | [Options] 30 | user: {{auth}} 31 | 32 | HTTP 200 33 | Content-Type: application/json 34 | 35 | [Asserts] 36 | jsonpath "$.name" == {{image_name}} 37 | jsonpath "$.tags" count > 0 38 | 39 | [Captures] 40 | tag_name: jsonpath "$.tags[0]" 41 | 42 | 43 | # GET TAG MANIFEST 44 | GET {{address}}/v2/{{image_name}}/manifests/{{tag_name}} 45 | [Options] 46 | user: {{auth}} 47 | 48 | HTTP 200 49 | Content-Type: application/vnd.docker.distribution.manifest.v2+json 50 | 51 | [Asserts] 52 | jsonpath "$.schemaVersion" == 2 53 | jsonpath "$.mediaType" == "application/vnd.docker.distribution.manifest.v2+json" 54 | 55 | jsonpath "$.config.mediaType" =="application/vnd.docker.container.image.v1+json" 56 | jsonpath "$.config.digest" split ":" nth 0 == "sha256" 57 | jsonpath "$.config.size" > 0 58 | 59 | jsonpath "$.layers" count > 0 60 | jsonpath "$.layers[0].mediaType" == "application/vnd.docker.image.rootfs.diff.tar.gzip" 61 | jsonpath "$.layers[0].digest" split ":" nth 0 == "sha256" 62 | jsonpath "$.layers[0].size" > 0 63 | 64 | [Captures] 65 | config_digest: jsonpath "$.config.digest" 66 | 67 | 68 | # GET IMAGE CONFIG 69 | GET {{address}}/v2/{{image_name}}/blobs/{{config_digest}} 70 | [Options] 71 | user: {{auth}} 72 | 73 | HTTP 200 74 | Content-Type: application/octet-stream 75 | Docker-Content-Digest: {{config_digest}} 76 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1731139594, 6 | "narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "rust-overlay": "rust-overlay" 23 | } 24 | }, 25 | "rust-overlay": { 26 | "inputs": { 27 | "nixpkgs": [ 28 | "nixpkgs" 29 | ] 30 | }, 31 | "locked": { 32 | "lastModified": 1731464916, 33 | "narHash": "sha256-WZ5rpjr/wCt7yBOUsvDE2i22hYz9g8W921jlwVktRQ4=", 34 | "owner": "oxalica", 35 | "repo": "rust-overlay", 36 | "rev": "2c19bad6e881b5a154cafb7f9106879b5b356d1f", 37 | "type": "github" 38 | }, 39 | "original": { 40 | "owner": "oxalica", 41 | "repo": "rust-overlay", 42 | "type": "github" 43 | } 44 | } 45 | }, 46 | "root": "root", 47 | "version": 7 48 | } 49 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; 4 | rust-overlay = { 5 | url = "github:oxalica/rust-overlay"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | }; 9 | 10 | outputs = { self, nixpkgs, rust-overlay }: 11 | let 12 | systems = [ "x86_64-linux" ]; 13 | 14 | forAllSystems = function: 15 | nixpkgs.lib.genAttrs systems (system: 16 | function (import nixpkgs { 17 | inherit system; 18 | overlays = [ (import rust-overlay) ]; 19 | })); 20 | in { 21 | devShells = forAllSystems (pkgs: { 22 | default = pkgs.mkShell { 23 | nativeBuildInputs = [ 24 | pkgs.fzf 25 | pkgs.just 26 | pkgs.hurl 27 | pkgs.elm2nix 28 | pkgs.watchexec 29 | pkgs.cargo-dist 30 | pkgs.rust-analyzer 31 | pkgs.elmPackages.elm 32 | pkgs.elmPackages.elm-format 33 | pkgs.rust-bin.stable.latest.default 34 | pkgs.elmPackages.elm-language-server 35 | pkgs.nodePackages.vscode-langservers-extracted 36 | 37 | (pkgs.writeShellApplication { 38 | name = "run"; 39 | text = "watchexec -r just run"; 40 | }) 41 | ]; 42 | }; 43 | }); 44 | 45 | packages = forAllSystems (pkgs: 46 | let 47 | ui = pkgs.callPackage ./ui/default.nix { }; 48 | 49 | rustPlatform = pkgs.makeRustPlatform { 50 | cargo = pkgs.rust-bin.stable.latest.default; 51 | rustc = pkgs.rust-bin.stable.latest.default; 52 | }; 53 | 54 | pname = "octopod"; 55 | version = "0.1.3"; 56 | in rec { 57 | default = rustPlatform.buildRustPackage { 58 | inherit pname version; 59 | src = ./.; 60 | 61 | preBuild = '' 62 | cp ${ui}/Main.js ui/Main.js 63 | ''; 64 | 65 | cargoLock.lockFile = ./Cargo.lock; 66 | }; 67 | 68 | image = pkgs.dockerTools.buildLayeredImage { 69 | name = pname; 70 | tag = "latest"; 71 | created = "now"; 72 | config.Cmd = [ "${default}/bin/octopod" ]; 73 | }; 74 | 75 | deploy = pkgs.writeShellScriptBin "deploy" '' 76 | ${pkgs.skopeo}/bin/skopeo --insecure-policy copy docker-archive:${image} docker://docker.io/frectonz/octopod:${version} --dest-creds="frectonz:$ACCESS_TOKEN" 77 | ${pkgs.skopeo}/bin/skopeo --insecure-policy copy docker://docker.io/frectonz/octopod:${version} docker://docker.io/frectonz/octopod:latest --dest-creds="frectonz:$ACCESS_TOKEN" 78 | ''; 79 | }); 80 | 81 | formatter = forAllSystems (pkgs: pkgs.nixfmt-classic); 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /local.hurl: -------------------------------------------------------------------------------- 1 | # INITIAL CHECK 2 | GET {{address}}/v2/ 3 | 4 | HTTP 200 5 | Content-Type: application/json 6 | 7 | {} 8 | 9 | 10 | # GET IMAGES CATALOG 11 | GET {{address}}/v2/_catalog 12 | 13 | HTTP 200 14 | Content-Type: application/json 15 | 16 | [Asserts] 17 | jsonpath "$.repositories" count > 0 18 | 19 | [Captures] 20 | image_name: jsonpath "$.repositories[0]" 21 | 22 | 23 | # GET IMAGE TAGS 24 | GET {{address}}/v2/{{image_name}}/tags/list 25 | 26 | HTTP 200 27 | Content-Type: application/json 28 | 29 | [Asserts] 30 | jsonpath "$.name" == {{image_name}} 31 | jsonpath "$.tags" count > 0 32 | 33 | [Captures] 34 | tag_name: jsonpath "$.tags[0]" 35 | 36 | 37 | # GET TAG MANIFEST 38 | GET {{address}}/v2/{{image_name}}/manifests/{{tag_name}} 39 | 40 | HTTP 200 41 | Content-Type: application/json 42 | 43 | [Asserts] 44 | jsonpath "$.schemaVersion" == 2 45 | jsonpath "$.mediaType" == "application/vnd.docker.distribution.manifest.v2+json" 46 | 47 | jsonpath "$.config.mediaType" =="application/vnd.docker.container.image.v1+json" 48 | jsonpath "$.config.digest" split ":" nth 0 == "sha256" 49 | jsonpath "$.config.size" > 0 50 | 51 | jsonpath "$.layers" count > 0 52 | jsonpath "$.layers[0].mediaType" == "application/vnd.docker.image.rootfs.diff.tar.gzip" 53 | jsonpath "$.layers[0].digest" split ":" nth 0 == "sha256" 54 | jsonpath "$.layers[0].size" > 0 55 | 56 | [Captures] 57 | config_digest: jsonpath "$.config.digest" 58 | 59 | 60 | # GET IMAGE CONFIG 61 | GET {{address}}/v2/{{image_name}}/blobs/{{config_digest}} 62 | 63 | HTTP 200 64 | Content-Type: application/json 65 | -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frectonz/octopod/276e10cd4ebb5b0b0f9c9c278190f83206e6c642/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frectonz/octopod/276e10cd4ebb5b0b0f9c9c278190f83206e6c642/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frectonz/octopod/276e10cd4ebb5b0b0f9c9c278190f83206e6c642/screenshots/3.png -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use fetcher::Fetcher; 3 | use warp::Filter; 4 | 5 | #[derive(Debug, Parser)] 6 | #[command(version, about)] 7 | struct Arguments { 8 | /// The address to bind to. 9 | #[arg(long, env, default_value = "127.0.0.1:3030")] 10 | address: Box, 11 | 12 | /// Registry URL to connect to. Example [http://127.0.0.1:3030] 13 | #[arg(long, env)] 14 | registry_url: Box, 15 | 16 | /// Registry username and password separated with a colon. Example [username:password] 17 | #[arg(long, env)] 18 | registry_credentials: Option, 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | struct Credentials { 23 | username: Box, 24 | password: Box, 25 | } 26 | 27 | impl std::str::FromStr for Credentials { 28 | type Err = String; 29 | 30 | fn from_str(s: &str) -> Result { 31 | let parts: Vec<&str> = s.split(':').collect(); 32 | if parts.len() != 2 { 33 | return Err("credentials must be in the format 'username:password'".to_string()); 34 | } 35 | Ok(Credentials { 36 | username: Box::from(parts[0]), 37 | password: Box::from(parts[1]), 38 | }) 39 | } 40 | } 41 | 42 | #[tokio::main] 43 | async fn main() -> color_eyre::Result<()> { 44 | color_eyre::install()?; 45 | 46 | let filter = std::env::var("RUST_LOG") 47 | .unwrap_or_else(|_| "tracing=info,warp=debug,octopod=debug".to_owned()); 48 | tracing_subscriber::fmt() 49 | .with_env_filter(filter) 50 | .with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE) 51 | .init(); 52 | 53 | let args = Arguments::parse(); 54 | 55 | let index_html = statics::get_index_html(&args.registry_url); 56 | 57 | let fetcher = Fetcher::new(&args.registry_url, args.registry_credentials); 58 | fetcher.check_auth().await?; 59 | 60 | let routes = statics::main_js() 61 | .or(statics::files()) 62 | .or(api::hander(fetcher)) 63 | .or(statics::index_html(index_html)); 64 | 65 | let address = args.address.parse::()?; 66 | warp::serve(routes).run(address).await; 67 | 68 | Ok(()) 69 | } 70 | 71 | mod statics { 72 | use std::path::Path; 73 | 74 | use include_dir::{include_dir, Dir}; 75 | use warp::{ 76 | http::{ 77 | header::{CACHE_CONTROL, CONTENT_TYPE}, 78 | Response, 79 | }, 80 | Filter, 81 | }; 82 | 83 | pub fn get_index_html(registry_url: &str) -> String { 84 | const INDEX_HTML: &str = include_str!("../ui/index.html"); 85 | 86 | let version = format!( 87 | r#""#, 88 | env!("CARGO_PKG_VERSION") 89 | ); 90 | let registry_url = format!(r#""#, registry_url); 91 | 92 | INDEX_HTML 93 | .replace("", &version) 94 | .replace("", ®istry_url) 95 | } 96 | 97 | pub fn index_html( 98 | page: String, 99 | ) -> impl Filter + Clone { 100 | warp::get() 101 | .and(warp::any().map(move || page.clone())) 102 | .map(|contents| { 103 | Response::builder() 104 | .header(CONTENT_TYPE, "text/html") 105 | .body(contents) 106 | .unwrap() 107 | }) 108 | } 109 | 110 | pub fn main_js() -> impl Filter + Clone 111 | { 112 | const MAIN_JS: &str = include_str!("../ui/Main.js"); 113 | 114 | warp::path!("Main.js").map(|| { 115 | Response::builder() 116 | .header(CONTENT_TYPE, "text/javascript") 117 | .body(MAIN_JS) 118 | .unwrap() 119 | }) 120 | } 121 | 122 | static STATIC_DIR: Dir = include_dir!("statics"); 123 | 124 | async fn send_file(path: warp::path::Tail) -> Result { 125 | let path = Path::new(path.as_str()); 126 | let file = STATIC_DIR 127 | .get_file(path) 128 | .ok_or_else(warp::reject::not_found)?; 129 | 130 | let content_type = match file.path().extension() { 131 | Some(ext) if ext == "css" => "text/css", 132 | Some(ext) if ext == "svg" => "image/svg+xml", 133 | Some(ext) if ext == "js" => "text/javascript", 134 | Some(ext) if ext == "html" => "text/html", 135 | _ => "application/octet-stream", 136 | }; 137 | 138 | let resp = Response::builder() 139 | .header(CONTENT_TYPE, content_type) 140 | .header(CACHE_CONTROL, "max-age=3600, must-revalidate") 141 | .body(file.contents()) 142 | .unwrap(); 143 | 144 | Ok(resp) 145 | } 146 | 147 | pub fn files() -> impl Filter + Clone { 148 | warp::get() 149 | .and(warp::path::path("statics")) 150 | .and(warp::path::tail()) 151 | .and_then(send_file) 152 | } 153 | } 154 | 155 | mod fetcher { 156 | use reqwest::Client; 157 | 158 | use crate::Credentials; 159 | 160 | #[derive(Clone)] 161 | pub struct Fetcher { 162 | client: Client, 163 | url: Box, 164 | auth: Option, 165 | } 166 | 167 | impl Fetcher { 168 | pub fn new(url: &str, auth: Option) -> Self { 169 | Self { 170 | client: Client::new(), 171 | url: Box::from(url), 172 | auth, 173 | } 174 | } 175 | 176 | pub async fn fetch(&self, url: &str) -> color_eyre::Result { 177 | let req = self.client.get(format!("{}/{url}", self.url)); 178 | 179 | let resp = match &self.auth { 180 | Some(Credentials { username, password }) => { 181 | req.basic_auth(username, Some(password)) 182 | } 183 | None => req, 184 | } 185 | .send() 186 | .await? 187 | .json() 188 | .await?; 189 | 190 | Ok(resp) 191 | } 192 | 193 | pub async fn check_auth(&self) -> color_eyre::Result<()> { 194 | let resp = self.fetch("v2").await?; 195 | 196 | if resp == serde_json::json!({}) { 197 | tracing::info!("registry connection check succeeded"); 198 | Ok(()) 199 | } else { 200 | color_eyre::eyre::bail!("registry connection check failed") 201 | } 202 | } 203 | } 204 | } 205 | 206 | mod api { 207 | use warp::Filter; 208 | 209 | use crate::fetcher::Fetcher; 210 | 211 | async fn send_request( 212 | fetcher: Fetcher, 213 | path: warp::path::Tail, 214 | ) -> Result { 215 | match fetcher.fetch(path.as_str()).await { 216 | Ok(json) => Ok(warp::reply::json(&json)), 217 | Err(error) => { 218 | tracing::error!( 219 | "encountered an error sending request to server: {path:?} {error:?}" 220 | ); 221 | Err(warp::reject()) 222 | } 223 | } 224 | } 225 | 226 | pub fn hander( 227 | fetcher: Fetcher, 228 | ) -> impl Filter + Clone { 229 | warp::get() 230 | .and(warp::path::path("api")) 231 | .and(warp::any().map(move || fetcher.clone())) 232 | .and(warp::path::tail()) 233 | .and_then(send_request) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /statics/box.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /statics/boxes.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /statics/github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /statics/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /statics/move-up-right.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /statics/radio.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /statics/reset.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | * { 8 | margin: 0; 9 | } 10 | 11 | body { 12 | -webkit-font-smoothing: antialiased; 13 | } 14 | 15 | img, 16 | picture, 17 | video, 18 | canvas, 19 | svg { 20 | display: block; 21 | max-width: 100%; 22 | } 23 | 24 | input, 25 | button, 26 | textarea, 27 | select { 28 | font: inherit; 29 | } 30 | 31 | p, 32 | h1, 33 | h2, 34 | h3, 35 | h4, 36 | h5, 37 | h6 { 38 | overflow-wrap: break-word; 39 | } -------------------------------------------------------------------------------- /statics/tag.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | Main.js 3 | -------------------------------------------------------------------------------- /ui/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | 3 | with pkgs; 4 | 5 | let 6 | mkDerivation = { srcs ? ./elm-srcs.nix, src, name, srcdir ? "./src" 7 | , targets ? [ ], registryDat ? ./registry.dat, outputJavaScript ? false }: 8 | stdenv.mkDerivation { 9 | inherit name src; 10 | 11 | buildInputs = [ elmPackages.elm ] 12 | ++ lib.optional outputJavaScript nodePackages.uglify-js; 13 | 14 | buildPhase = pkgs.elmPackages.fetchElmDeps { 15 | elmPackages = import srcs; 16 | elmVersion = "0.19.1"; 17 | inherit registryDat; 18 | }; 19 | 20 | installPhase = let 21 | elmfile = module: 22 | "${srcdir}/${builtins.replaceStrings [ "." ] [ "/" ] module}.elm"; 23 | extension = if outputJavaScript then "js" else "html"; 24 | in '' 25 | mkdir -p $out/share/doc 26 | ${lib.concatStrings (map (module: '' 27 | echo "compiling ${elmfile module}" 28 | elm make ${ 29 | elmfile module 30 | } --optimize --output $out/${module}.${extension} --docs $out/share/doc/${module}.json 31 | ${lib.optionalString outputJavaScript '' 32 | echo "minifying ${elmfile module}" 33 | uglifyjs $out/${module}.${extension} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \ 34 | | uglifyjs --mangle --output $out/${module}.min.${extension} 35 | ''} 36 | '') targets)} 37 | ''; 38 | }; 39 | in mkDerivation { 40 | name = "octopod-ui"; 41 | srcs = ./elm-srcs.nix; 42 | src = ./.; 43 | targets = [ "Main" ]; 44 | srcdir = "./src"; 45 | outputJavaScript = true; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /ui/elm-srcs.nix: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "basti1302/elm-human-readable-filesize" = { 4 | sha256 = "182hjyji8hj5zzn81f676p83fa15dxq5fgqq9li384qk33m5sh05"; 5 | version = "1.2.0"; 6 | }; 7 | 8 | "elm/browser" = { 9 | sha256 = "0nagb9ajacxbbg985r4k9h0jadqpp0gp84nm94kcgbr5sf8i9x13"; 10 | version = "1.0.2"; 11 | }; 12 | 13 | "elm/core" = { 14 | sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf"; 15 | version = "1.0.5"; 16 | }; 17 | 18 | "elm/html" = { 19 | sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k"; 20 | version = "1.0.0"; 21 | }; 22 | 23 | "elm/http" = { 24 | sha256 = "008bs76mnp48b4dw8qwjj4fyvzbxvlrl4xpa2qh1gg2kfwyw56v1"; 25 | version = "2.0.0"; 26 | }; 27 | 28 | "elm/json" = { 29 | sha256 = "0kjwrz195z84kwywaxhhlnpl3p251qlbm5iz6byd6jky2crmyqyh"; 30 | version = "1.1.3"; 31 | }; 32 | 33 | "elm/time" = { 34 | sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1"; 35 | version = "1.0.0"; 36 | }; 37 | 38 | "elm/url" = { 39 | sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4"; 40 | version = "1.0.0"; 41 | }; 42 | 43 | "rtfeldman/elm-iso8601-date-strings" = { 44 | sha256 = "1ah491kgyicgvy1c9myylqvhzb7ya9kgmn0hcsv23ymvqgaf6b1a"; 45 | version = "1.1.4"; 46 | }; 47 | 48 | "ryan-haskell/date-format" = { 49 | sha256 = "18r9h72h3i507snjf5aw099s2ymv2qsr3x3ibnsmap407s98355y"; 50 | version = "1.0.0"; 51 | }; 52 | 53 | "elm/bytes" = { 54 | sha256 = "02ywbf52akvxclpxwj9n04jydajcbsbcbsnjs53yjc5lwck3abwj"; 55 | version = "1.0.8"; 56 | }; 57 | 58 | "elm/file" = { 59 | sha256 = "1rljcb41dl97myidyjih2yliyzddkr2m7n74x7gg46rcw4jl0ny8"; 60 | version = "1.0.5"; 61 | }; 62 | 63 | "elm/parser" = { 64 | sha256 = "0a3cxrvbm7mwg9ykynhp7vjid58zsw03r63qxipxp3z09qks7512"; 65 | version = "1.1.0"; 66 | }; 67 | 68 | "elm/regex" = { 69 | sha256 = "0lijsp50w7n1n57mjg6clpn9phly8vvs07h0qh2rqcs0f1jqvsa2"; 70 | version = "1.0.0"; 71 | }; 72 | 73 | "elm/virtual-dom" = { 74 | sha256 = "1yvb8px2z62xd578ag2q0r5hd1vkz9y7dfkx05355iiy1d7jwq4v"; 75 | version = "1.0.3"; 76 | }; 77 | 78 | "myrho/elm-round" = { 79 | sha256 = "0zv0a60wzkx4xib7h07ijcg72mcyb3vb914hk5pjp6rf4k2lv9kj"; 80 | version = "1.0.5"; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /ui/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "basti1302/elm-human-readable-filesize": "1.2.0", 10 | "elm/browser": "1.0.2", 11 | "elm/core": "1.0.5", 12 | "elm/html": "1.0.0", 13 | "elm/http": "2.0.0", 14 | "elm/json": "1.1.3", 15 | "elm/time": "1.0.0", 16 | "elm/url": "1.0.0", 17 | "rtfeldman/elm-iso8601-date-strings": "1.1.4", 18 | "ryan-haskell/date-format": "1.0.0" 19 | }, 20 | "indirect": { 21 | "elm/bytes": "1.0.8", 22 | "elm/file": "1.0.5", 23 | "elm/parser": "1.1.0", 24 | "elm/regex": "1.0.0", 25 | "elm/virtual-dom": "1.0.3", 26 | "myrho/elm-round": "1.0.5" 27 | } 28 | }, 29 | "test-dependencies": { 30 | "direct": {}, 31 | "indirect": {} 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Octopod 9 | 10 | 11 | 12 | 13 | 14 | 143 | 144 | 145 | 146 | 147 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /ui/src/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Browser 4 | import Browser.Navigation as Nav 5 | import DateFormat 6 | import Filesize 7 | import Html exposing (..) 8 | import Html.Attributes exposing (..) 9 | import Http 10 | import Iso8601 11 | import Json.Decode as Decode exposing (Decoder) 12 | import Time exposing (Posix) 13 | import Url exposing (Protocol(..), Url) 14 | import Url.Parser as Parser exposing ((), Parser) 15 | import Url.Parser.Query as Query 16 | 17 | 18 | 19 | -- MAIN 20 | 21 | 22 | main : Program Metadata Model Msg 23 | main = 24 | Browser.application 25 | { init = init 26 | , view = view 27 | , update = update 28 | , subscriptions = subscriptions 29 | , onUrlChange = UrlChanged 30 | , onUrlRequest = LinkClicked 31 | } 32 | 33 | 34 | 35 | -- MODEL 36 | 37 | 38 | type alias Model = 39 | { key : Nav.Key 40 | , meta : Metadata 41 | , page : Page 42 | } 43 | 44 | 45 | type alias Metadata = 46 | { version : String 47 | , registryUrl : String 48 | } 49 | 50 | 51 | type Route 52 | = Index 53 | | Repo (Maybe String) 54 | | Tag (Maybe String) (Maybe String) 55 | 56 | 57 | type Page 58 | = HomePage (RemoteData Catalog) 59 | | SingleRepoPage String (RemoteData Repository) 60 | | ImagePage String String (RemoteData Image) (RemoteData ImageBlob) 61 | 62 | 63 | type RemoteData value 64 | = Failure 65 | | Loading 66 | | Success value 67 | 68 | 69 | type alias Catalog = 70 | { repositories : List String 71 | } 72 | 73 | 74 | type alias Repository = 75 | { name : String 76 | , tags : List String 77 | } 78 | 79 | 80 | type alias Image = 81 | { config : ImageDigest 82 | , layers : List ImageDigest 83 | , mediaType : String 84 | , schemaVersion : Int 85 | } 86 | 87 | 88 | type alias ImageDigest = 89 | { digest : String 90 | , mediaType : String 91 | , size : Int 92 | } 93 | 94 | 95 | type alias ImageBlob = 96 | { architecture : String 97 | , os : String 98 | , cmd : List String 99 | , createdAt : String 100 | } 101 | 102 | 103 | init : Metadata -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) 104 | init meta url key = 105 | let 106 | route = 107 | parseUrl url 108 | in 109 | ( { key = key 110 | , meta = meta 111 | , page = 112 | case route of 113 | Index -> 114 | HomePage Loading 115 | 116 | Repo (Just name) -> 117 | SingleRepoPage name Loading 118 | 119 | Tag (Just repo) (Just tag) -> 120 | ImagePage repo tag Loading Loading 121 | 122 | _ -> 123 | HomePage Loading 124 | } 125 | , case route of 126 | Index -> 127 | getCatalog 128 | 129 | Repo (Just name) -> 130 | getRepo name 131 | 132 | Tag (Just repo) (Just tag) -> 133 | getImage repo tag 134 | 135 | _ -> 136 | getCatalog 137 | ) 138 | 139 | 140 | 141 | -- UPDATE 142 | 143 | 144 | type Msg 145 | = LinkClicked Browser.UrlRequest 146 | | UrlChanged Url.Url 147 | | GotCatalog (Result Http.Error Catalog) 148 | | GotRepository (Result Http.Error Repository) 149 | | GotImage (Result Http.Error Image) 150 | | GotImageBlob (Result Http.Error ImageBlob) 151 | 152 | 153 | update : Msg -> Model -> ( Model, Cmd Msg ) 154 | update msg model = 155 | case ( msg, model.page ) of 156 | ( LinkClicked urlRequest, _ ) -> 157 | case urlRequest of 158 | Browser.Internal url -> 159 | let 160 | ( newModel, newCmd ) = 161 | init model.meta url model.key 162 | in 163 | ( newModel, Cmd.batch [ Nav.pushUrl model.key (Url.toString url), newCmd ] ) 164 | 165 | Browser.External href -> 166 | ( model, Nav.load href ) 167 | 168 | ( UrlChanged url, page ) -> 169 | let 170 | route = 171 | parseUrl url 172 | 173 | pageChanged = 174 | case ( route, page ) of 175 | ( Index, HomePage _ ) -> 176 | False 177 | 178 | ( Repo _, SingleRepoPage _ _ ) -> 179 | False 180 | 181 | ( Tag _ _, ImagePage _ _ _ _ ) -> 182 | False 183 | 184 | _ -> 185 | True 186 | in 187 | if pageChanged then 188 | init model.meta url model.key 189 | 190 | else 191 | ( model, Cmd.none ) 192 | 193 | ( GotCatalog result, HomePage _ ) -> 194 | case result of 195 | Ok catalog -> 196 | ( { model | page = HomePage (Success catalog) }, Cmd.none ) 197 | 198 | Err _ -> 199 | ( { model | page = HomePage Failure }, Cmd.none ) 200 | 201 | ( GotRepository result, SingleRepoPage name _ ) -> 202 | case result of 203 | Ok repo -> 204 | ( { model | page = SingleRepoPage name (Success repo) }, Cmd.none ) 205 | 206 | Err _ -> 207 | ( { model | page = SingleRepoPage name Failure }, Cmd.none ) 208 | 209 | ( GotImage result, ImagePage repo tag _ blob ) -> 210 | case result of 211 | Ok image -> 212 | ( { model | page = ImagePage repo tag (Success image) blob }, getImageBlob repo image.config.digest ) 213 | 214 | Err _ -> 215 | ( { model | page = ImagePage repo tag Failure blob }, Cmd.none ) 216 | 217 | ( GotImageBlob result, ImagePage repo tag imageData _ ) -> 218 | case result of 219 | Ok blob -> 220 | ( { model | page = ImagePage repo tag imageData (Success blob) }, Cmd.none ) 221 | 222 | Err _ -> 223 | ( { model | page = ImagePage repo tag imageData Failure }, Cmd.none ) 224 | 225 | _ -> 226 | ( model, Cmd.none ) 227 | 228 | 229 | 230 | -- SUBSCRIPTIONS 231 | 232 | 233 | subscriptions : Model -> Sub Msg 234 | subscriptions _ = 235 | Sub.none 236 | 237 | 238 | 239 | -- VIEW 240 | 241 | 242 | view : Model -> Browser.Document Msg 243 | view model = 244 | let 245 | pluralize singular plural num = 246 | if num == 1 then 247 | singular 248 | 249 | else 250 | plural 251 | 252 | stat = 253 | case model.page of 254 | HomePage (Success catalog) -> 255 | "Found " ++ (catalog.repositories |> List.length |> String.fromInt) ++ pluralize " repository" " repositories" (catalog.repositories |> List.length) 256 | 257 | SingleRepoPage _ (Success repo) -> 258 | "Found " ++ (repo.tags |> List.length |> String.fromInt) ++ pluralize " tag" " tags" (repo.tags |> List.length) 259 | 260 | ImagePage _ _ (Success image) _ -> 261 | "Found " ++ (image.layers |> List.length |> String.fromInt) ++ pluralize " layer" " layers" (image.layers |> List.length) 262 | 263 | _ -> 264 | "" 265 | in 266 | { title = "Octopod" 267 | , body = 268 | [ viewHeader model.meta.version 269 | , viewStat model.meta.registryUrl stat 270 | ] 271 | ++ (case model.page of 272 | HomePage data -> 273 | viewHomePage data 274 | 275 | SingleRepoPage name data -> 276 | viewSingleRepoPage name data 277 | 278 | ImagePage repo tag data blob -> 279 | viewImagePage model.meta.registryUrl (repo ++ ":" ++ tag) data blob 280 | ) 281 | } 282 | 283 | 284 | viewHeader : String -> Html msg 285 | viewHeader version = 286 | header [ class "header" ] 287 | [ div [ class "header__main" ] 288 | [ img [ src "/statics/logo.svg", class "header__logo" ] [] 289 | , h1 [ class "header__title" ] [ a [ href "/" ] [ text "Octopod" ] ] 290 | ] 291 | , div [ class "header__nav" ] 292 | [ p [] [ text ("[" ++ version ++ "]") ] 293 | , a [ href "https://github.com/frectonz/octopod" ] [ img [ src "/statics/github.svg" ] [] ] 294 | ] 295 | ] 296 | 297 | 298 | viewStat : String -> String -> Html msg 299 | viewStat registryUrl stat = 300 | section [ class "status" ] 301 | [ div [ class "status__registry" ] 302 | [ img [ src "/statics/radio.svg" ] [] 303 | , p [] [ text ("Connected to " ++ registryUrl) ] 304 | ] 305 | , div [ class "status__repositories" ] 306 | [ img [ src "/statics/boxes.svg" ] [] 307 | , p [] [ text stat ] 308 | ] 309 | ] 310 | 311 | 312 | viewPageTitle : String -> Html msg 313 | viewPageTitle title = 314 | section [ class "page__title" ] 315 | [ h1 [] [ text title ] 316 | ] 317 | 318 | 319 | viewHomePage : RemoteData Catalog -> List (Html msg) 320 | viewHomePage data = 321 | viewPageTitle "Repositories" 322 | :: viewRepositories data 323 | 324 | 325 | viewRepositories : RemoteData Catalog -> List (Html msg) 326 | viewRepositories data = 327 | case data of 328 | Loading -> 329 | [ section [ class "loading__title" ] [ h1 [] [ text "Loading..." ] ] ] 330 | 331 | Failure -> 332 | [ section [ class "failure__title" ] [ h1 [] [ text "Something went wrong" ] ] ] 333 | 334 | Success catalog -> 335 | catalog.repositories 336 | |> List.map 337 | (\repo -> 338 | section [ class "repo" ] 339 | [ div [] [ img [ src "/statics/box.svg" ] [] ] 340 | , div [] [ h1 [] [ a [ href ("/repos?r=" ++ repo) ] [ text repo ] ] ] 341 | , div [] [ img [ src "/statics/move-up-right.svg" ] [] ] 342 | ] 343 | ) 344 | 345 | 346 | viewSingleRepoPage : String -> RemoteData Repository -> List (Html msg) 347 | viewSingleRepoPage name data = 348 | viewPageTitle name 349 | :: viewRepoDetails data 350 | 351 | 352 | viewRepoDetails : RemoteData Repository -> List (Html msg) 353 | viewRepoDetails data = 354 | case data of 355 | Loading -> 356 | [ section [ class "loading__title" ] [ h1 [] [ text "Loading..." ] ] ] 357 | 358 | Failure -> 359 | [ section [ class "failure__title" ] [ h1 [] [ text "Something went wrong" ] ] ] 360 | 361 | Success repo -> 362 | repo.tags 363 | |> List.map 364 | (\tag -> 365 | section [ class "repo" ] 366 | [ div [] [ img [ src "/statics/tag.svg" ] [] ] 367 | , div [] [ h1 [] [ a [ href ("/tags?r=" ++ repo.name ++ "&t=" ++ tag) ] [ text tag ] ] ] 368 | , div [] [ img [ src "/statics/move-up-right.svg" ] [] ] 369 | ] 370 | ) 371 | 372 | 373 | viewImagePage : String -> String -> RemoteData Image -> RemoteData ImageBlob -> List (Html msg) 374 | viewImagePage registryUrl name data blob = 375 | viewPageTitle name 376 | :: viewImageDetail registryUrl name data blob 377 | 378 | 379 | viewImageDetail : String -> String -> RemoteData Image -> RemoteData ImageBlob -> List (Html msg) 380 | viewImageDetail registryUrl name data blob = 381 | case data of 382 | Loading -> 383 | [ section [ class "loading__title" ] [ h1 [] [ text "Loading..." ] ] ] 384 | 385 | Failure -> 386 | [ section [ class "failure__title" ] [ h1 [] [ text "Something went wrong" ] ] ] 387 | 388 | Success image -> 389 | let 390 | size = 391 | (image.layers |> List.map (\l -> l.size) |> List.sum) + image.config.size 392 | in 393 | [ section [ class "image" ] 394 | [ div [ class "layers" ] 395 | (h2 [ class "layers__title" ] [ text "Layers" ] 396 | :: (image.layers 397 | |> List.map 398 | (\layer -> 399 | div [] 400 | [ p [] [ text layer.digest ] 401 | , p [] [ layer.size |> Filesize.format |> text ] 402 | ] 403 | ) 404 | ) 405 | ) 406 | , div [ class "metadata" ] 407 | ([ div [] 408 | [ h2 [] [ text "Pull Command" ] 409 | , p [] [ text ("docker pull " ++ registryUrl ++ "/" ++ name) ] 410 | ] 411 | , div [] 412 | [ h2 [] [ text "Image Digest" ] 413 | , p [] [ text image.config.digest ] 414 | ] 415 | , div [] 416 | [ h2 [] [ text "Image Size" ] 417 | , p [] [ size |> Filesize.format |> text ] 418 | ] 419 | ] 420 | ++ (case blob of 421 | Loading -> 422 | [] 423 | 424 | Failure -> 425 | [] 426 | 427 | Success blobData -> 428 | [ div [] 429 | [ h2 [] [ text "Architecture" ] 430 | , p [] [ text blobData.architecture ] 431 | ] 432 | , div [] 433 | [ h2 [] [ text "Created At" ] 434 | , p [] [ text blobData.createdAt ] 435 | ] 436 | , div [] 437 | [ h2 [] [ text "OS" ] 438 | , p [] [ text blobData.os ] 439 | ] 440 | , div [] 441 | [ h2 [] [ text "CMD" ] 442 | , p [] [ blobData.cmd |> String.join " " |> text ] 443 | ] 444 | ] 445 | ) 446 | ) 447 | ] 448 | ] 449 | 450 | 451 | 452 | -- HTTP 453 | 454 | 455 | getCatalog : Cmd Msg 456 | getCatalog = 457 | Http.get 458 | { url = "/api/v2/_catalog" 459 | , expect = Http.expectJson GotCatalog decodeCatalog 460 | } 461 | 462 | 463 | decodeCatalog : Decoder Catalog 464 | decodeCatalog = 465 | Decode.map Catalog 466 | (Decode.field "repositories" (Decode.list Decode.string)) 467 | 468 | 469 | getRepo : String -> Cmd Msg 470 | getRepo repo = 471 | Http.get 472 | { url = "/api/v2/" ++ repo ++ "/tags/list" 473 | , expect = Http.expectJson GotRepository decodeRepo 474 | } 475 | 476 | 477 | decodeRepo : Decoder Repository 478 | decodeRepo = 479 | Decode.map2 Repository 480 | (Decode.field "name" Decode.string) 481 | (Decode.field "tags" (Decode.list Decode.string)) 482 | 483 | 484 | getImage : String -> String -> Cmd Msg 485 | getImage repo tag = 486 | Http.get 487 | { url = "/api/v2/" ++ repo ++ "/manifests/" ++ tag 488 | , expect = Http.expectJson GotImage decodeImage 489 | } 490 | 491 | 492 | decodeImage : Decoder Image 493 | decodeImage = 494 | Decode.map4 Image 495 | (Decode.field "config" decodeImageDigest) 496 | (Decode.field "layers" (Decode.list decodeImageDigest)) 497 | (Decode.field "mediaType" Decode.string) 498 | (Decode.field "schemaVersion" Decode.int) 499 | 500 | 501 | decodeImageDigest : Decoder ImageDigest 502 | decodeImageDigest = 503 | Decode.map3 ImageDigest 504 | (Decode.field "digest" Decode.string) 505 | (Decode.field "mediaType" Decode.string) 506 | (Decode.field "size" Decode.int) 507 | 508 | 509 | getImageBlob : String -> String -> Cmd Msg 510 | getImageBlob repo blobDigest = 511 | Http.get 512 | { url = "/api/v2/" ++ repo ++ "/blobs/" ++ blobDigest 513 | , expect = Http.expectJson GotImageBlob decodeImageBlob 514 | } 515 | 516 | 517 | decodeImageBlob : Decoder ImageBlob 518 | decodeImageBlob = 519 | Decode.map4 ImageBlob 520 | (Decode.field "architecture" Decode.string) 521 | (Decode.field "os" Decode.string) 522 | (Decode.field "config" (Decode.field "Cmd" (Decode.list Decode.string))) 523 | (Decode.field "created" (Iso8601.decoder |> Decode.map dateFormatter)) 524 | 525 | 526 | 527 | -- ROUTES 528 | 529 | 530 | parseUrl : Url -> Route 531 | parseUrl url = 532 | case Parser.parse routeParser url of 533 | Just route -> 534 | route 535 | 536 | Nothing -> 537 | Index 538 | 539 | 540 | routeParser : Parser (Route -> c) c 541 | routeParser = 542 | Parser.oneOf 543 | [ Parser.map Index Parser.top 544 | , Parser.map Repo (Parser.s "repos" Query.string "r") 545 | , Parser.map Tag (Parser.s "tags" Query.string "r" Query.string "t") 546 | ] 547 | 548 | 549 | 550 | -- DATE FORMATTER 551 | 552 | 553 | dateFormatter : Posix -> String 554 | dateFormatter = 555 | DateFormat.format 556 | [ DateFormat.monthNameFull 557 | , DateFormat.text " " 558 | , DateFormat.dayOfMonthSuffix 559 | , DateFormat.text ", " 560 | , DateFormat.yearNumber 561 | , DateFormat.text " " 562 | , DateFormat.hourFixed 563 | , DateFormat.text ":" 564 | , DateFormat.minuteFixed 565 | , DateFormat.text ":" 566 | , DateFormat.secondFixed 567 | ] 568 | Time.utc 569 | --------------------------------------------------------------------------------