diff --git a/.dockerignore b/.dockerignore
index e02d7a7..1142ba2 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,4 +1,5 @@
target/
-.github/
.git/
+.github/
+*.md
Dockerfile
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..874bfc2
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+
+# Markdown uses trailing whitespace for hard line breaks
+[*.md]
+trim_trailing_whitespace = false
+
+# Makefile recipes must be indented with real tabs
+[Makefile]
+indent_style = tab
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
deleted file mode 100644
index e69de29..0000000
diff --git a/.github/README.md b/.github/README.md
index c7a98bc..a345bce 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -283,253 +283,23 @@ Below is an outline + current status of each workflow.
| Workflow | Status |
|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| Build Docker image and push to registry | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/build-docker.yml) |
-| Compile binaries and upload artifacts to release | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/release-binaries.yml) |
-| Publish compiled app to crates.io | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/push-cargo.yml) |
+| Run quality + security checks on each PR and push | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/ci.yml) |
+| Bump version and push git tag on merge to main | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/tag.yml) |
+| Compile binaries and draft a release on each tag | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/release.yml) |
+| Publish compiled app to crates.io | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/cargo.yml) |
+| Build Docker image and push to registry | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/docker.yml) |
| Generate documentation from Rustdoc, upload to GH pages | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/publish-docs.yml) |
| Sync repo with downstream codeberg mirror | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/mirror.yml) |
-| Insert list of contributors + sponsors into readme | [](https://github.com/Lissy93/AdGuardian-Term/actions/workflows/insert-contributors.yml) |
---
## Credits
### Contributors
-
-
-
-
+[](https://github.com/lissy93/AdGuardian-Term/graphs/contributors)
### Sponsors
-
-
-
-
+[](https://github.com/sponsors/lissy93)
### Dependencies
@@ -637,4 +407,3 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
| / | / ~-. ~- _
|_____| |_____| ~ - . _ _~_-_
-->
-
diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml
deleted file mode 100644
index cf6f8c0..0000000
--- a/.github/workflows/build-docker.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-name: Build + Publish Docker Image π³
-
-on:
- workflow_dispatch:
- push:
- branches:
- - main
- tags:
- - '*'
- paths:
- - src/**
- - Cargo.toml
- - Dockerfile
-
-env:
- IMAGE_NAME: adguardian
- DOCKER_USER: lissy93
- GHCR_REGISTRY: ghcr.io
- DOCKERHUB_REGISTRY: docker.io
-
-jobs:
- docker:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Extract tag name
- shell: bash
- run: echo "GIT_TAG=$(echo ${GITHUB_REF#refs/tags/} | sed 's/\//_/g')" >> $GITHUB_ENV
-
- - name: Compute tags
- id: compute-tags
- run: |
- if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
- echo "GHCR_TAG=${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:latest" >> $GITHUB_ENV
- echo "DOCKERHUB_TAG=${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:latest" >> $GITHUB_ENV
- else
- echo "GHCR_TAG=${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:${GIT_TAG}" >> $GITHUB_ENV
- echo "DOCKERHUB_TAG=${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:${GIT_TAG}" >> $GITHUB_ENV
- fi
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v1
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
-
- - name: Login to GitHub Container Registry
- uses: docker/login-action@v1
- with:
- registry: ${{ env.GHCR_REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Login to DockerHub
- uses: docker/login-action@v1
- with:
- registry: ${{ env.DOCKERHUB_REGISTRY }}
- username: ${{ env.DOCKER_USER }}
- password: ${{ secrets.DOCKERHUB_PASSWORD }}
-
- - name: Build and push Docker images
- uses: docker/build-push-action@v2
- with:
- context: .
- file: ./Dockerfile
- push: true
- platforms: linux/amd64,linux/arm64,linux/arm/v7
- tags: |
- ${{ env.GHCR_TAG }}
- ${{ env.DOCKERHUB_TAG }}
-
diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml
new file mode 100644
index 0000000..2729701
--- /dev/null
+++ b/.github/workflows/cargo.yml
@@ -0,0 +1,49 @@
+# Publishes the crate to Crates.io for each new tag
+name: π¦ Publish crates
+
+on:
+ workflow_dispatch:
+ push:
+ tags:
+ - '*'
+
+permissions:
+ contents: read
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ publish:
+ name: π¦ Publish
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - name: ποΈ Checkout tag
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: π¦ Setup Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: π Verify Cargo.toml version matches tag
+ if: github.ref_type == 'tag'
+ env:
+ TAG: ${{ github.ref_name }}
+ run: |
+ set -euo pipefail
+ VERSION=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')
+ if [ "$VERSION" != "$TAG" ]; then
+ echo "::error::Cargo.toml version ($VERSION) does not match tag ($TAG)"
+ exit 1
+ fi
+ echo "Publishing adguardian v$VERSION"
+
+ - name: β
Run tests
+ run: cargo test --locked
+
+ - name: π¦ Publish to Crates.io
+ env:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
+ run: cargo publish --locked
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..1418aee
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,250 @@
+# CI checks for PRs
+name: π¦ CI
+
+on:
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+
+jobs:
+ # Detect which files changed, to determine which checks to run
+ changes:
+ name: π Detect changes
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ outputs:
+ docker: ${{ steps.filter.outputs.docker }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Filter changed paths
+ id: filter
+ uses: dorny/paths-filter@v4
+ with:
+ filters: |
+ docker:
+ - 'Dockerfile*'
+ - 'Cargo.toml'
+ - 'Cargo.lock'
+
+ # Run format check, to ensure code nice and pretty
+ fmt:
+ name: π¨ Format
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Setup Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: rustfmt
+
+ - name: Check formatting
+ run: cargo fmt --all --check
+
+ # Run clippy, to ensure no rust warnings
+ clippy:
+ name: π Clippy
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Setup Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: clippy
+
+ - name: Cache cargo build
+ uses: Swatinem/rust-cache@v2
+
+ - name: Run clippy
+ run: cargo clippy --all-targets --all-features --locked -- -D warnings
+
+ # Execute the tests
+ test:
+ name: π§ͺ Test
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Setup Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache cargo build
+ uses: Swatinem/rust-cache@v2
+
+ - name: Run tests
+ run: cargo test --all-features --locked
+
+ # Do build check, ensure everything builds
+ build:
+ name: π¨ Build
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Setup Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache cargo build
+ uses: Swatinem/rust-cache@v2
+
+ - name: Build release
+ run: cargo build --release --locked
+
+ # Check the docs build with no warnings or broken intra-doc links
+ docs:
+ name: π Docs
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ env:
+ RUSTDOCFLAGS: -D warnings
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Setup Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache cargo build
+ uses: Swatinem/rust-cache@v2
+
+ - name: Build docs
+ run: cargo doc --no-deps --all-features --locked
+
+ # Dependencies audit for known vulnerabilities
+ # audit:
+ # name: π‘οΈ Audit
+ # runs-on: ubuntu-latest
+ # timeout-minutes: 15
+ # steps:
+ # - name: Checkout
+ # uses: actions/checkout@v6
+ # with:
+ # persist-credentials: false
+
+ # - name: Install cargo-audit
+ # uses: taiki-e/install-action@v2
+ # with:
+ # tool: cargo-audit
+
+ # - name: Run cargo audit
+ # run: cargo audit
+
+ # Run the Docker image build and smoke test
+ docker:
+ name: π³ Docker build
+ needs: changes
+ if: needs.changes.outputs.docker == 'true' || github.event_name == 'workflow_dispatch'
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Build image (no push)
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: linux/amd64
+ push: false
+ load: false
+ cache-from: type=gha,scope=ci-docker
+ cache-to: type=gha,mode=max,scope=ci-docker
+
+ # Review the workflow files are valid and free of security issues
+ workflow-lint:
+ name: βοΈ Workflow lint
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Run actionlint
+ uses: raven-actions/actionlint@v2
+
+ - name: Setup Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.x'
+
+ - name: Run zizmor
+ run: |
+ python -m pip install --quiet zizmor
+ zizmor --offline .github/workflows/
+
+ # Check no super secret things committed
+ secrets:
+ name: π Secret scan
+ if: github.event_name == 'pull_request'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Scan for secrets with TruffleHog
+ uses: trufflesecurity/trufflehog@main
+ with:
+ extra_args: --only-verified
+
+ # Dependency changes have no known vulnerabilities
+ dependency-review:
+ name: βοΈβπ₯ Dependency review
+ if: github.event_name == 'pull_request'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Review dependencies
+ uses: actions/dependency-review-action@v5
+ with:
+ fail-on-severity: moderate
+ show-openssf-scorecard: false
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..b899bdf
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,184 @@
+name: π³ Docker
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: 'Docker tag to publish. Leave empty to update latest'
+ required: false
+ type: string
+ push:
+ branches:
+ - main
+ tags:
+ - '*'
+
+permissions:
+ contents: read
+ packages: write
+ id-token: write
+ attestations: write
+
+concurrency:
+ group: docker-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ IMAGE_NAME: adguardian
+ DOCKER_USER: lissy93
+ GHCR_REGISTRY: ghcr.io
+ DOCKERHUB_REGISTRY: docker.io
+
+jobs:
+ docker:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Compute image metadata
+ id: meta
+ shell: bash
+ env:
+ MANUAL_TAG: ${{ inputs.tag }}
+ DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
+ run: |
+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
+ if [[ -n "${MANUAL_TAG}" ]]; then
+ TAG="${MANUAL_TAG}"
+ else
+ TAG="latest"
+ fi
+ elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
+ TAG="${GITHUB_REF#refs/tags/}"
+ else
+ TAG="latest"
+ fi
+
+ TAG="$(echo "${TAG}" | sed -E 's/[^A-Za-z0-9_.-]+/-/g; s/^[.-]+//' | cut -c1-128)"
+ if [[ -z "${TAG}" ]]; then
+ echo "Computed Docker tag is empty" >&2
+ exit 1
+ fi
+
+ GHCR_IMAGE="${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}"
+ PUBLISH_IMAGES="${GHCR_IMAGE}"
+ if [[ -n "${DOCKERHUB_PASSWORD}" ]]; then
+ DOCKERHUB_IMAGE="${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}"
+ PUBLISH_IMAGES="${PUBLISH_IMAGES},${DOCKERHUB_IMAGE}"
+ echo "dockerhub_enabled=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "dockerhub_enabled=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ echo "TAG=${TAG}" >> "$GITHUB_ENV"
+ echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
+ echo "GHCR_IMAGE=${GHCR_IMAGE}" >> "$GITHUB_ENV"
+ echo "ghcr_image=${GHCR_IMAGE}" >> "$GITHUB_OUTPUT"
+ echo "PUBLISH_IMAGES=${PUBLISH_IMAGES}" >> "$GITHUB_ENV"
+ echo "publish_images=${PUBLISH_IMAGES}" >> "$GITHUB_OUTPUT"
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v4
+ with:
+ registry: ${{ env.GHCR_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to DockerHub
+ if: steps.meta.outputs.dockerhub_enabled == 'true'
+ uses: docker/login-action@v4
+ with:
+ registry: ${{ env.DOCKERHUB_REGISTRY }}
+ username: ${{ env.DOCKER_USER }}
+ password: ${{ secrets.DOCKERHUB_PASSWORD }}
+
+ # Build the ultra-lightweight from scratch version for 64-bit targets
+ - name: Build lightweight image (amd64, arm64)
+ id: light
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: linux/amd64,linux/arm64
+ provenance: false
+ sbom: false
+ cache-from: type=gha,scope=light
+ cache-to: type=gha,mode=max,scope=light
+ outputs: type=image,"name=${{ steps.meta.outputs.publish_images }}",push-by-digest=true,name-canonical=true,push=true
+
+ # Build the fat (Debian glibc) image for everything else
+ - name: Build full image (armv7, 386)
+ id: full
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: ./Dockerfile.full
+ platforms: linux/arm/v7,linux/386
+ provenance: false
+ sbom: false
+ cache-from: type=gha,scope=full
+ cache-to: type=gha,mode=max,scope=full
+ outputs: type=image,"name=${{ steps.meta.outputs.publish_images }}",push-by-digest=true,name-canonical=true,push=true
+
+ # Merge the variants into single manifest
+ - name: Create combined manifest list
+ id: manifest
+ shell: bash
+ env:
+ LIGHT_DIGEST: ${{ steps.light.outputs.digest }}
+ FULL_DIGEST: ${{ steps.full.outputs.digest }}
+ run: |
+ IFS=',' read -ra IMAGES <<< "${PUBLISH_IMAGES}"
+ for IMG in "${IMAGES[@]}"; do
+ docker buildx imagetools create -t "${IMG}:${TAG}" \
+ "${IMG}@${LIGHT_DIGEST}" \
+ "${IMG}@${FULL_DIGEST}"
+ done
+ GHCR_DIGEST=$(docker buildx imagetools inspect "${GHCR_IMAGE}:${TAG}" --format '{{.Manifest.Digest}}')
+ echo "ghcr_digest=${GHCR_DIGEST}" >> "$GITHUB_OUTPUT"
+
+ - name: Inspect published manifests
+ shell: bash
+ run: |
+ IFS=',' read -ra IMAGES <<< "${PUBLISH_IMAGES}"
+ for IMG in "${IMAGES[@]}"; do
+ echo "::group::${IMG}:${TAG}"
+ docker buildx imagetools inspect "${IMG}:${TAG}"
+ echo "::endgroup::"
+ done
+
+ - name: Attest build provenance
+ uses: actions/attest-build-provenance@v4
+ continue-on-error: true
+ with:
+ subject-name: ${{ steps.meta.outputs.ghcr_image }}
+ subject-digest: ${{ steps.manifest.outputs.ghcr_digest }}
+ push-to-registry: true
+
+ - name: Generate SBOM
+ uses: anchore/sbom-action@v0.24.0
+ continue-on-error: true
+ with:
+ image: ${{ steps.meta.outputs.ghcr_image }}@${{ steps.manifest.outputs.ghcr_digest }}
+ format: spdx-json
+ output-file: sbom.spdx.json
+
+ - name: Attest SBOM
+ uses: actions/attest@v4
+ continue-on-error: true
+ with:
+ subject-name: ${{ steps.meta.outputs.ghcr_image }}
+ subject-digest: ${{ steps.manifest.outputs.ghcr_digest }}
+ predicate-type: https://spdx.dev/Document
+ predicate-path: sbom.spdx.json
+ push-to-registry: true
diff --git a/.github/workflows/insert-contributors.yml b/.github/workflows/insert-contributors.yml
deleted file mode 100644
index bcc0308..0000000
--- a/.github/workflows/insert-contributors.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: π₯ Insert Credits
-on:
- workflow_dispatch:
- schedule:
- - cron: '0 1 * * 0'
-jobs:
-
- insert-sponsors:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
- - name: Contributors
- uses: akhilmhdh/contributors-readme-action@v2.3.6
- env:
- GITHUB_TOKEN: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
- with:
- image_size: 80
- collaborators: all
- readme_path: .github/README.md
- columns_per_row: 6
- commit_message: 'Updates contributor list'
- committer_username: liss-bot
- committer_email: liss-bot@d0h.co
diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml
index 59c4bac..25cb29f 100644
--- a/.github/workflows/mirror.yml
+++ b/.github/workflows/mirror.yml
@@ -1,16 +1,22 @@
-# Pushes the contents of the repo to the Codeberg mirror
-name: πͺ Mirror to Codeberg
+# Syncs the full source of the adguardian repo over to our Codeberg mirror
+# This is then accessible over at https://codeberg.org/alicia/adguardian
+name: πͺ Mirror
on:
workflow_dispatch:
schedule:
- - cron: '30 0 * * 0' # At 03:00 on Sunday
+ - cron: '30 3 * * 0' # At 03:30 on Sunday
+
+permissions:
+ contents: read
+
jobs:
codeberg:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- with: { fetch-depth: 0 }
- - uses: pixta-dev/repository-mirroring-action@v1
+ - uses: lissy93/repo-mirror-action@v1.5.0
with:
- target_repo_url: git@codeberg.org:alicia/adguardian.git
- ssh_private_key: ${{ secrets.CODEBERG_SSH }}
+ ssh_key: ${{ secrets.CODEBERG_SSH }}
+ host: git@codeberg.org
+ user: alicia
+ repo: adguardian
+ force_push: false
diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml
index a8cc966..4babf15 100644
--- a/.github/workflows/publish-docs.yml
+++ b/.github/workflows/publish-docs.yml
@@ -1,4 +1,7 @@
-name: Build Docs π
+# Generates Rustdoc API documentation and publishes it to the docs-src branch
+# (served via GitHub Pages). Runs on pushes to main that touch the source.
+
+name: π Docs
on:
workflow_dispatch:
@@ -9,24 +12,34 @@ on:
- src/**
- Cargo.toml
+permissions:
+ contents: write
+
+env:
+ CARGO_TERM_COLOR: always
+
jobs:
build:
+ name: π Build & publish docs
runs-on: ubuntu-latest
+ timeout-minutes: 15
steps:
- - name: Checkout repository
- uses: actions/checkout@v2
-
- - name: Generate docs
- uses: actions-rs/cargo@v1
+ - name: ποΈ Checkout repository
+ uses: actions/checkout@v6
with:
- command: doc
- args: --no-deps
+ persist-credentials: false
+
+ - name: π¦ Setup Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: π Generate docs
+ run: cargo doc --no-deps --locked
- - name: Create index.html
- run: echo "" > target/doc/index.html
+ - name: π Create index.html redirect
+ run: echo '' > target/doc/index.html
- - name: Publish docs
- uses: peaceiris/actions-gh-pages@v3.9.3
+ - name: π Publish docs
+ uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc
diff --git a/.github/workflows/push-cargo.yml b/.github/workflows/push-cargo.yml
deleted file mode 100644
index 354c97b..0000000
--- a/.github/workflows/push-cargo.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: Publish to Crates.io π¦
-
-on:
- workflow_dispatch:
- release:
- types: [published]
-
-jobs:
- publish:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v2
-
- - name: Install Rust
- uses: actions-rs/toolchain@v1
- with:
- toolchain: stable
- override: true
-
- - name: Run tests
- uses: actions-rs/cargo@v1
- with:
- command: test
-
- - name: Publish to Crates.io
- uses: actions-rs/cargo@v1
- with:
- command: publish
- args: --token ${{ secrets.CRATES_IO_TOKEN }} --allow-dirty
diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml
deleted file mode 100644
index 6f99da1..0000000
--- a/.github/workflows/release-binaries.yml
+++ /dev/null
@@ -1,275 +0,0 @@
-name: Release π
-
-on:
- workflow_dispatch:
- push:
- branches:
- - main
- paths:
- - src/**
- - Cargo.toml
-
-env:
- CARGO_TERM_COLOR: always
-
-jobs:
- release-x86_64:
- name: Release x86_64
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Get the version
- id: get_version
- run: echo ::set-output name=VERSION::$(grep "^version" Cargo.toml | awk '{print $3}' | tr -d '"')
-
- - name: Install dependencies
- run: sudo apt-get update -y && sudo apt-get install -y musl-tools
-
- - name: Install rust toolchain
- uses: actions-rs/toolchain@v1
- with:
- profile: minimal
- toolchain: stable
- override: true
-
- - name: Install target
- run: rustup target add x86_64-unknown-linux-musl
-
- - name: Build for x86_64
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --release --target=x86_64-unknown-linux-musl
-
- - name: Upload x86_64 Binary
- uses: actions/upload-artifact@v2
- with:
- name: adguardian-x86_64
- path: ./target/x86_64-unknown-linux-musl/release/adguardian
-
- release-arm-v7:
- name: Release ARM V7
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Get the version
- id: get_version
- run: echo ::set-output name=VERSION::$(grep "^version" Cargo.toml | awk '{print $3}' | tr -d '"')
-
- - name: Install dependencies
- run: sudo apt-get update -y && sudo apt-get install -y gcc-arm-linux-gnueabihf
-
- - name: Install rust toolchain
- uses: actions-rs/toolchain@v1
- with:
- profile: minimal
- toolchain: stable
- override: true
- target: armv7-unknown-linux-gnueabihf
-
- - name: Install cross
- run: cargo install cross
-
- - name: Build for ARM V7
- run: cross build --target=armv7-unknown-linux-gnueabihf --release
-
- - name: Upload ARM V7 Binary
- uses: actions/upload-artifact@v2
- with:
- name: adguardian-arm-v7
- path: ./target/armv7-unknown-linux-gnueabihf/release/adguardian
-
- release-arm64:
- name: Release ARM64
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Get the version
- id: get_version
- run: echo ::set-output name=VERSION::$(grep "^version" Cargo.toml | awk '{print $3}' | tr -d '"')
-
- - name: Install cross
- run: cargo install cross
-
- - name: Install aarch64 target
- run: rustup target add aarch64-unknown-linux-gnu
-
- - name: Build for ARM64
- run: cross build --target aarch64-unknown-linux-gnu --release
-
- - name: Upload ARM64 Binary
- uses: actions/upload-artifact@v2
- with:
- name: adguardian-arm64
- path: ./target/aarch64-unknown-linux-gnu/release/adguardian
-
-
- release-macos:
- name: Release MacOS
- runs-on: macos-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Get the version
- id: get_version
- run: echo ::set-output name=VERSION::$(grep "^version" Cargo.toml | awk '{print $3}' | tr -d '"')
-
- - name: Install rust toolchain
- uses: actions-rs/toolchain@v1
- with:
- profile: minimal
- toolchain: stable
- override: true
-
- - name: Build for MacOS
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --release
-
- - name: Upload MacOS Binary
- uses: actions/upload-artifact@v2
- with:
- name: adguardian-macos
- path: ./target/release/adguardian
-
- release-windows:
- name: Release Windows
- runs-on: windows-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Get the version
- id: get_version
- run: echo "::set-output name=VERSION::$(grep '^version' Cargo.toml | awk '{print $3}' | tr -d '"')"
-
- - name: Install rust toolchain
- uses: actions-rs/toolchain@v1
- with:
- profile: minimal
- toolchain: stable
- override: true
-
- - name: Build for Windows
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --release
-
- - name: Upload Windows Binary
- uses: actions/upload-artifact@v2
- with:
- name: adguardian-windows
- path: ./target/release/adguardian.exe
-
- create-release:
- name: Create Release
- needs: [release-x86_64, release-arm-v7, release-arm64, release-macos, release-windows]
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Get the version
- id: get_version
- run: echo ::set-output name=VERSION::$(grep "^version" Cargo.toml | awk '{print $3}' | tr -d '"')
-
- - name: Update or Create Release
- id: create_release
- uses: softprops/action-gh-release@v1
- with:
- tag_name: ${{ steps.get_version.outputs.VERSION }}
- release_name: Release ${{ steps.get_version.outputs.VERSION }}
- body_path: ./.github/CHANGELOG.md
- draft: false
- prerelease: false
- env:
- GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
-
- - name: Download x86_64 Binary
- uses: actions/download-artifact@v2
- with:
- name: adguardian-x86_64
- path: ./target/x86_64-unknown-linux-musl/release/
-
- - name: Download ARM V7 Binary
- uses: actions/download-artifact@v2
- with:
- name: adguardian-arm-v7
- path: ./target/armv7-unknown-linux-gnueabihf/release/
-
- - name: Download ARM64 Binary
- uses: actions/download-artifact@v2
- with:
- name: adguardian-arm64
- path: ./target/aarch64-unknown-linux-gnu/release/
-
- - name: Download MacOS Binary
- uses: actions/download-artifact@v2
- with:
- name: adguardian-macos
- path: ./target/release/
-
- - name: Download Windows Binary
- uses: actions/download-artifact@v2
- with:
- name: adguardian-windows
- path: ./target/release/
-
- - name: Upload x86_64 Binary
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./target/x86_64-unknown-linux-musl/release/adguardian
- asset_name: adguardian-x86_64
- asset_content_type: application/octet-stream
-
- - name: Upload ARM V7 Binary
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./target/armv7-unknown-linux-gnueabihf/release/adguardian
- asset_name: adguardian-arm-v7
- asset_content_type: application/octet-stream
-
- - name: Upload ARM64 Binary
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./target/aarch64-unknown-linux-gnu/release/adguardian
- asset_name: adguardian-arm64
- asset_content_type: application/octet-stream
-
- - name: Upload MacOS Binary
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./target/release/adguardian
- asset_name: adguardian-macos
- asset_content_type: application/octet-stream
-
- - name: Upload Windows Binary
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./target/release/adguardian.exe
- asset_name: adguardian-windows.exe
- asset_content_type: application/octet-stream
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..494eaa8
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,200 @@
+# Builds AdGuardian for all supported targets and drafts a GitHub release
+# with the compiled binaries, a SHA256 checksum and provenance attestations
+# The release is created as a draft, then human can review and hit publish
+#
+# Triggered by:
+# - Push of any minor/major (X.Y.0) git tag (patch tags just rebuild Docker)
+# - Manual dispatch with any existing tag (any version)
+
+name: π Release
+
+on:
+ push:
+ tags: ['*.*.0']
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: 'Existing git tag to release (e.g. 1.7.0)'
+ required: true
+
+concurrency:
+ group: ${{ github.workflow }}-${{ inputs.tag || github.ref_name }}
+ cancel-in-progress: false
+
+permissions:
+ contents: read
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ build:
+ name: ποΈ Build ${{ matrix.name }}
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+ env:
+ TAG: ${{ inputs.tag || github.ref_name }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: adguardian-linux
+ os: ubuntu-latest
+ target: x86_64-unknown-linux-musl
+ cross: false
+ apt: musl-tools
+ - name: adguardian-linux-arm64
+ os: ubuntu-latest
+ target: aarch64-unknown-linux-gnu
+ cross: true
+ - name: adguardian-linux-armv7
+ os: ubuntu-latest
+ target: armv7-unknown-linux-gnueabihf
+ cross: true
+ - name: adguardian-macos
+ os: macos-latest
+ target: aarch64-apple-darwin
+ cross: false
+ - name: adguardian-macos-x86_64
+ os: macos-latest
+ target: x86_64-apple-darwin
+ cross: false
+ - name: adguardian-windows.exe
+ os: windows-latest
+ target: x86_64-pc-windows-msvc
+ cross: false
+ steps:
+ - name: ποΈ Checkout tag
+ uses: actions/checkout@v6
+ with:
+ ref: refs/tags/${{ env.TAG }}
+ persist-credentials: false
+
+ - name: π¦ Setup Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ matrix.target }}
+
+ - name: π¦ Install apt dependencies
+ if: matrix.apt != ''
+ run: sudo apt-get update -y && sudo apt-get install -y ${{ matrix.apt }}
+
+ - name: π§ Install cross
+ if: matrix.cross
+ uses: taiki-e/install-action@v2
+ with:
+ tool: cross
+
+ - name: ποΈ Build with cargo
+ if: ${{ !matrix.cross }}
+ run: cargo build --release --locked --target ${{ matrix.target }}
+
+ - name: ποΈ Build with cross
+ if: matrix.cross
+ run: cross build --release --locked --target ${{ matrix.target }}
+
+ - name: π€ Stage binary as ${{ matrix.name }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ SRC="target/${{ matrix.target }}/release/adguardian"
+ [ -f "${SRC}.exe" ] && SRC="${SRC}.exe"
+ cp "$SRC" "${{ matrix.name }}"
+
+ - name: β¬οΈ Upload build artifact
+ uses: actions/upload-artifact@v7
+ with:
+ name: ${{ matrix.name }}
+ path: ${{ matrix.name }}
+ if-no-files-found: error
+ retention-days: 1
+
+ release:
+ name: π Draft Release
+ needs: build
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ permissions:
+ contents: write
+ id-token: write
+ attestations: write
+ env:
+ TAG: ${{ inputs.tag || github.ref_name }}
+ steps:
+ - name: ποΈ Checkout tag
+ uses: actions/checkout@v6
+ with:
+ ref: refs/tags/${{ env.TAG }}
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: π₯ Download all binaries
+ uses: actions/download-artifact@v8
+ with:
+ path: artifacts
+ merge-multiple: true
+
+ - name: π’ Generate SHA256 checksums
+ run: |
+ set -euo pipefail
+ cd artifacts
+ sha256sum adguardian-* > SHA256SUMS
+ cat SHA256SUMS
+
+ - name: πͺͺ Generate build provenance attestation
+ uses: actions/attest-build-provenance@v4
+ with:
+ subject-path: 'artifacts/adguardian-*'
+
+ - name: π Find previous release tag
+ id: prev
+ env:
+ CURRENT_TAG: ${{ env.TAG }}
+ run: |
+ set -euo pipefail
+ git fetch --tags --force
+ PREV=$({ echo "$CURRENT_TAG"; git tag | grep -E '^[0-9]+\.[0-9]+\.0$' || true; } \
+ | sort -uV \
+ | awk -v cur="$CURRENT_TAG" '$0 == cur { print prev; exit } { prev = $0 }')
+ echo "tag=$PREV" >> "$GITHUB_OUTPUT"
+
+ - name: π Create draft release
+ id: release
+ uses: softprops/action-gh-release@v3 # zizmor: ignore[superfluous-actions]
+ with:
+ tag_name: ${{ env.TAG }}
+ name: Release ${{ env.TAG }}
+ draft: true
+ prerelease: false
+ generate_release_notes: true
+ previous_tag: ${{ steps.prev.outputs.tag }}
+ fail_on_unmatched_files: true
+ files: |
+ artifacts/adguardian-*
+ artifacts/SHA256SUMS
+ token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
+
+ - name: π Job summary
+ if: always()
+ env:
+ REPO_URL: ${{ github.server_url }}/${{ github.repository }}
+ PREV_TAG: ${{ steps.prev.outputs.tag }}
+ RELEASE_URL: ${{ steps.release.outputs.url }}
+ run: |
+ set -euo pipefail
+ {
+ echo "## π Release Draft"
+ echo ""
+ echo "| Item | Value |"
+ echo "|------|-------|"
+ echo "| Tag | [\`${TAG}\`](${REPO_URL}/releases/tag/${TAG}) |"
+ if [ -n "$PREV_TAG" ]; then
+ echo "| Notes since | [\`${PREV_TAG}\`](${REPO_URL}/releases/tag/${PREV_TAG}) |"
+ fi
+ echo "| Assets | $(find artifacts -maxdepth 1 -type f -printf '%f ') |"
+ if [ -n "$RELEASE_URL" ]; then
+ echo "| Draft release | β
[Review and publish](${RELEASE_URL}) |"
+ else
+ echo "| Draft release | β Failed |"
+ fi
+ } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml
new file mode 100644
index 0000000..46de570
--- /dev/null
+++ b/.github/workflows/tag.yml
@@ -0,0 +1,411 @@
+# Creates a new git tag when a PR is merged, or on manual dispatch
+#
+# PR trigger flow:
+# - Triggered whenever a PR is merged, if that PR made code changes
+# - If version wasn't bumped in PR, increment patch version in Cargo.toml
+# - Otherwise (if the PR did bump version) we use the new version from Cargo.toml
+# - Creates and pushes a git tag for the new version
+# - That git tag then triggers Docker publishing (any tag) and, for minor/major
+# (X.Y.0) tags, the release-drafting workflow
+# - Add labels and release comments to referenced issues (if applicable)
+# - Finally, shows summary of actions taken and new tag published
+#
+# Manual dispatch flow:
+# - If a version is provided, sets Cargo.toml to that version
+# - If no version is provided, increments patch version automatically
+# - Creates and pushes a git tag
+#
+# Note: pushing the tag must trigger downstream workflows (release, docker), so
+# checkout uses a PAT (secrets.BOT_TOKEN). With the default GITHUB_TOKEN, GitHub
+# suppresses workflow runs triggered by the tag push.
+
+name: π Tag
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version to tag (e.g. 1.7.0). Leave blank to auto-increment patch.'
+ required: false
+ type: string
+ # Btw, this is a safe and intentional trigger
+ # It only runs once reviewed PR merged, and secrets are scoped
+ pull_request_target: # zizmor: ignore[dangerous-triggers]
+ types: [closed]
+ branches: [main]
+
+concurrency:
+ group: auto-version-and-tag
+ cancel-in-progress: false
+
+permissions:
+ contents: write
+ pull-requests: read
+ issues: write
+
+env:
+ IS_MANUAL: ${{ github.event_name == 'workflow_dispatch' }}
+
+jobs:
+ version-and-tag:
+ if: >-
+ github.event_name == 'workflow_dispatch'
+ || github.event.pull_request.merged == true
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+
+ steps:
+ - name: π’ Validate manual dispatch
+ if: env.IS_MANUAL == 'true'
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
+ DISPATCH_REF: ${{ github.ref }}
+ run: |
+ set -euo pipefail
+ if [ "$DISPATCH_REF" != "refs/heads/main" ]; then
+ echo "::error::Manual dispatch only allowed from main (got: $DISPATCH_REF)"
+ exit 1
+ fi
+ if [ -n "$INPUT_VERSION" ] && ! printf '%s' "$INPUT_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
+ echo "::error::Invalid version '${INPUT_VERSION}'. Must be semver (e.g. 1.7.0)."
+ exit 1
+ fi
+
+ - name: π Check PR for code changes and version bump
+ id: check_pr
+ if: env.IS_MANUAL != 'true'
+ uses: actions/github-script@v8
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const pull_number = context.payload.pull_request.number;
+
+ const files = await github.paginate(
+ github.rest.pulls.listFiles, { owner, repo, pull_number }
+ );
+ const codePatterns = [
+ /^src\//, /^build\.rs$/, /^Cargo\.lock$/, /^Dockerfile/,
+ ];
+ const codeChanged = files.some(f =>
+ codePatterns.some(p => p.test(f.filename))
+ );
+ const manifestChanged = files.some(f => f.filename === 'Cargo.toml');
+
+ if (!codeChanged && !manifestChanged) {
+ core.info('No code or Cargo.toml changes, skipping');
+ core.setOutput('needs_bump', 'false');
+ core.setOutput('needs_tag', 'false');
+ return;
+ }
+
+ const parseVersion = (text) => {
+ const m = text.match(/^version\s*=\s*"([^"]+)"/m);
+ return m ? m[1] : null;
+ };
+
+ let versionBumped = false;
+ if (manifestChanged) {
+ const mergeSha = context.payload.pull_request.merge_commit_sha;
+ const { data: mergeCommit } = await github.rest.git.getCommit({
+ owner, repo, commit_sha: mergeSha,
+ });
+ const parentSha = mergeCommit.parents[0].sha;
+ const getVersion = async (ref) => {
+ const { data } = await github.rest.repos.getContent({
+ owner, repo, path: 'Cargo.toml', ref,
+ });
+ return parseVersion(Buffer.from(data.content, 'base64').toString());
+ };
+ const [prevVersion, mergeVersion] = await Promise.all([
+ getVersion(parentSha), getVersion(mergeSha),
+ ]);
+ versionBumped = prevVersion !== mergeVersion;
+ core.info(`Version: ${prevVersion} β ${mergeVersion}`);
+ }
+
+ const needsBump = codeChanged && !versionBumped;
+ const needsTag = codeChanged || versionBumped;
+ core.info(`Needs bump: ${needsBump}, Needs tag: ${needsTag}`);
+ core.setOutput('needs_bump', needsBump.toString());
+ core.setOutput('needs_tag', needsTag.toString());
+
+ - name: π Validate release token
+ if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true'
+ env:
+ BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
+ run: |
+ set -euo pipefail
+ if [ -z "$BOT_TOKEN" ]; then
+ echo "::error::BOT_TOKEN is required so pushed tags trigger downstream release workflows"
+ exit 1
+ fi
+
+ - name: ποΈ Checkout repository
+ if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true'
+ uses: actions/checkout@v6 # zizmor: ignore[artipacked]
+ with:
+ ref: ${{ github.event.pull_request.merge_commit_sha || github.ref }}
+ token: ${{ secrets.BOT_TOKEN }}
+
+ - name: π€ Configure git identity
+ if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true'
+ run: |
+ git config user.name "Liss-Bot"
+ git config user.email "liss-bot@d0h.co"
+
+ - name: π Resolve tag version
+ id: version
+ if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true'
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
+ NEEDS_BUMP: ${{ steps.check_pr.outputs.needs_bump }}
+ run: |
+ set -euo pipefail
+ read_version() { grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/'; }
+ CUR=$(read_version)
+ if [ "$IS_MANUAL" = "true" ] && [ -n "$INPUT_VERSION" ]; then
+ VERSION="$INPUT_VERSION"
+ elif [ "$IS_MANUAL" = "true" ] || [ "$NEEDS_BUMP" = "true" ]; then
+ IFS=. read -r MA MI PA <<< "$CUR"
+ VERSION="${MA}.${MI}.$((PA + 1))"
+ else
+ VERSION="$CUR"
+ fi
+
+ git fetch --tags --force
+ if git rev-parse "refs/tags/$VERSION" >/dev/null 2>&1; then
+ echo "::error::Tag $VERSION already exists"
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+ exit 1
+ fi
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+
+ - name: π Extract referenced issues
+ id: issues
+ if: env.IS_MANUAL != 'true' && steps.check_pr.outputs.needs_tag == 'true'
+ uses: actions/github-script@v8
+ with:
+ script: |
+ const body = context.payload.pull_request.body || '';
+ const prNumber = String(context.payload.pull_request.number);
+ const matches = body.match(/#(\d+)(?![a-fA-F0-9])/g);
+ if (!matches) {
+ core.info('No issue references found in PR body');
+ core.setOutput('numbers', '');
+ return;
+ }
+ const unique = [...new Set(matches.map(m => m.replace('#', '')))]
+ .filter(n => n !== prNumber && parseInt(n, 10) > 0);
+ if (unique.length === 0) {
+ core.info('No issue references after filtering');
+ core.setOutput('numbers', '');
+ return;
+ }
+ core.info(`Found issue references: ${unique.join(', ')}`);
+ core.setOutput('numbers', unique.join(','));
+
+ - name: β¬οΈ Bump version
+ id: bump
+ if: >-
+ env.IS_MANUAL == 'true'
+ || steps.check_pr.outputs.needs_bump == 'true'
+ env:
+ VERSION: ${{ steps.version.outputs.version }}
+ run: |
+ set -euo pipefail
+ read_version() { grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/'; }
+ CUR=$(read_version)
+ NEW="$VERSION"
+
+ if [ "$NEW" = "$CUR" ]; then
+ echo "Cargo.toml already at $NEW, nothing to commit"
+ echo "bumped=false" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+
+ # Update the package version (first top-level `version = "..."` only)
+ sed -i -E "0,/^version = \".*\"/ s//version = \"${NEW}\"/" Cargo.toml
+ # Keep Cargo.lock in sync (version line directly follows the package name)
+ if [ -f Cargo.lock ]; then
+ sed -i -E "/^name = \"adguardian\"$/{n; s/^version = \".*\"/version = \"${NEW}\"/}" Cargo.lock
+ fi
+
+ git add Cargo.toml Cargo.lock
+ if git diff --cached --quiet; then
+ echo "No changes to commit"
+ echo "bumped=false" >> "$GITHUB_OUTPUT"
+ else
+ git commit -m "Bump version to ${NEW}"
+ git push origin HEAD:main
+ echo "bumped=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: π·οΈ Create and push tag
+ id: tag
+ if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true'
+ env:
+ PR_NUMBER: ${{ github.event.pull_request.number || '' }}
+ PR_TITLE: ${{ github.event.pull_request.title || '' }}
+ PR_AUTHOR: ${{ github.event.pull_request.user.login || github.actor }}
+ MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
+ ISSUES: ${{ steps.issues.outputs.numbers }}
+ VERSION: ${{ steps.version.outputs.version }}
+ run: |
+ set -euo pipefail
+ git fetch --tags --force
+
+ {
+ printf 'AdGuardian v%s π\n\n' "$VERSION"
+ if [ -n "$PR_NUMBER" ]; then
+ printf 'PR: #%s - %s\n' "$PR_NUMBER" "$PR_TITLE"
+ else
+ printf 'Manual release by @%s\n' "$PR_AUTHOR"
+ fi
+ if [ -n "$ISSUES" ]; then
+ printf 'Resolves: %s\n' "$(echo "$ISSUES" | sed 's/,/, #/g; s/^/#/')"
+ fi
+ printf 'Author: @%s\n' "$PR_AUTHOR"
+ printf 'Commit: %s\n' "$MERGE_SHA"
+ } > tag-message.txt
+
+ git tag -a "$VERSION" -F tag-message.txt
+ git push origin "$VERSION"
+ echo "result=created" >> "$GITHUB_OUTPUT"
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+
+ - name: π©οΈ Label referenced issues
+ id: label
+ if: >-
+ env.IS_MANUAL != 'true'
+ && steps.check_pr.outputs.needs_tag == 'true'
+ && steps.issues.outputs.numbers != ''
+ continue-on-error: true
+ uses: actions/github-script@v8
+ env:
+ ISSUES: ${{ steps.issues.outputs.numbers }}
+ with:
+ github-token: ${{ secrets.BOT_TOKEN }}
+ script: |
+ const { owner, repo } = context.repo;
+ const manifest = require('fs').readFileSync('Cargo.toml', 'utf8');
+ const version = manifest.match(/^version\s*=\s*"([^"]+)"/m)[1];
+ const labelName = `π©οΈ Released ${version}`;
+ const issues = process.env.ISSUES.split(',').filter(Boolean);
+
+ try {
+ await github.rest.issues.createLabel({
+ owner, repo,
+ name: labelName,
+ color: 'EDEDED',
+ description: `Included in release v${version}`,
+ });
+ core.info(`Created label: ${labelName}`);
+ } catch (e) {
+ if (e.status === 422) {
+ core.info(`Label already exists: ${labelName}`);
+ } else {
+ core.warning(`Failed to create label: ${e.message}`);
+ }
+ }
+
+ const prNumber = context.payload.pull_request.number;
+ const prAuthor = context.payload.pull_request.user?.login;
+ const creditAuthor = prAuthor && prAuthor.toLowerCase() !== 'lissy93';
+ const marker = `released-${version}`;
+ for (const num of issues) {
+ const issue_number = parseInt(num, 10);
+ try {
+ const [{ data: issue }, comments] = await Promise.all([
+ github.rest.issues.get({ owner, repo, issue_number }),
+ github.rest.issues.listComments({
+ owner, repo, issue_number, per_page: 100,
+ }),
+ ]);
+ const alreadyCommented = comments.data.some(
+ c => c.body?.includes(marker)
+ );
+ if (!alreadyCommented) {
+ const author = issue.user?.login;
+ const greeting = author ? `Hey @${author},` : 'Hey,';
+ const sixMonthsAgo = new Date();
+ sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
+ const isOld = new Date(issue.created_at) < sixMonthsAgo;
+ const byLine = creditAuthor ? ` by @${prAuthor}` : '';
+ const parts = [
+ greeting,
+ `this has now been implemented${byLine} in #${prNumber},`,
+ `and will be released shortly in ${version} π`,
+ ];
+ if (isOld) parts.push(`\n\nWe're sorry this one took so long π`);
+ parts.push(``);
+ const body = parts.join(' ');
+ await github.rest.issues.createComment({
+ owner, repo, issue_number, body,
+ });
+ }
+ await github.rest.issues.addLabels({
+ owner, repo, issue_number, labels: [labelName, 'β
Fixed'],
+ });
+ core.info(alreadyCommented
+ ? `Already commented on #${num}, labels applied`
+ : `Commented and labeled #${num}`);
+ } catch (e) {
+ core.warning(`Failed to process #${num}: ${e.message}`);
+ }
+ }
+
+ - name: π Job summary
+ if: always()
+ env:
+ PR_NUMBER: ${{ github.event.pull_request.number || '' }}
+ PR_TITLE: ${{ github.event.pull_request.title || '' }}
+ REPO_URL: ${{ github.server_url }}/${{ github.repository }}
+ NEEDS_BUMP: ${{ steps.check_pr.outputs.needs_bump }}
+ NEEDS_TAG: ${{ steps.check_pr.outputs.needs_tag }}
+ ISSUES: ${{ steps.issues.outputs.numbers }}
+ BUMPED: ${{ steps.bump.outputs.bumped }}
+ TAG_OUTCOME: ${{ steps.tag.outcome }}
+ TAG_RESULT: ${{ steps.tag.outputs.result }}
+ TAG_VERSION: ${{ steps.tag.outputs.version }}
+ LABEL_OUTCOME: ${{ steps.label.outcome }}
+ run: |
+ set -euo pipefail
+ VERSION="${TAG_VERSION:-$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/' 2>/dev/null || echo "unknown")}"
+
+ {
+ echo "## π Auto Version & Tag"
+ echo ""
+ echo "| Step | Result |"
+ echo "|------|--------|"
+
+ if [ "$IS_MANUAL" = "true" ]; then
+ echo "| Trigger | Manual dispatch |"
+ elif [ -n "$PR_NUMBER" ]; then
+ echo "| PR | [#${PR_NUMBER}](${REPO_URL}/pull/${PR_NUMBER}) β ${PR_TITLE} |"
+ fi
+
+ if [ "$BUMPED" = "true" ]; then
+ echo "| Version bump | β
\`${VERSION}\` |"
+ else
+ echo "| Version bump | βοΈ Skipped |"
+ fi
+
+ if [ "$TAG_RESULT" = "created" ]; then
+ echo "| Tag | β
[\`${VERSION}\`](${REPO_URL}/releases/tag/${VERSION}) |"
+ elif [ "$TAG_RESULT" = "existed" ]; then
+ echo "| Tag | βοΈ Already exists: \`${VERSION}\` |"
+ elif [ "$TAG_OUTCOME" = "failure" ]; then
+ echo "| Tag | β Failed |"
+ else
+ echo "| Tag | βοΈ Skipped |"
+ fi
+
+ if [ -n "$ISSUES" ]; then
+ ISSUE_LINKS=$(echo "$ISSUES" | tr ',' '\n' | sed "s|.*|[#&](${REPO_URL}/issues/&)|" | paste -sd ' ' -)
+ if [ "$LABEL_OUTCOME" = "success" ]; then
+ echo "| Issues labeled | β
${ISSUE_LINKS} |"
+ else
+ echo "| Issues labeled | β οΈ ${ISSUE_LINKS} |"
+ fi
+ fi
+ } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/zizmor.yml b/.github/zizmor.yml
new file mode 100644
index 0000000..83d148e
--- /dev/null
+++ b/.github/zizmor.yml
@@ -0,0 +1,16 @@
+# zizmor configuration β https://docs.zizmor.sh/configuration/
+#
+# House style: actions are pinned to version tags (e.g. @v6), not commit SHAs.
+# We rely on tag immutability plus Dependabot to keep them current, so allow a
+# ref-pin for every action rather than requiring a hash.
+rules:
+ unpinned-uses:
+ config:
+ policies:
+ "*": ref-pin
+
+ # The crate is published to crates.io with an API token by deliberate choice;
+ # OIDC trusted publishing isn't configured for this project.
+ use-trusted-publishing:
+ ignore:
+ - cargo.yml
diff --git a/Cargo.lock b/Cargo.lock
index 896cd6d..64a3201 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,13 +1,13 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "adguardian"
version = "1.6.0"
dependencies = [
"anyhow",
- "base64 0.13.1",
+ "base64",
"chrono",
"colored",
"crossterm 0.22.1",
@@ -31,38 +31,49 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.71"
+version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
-name = "atty"
-version = "0.2.14"
+name = "atomic-waker"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi 0.1.19",
- "libc",
- "winapi",
-]
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
-version = "1.1.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
-name = "base64"
-version = "0.13.1"
+name = "aws-lc-rs"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
+dependencies = [
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+]
[[package]]
name = "base64"
-version = "0.21.0"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
@@ -70,17 +81,23 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
[[package]]
name = "bumpalo"
-version = "3.12.1"
+version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytes"
-version = "1.4.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cassowary"
@@ -90,57 +107,90 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cc"
-version = "1.0.79"
+version = "1.2.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfg-if"
-version = "1.0.0"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
-version = "0.4.24"
+version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
- "num-integer",
"num-traits",
- "time",
"wasm-bindgen",
- "winapi",
+ "windows-link",
]
[[package]]
-name = "codespan-reporting"
-version = "0.11.1"
+name = "cmake"
+version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
- "termcolor",
- "unicode-width",
+ "cc",
]
[[package]]
name = "colored"
-version = "2.0.0"
+version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
+checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
- "atty",
- "lazy_static",
- "winapi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
]
[[package]]
name = "core-foundation-sys"
-version = "0.8.4"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossterm"
@@ -148,8 +198,9 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
"crossterm_winapi",
+ "futures-core",
"libc",
"mio 0.7.14",
"parking_lot 0.11.2",
@@ -165,11 +216,11 @@ version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
"crossterm_winapi",
"libc",
- "mio 0.8.6",
- "parking_lot 0.12.1",
+ "mio 0.8.11",
+ "parking_lot 0.12.5",
"signal-hook",
"signal-hook-mio",
"winapi",
@@ -177,86 +228,66 @@ dependencies = [
[[package]]
name = "crossterm_winapi"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
-name = "cxx"
-version = "1.0.94"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
-dependencies = [
- "cc",
- "cxxbridge-flags",
- "cxxbridge-macro",
- "link-cplusplus",
-]
-
-[[package]]
-name = "cxx-build"
-version = "1.0.94"
+name = "displaydoc"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
- "cc",
- "codespan-reporting",
- "once_cell",
"proc-macro2",
"quote",
- "scratch",
- "syn 2.0.15",
+ "syn",
]
[[package]]
-name = "cxxbridge-flags"
-version = "1.0.94"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
-
-[[package]]
-name = "cxxbridge-macro"
-version = "1.0.94"
+name = "dunce"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.15",
-]
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
-name = "encoding_rs"
-version = "0.8.32"
+name = "errno"
+version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
- "cfg-if",
+ "libc",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "fnv"
-version = "1.0.7"
+name = "find-msvc-tools"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "form_urlencoded"
-version = "1.1.0"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
[[package]]
name = "futures"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
+checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
@@ -269,9 +300,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
@@ -279,15 +310,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
+checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
@@ -296,38 +327,38 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-macro"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.15",
+ "syn",
]
[[package]]
name = "futures-sink"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
-version = "0.3.28"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-channel",
"futures-core",
@@ -337,249 +368,385 @@ dependencies = [
"futures-task",
"memchr",
"pin-project-lite",
- "pin-utils",
- "slab",
-]
-
-[[package]]
-name = "h2"
-version = "0.3.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21"
-dependencies = [
- "bytes",
- "fnv",
- "futures-core",
- "futures-sink",
- "futures-util",
- "http",
- "indexmap",
"slab",
- "tokio",
- "tokio-util",
- "tracing",
]
[[package]]
-name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-
-[[package]]
-name = "hermit-abi"
-version = "0.1.19"
+name = "getrandom"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
+ "cfg-if",
+ "js-sys",
"libc",
+ "wasi",
+ "wasm-bindgen",
]
[[package]]
-name = "hermit-abi"
-version = "0.2.6"
+name = "getrandom"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
+ "cfg-if",
+ "js-sys",
"libc",
+ "r-efi",
+ "wasip2",
+ "wasm-bindgen",
]
[[package]]
name = "http"
-version = "0.2.9"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
dependencies = [
"bytes",
- "fnv",
"itoa",
]
[[package]]
name = "http-body"
-version = "0.4.5"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
- "pin-project-lite",
]
[[package]]
-name = "httparse"
-version = "1.8.0"
+name = "http-body-util"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
[[package]]
-name = "httpdate"
-version = "1.0.2"
+name = "httparse"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "hyper"
-version = "0.14.26"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [
+ "atomic-waker",
"bytes",
"futures-channel",
"futures-core",
- "futures-util",
- "h2",
"http",
"http-body",
"httparse",
- "httpdate",
"itoa",
"pin-project-lite",
- "socket2",
+ "smallvec",
"tokio",
- "tower-service",
- "tracing",
"want",
]
[[package]]
name = "hyper-rustls"
-version = "0.23.2"
+version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http",
"hyper",
+ "hyper-util",
"rustls",
"tokio",
"tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
]
[[package]]
name = "iana-time-zone"
-version = "0.1.56"
+version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
+ "log",
"wasm-bindgen",
- "windows",
+ "windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
-version = "0.1.1"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+
+[[package]]
+name = "icu_properties"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+
+[[package]]
+name = "icu_provider"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
- "cxx",
- "cxx-build",
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
]
[[package]]
name = "idna"
-version = "0.3.0"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
- "unicode-bidi",
- "unicode-normalization",
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
]
[[package]]
-name = "indexmap"
-version = "1.9.3"
+name = "idna_adapter"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
- "autocfg",
- "hashbrown",
+ "icu_normalizer",
+ "icu_properties",
]
[[package]]
name = "instant"
-version = "0.1.12"
+version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
-version = "2.7.2"
+version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "itoa"
-version = "1.0.6"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
-name = "js-sys"
-version = "0.3.61"
+name = "jni"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [
- "wasm-bindgen",
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys 0.3.1",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
+dependencies = [
+ "jni-sys 0.4.1",
]
[[package]]
-name = "lazy_static"
-version = "1.4.0"
+name = "jni-sys"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
[[package]]
-name = "libc"
-version = "0.2.143"
+name = "jni-sys-macros"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edc207893e85c5d6be840e969b496b53d94cec8be2d501b214f50daa97fa8024"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn",
+]
[[package]]
-name = "link-cplusplus"
-version = "1.0.8"
+name = "jobserver"
+version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
- "cc",
+ "getrandom 0.3.4",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "once_cell",
+ "wasm-bindgen",
]
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
[[package]]
name = "lock_api"
-version = "0.4.9"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
- "autocfg",
"scopeguard",
]
[[package]]
name = "log"
-version = "0.4.17"
+version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
[[package]]
-name = "memchr"
-version = "2.5.0"
+name = "lru-slab"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
-name = "mime"
-version = "0.3.17"
+name = "memchr"
+version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]]
name = "mio"
@@ -596,14 +763,25 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.6"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
- "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.45.0",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -624,40 +802,26 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "num-integer"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
[[package]]
name = "num-traits"
-version = "0.2.15"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
-name = "num_cpus"
-version = "1.15.0"
+name = "once_cell"
+version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
-dependencies = [
- "hermit-abi 0.2.6",
- "libc",
-]
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
-name = "once_cell"
-version = "1.17.1"
+name = "openssl-probe"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "parking_lot"
@@ -672,12 +836,12 @@ dependencies = [
[[package]]
name = "parking_lot"
-version = "0.12.1"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
- "parking_lot_core 0.9.7",
+ "parking_lot_core 0.9.12",
]
[[package]]
@@ -689,67 +853,170 @@ dependencies = [
"cfg-if",
"instant",
"libc",
- "redox_syscall",
+ "redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
name = "parking_lot_core"
-version = "0.9.7"
+version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall",
+ "redox_syscall 0.5.18",
"smallvec",
- "windows-sys 0.45.0",
+ "windows-link",
]
[[package]]
name = "percent-encoding"
-version = "2.2.0"
+version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
-version = "0.2.9"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
-name = "pin-utils"
-version = "0.1.0"
+name = "potential_utf"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
[[package]]
-name = "proc-macro2"
-version = "1.0.56"
+name = "ppv-lite86"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
- "unicode-ident",
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
+dependencies = [
+ "aws-lc-rs",
+ "bytes",
+ "getrandom 0.3.4",
+ "lru-slab",
+ "rand",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.52.0",
]
[[package]]
name = "quote"
-version = "1.0.26"
+version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
[[package]]
name = "ratatui"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc0d032bccba900ee32151ec0265667535c230169f5a011154cdcd984e16829"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
"cassowary",
"crossterm 0.26.1",
"unicode-segmentation",
@@ -762,166 +1029,264 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.11.1",
]
[[package]]
name = "reqwest"
-version = "0.11.17"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
+checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
dependencies = [
- "base64 0.21.0",
+ "base64",
"bytes",
- "encoding_rs",
"futures-core",
- "futures-util",
- "h2",
"http",
"http-body",
+ "http-body-util",
"hyper",
"hyper-rustls",
- "ipnet",
+ "hyper-util",
"js-sys",
"log",
- "mime",
- "once_cell",
"percent-encoding",
"pin-project-lite",
+ "quinn",
"rustls",
- "rustls-pemfile",
+ "rustls-pki-types",
+ "rustls-platform-verifier",
"serde",
"serde_json",
- "serde_urlencoded",
+ "sync_wrapper",
"tokio",
"tokio-rustls",
+ "tower",
+ "tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
- "winreg",
]
[[package]]
name = "ring"
-version = "0.16.20"
+version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
+ "cfg-if",
+ "getrandom 0.2.17",
"libc",
- "once_cell",
- "spin",
"untrusted",
- "web-sys",
- "winapi",
+ "windows-sys 0.52.0",
]
+[[package]]
+name = "rustc-hash"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+
[[package]]
name = "rustls"
-version = "0.20.8"
+version = "0.23.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
+dependencies = [
+ "aws-lc-rs",
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [
+ "openssl-probe",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
+dependencies = [
+ "core-foundation",
+ "core-foundation-sys",
+ "jni",
"log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
+dependencies = [
+ "aws-lc-rs",
"ring",
- "sct",
- "webpki",
+ "rustls-pki-types",
+ "untrusted",
]
[[package]]
-name = "rustls-pemfile"
-version = "1.0.2"
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
- "base64 0.21.0",
+ "winapi-util",
]
[[package]]
-name = "ryu"
-version = "1.0.13"
+name = "schannel"
+version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
+dependencies = [
+ "windows-sys 0.61.2",
+]
[[package]]
name = "scopeguard"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
-name = "scratch"
-version = "1.0.5"
+name = "security-framework"
+version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
+dependencies = [
+ "bitflags 2.11.1",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
[[package]]
-name = "sct"
-version = "0.7.0"
+name = "security-framework-sys"
+version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
- "ring",
- "untrusted",
+ "core-foundation-sys",
+ "libc",
]
[[package]]
name = "semver"
-version = "1.0.17"
+version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
-version = "1.0.162"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.162"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.15",
+ "syn",
]
[[package]]
name = "serde_json"
-version = "1.0.96"
+version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
- "ryu",
+ "memchr",
"serde",
+ "serde_core",
+ "zmij",
]
[[package]]
-name = "serde_urlencoded"
-version = "0.7.1"
+name = "shlex"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
-dependencies = [
- "form_urlencoded",
- "itoa",
- "ryu",
- "serde",
-]
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "signal-hook"
-version = "0.3.15"
+version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [
"libc",
"signal-hook-registry",
@@ -929,61 +1294,65 @@ dependencies = [
[[package]]
name = "signal-hook-mio"
-version = "0.2.3"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
dependencies = [
"libc",
"mio 0.7.14",
- "mio 0.8.6",
+ "mio 0.8.11",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
-version = "1.4.1"
+version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
+ "errno",
"libc",
]
[[package]]
name = "slab"
-version = "0.4.8"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
-dependencies = [
- "autocfg",
-]
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
-version = "1.10.0"
+version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
-version = "0.4.9"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [
"libc",
- "winapi",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "spin"
-version = "0.5.2"
+name = "stable_deref_trait"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "1.0.109"
+version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -991,41 +1360,80 @@ dependencies = [
]
[[package]]
-name = "syn"
-version = "2.0.15"
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "unicode-ident",
+ "syn",
]
[[package]]
-name = "termcolor"
-version = "1.2.0"
+name = "thiserror"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "winapi-util",
+ "thiserror-impl 1.0.69",
]
[[package]]
-name = "time"
-version = "0.1.45"
+name = "thiserror"
+version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
- "libc",
- "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi",
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "zerovec",
]
[[package]]
name = "tinyvec"
-version = "1.6.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
@@ -1038,205 +1446,216 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.28.0"
+version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
- "autocfg",
"bytes",
"libc",
- "mio 0.8.6",
- "num_cpus",
- "parking_lot 0.12.1",
+ "mio 1.2.1",
+ "parking_lot 0.12.5",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
- "windows-sys 0.48.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
-version = "2.1.0"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
+checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.15",
+ "syn",
]
[[package]]
name = "tokio-rustls"
-version = "0.23.4"
+version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
- "webpki",
]
[[package]]
-name = "tokio-util"
-version = "0.7.8"
+name = "tower"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
- "bytes",
"futures-core",
- "futures-sink",
+ "futures-util",
"pin-project-lite",
+ "sync_wrapper",
"tokio",
- "tracing",
+ "tower-layer",
+ "tower-service",
]
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags 2.11.1",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
[[package]]
name = "tower-service"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
-version = "0.1.37"
+version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
- "cfg-if",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
-version = "0.1.30"
+version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
-
-[[package]]
-name = "unicode-bidi"
-version = "0.3.13"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "unicode-ident"
-version = "1.0.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
-
-[[package]]
-name = "unicode-normalization"
-version = "0.1.22"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
-dependencies = [
- "tinyvec",
-]
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-segmentation"
-version = "1.10.1"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]]
name = "unicode-width"
-version = "0.1.10"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "untrusted"
-version = "0.7.1"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
-version = "2.3.1"
+version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
+ "serde",
]
[[package]]
-name = "want"
-version = "0.3.0"
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
- "log",
- "try-lock",
+ "same-file",
+ "winapi-util",
]
[[package]]
-name = "wasi"
-version = "0.10.0+wasi-snapshot-preview1"
+name = "want"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
[[package]]
name = "wasi"
-version = "0.11.0+wasi-snapshot-preview1"
+version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
-name = "wasm-bindgen"
-version = "0.2.84"
+name = "wasip2"
+version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
- "cfg-if",
- "wasm-bindgen-macro",
+ "wit-bindgen",
]
[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.84"
+name = "wasm-bindgen"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
dependencies = [
- "bumpalo",
- "log",
+ "cfg-if",
"once_cell",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
+ "rustversion",
+ "wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.34"
+version = "0.4.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
+checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
dependencies = [
- "cfg-if",
"js-sys",
"wasm-bindgen",
- "web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.84"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1244,50 +1663,62 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.84"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
dependencies = [
+ "bumpalo",
"proc-macro2",
"quote",
- "syn 1.0.109",
- "wasm-bindgen-backend",
+ "syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.84"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
+dependencies = [
+ "unicode-ident",
+]
[[package]]
name = "web-sys"
-version = "0.3.61"
+version = "0.3.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
+checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
-name = "webpki"
-version = "0.22.0"
+name = "web-time"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
- "ring",
- "untrusted",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
+dependencies = [
+ "rustls-pki-types",
]
[[package]]
name = "webpki-roots"
-version = "0.22.6"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
+checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
dependencies = [
- "webpki",
+ "rustls-pki-types",
]
[[package]]
@@ -1308,11 +1739,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.5"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "winapi",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1322,12 +1753,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
-name = "windows"
-version = "0.48.0"
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
- "windows-targets 0.48.0",
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
]
[[package]]
@@ -1345,7 +1826,25 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
- "windows-targets 0.48.0",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
]
[[package]]
@@ -1365,17 +1864,33 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.48.0"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "windows_aarch64_gnullvm 0.48.0",
- "windows_aarch64_msvc 0.48.0",
- "windows_i686_gnu 0.48.0",
- "windows_i686_msvc 0.48.0",
- "windows_x86_64_gnu 0.48.0",
- "windows_x86_64_gnullvm 0.48.0",
- "windows_x86_64_msvc 0.48.0",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
]
[[package]]
@@ -1386,9 +1901,15 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
@@ -1398,9 +1919,15 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
@@ -1410,9 +1937,21 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
-version = "0.48.0"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
@@ -1422,9 +1961,15 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
@@ -1434,9 +1979,15 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -1446,9 +1997,15 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
@@ -1458,15 +2015,133 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
-name = "winreg"
-version = "0.10.1"
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
+name = "yoke"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
- "winapi",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/Cargo.toml b/Cargo.toml
index f022445..bdefc86 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,17 +14,17 @@ categories = ["command-line-utilities"]
# Error objecr for idiomatic error handling
anyhow = "1.0"
# For encoding auth headers
-base64 = "0.13"
+base64 = "0.22"
# Date + time parsing + manipulating
chrono = "0.4"
# Handling of terminal colors
-colored = "2.0"
+colored = "3"
# Term manipulation for kb + mouse events
-crossterm = { version = "0.22.0", features = ["serde"] }
+crossterm = { version = "0.22.0", features = ["serde", "event-stream"] }
# Extension of futures for async computation
futures = "0.3"
# HTTP client
-reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls"] }
+reqwest = { version = "0.13", default-features = false, features = ["json", "rustls", "webpki-roots"] }
# Decerilization of responses
serde = { version = "1.0", features = ["derive"] }
# Decerilization of JSON responses
diff --git a/Dockerfile b/Dockerfile
index 3020bfd..140f8bb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,13 +1,22 @@
-# syntax=docker/dockerfile:1.2
+# syntax=docker/dockerfile:1
-# Build application - Copy assets, install deps and compile binary
-FROM --platform=$BUILDPLATFORM rust:1.69.0-alpine AS builder
-RUN apk add --no-cache pkgconfig openssl openssl-dev musl-dev
-WORKDIR /usr/src/adguardian
+# Build: compile a static musl binary for the TARGET arch
+FROM rust:1.90-alpine AS builder
+RUN apk add --no-cache build-base cmake perl ca-certificates
+WORKDIR /app
COPY . .
-RUN cargo build --release
+ARG TARGETPLATFORM
+RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-reg-${TARGETPLATFORM} \
+ cargo build --release && cp target/release/adguardian /adguardian
-# Run application - Using lightweight base, execute the binary
+# Runtime: just the static binary on an empty image ---
FROM scratch
-COPY --from=builder /usr/src/adguardian/target/release/adguardian /
+LABEL org.opencontainers.image.title="AdGuardian-Term" \
+ org.opencontainers.image.description="Real-time traffic monitoring for AdGuard Home, in your terminal" \
+ org.opencontainers.image.source="https://github.com/Lissy93/AdGuardian-Term" \
+ org.opencontainers.image.licenses="MIT"
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
+COPY --from=builder /adguardian /adguardian
+USER 65534:65534
ENTRYPOINT ["/adguardian"]
diff --git a/Dockerfile.full b/Dockerfile.full
new file mode 100644
index 0000000..28e09bd
--- /dev/null
+++ b/Dockerfile.full
@@ -0,0 +1,28 @@
+# syntax=docker/dockerfile:1
+
+# glibc (Debial) based Docker image for AdGuardian-Term
+# Because the ultra-lightweight scratch version doesn't work on 32-bit arch
+
+# Build: compile the binary for the TARGET arch
+FROM rust:1.90-slim-bookworm AS builder
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends build-essential cmake clang perl \
+ && rm -rf /var/lib/apt/lists/*
+WORKDIR /app
+COPY . .
+ARG TARGETPLATFORM
+RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-reg-full-${TARGETPLATFORM} \
+ cargo build --release && cp target/release/adguardian /adguardian
+
+# Runtime: minimal glibc base with CA certs, run as non-root
+FROM debian:bookworm-slim
+LABEL org.opencontainers.image.title="AdGuardian-Term" \
+ org.opencontainers.image.description="Real-time traffic monitoring for AdGuard Home, in your terminal" \
+ org.opencontainers.image.source="https://github.com/Lissy93/AdGuardian-Term" \
+ org.opencontainers.image.licenses="MIT"
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+COPY --from=builder /adguardian /usr/local/bin/adguardian
+USER 65534:65534
+ENTRYPOINT ["/usr/local/bin/adguardian"]
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..8f9e576
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+tab_spaces = 2 # (don't come at me. the tab key is too far to reach)
diff --git a/src/fetch/fetch_filters.rs b/src/fetch/fetch_filters.rs
index 7649135..18ed770 100644
--- a/src/fetch/fetch_filters.rs
+++ b/src/fetch/fetch_filters.rs
@@ -1,34 +1,34 @@
-use reqwest::{Client, Response, header::HeaderMap};
+use base64::{engine::general_purpose::STANDARD, Engine as _};
+use reqwest::{header::HeaderMap, Client, Response};
use serde::Deserialize;
#[derive(Deserialize)]
pub struct AdGuardFilteringStatus {
- pub filters: Option>,
+ pub filters: Option>,
}
#[derive(Deserialize)]
pub struct Filter {
- pub url: String,
- pub name: String,
- pub rules_count: u32,
- pub enabled: bool,
+ pub name: String,
+ pub rules_count: u32,
+ pub enabled: bool,
}
pub async fn fetch_adguard_filter_list(
- client: &Client,
- endpoint: &str,
- username: &str,
- password: &str,
-) -> Result {
- let url = format!("{}/control/filtering/status", endpoint);
+ client: &Client,
+ endpoint: &str,
+ username: &str,
+ password: &str,
+) -> Result {
+ let url = format!("{}/control/filtering/status", endpoint);
- let auth_string = format!("{}:{}", username, password);
- let auth_header_value = format!("Basic {}", base64::encode(&auth_string));
- let mut headers = HeaderMap::new();
- headers.insert("Authorization", auth_header_value.parse().unwrap());
+ let auth_string = format!("{}:{}", username, password);
+ let auth_header_value = format!("Basic {}", STANDARD.encode(&auth_string));
+ let mut headers = HeaderMap::new();
+ headers.insert("Authorization", auth_header_value.parse()?);
- let res: Response = client.get(&url).headers(headers).send().await?;
- let status: AdGuardFilteringStatus = res.json().await?;
+ let res: Response = client.get(&url).headers(headers).send().await?;
+ let status: AdGuardFilteringStatus = res.json().await?;
- Ok(status)
+ Ok(status)
}
diff --git a/src/fetch/fetch_query_log.rs b/src/fetch/fetch_query_log.rs
index f0de9bf..2ee0972 100644
--- a/src/fetch/fetch_query_log.rs
+++ b/src/fetch/fetch_query_log.rs
@@ -1,31 +1,30 @@
-use reqwest::{
- header::{HeaderValue, CONTENT_LENGTH, AUTHORIZATION},
-};
+use base64::{engine::general_purpose::STANDARD, Engine as _};
+use reqwest::header::{HeaderValue, AUTHORIZATION, CONTENT_LENGTH};
use serde::Deserialize;
#[derive(Deserialize)]
pub struct QueryResponse {
- pub data: Vec,
+ pub data: Vec,
}
#[derive(Deserialize)]
pub struct Query {
- pub cached: bool,
- pub client: String,
- pub upstream: String,
- #[serde(rename = "elapsedMs")]
- pub elapsed_ms: String,
- pub question: Question,
- pub reason: String,
- pub time: String,
+ pub cached: bool,
+ pub client: String,
+ pub upstream: String,
+ #[serde(rename = "elapsedMs")]
+ pub elapsed_ms: String,
+ pub question: Question,
+ pub reason: String,
+ pub time: String,
}
#[derive(Deserialize)]
pub struct Question {
- pub class: String,
- pub name: String,
- #[serde(rename = "type")]
- pub question_type: String,
+ pub class: String,
+ pub name: String,
+ #[serde(rename = "type")]
+ pub question_type: String,
}
pub async fn fetch_adguard_query_log(
@@ -33,20 +32,23 @@ pub async fn fetch_adguard_query_log(
endpoint: &str,
username: &str,
password: &str,
+ limit: u32,
) -> Result {
let auth_string = format!("{}:{}", username, password);
- let auth_header_value = format!("Basic {}", base64::encode(&auth_string));
+ let auth_header_value = format!("Basic {}", STANDARD.encode(&auth_string));
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(AUTHORIZATION, auth_header_value.parse()?);
headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
- let url = format!("{}/control/querylog", endpoint);
+ let url = format!("{}/control/querylog?limit={}", endpoint, limit);
let response = client.get(&url).headers(headers).send().await?;
if !response.status().is_success() {
- return Err(anyhow::anyhow!("Request failed with status code {}", response.status()));
+ return Err(anyhow::anyhow!(
+ "Request failed with status code {}",
+ response.status()
+ ));
}
let data = response.json().await?;
Ok(data)
}
-
diff --git a/src/fetch/fetch_stats.rs b/src/fetch/fetch_stats.rs
index 74516ad..c19bc09 100644
--- a/src/fetch/fetch_stats.rs
+++ b/src/fetch/fetch_stats.rs
@@ -1,78 +1,89 @@
+use base64::{engine::general_purpose::STANDARD, Engine as _};
/// This module fetches data from AdGuard's stats API
/// This includes total number of blocked / allowed queries in each category,
/// and 30-day query count history
-
-use reqwest::{
- header::{HeaderValue, CONTENT_LENGTH, AUTHORIZATION},
-};
+use reqwest::header::{HeaderValue, AUTHORIZATION, CONTENT_LENGTH};
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Deserialize, Clone)]
pub struct DomainData {
- pub name: String,
- pub count: i32,
+ pub name: String,
+ pub count: i32,
}
#[derive(Debug, Deserialize, Clone)]
pub struct StatsResponse {
- pub num_dns_queries: u64,
- pub num_blocked_filtering: u64,
- pub num_replaced_safebrowsing: u64,
- pub num_replaced_safesearch: u64,
- pub num_replaced_parental: u64,
- pub avg_processing_time: f64,
- pub dns_queries: Vec,
- pub blocked_filtering: Vec,
- pub replaced_safebrowsing: Vec,
- pub replaced_parental: Vec,
+ pub num_dns_queries: u64,
+ pub num_blocked_filtering: u64,
+ pub num_replaced_safebrowsing: u64,
+ pub num_replaced_safesearch: u64,
+ pub num_replaced_parental: u64,
+ pub avg_processing_time: f64,
+ pub dns_queries: Vec,
+ pub blocked_filtering: Vec,
+ pub replaced_safebrowsing: Vec,
+ pub replaced_parental: Vec,
- #[serde(default, skip_deserializing)]
- pub dns_queries_chart: Vec<(f64, f64)>,
- #[serde(default, skip_deserializing)]
- pub blocked_filtering_chart: Vec<(f64, f64)>,
+ #[serde(default, skip_deserializing)]
+ pub dns_queries_chart: Vec<(f64, f64)>,
+ #[serde(default, skip_deserializing)]
+ pub blocked_filtering_chart: Vec<(f64, f64)>,
- #[serde(rename = "top_queried_domains", deserialize_with = "deserialize_domains")]
- pub top_queried_domains: Vec,
- #[serde(rename = "top_blocked_domains", deserialize_with = "deserialize_domains")]
- pub top_blocked_domains: Vec,
- #[serde(rename = "top_clients", deserialize_with = "deserialize_domains")]
- pub top_clients: Vec,
+ #[serde(
+ rename = "top_queried_domains",
+ deserialize_with = "deserialize_domains"
+ )]
+ pub top_queried_domains: Vec,
+ #[serde(
+ rename = "top_blocked_domains",
+ deserialize_with = "deserialize_domains"
+ )]
+ pub top_blocked_domains: Vec,
+ #[serde(rename = "top_clients", deserialize_with = "deserialize_domains")]
+ pub top_clients: Vec,
}
pub async fn fetch_adguard_stats(
- client: &reqwest::Client,
- endpoint: &str,
- username: &str,
- password: &str,
+ client: &reqwest::Client,
+ endpoint: &str,
+ username: &str,
+ password: &str,
) -> Result {
- let auth_string = format!("{}:{}", username, password);
- let auth_header_value = format!("Basic {}", base64::encode(&auth_string));
- let mut headers = reqwest::header::HeaderMap::new();
- headers.insert(AUTHORIZATION, auth_header_value.parse()?);
- headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
+ let auth_string = format!("{}:{}", username, password);
+ let auth_header_value = format!("Basic {}", STANDARD.encode(&auth_string));
+ let mut headers = reqwest::header::HeaderMap::new();
+ headers.insert(AUTHORIZATION, auth_header_value.parse()?);
+ headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
- let url = format!("{}/control/stats", endpoint);
- let response = client.get(&url).headers(headers).send().await?;
- if !response.status().is_success() {
- return Err(anyhow::anyhow!("Request failed with status code {}", response.status()));
- }
+ let url = format!("{}/control/stats", endpoint);
+ let response = client.get(&url).headers(headers).send().await?;
+ if !response.status().is_success() {
+ return Err(anyhow::anyhow!(
+ "Request failed with status code {}",
+ response.status()
+ ));
+ }
- let data = response.json().await?;
- Ok(data)
+ let data = response.json().await?;
+ Ok(data)
}
/// Deserialize a list of domains from the JSON data
fn deserialize_domains<'de, D>(deserializer: D) -> Result, D::Error>
where
- D: serde::Deserializer<'de>,
+ D: serde::Deserializer<'de>,
{
- let raw_vec: Vec> = serde::Deserialize::deserialize(deserializer)?;
- Ok(raw_vec
- .into_iter()
- .flat_map(|mut map| {
- map.drain().map(|(name, count)| DomainData { name, count }).collect::>()
- })
- .collect())
+ let raw_vec: Vec> = serde::Deserialize::deserialize(deserializer)?;
+ Ok(
+ raw_vec
+ .into_iter()
+ .flat_map(|mut map| {
+ map
+ .drain()
+ .map(|(name, count)| DomainData { name, count })
+ .collect::>()
+ })
+ .collect(),
+ )
}
-
diff --git a/src/fetch/fetch_status.rs b/src/fetch/fetch_status.rs
index b094683..f3c25f5 100644
--- a/src/fetch/fetch_status.rs
+++ b/src/fetch/fetch_status.rs
@@ -1,11 +1,10 @@
-use reqwest::{
- header::{HeaderValue, CONTENT_LENGTH, AUTHORIZATION},
-};
+use base64::{engine::general_purpose::STANDARD, Engine as _};
+use reqwest::header::{HeaderValue, AUTHORIZATION, CONTENT_LENGTH};
use serde::Deserialize;
/// Represents the status response from the AdGuard Home API.
///
-/// This struct is used to deserialize the JSON response from the
+/// This struct is used to deserialize the JSON response from the
/// `/control/status` endpoint.
///
/// # Example
@@ -24,31 +23,24 @@ use serde::Deserialize;
/// # Fields
///
/// * `version` - The version of the AdGuard Home instance.
-/// * `language` - The language currently used in the AdGuard Home instance.
-/// * `dns_addresses` - The DNS addresses used by the AdGuard Home instance.
/// * `dns_port` - The port number on which the DNS server is running.
/// * `http_port` - The port number on which the HTTP server is running.
-/// * `protection_disabled_duration` - The duration for which protection is disabled (in seconds).
/// * `protection_enabled` - Whether or not protection is currently enabled.
/// * `dhcp_available` - Whether or not DHCP is available.
/// * `running` - Whether or not the AdGuard Home instance is currently running.
#[derive(Debug, Deserialize, Clone)]
pub struct StatusResponse {
- pub version: String,
- pub language: String,
- pub dns_addresses: Vec,
- pub dns_port: u16,
- pub http_port: u16,
- pub protection_disabled_duration: u64,
- pub protection_enabled: bool,
- pub dhcp_available: bool,
- pub running: bool,
+ pub version: String,
+ pub dns_port: u16,
+ pub http_port: u16,
+ pub protection_enabled: bool,
+ pub dhcp_available: bool,
+ pub running: bool,
}
-
/// Fetches the current status from the AdGuard Home instance.
///
-/// This function sends a GET request to the `/control/status` endpoint of the
+/// This function sends a GET request to the `/control/status` endpoint of the
/// AdGuard Home API, then deserializes the JSON response into a `StatusResponse`.
///
/// # Arguments
@@ -74,23 +66,26 @@ pub struct StatusResponse {
/// println!("AdGuard Status: {:?}", status);
/// ```
pub async fn fetch_adguard_status(
- client: &reqwest::Client,
- endpoint: &str,
- username: &str,
- password: &str,
+ client: &reqwest::Client,
+ endpoint: &str,
+ username: &str,
+ password: &str,
) -> Result {
- let auth_string = format!("{}:{}", username, password);
- let auth_header_value = format!("Basic {}", base64::encode(&auth_string));
- let mut headers = reqwest::header::HeaderMap::new();
- headers.insert(AUTHORIZATION, auth_header_value.parse()?);
- headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
+ let auth_string = format!("{}:{}", username, password);
+ let auth_header_value = format!("Basic {}", STANDARD.encode(&auth_string));
+ let mut headers = reqwest::header::HeaderMap::new();
+ headers.insert(AUTHORIZATION, auth_header_value.parse()?);
+ headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
- let url = format!("{}/control/status", endpoint);
- let response = client.get(&url).headers(headers).send().await?;
- if !response.status().is_success() {
- return Err(anyhow::anyhow!("Request failed with status code {}", response.status()));
- }
+ let url = format!("{}/control/status", endpoint);
+ let response = client.get(&url).headers(headers).send().await?;
+ if !response.status().is_success() {
+ return Err(anyhow::anyhow!(
+ "Request failed with status code {}",
+ response.status()
+ ));
+ }
- let data = response.json().await?;
- Ok(data)
+ let data = response.json().await?;
+ Ok(data)
}
diff --git a/src/fetch/mod.rs b/src/fetch/mod.rs
index 61b6f3b..19d1e32 100644
--- a/src/fetch/mod.rs
+++ b/src/fetch/mod.rs
@@ -1,4 +1,4 @@
+pub mod fetch_filters;
pub mod fetch_query_log;
-pub mod fetch_status;
pub mod fetch_stats;
-pub mod fetch_filters;
+pub mod fetch_status;
diff --git a/src/main.rs b/src/main.rs
index ccc2caf..57f85b7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,101 +1,127 @@
mod fetch;
mod ui;
-mod widgets;
mod welcome;
+mod widgets;
-use std::{env, sync::Arc, time::Duration};
use reqwest::Client;
-use tokio::time::interval;
+use std::{env, time::Duration};
+use tokio::time::{interval, MissedTickBehavior};
use ui::draw_ui;
use fetch::{
- fetch_query_log::fetch_adguard_query_log,
- fetch_stats::fetch_adguard_stats,
- fetch_status::fetch_adguard_status,
- fetch_filters::fetch_adguard_filter_list
+ fetch_filters::fetch_adguard_filter_list,
+ fetch_query_log::{fetch_adguard_query_log, Query},
+ fetch_stats::{fetch_adguard_stats, StatsResponse},
+ fetch_status::{fetch_adguard_status, StatusResponse},
};
-async fn run() -> anyhow::Result<()> {
-
- // Create a reqwest client
- let client = Client::new();
-
- // AdGuard instance details, from env vars (verified in welcome.rs)
- let ip = env::var("ADGUARD_IP")?;
- let port = env::var("ADGUARD_PORT")?;
- let protocol = env::var("ADGUARD_PROTOCOL").unwrap_or("http".to_string());
- let hostname = format!("{}://{}:{}", protocol, ip, port);
- let username = env::var("ADGUARD_USERNAME")?;
- let password = env::var("ADGUARD_PASSWORD")?;
-
-
- // Fetch data that doesn't require updates
- let filters = fetch_adguard_filter_list(&client, &hostname, &username, &password).await?;
-
- // Open channels for data fetching where updates are required
- let (queries_tx, queries_rx) = tokio::sync::mpsc::channel(1);
- let (stats_tx, stats_rx) = tokio::sync::mpsc::channel(1);
- let (status_tx, status_rx) = tokio::sync::mpsc::channel(1);
-
- // Create a channel for the UI to notify the fetcher to shutdown
- let shutdown = Arc::new(tokio::sync::Notify::new());
-
- // Spawn the UI task, pass data and update channels
- let draw_ui_task = tokio::spawn(
- draw_ui(queries_rx, stats_rx, status_rx, filters, Arc::clone(&shutdown))
- );
-
- // Get update interval (in seconds)
- let interval_secs: u64 = env::var("ADGUARD_UPDATE_INTERVAL")
- .unwrap_or_else(|_| "2".into()).parse()?;
- let mut interval = interval(Duration::from_secs(interval_secs));
-
- // Open loop for fetching data at the specified interval
- loop {
- tokio::select! {
- _ = interval.tick() => {
- let queries = fetch_adguard_query_log(&client, &hostname, &username, &password).await?;
- if queries_tx.send(queries.data).await.is_err() {
- return Err(anyhow::anyhow!("Failed to send query data"));
- }
-
- let stats = fetch_adguard_stats(&client, &hostname, &username, &password).await?;
- if stats_tx.send(stats).await.is_err() {
- return Err(anyhow::anyhow!("Failed to send stats data"));
- }
+/// Fetch the query log, stats and status together, so a failure leaves the UI's
+/// data in sync (all-or-nothing) rather than partially updated.
+async fn fetch_all(
+ client: &Client,
+ hostname: &str,
+ username: &str,
+ password: &str,
+ query_log_limit: u32,
+) -> anyhow::Result<(Vec, StatsResponse, StatusResponse)> {
+ let queries =
+ fetch_adguard_query_log(client, hostname, username, password, query_log_limit).await?;
+ let stats = fetch_adguard_stats(client, hostname, username, password).await?;
+ let status = fetch_adguard_status(client, hostname, username, password).await?;
+ Ok((queries.data, stats, status))
+}
- let status = fetch_adguard_status(&client, &hostname, &username, &password).await?;
- if status_tx.send(status).await.is_err() {
- return Err(anyhow::anyhow!("Failed to send status data"));
+async fn run() -> anyhow::Result<()> {
+ // Create a reqwest client
+ let client = Client::new();
+
+ // AdGuard instance details, from env vars (verified in welcome.rs)
+ let ip = env::var("ADGUARD_IP")?;
+ let port = env::var("ADGUARD_PORT")?;
+ let protocol = env::var("ADGUARD_PROTOCOL").unwrap_or("http".to_string());
+ let hostname = format!("{}://{}:{}", protocol, ip, port);
+ let username = env::var("ADGUARD_USERNAME")?;
+ let password = env::var("ADGUARD_PASSWORD")?;
+
+ // Fetch data that doesn't require updates
+ let filters = fetch_adguard_filter_list(&client, &hostname, &username, &password).await?;
+
+ // Open channels for data fetching where updates are required
+ let (queries_tx, queries_rx) = tokio::sync::mpsc::channel(1);
+ let (stats_tx, stats_rx) = tokio::sync::mpsc::channel(1);
+ let (status_tx, status_rx) = tokio::sync::mpsc::channel(1);
+
+ // Shutdown signal, set by the UI when the user quits
+ let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false);
+
+ // Spawn the UI task, pass data and update channels
+ let draw_ui_task = tokio::spawn(draw_ui(
+ queries_rx,
+ stats_rx,
+ status_rx,
+ filters,
+ shutdown_tx,
+ ));
+
+ // Get update interval (in seconds)
+ let interval_secs: u64 = env::var("ADGUARD_UPDATE_INTERVAL")
+ .unwrap_or_else(|_| "2".into())
+ .parse()?;
+ let mut interval = interval(Duration::from_secs(interval_secs));
+ interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
+
+ // Max num of query log entries to fetch per update
+ let query_log_limit: u32 = env::var("ADGUARD_QUERYLOG_LIMIT")
+ .unwrap_or_else(|_| "100".into())
+ .parse()?;
+
+ // Open loop for fetching data at the specified interval
+ loop {
+ tokio::select! {
+ _ = interval.tick() => {
+ // Check data is ok, just skip this update on transient error
+ if let Ok((queries, stats, status)) =
+ fetch_all(&client, &hostname, &username, &password, query_log_limit).await
+ {
+ // A send error means the UI has shut down, so stop fetching
+ if queries_tx.send(queries).await.is_err()
+ || stats_tx.send(stats).await.is_err()
+ || status_tx.send(status).await.is_err()
+ {
+ break;
}
}
- _ = shutdown.notified() => {
- break;
- }
+ }
+ // Resolves when the UI sets the shutdown flag, or drops the sender
+ _ = shutdown_rx.changed() => {
+ break;
}
}
+ }
- draw_ui_task.await??;
+ draw_ui_task.await??;
- Ok(())
+ Ok(())
}
fn main() {
- let rt = tokio::runtime::Runtime::new().unwrap();
- rt.block_on(async {
- welcome::welcome().await.map_err(|e| {
- eprintln!("Failed to initialize: {}", e);
- std::io::Error::new(std::io::ErrorKind::Other, "Failed to initialize")
- }).unwrap();
-
- run().await.map_err(|e| {
- eprintln!("Failed to run: {}", e);
- std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to run: {}", e))
- }).unwrap_or_else(|e| {
- eprintln!("Error: {}", e);
- std::process::exit(1);
- });
+ let rt = tokio::runtime::Runtime::new().unwrap();
+ rt.block_on(async {
+ welcome::welcome().await.unwrap_or_else(|e| {
+ eprintln!("Failed to initialize: {}", e);
+ std::process::exit(1);
});
-}
+ run()
+ .await
+ .map_err(|e| {
+ eprintln!("Failed to run: {}", e);
+ std::io::Error::other(format!("Failed to run: {}", e))
+ })
+ .unwrap_or_else(|e| {
+ eprintln!("Error: {}", e);
+ std::process::exit(1);
+ });
+ });
+}
diff --git a/src/ui.rs b/src/ui.rs
index 1435751..cc5f0c4 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -1,13 +1,15 @@
-use std::{
- io::stdout,
- sync::Arc,
- time::Duration,
-};
use crossterm::{
- event::{poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers},
+ cursor::Show,
+ event::{
+ DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode, KeyEvent, KeyModifiers,
+ },
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
+use futures::StreamExt;
+use std::io::stdout;
+use std::sync::Arc;
+use tokio::sync::watch;
use tui::{
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout},
@@ -15,175 +17,190 @@ use tui::{
Terminal,
};
-use crate::fetch::fetch_stats::StatsResponse;
+use crate::fetch::fetch_filters::{AdGuardFilteringStatus, Filter};
use crate::fetch::fetch_query_log::Query;
+use crate::fetch::fetch_stats::StatsResponse;
use crate::fetch::fetch_status::StatusResponse;
-use crate::fetch::fetch_filters::{AdGuardFilteringStatus, Filter};
-use crate::widgets::gauge::make_gauge;
-use crate::widgets::table::make_query_table;
use crate::widgets::chart::{make_history_chart, prepare_chart_data};
-use crate::widgets::status::render_status_paragraph;
use crate::widgets::filters::make_filters_list;
+use crate::widgets::gauge::make_gauge;
use crate::widgets::list::make_list;
+use crate::widgets::status::render_status_paragraph;
+use crate::widgets::table::make_query_table;
pub async fn draw_ui(
- mut data_rx: tokio::sync::mpsc::Receiver>,
- mut stats_rx: tokio::sync::mpsc::Receiver,
- mut status_rx: tokio::sync::mpsc::Receiver,
- filters: AdGuardFilteringStatus,
- shutdown: Arc
+ mut data_rx: tokio::sync::mpsc::Receiver>,
+ mut stats_rx: tokio::sync::mpsc::Receiver,
+ mut status_rx: tokio::sync::mpsc::Receiver,
+ filters: AdGuardFilteringStatus,
+ shutdown_tx: watch::Sender,
) -> Result<(), anyhow::Error> {
- enable_raw_mode()?;
- let mut stdout = stdout();
- execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
- let backend = CrosstermBackend::new(stdout);
- let mut terminal = Terminal::new(backend)?;
- terminal.clear()?;
-
+ // Guard restores the terminal on drop, even if we return early via `?`
+ let _guard = TerminalGuard::new()?;
+ let backend = CrosstermBackend::new(stdout());
+ let mut terminal = Terminal::new(backend)?;
+ terminal.clear()?;
+
+ // Handle quit keys (q / Ctrl+C) in a separate task, so input never blocks on data
+ let shutdown_tx = Arc::new(shutdown_tx);
+ let input_shutdown_tx = Arc::clone(&shutdown_tx);
+ let input_task = tokio::spawn(async move {
+ let mut reader = EventStream::new();
+ let mut shutdown_rx = input_shutdown_tx.subscribe();
loop {
- // Receive query log and stats data from the fetcher
- let data = match data_rx.recv().await {
+ tokio::select! {
+ maybe_event = reader.next() => {
+ match maybe_event {
+ Some(Ok(Event::Key(key))) if is_quit_key(key) => {
+ let _ = input_shutdown_tx.send(true);
+ break;
+ }
+ Some(Ok(_)) => {}
+ Some(Err(_)) | None => break,
+ }
+ }
+ // Stop if shutdown was triggered elsewhere (e.g. channels closed)
+ _ = shutdown_rx.changed() => break,
+ }
+ }
+ });
+
+ let mut shutdown_rx = shutdown_tx.subscribe();
+
+ loop {
+ // Wait for the next batch of data, but bail out immediately on shutdown
+ let data = tokio::select! {
+ biased;
+ _ = shutdown_rx.changed() => break,
+ maybe_data = data_rx.recv() => match maybe_data {
Some(data) => data,
- None => break, // Channel has been closed, so we break the loop
- };
- let mut stats = match stats_rx.recv().await {
- Some(stats) => stats,
- None => break,
- };
- let status = match status_rx.recv().await {
- Some(status) => status,
None => break,
- };
-
- // Prepare the data for the chart
- prepare_chart_data(&mut stats);
-
- terminal.draw(|f| {
- let size = f.size();
-
- // Make the charts
- let gauge = make_gauge(&stats);
- let table = make_query_table(&data, size.width);
- let graph = make_history_chart(&stats);
- let paragraph = render_status_paragraph(&status, &stats);
- let filter_items: &[Filter] = filters
- .filters
- .as_deref()
- .unwrap_or(&[]);
- let filters_list = make_filters_list(filter_items, size.width);
- let top_queried_domains = make_list("Top Queried Domains", &stats.top_queried_domains, Color::Green, size.width);
- let top_blocked_domains = make_list("Top Blocked Domains", &stats.top_blocked_domains, Color::Red, size.width);
- let top_clients = make_list("Top Clients", &stats.top_clients, Color::Cyan, size.width);
-
- let constraints = if size.height > 42 {
- vec![
- Constraint::Percentage(30),
- Constraint::Min(1),
- Constraint::Percentage(20)
- ]
- } else {
- vec![
- Constraint::Percentage(30),
- Constraint::Min(1),
- Constraint::Percentage(0)
- ]
- };
-
- let chunks = Layout::default()
- .direction(Direction::Vertical)
- .constraints(&*constraints)
- .split(size);
-
- // Split the top part (charts + gauge) into left (gauge + block) and right (line chart)
- let top_chunks = Layout::default()
- .direction(Direction::Horizontal)
- .constraints(
- [
- Constraint::Percentage(30),
- Constraint::Percentage(70),
- ]
- .as_ref(),
- )
- .split(chunks[0]);
-
- // Split the left part of top (gauge + block) into top (gauge) and bottom (block)
- let left_chunks = Layout::default()
- .direction(Direction::Vertical)
- .constraints(
- [
- Constraint::Min(0),
- Constraint::Length(3),
- ]
- .as_ref(),
- )
- .split(top_chunks[0]);
-
- let bottom_chunks = Layout::default()
- .direction(Direction::Horizontal)
- .constraints(
- [
- Constraint::Percentage(25),
- Constraint::Percentage(25),
- Constraint::Percentage(25),
- Constraint::Percentage(25),
- ]
- .as_ref(),
- )
- .split(chunks[2]);
-
- // Render the widgets to the UI
- f.render_widget(paragraph, left_chunks[0]);
- f.render_widget(gauge, left_chunks[1]);
- f.render_widget(graph, top_chunks[1]);
- f.render_widget(table, chunks[1]);
- if size.height > 42 {
- f.render_widget(filters_list, bottom_chunks[0]);
- f.render_widget(top_queried_domains, bottom_chunks[1]);
- f.render_widget(top_blocked_domains, bottom_chunks[2]);
- f.render_widget(top_clients, bottom_chunks[3]);
- }
- })?;
-
- // Check for user input events
- if poll(Duration::from_millis(100))? {
- match read()? {
- Event::Key(KeyEvent {
- code: KeyCode::Char('q'),
- ..
- }) => {
- // std::process::exit(0);
- shutdown.notify_waiters();
- break;
- }
- Event::Key(KeyEvent {
- code: KeyCode::Char('Q'),
- ..
- }) => {
- shutdown.notify_waiters();
- break;
- }
- Event::Key(KeyEvent {
- code: KeyCode::Char('c'),
- modifiers: KeyModifiers::CONTROL,
- }) => {
- shutdown.notify_waiters();
- break;
- }
- Event::Resize(_, _) => {}, // Handle resize event, loop will redraw the UI
- _ => {}
- }
- }
+ },
+ };
+ let mut stats = match stats_rx.recv().await {
+ Some(stats) => stats,
+ None => break,
+ };
+ let status = match status_rx.recv().await {
+ Some(status) => status,
+ None => break,
+ };
+
+ // Prepare the data for the chart
+ prepare_chart_data(&mut stats);
+
+ terminal.draw(|f| {
+ let size = f.size();
+
+ // Make the charts
+ let gauge = make_gauge(&stats);
+ let table = make_query_table(&data, size.width);
+ let graph = make_history_chart(&stats);
+ let paragraph = render_status_paragraph(&status, &stats);
+ let filter_items: &[Filter] = filters.filters.as_deref().unwrap_or(&[]);
+ let filters_list = make_filters_list(filter_items, size.width);
+ let top_queried_domains = make_list(
+ "Top Queried Domains",
+ &stats.top_queried_domains,
+ Color::Green,
+ size.width,
+ );
+ let top_blocked_domains = make_list(
+ "Top Blocked Domains",
+ &stats.top_blocked_domains,
+ Color::Red,
+ size.width,
+ );
+ let top_clients = make_list("Top Clients", &stats.top_clients, Color::Cyan, size.width);
+
+ let constraints = if size.height > 42 {
+ vec![
+ Constraint::Percentage(30),
+ Constraint::Min(1),
+ Constraint::Percentage(20),
+ ]
+ } else {
+ vec![
+ Constraint::Percentage(30),
+ Constraint::Min(1),
+ Constraint::Percentage(0),
+ ]
+ };
+
+ let chunks = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(&*constraints)
+ .split(size);
+
+ // Split the top part (charts + gauge) into left (gauge + block) and right (line chart)
+ let top_chunks = Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref())
+ .split(chunks[0]);
+
+ // Split the left part of top (gauge + block) into top (gauge) and bottom (block)
+ let left_chunks = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref())
+ .split(top_chunks[0]);
+
+ let bottom_chunks = Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints(
+ [
+ Constraint::Percentage(25),
+ Constraint::Percentage(25),
+ Constraint::Percentage(25),
+ Constraint::Percentage(25),
+ ]
+ .as_ref(),
+ )
+ .split(chunks[2]);
+
+ // Render the widgets to the UI
+ f.render_widget(paragraph, left_chunks[0]);
+ f.render_widget(gauge, left_chunks[1]);
+ f.render_widget(graph, top_chunks[1]);
+ f.render_widget(table, chunks[1]);
+ if size.height > 42 {
+ f.render_widget(filters_list, bottom_chunks[0]);
+ f.render_widget(top_queried_domains, bottom_chunks[1]);
+ f.render_widget(top_blocked_domains, bottom_chunks[2]);
+ f.render_widget(top_clients, bottom_chunks[3]);
+ }
+ })?;
+ }
+
+ // Signal shutdown to the input task and fetcher
+ let _ = shutdown_tx.send(true);
+ let _ = input_task.await;
+ Ok(())
+}
- }
+/// Enables raw mode + alternate screen, and restores them on drop.
+struct TerminalGuard;
- terminal.show_cursor()?;
- execute!(
- terminal.backend_mut(),
- LeaveAlternateScreen,
- DisableMouseCapture
- )?;
- disable_raw_mode()?;
- Ok(())
+impl TerminalGuard {
+ fn new() -> Result {
+ enable_raw_mode()?;
+ execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?;
+ Ok(Self)
+ }
}
+impl Drop for TerminalGuard {
+ fn drop(&mut self) {
+ let _ = execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture, Show);
+ let _ = disable_raw_mode();
+ }
+}
+
+/// Returns `true` if a key event should quit the app: `q`, `Q`, or Ctrl+C.
+fn is_quit_key(key: KeyEvent) -> bool {
+ match key.code {
+ KeyCode::Char('q') | KeyCode::Char('Q') => true,
+ KeyCode::Char('c') => key.modifiers.contains(KeyModifiers::CONTROL),
+ _ => false,
+ }
+}
diff --git a/src/welcome.rs b/src/welcome.rs
index 382d3cb..826818f 100644
--- a/src/welcome.rs
+++ b/src/welcome.rs
@@ -1,27 +1,28 @@
+use base64::{engine::general_purpose::STANDARD, Engine as _};
+use colored::*;
+use reqwest::{Client, Error};
use std::{
- io:: {self, Write},
- env,
- time::Duration
+ env,
+ io::{self, Write},
+ time::Duration,
};
-use reqwest::{Client, Error};
-use colored::*;
-use serde_json::Value;
+use semver::Version;
use serde::Deserialize;
-use semver::{Version};
+use serde_json::Value;
/// Reusable function that just prints success messages to the console
fn print_info(text: &str, is_secondary: bool) {
- if is_secondary {
- println!("{}", text.green().italic().dimmed());
- } else {
- println!("{}", text.green());
- };
+ if is_secondary {
+ println!("{}", text.green().italic().dimmed());
+ } else {
+ println!("{}", text.green());
+ };
}
/// Prints the AdGuardian ASCII art to console
fn print_ascii_art() {
- let art = r"
+ let art = r"
ββββββ βββββββ βββββββ βββ βββ ββββββ βββββββ βββββββ βββ ββββββ ββββ βββ
ββββββββββββββββββββββββ βββ βββββββββββββββββββββββββββββββββββββββββββ βββ
βββββββββββ ββββββ βββββββ ββββββββββββββββββββββ ββββββββββββββββββββ βββ
@@ -29,202 +30,243 @@ fn print_ascii_art() {
βββ ββββββββββββββββββββββββββββββββ ββββββ βββββββββββββββββ ββββββ ββββββ
βββ ββββββββββ βββββββ βββββββ βββ ββββββ ββββββββββ ββββββ ββββββ βββββ
";
- print_info(art, false);
- print_info("\nWelcome to AdGuardian Terminal Edition!", false);
- print_info("Terminal-based, real-time traffic monitoring and statistics for your AdGuard Home instance", true);
- print_info("For documentation and support, please visit: https://github.com/lissy93/adguardian-term", true);
+ print_info(art, false);
+ print_info("\nWelcome to AdGuardian Terminal Edition!", false);
+ print_info(
+ "Terminal-based, real-time traffic monitoring and statistics for your AdGuard Home instance",
+ true,
+ );
+ print_info(
+ "For documentation and support, please visit: https://github.com/lissy93/adguardian-term",
+ true,
+ );
}
/// Print error message, along with (optional) stack trace, then exit
fn print_error(message: &str, sub_message: &str, error: Option<&Error>) {
- eprintln!(
- "{}{}{}",
- format!("{}", message).red(),
- match error {
- Some(err) => format!("\n{}", err).red().dimmed(),
- None => "".red().dimmed(),
- },
- format!("\n{}", sub_message).yellow(),
- );
+ eprintln!(
+ "{}{}{}",
+ message.red(),
+ match error {
+ Some(err) => format!("\n{}", err).red().dimmed(),
+ None => "".red().dimmed(),
+ },
+ format!("\n{}", sub_message).yellow(),
+ );
- std::process::exit(1);
+ std::process::exit(1);
}
/// Given a key, get the value from the environmental variables, and print it to the console
fn get_env(key: &str) -> Result {
- env::var(key).map(|v| {
- println!(
- "{}",
- format!(
- "{} is set to {}",
- key.bold(),
- if key.contains("PASSWORD") { "******" } else { &v }
- )
- .green()
- );
- v
- })
+ env::var(key).inspect(|v| {
+ println!(
+ "{}",
+ format!(
+ "{} is set to {}",
+ key.bold(),
+ if key.contains("PASSWORD") {
+ "******"
+ } else {
+ v
+ }
+ )
+ .green()
+ );
+ })
}
/// Given a possibly undefined version number, check if it's present and supported
fn check_version(version: Option<&str>) {
- let min_version = Version::parse("0.107.29").unwrap();
-
- match version {
- Some(version_str) => {
- let adguard_version = Version::parse(version_str.strip_prefix('v').unwrap_or(version_str)).unwrap();
-
- if adguard_version < min_version {
- print_error(
- "AdGuard Home version is too old, and is now unsupported",
- format!("You're running AdGuard {}. Please upgrade to v{} or later.", version_str, min_version.to_string()).as_str(),
- None,
- );
- }
- },
- None => {
- print_error(
- "Unsupported AdGuard Home version",
- format!(
- concat!(
- "Failed to get the version number of your AdGuard Home instance.\n",
- "This usually means you're running an old, and unsupported version.\n",
- "Please upgrade to v{} or later."
- ), min_version.to_string()
- ).as_str(),
- None,
- );
- }
+ let min_version = Version::parse("0.107.29").unwrap();
+
+ match version {
+ Some(version_str) => {
+ match Version::parse(version_str.strip_prefix('v').unwrap_or(version_str)) {
+ Ok(adguard_version) if adguard_version < min_version => print_error(
+ "AdGuard Home version is too old, and is now unsupported",
+ format!(
+ "You're running AdGuard {}. Please upgrade to v{} or later.",
+ version_str, min_version
+ )
+ .as_str(),
+ None,
+ ),
+ Ok(_) => {}
+ Err(_) => print_error(
+ "Unsupported AdGuard Home version",
+ "Couldn't parse the version number reported by your AdGuard Home instance.",
+ None,
+ ),
+ }
+ }
+ None => {
+ print_error(
+ "Unsupported AdGuard Home version",
+ format!(
+ concat!(
+ "Failed to get the version number of your AdGuard Home instance.\n",
+ "This usually means you're running an old, and unsupported version.\n",
+ "Please upgrade to v{} or later."
+ ),
+ min_version
+ )
+ .as_str(),
+ None,
+ );
}
+ }
}
/// With the users specified AdGuard details, verify the connection (exit on fail)
async fn verify_connection(
- client: &Client,
- ip: String,
- port: String,
- protocol: String,
- username: String,
- password: String,
+ client: &Client,
+ ip: String,
+ port: String,
+ protocol: String,
+ username: String,
+ password: String,
) -> Result<(), Box> {
- println!("{}", "\nVerifying connection to your AdGuard instance...".blue());
+ println!(
+ "{}",
+ "\nVerifying connection to your AdGuard instance...".blue()
+ );
- let auth_string = format!("{}:{}", username, password);
- let auth_header_value = format!("Basic {}", base64::encode(&auth_string));
- let mut headers = reqwest::header::HeaderMap::new();
- headers.insert("Authorization", auth_header_value.parse()?);
+ let auth_string = format!("{}:{}", username, password);
+ let auth_header_value = format!("Basic {}", STANDARD.encode(&auth_string));
+ let mut headers = reqwest::header::HeaderMap::new();
+ headers.insert("Authorization", auth_header_value.parse()?);
- let url = format!("{}://{}:{}/control/status", protocol, ip, port);
+ let url = format!("{}://{}:{}/control/status", protocol, ip, port);
- match client
- .get(&url)
- .headers(headers)
- .timeout(Duration::from_secs(2))
- .send()
- .await {
- Ok(res) if res.status().is_success() => {
- // Get version string (if present), and check if valid - exit if not
- let body: Value = res.json().await?;
- check_version(body["version"].as_str());
- // All good! Print success message :)
- let safe_version = body["version"].as_str().unwrap_or("mystery version");
- println!("{}", format!("AdGuard ({}) connection successful!\n", safe_version).green());
- Ok(())
- }
- // Connection failed to authenticate. Print error and exit
- Ok(_) => {
- print_error(
- &format!("Authentication with AdGuard at {}:{} failed", ip, port),
- "Please check your environmental variables and try again.",
- None,
- );
- Ok(())
- },
- // Connection failed to establish. Print error and exit
- Err(e) => {
- print_error(
- &format!("Failed to connect to AdGuard at: {}:{}", ip, port),
- "Please check your environmental variables and try again.",
- Some(&e),
- );
- Ok(())
- }
+ match client
+ .get(&url)
+ .headers(headers)
+ .timeout(Duration::from_secs(2))
+ .send()
+ .await
+ {
+ Ok(res) if res.status().is_success() => {
+ // Get version string (if present), and check if valid - exit if not
+ let body: Value = res.json().await?;
+ check_version(body["version"].as_str());
+ // All good! Print success message :)
+ let safe_version = body["version"].as_str().unwrap_or("mystery version");
+ println!(
+ "{}",
+ format!("AdGuard ({}) connection successful!\n", safe_version).green()
+ );
+ Ok(())
+ }
+ // Connection failed to authenticate. Print error and exit
+ Ok(_) => {
+ print_error(
+ &format!("Authentication with AdGuard at {}:{} failed", ip, port),
+ "Please check your environmental variables and try again.",
+ None,
+ );
+ Ok(())
}
+ // Connection failed to establish. Print error and exit
+ Err(e) => {
+ print_error(
+ &format!("Failed to connect to AdGuard at: {}:{}", ip, port),
+ "Please check your environmental variables and try again.",
+ Some(&e),
+ );
+ Ok(())
+ }
+ }
}
#[derive(Deserialize)]
struct CratesIoResponse {
- #[serde(rename = "crate")]
- krate: Crate,
+ #[serde(rename = "crate")]
+ krate: Crate,
}
#[derive(Deserialize)]
struct Crate {
- max_version: String,
+ max_version: String,
}
/// Gets the latest version of the crate from crates.io
async fn get_latest_version(crate_name: &str) -> Result> {
- let url = format!("https://crates.io/api/v1/crates/{}", crate_name);
- let client = reqwest::Client::new();
- let res = client.get(&url)
- .header(reqwest::header::USER_AGENT, "version_check (adguardian.as93.net)")
- .send()
- .await?;
+ let url = format!("https://crates.io/api/v1/crates/{}", crate_name);
+ let client = reqwest::Client::new();
+ let res = client
+ .get(&url)
+ .header(
+ reqwest::header::USER_AGENT,
+ "version_check (adguardian.as93.net)",
+ )
+ .send()
+ .await?;
- if res.status().is_success() {
- let response: CratesIoResponse = res.json().await?;
- Ok(response.krate.max_version)
- } else {
- let status = res.status();
- let body = res.text().await?;
- Err(format!("Request failed with status {}: body: {}", status, body).into())
- }
+ if res.status().is_success() {
+ let response: CratesIoResponse = res.json().await?;
+ Ok(response.krate.max_version)
+ } else {
+ let status = res.status();
+ let body = res.text().await?;
+ Err(format!("Request failed with status {}: body: {}", status, body).into())
+ }
}
/// Checks for updates to the crate, and prints a message if an update is available
async fn check_for_updates() {
- // Get crate name and version from Cargo.toml
- let crate_name = env!("CARGO_PKG_NAME");
- let crate_version = env!("CARGO_PKG_VERSION");
- println!("{}", "\nChecking for updates...".blue());
- // Parse the current version, and fetch and parse the latest version
- let current_version = Version::parse(crate_version).unwrap_or_else(|_| {
- Version::parse("0.0.0").unwrap()
- });
- let latest_version = Version::parse(
- &get_latest_version(crate_name).await.unwrap_or_else(|_| {
- "0.0.0".to_string()
- })
- ).unwrap();
+ // Get crate name and version from Cargo.toml
+ let crate_name = env!("CARGO_PKG_NAME");
+ let crate_version = env!("CARGO_PKG_VERSION");
+ println!("{}", "\nChecking for updates...".blue());
+ // Parse the current version, and fetch and parse the latest version
+ let current_version =
+ Version::parse(crate_version).unwrap_or_else(|_| Version::parse("0.0.0").unwrap());
+ let latest_version = Version::parse(
+ &get_latest_version(crate_name)
+ .await
+ .unwrap_or_else(|_| "0.0.0".to_string()),
+ )
+ .unwrap_or_else(|_| Version::parse("0.0.0").unwrap());
- // Compare the current and latest versions, and print the appropriate message
- if current_version == Version::parse("0.0.0").unwrap() || latest_version == Version::parse("0.0.0").unwrap() {
- println!("{}", "Unable to check for updates".yellow());
- } else if current_version < latest_version {
- println!("{}",
- format!(
- "A new version of AdGuardian is available.\nUpdate from {} to {} for the best experience",
- current_version.to_string().bold(),
- latest_version.to_string().bold()
- ).yellow()
- );
- } else if current_version == latest_version {
- println!(
- "{}",
- format!("AdGuardian is up-to-date, running version {}", current_version.to_string().bold()).green()
- );
- } else if current_version > latest_version {
- println!(
- "{}",
- format!("Running a pre-released edition of AdGuardian, version {}", current_version.to_string().bold()).green()
- );
- } else {
- println!("{}", "Unable to check for updates".yellow());
- }
+ // Compare the current and latest versions, and print the appropriate message
+ if current_version == Version::parse("0.0.0").unwrap()
+ || latest_version == Version::parse("0.0.0").unwrap()
+ {
+ println!("{}", "Unable to check for updates".yellow());
+ } else if current_version < latest_version {
+ println!(
+ "{}",
+ format!(
+ "A new version of AdGuardian is available.\nUpdate from {} to {} for the best experience",
+ current_version.to_string().bold(),
+ latest_version.to_string().bold()
+ )
+ .yellow()
+ );
+ } else if current_version == latest_version {
+ println!(
+ "{}",
+ format!(
+ "AdGuardian is up-to-date, running version {}",
+ current_version.to_string().bold()
+ )
+ .green()
+ );
+ } else if current_version > latest_version {
+ println!(
+ "{}",
+ format!(
+ "Running a pre-released edition of AdGuardian, version {}",
+ current_version.to_string().bold()
+ )
+ .green()
+ );
+ } else {
+ println!("{}", "Unable to check for updates".yellow());
+ }
}
-
/// Initiate the welcome script
/// This function will:
/// - Print the AdGuardian ASCII art
@@ -236,64 +278,71 @@ async fn check_for_updates() {
/// - Verify the AdGuard Home version is supported
/// - Then either print a success message, or show instructions to fix and exit
pub async fn welcome() -> Result<(), Box> {
- print_ascii_art();
+ print_ascii_art();
- // Check for updates
- check_for_updates().await;
+ // Check for updates
+ check_for_updates().await;
- println!("{}", "\nStarting initialization checks...".blue());
+ println!("{}", "\nStarting initialization checks...".blue());
- let client = Client::new();
+ let client = Client::new();
- // List of available flags, ant their associated env vars
- let flags = [
- ("--adguard-ip", "ADGUARD_IP"),
- ("--adguard-port", "ADGUARD_PORT"),
- ("--adguard-username", "ADGUARD_USERNAME"),
- ("--adguard-password", "ADGUARD_PASSWORD"),
- ];
+ // List of available flags, ant their associated env vars
+ let flags = [
+ ("--adguard-ip", "ADGUARD_IP"),
+ ("--adguard-port", "ADGUARD_PORT"),
+ ("--adguard-username", "ADGUARD_USERNAME"),
+ ("--adguard-password", "ADGUARD_PASSWORD"),
+ ];
- let protocol: String = env::var("ADGUARD_PROTOCOL").unwrap_or_else(|_| "http".into()).parse()?;
- env::set_var("ADGUARD_PROTOCOL", protocol);
+ let protocol: String = env::var("ADGUARD_PROTOCOL")
+ .unwrap_or_else(|_| "http".into())
+ .parse()?;
+ env::set_var("ADGUARD_PROTOCOL", protocol);
- // Parse command line arguments
- let mut args = std::env::args().peekable();
- while let Some(arg) = args.next() {
- for &(flag, var) in &flags {
- if arg == flag {
- if let Some(value) = args.peek() {
- env::set_var(var, value);
- args.next();
- }
- }
+ // Parse command line arguments
+ let mut args = std::env::args().peekable();
+ while let Some(arg) = args.next() {
+ for &(flag, var) in &flags {
+ if arg == flag {
+ if let Some(value) = args.peek() {
+ env::set_var(var, value);
+ args.next();
}
+ }
}
+ }
- // If any of the env variables or flags are not yet set, prompt the user to enter them
- for &key in &["ADGUARD_IP", "ADGUARD_PORT", "ADGUARD_USERNAME", "ADGUARD_PASSWORD"] {
- if env::var(key).is_err() {
- println!(
- "{}",
- format!("The {} environmental variable is not yet set", key.bold()).yellow()
- );
- print!("{}", format!("βΊ Enter a value for {}: ", key).blue().bold());
- io::stdout().flush()?;
+ // If any of the env variables or flags are not yet set, prompt the user to enter them
+ for &key in &[
+ "ADGUARD_IP",
+ "ADGUARD_PORT",
+ "ADGUARD_USERNAME",
+ "ADGUARD_PASSWORD",
+ ] {
+ if env::var(key).is_err() {
+ println!(
+ "{}",
+ format!("The {} environmental variable is not yet set", key.bold()).yellow()
+ );
+ print!("{}", format!("βΊ Enter a value for {}: ", key).blue().bold());
+ io::stdout().flush()?;
- let mut value = String::new();
- io::stdin().read_line(&mut value)?;
- env::set_var(key, value.trim());
- }
+ let mut value = String::new();
+ io::stdin().read_line(&mut value)?;
+ env::set_var(key, value.trim());
}
+ }
+
+ // Grab the values of the (now set) environmental variables
+ let ip = get_env("ADGUARD_IP")?;
+ let port = get_env("ADGUARD_PORT")?;
+ let protocol = get_env("ADGUARD_PROTOCOL")?;
+ let username = get_env("ADGUARD_USERNAME")?;
+ let password = get_env("ADGUARD_PASSWORD")?;
- // Grab the values of the (now set) environmental variables
- let ip = get_env("ADGUARD_IP")?;
- let port = get_env("ADGUARD_PORT")?;
- let protocol = get_env("ADGUARD_PROTOCOL")?;
- let username = get_env("ADGUARD_USERNAME")?;
- let password = get_env("ADGUARD_PASSWORD")?;
-
- // Verify that we can connect, authenticate, and that version is supported (exit on failure)
- verify_connection(&client, ip, port, protocol, username, password).await?;
+ // Verify that we can connect, authenticate, and that version is supported (exit on failure)
+ verify_connection(&client, ip, port, protocol, username, password).await?;
- Ok(())
+ Ok(())
}
diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs
index bb1da66..37371ee 100644
--- a/src/widgets/chart.rs
+++ b/src/widgets/chart.rs
@@ -1,56 +1,59 @@
-
use tui::{
- style::{Color, Modifier, Style},
- widgets::{Axis, Block, Borders, Dataset, Chart},
- text::{Span},
- symbols,
+ style::{Color, Modifier, Style},
+ symbols,
+ text::Span,
+ widgets::{Axis, Block, Borders, Chart, Dataset},
};
-use crate::fetch::fetch_stats::{StatsResponse};
-
+use crate::fetch::fetch_stats::StatsResponse;
pub fn make_history_chart(stats: &StatsResponse) -> Chart<'_> {
- // Convert datasets into vector that can be consumed by chart
- let datasets = make_history_datasets(stats);
- // Find uppermost x and y-axis bounds for chart
- let (x_bound, y_bound) = find_bounds(stats);
- // Generate incremental labels from data's values, to render on axis
- let x_labels = generate_x_labels(stats.dns_queries.len() as i32, 5);
- let y_labels = generate_y_labels(y_bound as i32, 5);
- // Create chart
- let chart = Chart::new(datasets)
- .block(
- Block::default()
- .title(Span::styled(
- "History",
- Style::default().add_modifier(Modifier::BOLD),
- ))
- .borders(Borders::ALL)
- )
- .x_axis(
- Axis::default()
- .title("Time (Days ago)")
- .bounds([0.0, x_bound])
- .labels(x_labels),
- )
- .y_axis(Axis::default().title("Query Count").labels(y_labels).bounds([0.0, y_bound]));
-
- chart
+ // Convert datasets into vector that can be consumed by chart
+ let datasets = make_history_datasets(stats);
+ // Find uppermost x and y-axis bounds for chart
+ let (x_bound, y_bound) = find_bounds(stats);
+ // Generate incremental labels from data's values, to render on axis
+ let x_labels = generate_x_labels(stats.dns_queries.len() as i32, 5);
+ let y_labels = generate_y_labels(y_bound as i32, 5);
+ // Create chart
+ let chart = Chart::new(datasets)
+ .block(
+ Block::default()
+ .title(Span::styled(
+ "History",
+ Style::default().add_modifier(Modifier::BOLD),
+ ))
+ .borders(Borders::ALL),
+ )
+ .x_axis(
+ Axis::default()
+ .title("Time (Days ago)")
+ .bounds([0.0, x_bound])
+ .labels(x_labels),
+ )
+ .y_axis(
+ Axis::default()
+ .title("Query Count")
+ .labels(y_labels)
+ .bounds([0.0, y_bound]),
+ );
+
+ chart
}
// Returns a dataset that's consumable by the chart widget
fn make_history_datasets(stats: &StatsResponse) -> Vec> {
let dns_queries_dataset = Dataset::default()
- .name("DNS Queries")
- .marker(symbols::Marker::Braille)
- .style(Style::default().fg(Color::Green))
- .data(&stats.dns_queries_chart);
+ .name("DNS Queries")
+ .marker(symbols::Marker::Braille)
+ .style(Style::default().fg(Color::Green))
+ .data(&stats.dns_queries_chart);
let blocked_filtering_dataset = Dataset::default()
- .name("Blocked Filtering")
- .marker(symbols::Marker::Braille)
- .style(Style::default().fg(Color::Red))
- .data(&stats.blocked_filtering_chart);
+ .name("Blocked Filtering")
+ .marker(symbols::Marker::Braille)
+ .style(Style::default().fg(Color::Red))
+ .data(&stats.blocked_filtering_chart);
let datasets = vec![dns_queries_dataset, blocked_filtering_dataset];
@@ -59,88 +62,102 @@ fn make_history_datasets(stats: &StatsResponse) -> Vec> {
// Determine the uppermost bounds for the x and y axis
fn find_bounds(stats: &StatsResponse) -> (f64, f64) {
- let mut max_length = 0;
- let mut max_value = f64::MIN;
-
- for dataset in &[&stats.dns_queries_chart, &stats.blocked_filtering_chart] {
- let length = dataset.len();
- if length > max_length {
- max_length = length;
- }
-
- let max_in_dataset = dataset
- .iter()
- .map(|&(_, y)| y)
- .fold(f64::MIN, f64::max);
- if max_in_dataset > max_value {
- max_value = max_in_dataset;
- }
+ let mut max_length = 0;
+ // Floor at 0 so empty data can't leave this at f64::MIN (which would later
+ // overflow when cast to an axis-label step)
+ let mut max_value = 0.0;
+
+ for dataset in &[&stats.dns_queries_chart, &stats.blocked_filtering_chart] {
+ let length = dataset.len();
+ if length > max_length {
+ max_length = length;
}
- (max_length as f64, max_value)
+
+ let max_in_dataset = dataset.iter().map(|&(_, y)| y).fold(f64::MIN, f64::max);
+ if max_in_dataset > max_value {
+ max_value = max_in_dataset;
+ }
+ }
+ (max_length as f64, max_value)
}
// Generate periodic labels to render on the y-axis (query count)
fn generate_y_labels(max: i32, count: usize) -> Vec> {
let step = max / (count - 1) as i32;
-
+
(0..count)
- .map(|x| Span::raw(format!("{}", x * step as usize)))
- .collect::>>()
+ .map(|x| Span::raw(format!("{}", x * step as usize)))
+ .collect::>>()
}
// Generate periodic labels to render on the x-axis (days ago)
fn generate_x_labels(max_days: i32, num_labels: i32) -> Vec> {
- let step = max_days / (num_labels - 1);
- (0..num_labels)
- .map(|i| {
- let day = (max_days - i * step).to_string();
- if i == num_labels - 1 {
- Span::styled("Today", Style::default().add_modifier(Modifier::BOLD))
- } else {
- Span::raw(day)
- }
- })
- .collect()
+ let step = max_days / (num_labels - 1);
+ (0..num_labels)
+ .map(|i| {
+ let day = (max_days - i * step).to_string();
+ if i == num_labels - 1 {
+ Span::styled("Today", Style::default().add_modifier(Modifier::BOLD))
+ } else {
+ Span::raw(day)
+ }
+ })
+ .collect()
}
// Formats vector data into a format that can be consumed by the chart widget
fn convert_to_chart_data(data: Vec) -> Vec<(f64, f64)> {
- data.iter().enumerate().map(|(i, &v)| (i as f64, v)).collect()
+ data
+ .iter()
+ .enumerate()
+ .map(|(i, &v)| (i as f64, v))
+ .collect()
}
// Interpolates data, adding n number of points, to make the chart look smoother
-fn interpolate(input: &Vec, points_between: usize) -> Vec {
- let mut output = Vec::new();
-
- for window in input.windows(2) {
- let start = window[0];
- let end = window[1];
- let step = (end - start) / (points_between as f64 + 1.0);
-
- output.push(start);
- for i in 1..=points_between {
- output.push(start + step * i as f64);
- }
+fn interpolate(input: &[f64], points_between: usize) -> Vec {
+ // Nothing to interpolate between - avoids a panic on the `last()` below
+ let Some(&last) = input.last() else {
+ return Vec::new();
+ };
+
+ let mut output = Vec::new();
+ for window in input.windows(2) {
+ let start = window[0];
+ let end = window[1];
+ let step = (end - start) / (points_between as f64 + 1.0);
+
+ output.push(start);
+ for i in 1..=points_between {
+ output.push(start + step * i as f64);
}
+ }
- output.push(*input.last().unwrap());
- output
+ output.push(last);
+ output
}
// Adds data formatted for the time-series chart to the stats object
pub fn prepare_chart_data(stats: &mut StatsResponse) {
- let dns_queries = stats.dns_queries.iter().map(|&v| v as f64).collect::>();
- let interpolated_dns_queries = interpolate(&dns_queries, 3);
- stats.dns_queries_chart = convert_to_chart_data(interpolated_dns_queries);
-
- let blocked_filtering: Vec = stats.blocked_filtering.iter()
- .zip(&stats.replaced_safebrowsing)
- .zip(&stats.replaced_parental)
- .map(|((&b, &s), &p)| (b + s + p) as f64)
- .collect();
-
- let interpolated_blocked_filtering = interpolate(&blocked_filtering, 3);
- let blocked_filtering_chart: Vec<(f64, f64)> = convert_to_chart_data(interpolated_blocked_filtering);
-
- stats.blocked_filtering_chart = blocked_filtering_chart;
+ let dns_queries = stats
+ .dns_queries
+ .iter()
+ .map(|&v| v as f64)
+ .collect::>();
+ let interpolated_dns_queries = interpolate(&dns_queries, 3);
+ stats.dns_queries_chart = convert_to_chart_data(interpolated_dns_queries);
+
+ let blocked_filtering: Vec = stats
+ .blocked_filtering
+ .iter()
+ .zip(&stats.replaced_safebrowsing)
+ .zip(&stats.replaced_parental)
+ .map(|((&b, &s), &p)| (b + s + p) as f64)
+ .collect();
+
+ let interpolated_blocked_filtering = interpolate(&blocked_filtering, 3);
+ let blocked_filtering_chart: Vec<(f64, f64)> =
+ convert_to_chart_data(interpolated_blocked_filtering);
+
+ stats.blocked_filtering_chart = blocked_filtering_chart;
}
diff --git a/src/widgets/filters.rs b/src/widgets/filters.rs
index 52148a0..f37803c 100644
--- a/src/widgets/filters.rs
+++ b/src/widgets/filters.rs
@@ -1,45 +1,50 @@
-// filters.rs
-
use tui::{
+ style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, List, ListItem},
- style::{Color, Style, Modifier},
};
use crate::fetch::fetch_filters::Filter;
fn truncate(text: &str, width: usize) -> String {
if text.chars().count() <= width {
- text.to_string()
+ text.to_string()
} else {
- text.chars().take(width - 3).collect::() + "..."
+ text
+ .chars()
+ .take(width.saturating_sub(3))
+ .collect::()
+ + "..."
}
}
-pub fn make_filters_list(filters: &[Filter], width: u16) -> List {
+pub fn make_filters_list(filters: &[Filter], width: u16) -> List<'_> {
let items: Vec = filters
.iter()
.map(|filter| {
- let (status_text, color) = if filter.enabled {
- ("β", Color::Green)
- } else {
- ("β", Color::Red)
- };
- let status = Span::styled(status_text, Style::default().fg(color));
- let rule_count = Span::styled(format!(" ({})", filter.rules_count), Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD));
- let name = Span::raw(format!(" {}", truncate(&filter.name, width as usize / 4 - 12)));
- let content = Spans::from(vec![status, name, rule_count]);
- ListItem::new(content)
+ let (status_text, color) = if filter.enabled {
+ ("β", Color::Green)
+ } else {
+ ("β", Color::Red)
+ };
+ let status = Span::styled(status_text, Style::default().fg(color));
+ let rule_count = Span::styled(
+ format!(" ({})", filter.rules_count),
+ Style::default()
+ .fg(Color::Magenta)
+ .add_modifier(Modifier::BOLD),
+ );
+ let name = Span::raw(format!(
+ " {}",
+ truncate(&filter.name, (width as usize / 4).saturating_sub(12))
+ ));
+ let content = Spans::from(vec![status, name, rule_count]);
+ ListItem::new(content)
})
.collect();
- List::new(items)
- .block(
- Block::default()
- .borders(Borders::ALL)
- .title(Span::styled(
- "Filters",
- Style::default().add_modifier(Modifier::BOLD),
- )),
- )
+ List::new(items).block(Block::default().borders(Borders::ALL).title(Span::styled(
+ "Filters",
+ Style::default().add_modifier(Modifier::BOLD),
+ )))
}
diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs
index 0209424..feb0929 100644
--- a/src/widgets/gauge.rs
+++ b/src/widgets/gauge.rs
@@ -1,30 +1,35 @@
use tui::{
- style::{Color, Style, Modifier},
+ style::{Color, Modifier, Style},
+ text::Span,
widgets::{Block, Borders, Gauge},
- text::{Span},
};
use crate::fetch::fetch_stats::StatsResponse;
-pub fn make_gauge(stats: &StatsResponse) -> Gauge {
-
+pub fn make_gauge(stats: &StatsResponse) -> Gauge<'_> {
let total_blocked = stats.num_blocked_filtering
+ stats.num_replaced_parental
+ stats.num_replaced_safebrowsing
+ stats.num_replaced_safesearch;
- let percent = (total_blocked as f64 / stats.num_dns_queries as f64 * 100.0) as u16;
+ // `max(1)` avoids a divide-by-zero, and the clamp keeps it in the 0..=100
+ // range that `Gauge::percent` requires (it panics otherwise)
+ let percent =
+ (total_blocked as f64 / stats.num_dns_queries.max(1) as f64 * 100.0).min(100.0) as u16;
- let label = format!("Blocked {} out of {} ({}%)", total_blocked, stats.num_dns_queries, percent);
+ let label = format!(
+ "Blocked {} out of {} ({}%)",
+ total_blocked, stats.num_dns_queries, percent
+ );
Gauge::default()
- .block(
- Block::default()
+ .block(
+ Block::default()
.title(Span::styled(
"Block Percentage",
Style::default().add_modifier(Modifier::BOLD),
))
- .borders(Borders::ALL)
+ .borders(Borders::ALL),
)
.gauge_style(Style::default().fg(Color::Red).bg(Color::Green))
.percent(percent)
diff --git a/src/widgets/list.rs b/src/widgets/list.rs
index 49ecf79..dbd8d0c 100644
--- a/src/widgets/list.rs
+++ b/src/widgets/list.rs
@@ -1,38 +1,43 @@
-
use crate::fetch::fetch_stats::DomainData;
-
use tui::{
+ style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, List, ListItem},
- style::{Color, Style, Modifier},
};
fn truncate(text: &str, width: usize) -> String {
if text.chars().count() <= width {
- text.to_string()
+ text.to_string()
} else {
- text.chars().take(width - 3).collect::() + "..."
+ text
+ .chars()
+ .take(width.saturating_sub(3))
+ .collect::()
+ + "..."
}
}
pub fn make_list<'a>(title: &'a str, data: &[DomainData], color: Color, width: u16) -> List<'a> {
let items: Vec = data
- .iter()
- .map(|data| {
-
- let name = Span::raw(format!(" {}", truncate(&data.name, width as usize / 4 - 12)));
- let count = Span::styled(format!(" ({})", data.count), Style::default().fg(color).add_modifier(Modifier::BOLD));
- ListItem::new(Spans::from(vec![name, count]))
- })
- .collect();
+ .iter()
+ .map(|data| {
+ let name = Span::raw(format!(
+ " {}",
+ truncate(&data.name, (width as usize / 4).saturating_sub(12))
+ ));
+ let count = Span::styled(
+ format!(" ({})", data.count),
+ Style::default().fg(color).add_modifier(Modifier::BOLD),
+ );
+ ListItem::new(Spans::from(vec![name, count]))
+ })
+ .collect();
List::new(items)
- .block(Block::default().borders(Borders::ALL)
- .title(Span::styled(
- title,
- Style::default().add_modifier(Modifier::BOLD),
- )))
- .highlight_style(Style::default().add_modifier(Modifier::BOLD))
+ .block(Block::default().borders(Borders::ALL).title(Span::styled(
+ title,
+ Style::default().add_modifier(Modifier::BOLD),
+ )))
+ .highlight_style(Style::default().add_modifier(Modifier::BOLD))
}
-
diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs
index c3cfa0c..b36a30d 100644
--- a/src/widgets/mod.rs
+++ b/src/widgets/mod.rs
@@ -1,6 +1,6 @@
-pub mod gauge;
-pub mod table;
pub mod chart;
+pub mod filters;
+pub mod gauge;
pub mod list;
pub mod status;
-pub mod filters;
+pub mod table;
diff --git a/src/widgets/status.rs b/src/widgets/status.rs
index 2e71012..2565855 100644
--- a/src/widgets/status.rs
+++ b/src/widgets/status.rs
@@ -1,65 +1,80 @@
use tui::{
- style::{Color, Style, Modifier},
+ style::{Color, Modifier, Style},
text::{Span, Spans},
- widgets::{Paragraph, Wrap, Borders, Block},
+ widgets::{Block, Borders, Paragraph, Wrap},
};
-use crate::fetch::fetch_status::StatusResponse;
use crate::fetch::fetch_stats::StatsResponse;
+use crate::fetch::fetch_status::StatusResponse;
-pub fn render_status_paragraph<'a>(status: &'a StatusResponse, stats: &'a StatsResponse) -> Paragraph<'a> {
-
- let block = Block::default()
- .borders(Borders::ALL)
- .title(Span::styled(
- "Status",
- Style::default().add_modifier(Modifier::BOLD),
- ));
+pub fn render_status_paragraph<'a>(
+ status: &'a StatusResponse,
+ stats: &'a StatsResponse,
+) -> Paragraph<'a> {
+ let block = Block::default().borders(Borders::ALL).title(Span::styled(
+ "Status",
+ Style::default().add_modifier(Modifier::BOLD),
+ ));
- let get_color = |enabled: bool| {
- if enabled { Color::Green } else { Color::Red }
- };
+ let get_color = |enabled: bool| {
+ if enabled {
+ Color::Green
+ } else {
+ Color::Red
+ }
+ };
- let value_style = Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD);
+ let value_style = Style::default()
+ .fg(Color::Blue)
+ .add_modifier(Modifier::BOLD);
- let coloured = |color: Color| {
- Style::default().fg(color).add_modifier(Modifier::BOLD)
- };
-
+ let coloured = |color: Color| Style::default().fg(color).add_modifier(Modifier::BOLD);
let text = vec![
- Spans::from(vec![
- Span::styled("Running: ", Style::default()),
- Span::styled(
- format!("{}", status.running),
- Style::default().fg(get_color(status.running)).add_modifier(Modifier::BOLD)
- ),
+ Spans::from(vec![
+ Span::styled("Running: ", Style::default()),
+ Span::styled(
+ format!("{}", status.running),
+ Style::default()
+ .fg(get_color(status.running))
+ .add_modifier(Modifier::BOLD),
+ ),
]),
- Spans::from(vec![
- Span::styled("Protection Enabled: ", Style::default()),
- Span::styled(
- format!("{}", status.protection_enabled),
- Style::default().fg(get_color(status.protection_enabled)).add_modifier(Modifier::BOLD)
- ),
+ Spans::from(vec![
+ Span::styled("Protection Enabled: ", Style::default()),
+ Span::styled(
+ format!("{}", status.protection_enabled),
+ Style::default()
+ .fg(get_color(status.protection_enabled))
+ .add_modifier(Modifier::BOLD),
+ ),
]),
- Spans::from(vec![
- Span::styled("DHCP Available: ", Style::default()),
- Span::styled(
- format!("{}", status.dhcp_available),
- Style::default().fg(get_color(status.dhcp_available)).add_modifier(Modifier::BOLD)
- ),
+ Spans::from(vec![
+ Span::styled("DHCP Available: ", Style::default()),
+ Span::styled(
+ format!("{}", status.dhcp_available),
+ Style::default()
+ .fg(get_color(status.dhcp_available))
+ .add_modifier(Modifier::BOLD),
+ ),
]),
Spans::from(vec![
Span::styled("Avg Processing Time: ", Style::default()),
- Span::styled(format!("{}ms", (stats.avg_processing_time * 1000.0) as i16), value_style),
+ Span::styled(
+ format!("{}ms", (stats.avg_processing_time * 1000.0) as i16),
+ value_style,
+ ),
]),
Spans::from(vec![
Span::styled("Version: ", Style::default()),
Span::styled(status.version.to_string(), value_style),
]),
Spans::from(vec![
- Span::styled("Ports: ", Style::default()),
- Span::styled(format!(":{} (DNS), :{} (HTTP)", status.dns_port, status.http_port), value_style),
+ Span::styled("Ports: ", Style::default()),
+ Span::styled(
+ format!(":{} (DNS), :{} (HTTP)", status.dns_port, status.http_port),
+ value_style,
+ ),
]),
Spans::from(vec![
Span::styled("Total Queries: ", Style::default()),
@@ -67,22 +82,32 @@ pub fn render_status_paragraph<'a>(status: &'a StatusResponse, stats: &'a StatsR
]),
Spans::from(vec![
Span::styled("Filtered: ", Style::default()),
- Span::styled(stats.num_blocked_filtering.to_string(), coloured(Color::Yellow)),
+ Span::styled(
+ stats.num_blocked_filtering.to_string(),
+ coloured(Color::Yellow),
+ ),
]),
Spans::from(vec![
Span::styled("Malware Blocked: ", Style::default()),
- Span::styled(stats.num_replaced_safebrowsing.to_string(), coloured(Color::Red)),
+ Span::styled(
+ stats.num_replaced_safebrowsing.to_string(),
+ coloured(Color::Red),
+ ),
]),
Spans::from(vec![
Span::styled("Parental Controls: ", Style::default()),
- Span::styled(stats.num_replaced_parental.to_string(), coloured(Color::Magenta)),
+ Span::styled(
+ stats.num_replaced_parental.to_string(),
+ coloured(Color::Magenta),
+ ),
]),
Spans::from(vec![
Span::styled("Safe Search: ", Style::default()),
- Span::styled(stats.num_replaced_safesearch.to_string(), coloured(Color::Cyan)),
+ Span::styled(
+ stats.num_replaced_safesearch.to_string(),
+ coloured(Color::Cyan),
+ ),
]),
];
- Paragraph::new(text)
- .wrap(Wrap { trim: true })
- .block(block)
+ Paragraph::new(text).wrap(Wrap { trim: true }).block(block)
}
diff --git a/src/widgets/table.rs b/src/widgets/table.rs
index 57cbbcb..0607b0b 100644
--- a/src/widgets/table.rs
+++ b/src/widgets/table.rs
@@ -1,26 +1,26 @@
-
+use chrono::{DateTime, Utc};
use tui::{
+ layout::Constraint,
style::{Color, Modifier, Style},
+ text::Span,
widgets::{Block, Borders, Cell, Row, Table},
- text::{Span},
- layout::{Constraint},
};
-use chrono::{DateTime, Utc};
use crate::fetch::fetch_query_log::{Query, Question};
pub fn make_query_table(data: &[Query], width: u16) -> Table<'_> {
- let rows = data.iter().map(|query| {
- let time = Cell::from(
- time_ago(query.time.as_str()).unwrap_or("unknown".to_string())
- ).style(Style::default().fg(Color::Gray));
-
+ let rows = data
+ .iter()
+ .map(|query| {
+ let time = Cell::from(time_ago(query.time.as_str()).unwrap_or("unknown".to_string()))
+ .style(Style::default().fg(Color::Gray));
+
let question = Cell::from(make_request_cell(&query.question).unwrap())
- .style(Style::default().add_modifier(Modifier::BOLD));
+ .style(Style::default().add_modifier(Modifier::BOLD));
- let client = Cell::from(query.client.as_str())
- .style(Style::default().fg(Color::Blue));
+ let client = Cell::from(query.client.as_str()).style(Style::default().fg(Color::Blue));
- let (time_taken, elapsed_color) = make_time_taken_and_color(&query.elapsed_ms).unwrap();
+ let (time_taken, elapsed_color) = make_time_taken_and_color(&query.elapsed_ms)
+ .unwrap_or_else(|_| ("? ms".to_string(), Color::Gray));
let elapsed_ms = Cell::from(time_taken).style(Style::default().fg(elapsed_color));
let (status_txt, status_color) = block_status_text(&query.reason, query.cached);
@@ -30,57 +30,52 @@ pub fn make_query_table(data: &[Query], width: u16) -> Table<'_> {
let color = make_row_color(&query.reason);
Row::new(vec![time, question, status, elapsed_ms, client, upstream])
- .style(Style::default().fg(color))
- }).collect::>();
+ .style(Style::default().fg(color))
+ })
+ .collect::>();
-
- let title = Span::styled(
- "Query Log",
- Style::default().add_modifier(Modifier::BOLD),
- );
+ let title = Span::styled("Query Log", Style::default().add_modifier(Modifier::BOLD));
- let block = Block::default()
- .title(title)
- .borders(Borders::ALL);
+ let block = Block::default().title(title).borders(Borders::ALL);
let mut headers = vec![
- Cell::from(Span::raw("Time")),
- Cell::from(Span::raw("Request")),
- Cell::from(Span::raw("Status")),
- Cell::from(Span::raw("Time Taken")),
+ Cell::from(Span::raw("Time")),
+ Cell::from(Span::raw("Request")),
+ Cell::from(Span::raw("Status")),
+ Cell::from(Span::raw("Time Taken")),
];
if width > 120 {
- headers.extend(vec![
- Cell::from(Span::raw("Client")),
- Cell::from(Span::raw("Upstream DNS")),
- ]);
-
- let widths = &[
- Constraint::Percentage(15),
- Constraint::Percentage(35),
- Constraint::Percentage(10),
- Constraint::Percentage(10),
- Constraint::Percentage(15),
- Constraint::Percentage(15),
- ];
-
- Table::new(rows)
- .header(Row::new(headers))
- .widths(widths)
- .block(block)
+ headers.extend(vec![
+ Cell::from(Span::raw("Client")),
+ Cell::from(Span::raw("Upstream DNS")),
+ ]);
+
+ let widths = &[
+ Constraint::Percentage(15),
+ Constraint::Percentage(35),
+ Constraint::Percentage(10),
+ Constraint::Percentage(10),
+ Constraint::Percentage(15),
+ Constraint::Percentage(15),
+ ];
+
+ Table::new(rows)
+ .header(Row::new(headers))
+ .widths(widths)
+ .block(block)
} else {
- let widths = &[
- Constraint::Percentage(20),
- Constraint::Percentage(40),
- Constraint::Percentage(20),
- Constraint::Percentage(20),
- ];
-
- Table::new(rows)
- .header(Row::new(headers))
- .widths(widths)
- .block(block)
+ let widths = &[
+ Constraint::Percentage(20),
+ Constraint::Percentage(40),
+ Constraint::Percentage(20),
+ Constraint::Percentage(20),
+ ];
+
+ Table::new(rows)
+ .header(Row::new(headers))
+ .widths(widths)
+ .block(block)
}
}
@@ -93,9 +88,9 @@ fn time_ago(timestamp: &str) -> Result {
let duration = now - datetime_utc;
if duration.num_minutes() < 1 {
- Ok(format!("{} sec ago", duration.num_seconds()))
+ Ok(format!("{} sec ago", duration.num_seconds()))
} else {
- Ok(format!("{} min ago", duration.num_minutes()))
+ Ok(format!("{} min ago", duration.num_minutes()))
}
}
@@ -110,11 +105,11 @@ fn make_time_taken_and_color(elapsed: &str) -> Result<(String, Color), anyhow::E
let rounded_elapsed = (elapsed_f64 * 100.0).round() / 100.0;
let time_taken = format!("{:.2} ms", rounded_elapsed);
let color = if elapsed_f64 < 1.0 {
- Color::Green
+ Color::Green
} else if (1.0..=20.0).contains(&elapsed_f64) {
- Color::Yellow
+ Color::Yellow
} else {
- Color::Red
+ Color::Red
};
Ok((time_taken, color))
}
@@ -122,26 +117,24 @@ fn make_time_taken_and_color(elapsed: &str) -> Result<(String, Color), anyhow::E
// Return color for a row, based on the allow/block reason
fn make_row_color(reason: &str) -> Color {
if reason == "NotFilteredNotFound" {
- Color::Green
+ Color::Green
} else if reason == "FilteredBlackList" {
- Color::Red
+ Color::Red
} else {
- Color::Yellow
+ Color::Yellow
}
}
// Return text and color for the status cell based on allow/ block reason
fn block_status_text(reason: &str, cached: bool) -> (String, Color) {
- let (text, color) =
- if reason == "FilteredBlackList" {
- ("Blacklisted".to_string(), Color::Red)
+ let (text, color) = if reason == "FilteredBlackList" {
+ ("Blacklisted".to_string(), Color::Red)
} else if cached {
- ("Cached".to_string(), Color::Cyan)
+ ("Cached".to_string(), Color::Cyan)
} else if reason == "NotFilteredNotFound" {
- ("Allowed".to_string(), Color::Green)
+ ("Allowed".to_string(), Color::Green)
} else {
- ("Other Block".to_string(), Color::Yellow)
+ ("Other Block".to_string(), Color::Yellow)
};
(text, color)
}
-