diff --git a/.forgejo/workflows/pre-release.yaml b/.forgejo/workflows/pre-release.yaml deleted file mode 100644 index 140136c..0000000 --- a/.forgejo/workflows/pre-release.yaml +++ /dev/null @@ -1,180 +0,0 @@ -name: Multi-Platform Release Build -on: - push: - tags: - - "v*.*.*-*" -jobs: - build: - runs-on: docker - strategy: - fail-fast: false - matrix: - include: - - target: native - cross: false - platform: linux/arm64 - - - target: x86_64-unknown-linux-gnu - cross: true - platform: linux/amd64 - deps: "build-essential gcc-x86-64-linux-gnu libc6-dev-amd64-cross pkg-config binutils-x86-64-linux-gnu" - env: - CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: x86_64-linux-gnu-gcc - - - target: x86_64-pc-windows-gnu - cross: true - platform: linux/amd64 - deps: "gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 cmake nasm" - env: - CC_x86_64_pc_windows_gnu: x86_64-w64-mingw32-gcc - CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc - - steps: - - name: Checkout code - uses: https://code.forgejo.org/actions/checkout@v4 - - - name: Setup Rust - uses: https://github.com/dtolnay/rust-toolchain@stable - with: - toolchain: stable - - - name: Cache Rust dependencies - uses: https://code.forgejo.org/actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-${{ matrix.target }}- - ${{ runner.os }}-cargo- - - - name: Native Build Release - if: matrix.target == 'native' - run: | - NATIVE_TARGET=$(rustc -vV | grep 'host:' | cut -d' ' -f2) - echo "Building for native target: $NATIVE_TARGET" - cargo build --release - TARGET_BINARY="target/release/uptime-kuma-dashboard" - OUTPUT_NAME="uptime-kuma-dashboard-$NATIVE_TARGET" - mkdir -p release-artifacts - cp "$TARGET_BINARY" "release-artifacts/$OUTPUT_NAME" - strip "release-artifacts/$OUTPUT_NAME" 2>/dev/null || true - chmod +x "release-artifacts/$OUTPUT_NAME" - ls -lh "release-artifacts/$OUTPUT_NAME" - - - name: Get Rust version - id: rust-version - run: echo "version=$(rustc --version | cut -d' ' -f2)" >> $GITHUB_OUTPUT - - - name: Cross-Build Linux x86_64 Release - if: matrix.target == 'x86_64-unknown-linux-gnu' - uses: docker://rust:slim - with: - args: > - sh -c " - rustup target add ${{ matrix.target }} && - apt-get update && apt-get install -y ${{ matrix.deps }} && - mkdir -p ~/.cargo && - echo '[target.${{ matrix.target }}]\nlinker = \"x86_64-linux-gnu-gcc\"' > ~/.cargo/config.toml && - cargo build --release --target ${{ matrix.target }} && - mkdir -p release-artifacts && - cp target/${{ matrix.target }}/release/uptime-kuma-dashboard release-artifacts/uptime-kuma-dashboard-${{ matrix.target }} && - x86_64-linux-gnu-strip release-artifacts/uptime-kuma-dashboard-${{ matrix.target }} && - chmod +x release-artifacts/uptime-kuma-dashboard-${{ matrix.target }}" - env: ${{ matrix.env }} - options: --platform ${{ matrix.platform }} - - - name: Cross-Build Windows x86_64 Release - if: matrix.target == 'x86_64-pc-windows-gnu' - uses: docker://rust:slim - with: - args: > - sh -c " - rustup target add ${{ matrix.target }} && - apt-get update && apt-get install -y ${{ matrix.deps }} && - cargo build --release --target ${{ matrix.target }} && - mkdir -p release-artifacts && - cp target/${{ matrix.target }}/release/uptime-kuma-dashboard.exe release-artifacts/uptime-kuma-dashboard-${{ matrix.target }}.exe && - ls -lh release-artifacts/" - env: ${{ matrix.env }} - options: --platform ${{ matrix.platform }} - - - name: Upload artifacts - uses: https://data.forgejo.org/forgejo/upload-artifact@v4 - with: - name: binary-${{ matrix.target }} - path: release-artifacts/ - retention-days: 1 - - create-release: - runs-on: docker - needs: build - permissions: - contents: write - steps: - - name: Checkout code - uses: https://code.forgejo.org/actions/checkout@v4 - - - name: Download all artifacts - uses: https://data.forgejo.org/forgejo/download-artifact@v4 - with: - path: all-artifacts/ - - - name: Prepare release artifacts - run: | - mkdir -p release-artifacts - find all-artifacts -type f -name "uptime-kuma-dashboard-*" -exec cp {} release-artifacts/ \; - ls -lh release-artifacts/ - - - name: Generate checksums - run: | - cd release-artifacts - sha256sum uptime-kuma-dashboard-* > SHA256SUMS.txt - cat SHA256SUMS.txt - - - name: Create Release - uses: https://code.forgejo.org/actions/forgejo-release@v2 - with: - direction: upload - url: https://git.marcodearaujo.com - repo: marcodearaujo/uptime-kuma-dashboard - token: ${{ secrets.FORGEJO_TOKEN }} - release-dir: release-artifacts - title: "Pre-Release ${{ forgejo.ref_name }}" - prerelease: true - tag: ${{ forgejo.ref_name }} - release-notes: | - ## 🚀 Multi-Platform Release - Compiled on Forgejo Runner **${{ runner.arch }}** - - ### 📦 Available Binaries - This release includes binaries for the following platforms: - - **Linux x86_64** (Intel/AMD 64-bit) - - **Linux ARM64** (aarch64) - Raspberry Pi 4, ARM servers - - **Windows x86_64** (64-bit) - - ### 🚀 How to use - #### Linux/ARM: - # Download the appropriate binary - wget https://your-forgejo.com/releases/download/${{ forgejo.ref_name }}/uptime-kuma-dashboard- - # Make executable - chmod +x uptime-kuma-dashboard-* - # Run - ./uptime-kuma-dashboard-* --base-url https://your-kuma --slug your-slug - - #### Windows: - # Download uptime-kuma-dashboard-x86_64-pc-windows-gnu.exe - # Run in PowerShell or CMD - .\uptime-kuma-dashboard-x86_64-pc-windows-gnu.exe --base-url https://your-kuma --slug your-slug - - ### ✅ Verify Checksums - sha256sum -c SHA256SUMS.txt - - ### 🏗️ Build Information - - Rust Version: ${{ steps.rust-version.outputs.version }} - - Build Date: $(date +'%Y-%m-%d') - - Build Type: Release (optimized with LTO) diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml index e62f186..733f66b 100644 --- a/.forgejo/workflows/release.yaml +++ b/.forgejo/workflows/release.yaml @@ -12,99 +12,98 @@ jobs: include: - target: native cross: false - platform: linux/arm64 - - target: x86_64-unknown-linux-gnu cross: true - platform: linux/amd64 - deps: "build-essential gcc-x86-64-linux-gnu libc6-dev-amd64-cross pkg-config binutils-x86-64-linux-gnu" - env: - CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: x86_64-linux-gnu-gcc - + linker: x86_64-linux-gnu-gcc + - target: aarch64-unknown-linux-gnu + cross: true + linker: aarch64-linux-gnu-gcc + - target: armv7-unknown-linux-gnueabihf + cross: true + linker: arm-linux-gnueabihf-gcc - target: x86_64-pc-windows-gnu cross: true - platform: linux/amd64 - deps: "gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 cmake nasm" - env: - CC_x86_64_pc_windows_gnu: x86_64-w64-mingw32-gcc - CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc - + linker: x86_64-w64-mingw32-gcc steps: - name: Checkout code - uses: https://code.forgejo.org/actions/checkout@v4 + uses: actions/checkout@v4 - name: Setup Rust - uses: https://github.com/dtolnay/rust-toolchain@stable - with: - toolchain: stable + uses: docker://rust:1.84.0-alpine - - name: Cache Rust dependencies - uses: https://code.forgejo.org/actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-${{ matrix.target }}- - ${{ runner.os }}-cargo- - - - name: Native Build Release - if: matrix.target == 'native' + - name: Install build dependencies run: | - NATIVE_TARGET=$(rustc -vV | grep 'host:' | cut -d' ' -f2) - echo "Building for native target: $NATIVE_TARGET" - cargo build --release - TARGET_BINARY="target/release/uptime-kuma-dashboard" - OUTPUT_NAME="uptime-kuma-dashboard-$NATIVE_TARGET" + apk update + apk add --no-cache \ + build-base \ + gcc-arm-linux-gnueabihf \ + gcc-aarch64-linux-gnu \ + gcc-x86_64-linux-gnu \ + g++-arm-linux-gnueabihf \ + g++-aarch64-linux-gnu \ + mingw-w64 \ + libc6-dev-armhf-cross \ + libc6-dev-arm64-cross \ + musl-dev \ + linux-headers + + - name: Add Rust target + if: matrix.target != 'native' + run: rustup target add ${{ matrix.target }} + + - name: Configure cross-compilation + if: matrix.cross && matrix.target != 'native' + run: | + mkdir -p .cargo + cat >> .cargo/config.toml << EOF + [target.${{ matrix.target }}] + linker = "${{ matrix.linker }}" + EOF + + - name: Build release + run: | + if [ "${{ matrix.target }}" = "native" ]; then + # Build nativo para a arquitetura do runner + NATIVE_TARGET=$(rustc -vV | grep 'host:' | cut -d' ' -f2) + echo "Building for native target: $NATIVE_TARGET" + cargo build --release --features production + TARGET_BINARY="target/release/uptime-kuma-dashboard" + OUTPUT_NAME="uptime-kuma-dashboard-$NATIVE_TARGET" + else + # Build com target específico + echo "Building for target: ${{ matrix.target }}" + cargo build --release --target ${{ matrix.target }} --features production + if [[ "${{ matrix.target }}" == *"windows"* ]]; then + TARGET_BINARY="target/${{ matrix.target }}/release/uptime-kuma-dashboard.exe" + OUTPUT_NAME="uptime-kuma-dashboard-${{ matrix.target }}.exe" + else + TARGET_BINARY="target/${{ matrix.target }}/release/uptime-kuma-dashboard" + OUTPUT_NAME="uptime-kuma-dashboard-${{ matrix.target }}" + fi + fi + # Criar diretório de artifacts mkdir -p release-artifacts + # Verificar se o binário foi criado + if [ ! -f "$TARGET_BINARY" ]; then + echo "Error: Binary not found at $TARGET_BINARY" + exit 1 + fi + # Copiar binário cp "$TARGET_BINARY" "release-artifacts/$OUTPUT_NAME" - strip "release-artifacts/$OUTPUT_NAME" 2>/dev/null || true - chmod +x "release-artifacts/$OUTPUT_NAME" + # Strip binário para Linux (reduz tamanho) + if [[ "${{ matrix.target }}" == *"linux"* ]] || [ "${{ matrix.target }}" = "native" ]; then + strip "release-artifacts/$OUTPUT_NAME" 2>/dev/null || echo "Strip failed, continuing..." + fi + # Tornar executável + if [[ "${{ matrix.target }}" != *"windows"* ]]; then + chmod +x "release-artifacts/$OUTPUT_NAME" + fi + # Mostrar informações do arquivo ls -lh "release-artifacts/$OUTPUT_NAME" - - - name: Get Rust version - id: rust-version - run: echo "version=$(rustc --version | cut -d' ' -f2)" >> $GITHUB_OUTPUT - - - name: Cross-Build Linux x86_64 Release - if: matrix.target == 'x86_64-unknown-linux-gnu' - uses: docker://rust:slim - with: - args: > - sh -c " - rustup target add ${{ matrix.target }} && - apt-get update && apt-get install -y ${{ matrix.deps }} && - mkdir -p ~/.cargo && - echo '[target.${{ matrix.target }}]\nlinker = \"x86_64-linux-gnu-gcc\"' > ~/.cargo/config.toml && - cargo build --release --target ${{ matrix.target }} && - mkdir -p release-artifacts && - cp target/${{ matrix.target }}/release/uptime-kuma-dashboard release-artifacts/uptime-kuma-dashboard-${{ matrix.target }} && - x86_64-linux-gnu-strip release-artifacts/uptime-kuma-dashboard-${{ matrix.target }} && - chmod +x release-artifacts/uptime-kuma-dashboard-${{ matrix.target }}" - env: ${{ matrix.env }} - options: --platform ${{ matrix.platform }} - - - name: Cross-Build Windows x86_64 Release - if: matrix.target == 'x86_64-pc-windows-gnu' - uses: docker://rust:slim - with: - args: > - sh -c " - rustup target add ${{ matrix.target }} && - apt-get update && apt-get install -y ${{ matrix.deps }} && - cargo build --release --target ${{ matrix.target }} && - mkdir -p release-artifacts && - cp target/${{ matrix.target }}/release/uptime-kuma-dashboard.exe release-artifacts/uptime-kuma-dashboard-${{ matrix.target }}.exe && - ls -lh release-artifacts/" - env: ${{ matrix.env }} - options: --platform ${{ matrix.platform }} + file "release-artifacts/$OUTPUT_NAME" || true - name: Upload artifacts - uses: https://data.forgejo.org/forgejo/upload-artifact@v4 + uses: actions/upload-artifact@v4 with: name: binary-${{ matrix.target }} path: release-artifacts/ @@ -117,10 +116,10 @@ jobs: contents: write steps: - name: Checkout code - uses: https://code.forgejo.org/actions/checkout@v4 + uses: actions/checkout@v4 - name: Download all artifacts - uses: https://data.forgejo.org/forgejo/download-artifact@v4 + uses: actions/download-artifact@v4 with: path: all-artifacts/ @@ -137,43 +136,42 @@ jobs: cat SHA256SUMS.txt - name: Create Release - uses: https://code.forgejo.org/actions/forgejo-release@v2 + uses: actions/gitea-release-action@v1 with: - direction: upload - url: https://git.marcodearaujo.com - repo: marcodearaujo/uptime-kuma-dashboard - token: ${{ secrets.FORGEJO_TOKEN }} - release-dir: release-artifacts - title: "Release ${{ forgejo.ref_name }}" - tag: ${{ forgejo.ref_name }} - release-notes: | + files: release-artifacts/* + token: ${{ secrets.FORGEGO_TOKEN }} + tag_name: ${{ forgejo.ref_name }} + name: Release ${{ forgejo.ref_name }} + body: | ## 🚀 Multi-Platform Release - Compiled on Forgejo Runner **${{ runner.arch }}** - + Compiled on runner **${{ env.RUNNER_ARCH }}** ### 📦 Available Binaries This release includes binaries for the following platforms: - **Linux x86_64** (Intel/AMD 64-bit) - - **Linux ARM64** (aarch64) - Raspberry Pi 4, ARM servers + - **Linux ARM64** (aarch64) - Raspberry Pi 4, Apple Silicon Linux, ARM servers + - **Linux ARMv7** (armhf) - Raspberry Pi 3 and earlier - **Windows x86_64** (64-bit) - ### 🚀 How to use #### Linux/ARM: - # Download the appropriate binary - wget https://your-forgejo.com/releases/download/${{ forgejo.ref_name }}/uptime-kuma-dashboard- - # Make executable - chmod +x uptime-kuma-dashboard-* - # Run - ./uptime-kuma-dashboard-* --base-url https://your-kuma --slug your-slug - + ```bash + # Download the appropriate binary + wget https://your-forgejo.com/releases/download/${{ forgejo.ref_name }}/uptime-kuma-dashboard- + # Make executable + chmod +x uptime-kuma-dashboard-* + # Run + ./uptime-kuma-dashboard-* --base-url https://your-kuma --slug your-slug + ``` #### Windows: - # Download uptime-kuma-dashboard-x86_64-pc-windows-gnu.exe - # Run in PowerShell or CMD - .\uptime-kuma-dashboard-x86_64-pc-windows-gnu.exe --base-url https://your-kuma --slug your-slug - + ```powershell + # Download uptime-kuma-dashboard-x86_64-pc-windows-gnu.exe + # Run in PowerShell or CMD + .\uptime-kuma-dashboard-x86_64-pc-windows-gnu.exe --base-url https://your-kuma --slug your-slug + ``` ### ✅ Verify Checksums - sha256sum -c SHA256SUMS.txt - + ```bash + sha256sum -c SHA256SUMS.txt + ``` ### 🏗️ Build Information - - Rust Version: ${{ steps.rust-version.outputs.version }} - - Build Date: $(date +'%Y-%m-%d') - - Build Type: Release (optimized with LTO) + - Runner Architecture: ${{ env.RUNNER_ARCH }} + - Rust Version: 1.84.0 + - Build Type: Release (optimized) diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index aa677e3..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Debug executable 'uptime-kuma-dashboard'", - "type": "lldb", - "request": "launch", - "cargo": { - "args": [ - "run", - "--bin=uptime-kuma-dashboard" - ] - }, - "args": [ - "--base-url=http://192.168.69.10:3001/", - "--slug=formgen", - ] - }, - { - "name": "Debug unit tests in executable 'uptime-kuma-dashboard'", - "type": "lldb", - "request": "launch", - "cargo": { - "args": [ - "test", - "--bin=uptime-kuma-dashboard" - ] - } - } - ] -} diff --git a/Cargo.lock b/Cargo.lock index 62df022..79170dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "atomic" @@ -103,28 +103,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-lc-rs" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "base64" version = "0.22.1" @@ -211,17 +189,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" 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 = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -236,9 +206,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "js-sys", @@ -249,9 +219,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +229,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -271,9 +241,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -287,31 +257,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" -[[package]] -name = "cmake" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" -dependencies = [ - "cc", -] - [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "compact_str" version = "0.9.0" @@ -337,9 +288,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.1" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -534,18 +485,21 @@ dependencies = [ "litrs", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -581,6 +535,12 @@ dependencies = [ "regex", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "filedescriptor" version = "0.8.3" @@ -698,6 +658,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -707,12 +682,6 @@ 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-channel" version = "0.3.31" @@ -780,10 +749,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -793,11 +760,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi", "wasip2", - "wasm-bindgen", ] [[package]] @@ -813,6 +778,25 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -885,6 +869,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -912,6 +897,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.19" @@ -931,9 +932,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1084,6 +1087,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "indoc" version = "2.0.7" @@ -1171,38 +1184,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "js-sys" version = "0.3.83" @@ -1293,12 +1274,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "mac_address" version = "1.1.8" @@ -1330,6 +1305,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1348,6 +1329,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.29.0" @@ -1419,10 +1417,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "openssl-probe" -version = "0.2.0" +name = "openssl" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "ordered-float" @@ -1532,7 +1568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand", ] [[package]] @@ -1569,6 +1605,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "portable-atomic" version = "1.13.0" @@ -1590,15 +1632,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -1614,62 +1647,6 @@ 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.17", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.17", - "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.60.2", -] - [[package]] name = "quote" version = "1.0.42" @@ -1691,27 +1668,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core 0.9.3", -] - -[[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 0.9.3", + "rand_core", ] [[package]] @@ -1720,15 +1677,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "ratatui" version = "0.30.0" @@ -1874,34 +1822,37 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.13.1" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "mime", + "native-tls", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", "rustls-pki-types", - "rustls-platform-verifier", "serde", "serde_json", + "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower", "tower-http", "tower-service", @@ -1955,75 +1906,32 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ - "aws-lc-rs", "once_cell", - "ring", "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 = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pki-types" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" 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.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2067,9 +1975,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.5.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.10.0", "core-foundation", @@ -2132,9 +2040,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", @@ -2143,6 +2051,18 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2323,6 +2243,40 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "terminfo" version = "0.9.0" @@ -2458,21 +2412,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.48.0" @@ -2487,6 +2426,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -2497,6 +2446,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.5.2" @@ -2668,7 +2630,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "uptime-kuma-dashboard" -version = "0.38.2" +version = "0.1.0" dependencies = [ "anyhow", "chrono", @@ -2679,7 +2641,6 @@ dependencies = [ "ratatui", "rayon", "reqwest", - "rustls", "serde", "serde_json", "sys-locale", @@ -2689,9 +2650,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.8" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -2723,6 +2684,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -2840,25 +2807,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "wezterm-bidi" version = "0.2.3" @@ -3003,6 +2951,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -3021,15 +2980,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -3057,21 +3007,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3105,12 +3040,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3123,12 +3052,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3141,12 +3064,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3171,12 +3088,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3189,12 +3100,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3207,12 +3112,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3225,12 +3124,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3278,26 +3171,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index acc8090..0765f37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,23 @@ [package] name = "uptime-kuma-dashboard" -version = "0.38.2" +version = "0.1.0" edition = "2024" [dependencies] -clap = { version = "4.5.57", features = ["derive", "env"] } -reqwest = { version = "0.13.1", default-features = false, features = [ - "blocking", - "json", - "rustls", -] } -anyhow = "1.0.101" +clap = { version = "4.5", features = ["derive", "env"] } +reqwest = { version = "0.12.28", features = ["blocking", "json"] } +anyhow = "1.0" fluent-templates = "0.13.2" unic-langid = "0.9.6" sys-locale = "0.3.2" serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.149" +serde_json = "1.0.148" once_cell = "1.19" -url = "2.5.8" +url = "2.5.7" ratatui = "0.30.0" crossterm = "0.29.0" -chrono = "0.4.43" +chrono = "0.4.42" rayon = "1.11.0" -rustls = { version = "0.23.36", default-features = false, features = ["ring"] } [features] default = [] @@ -38,7 +33,7 @@ lto = true codegen-units = 1 strip = true panic = "abort" -debug = false +debug = false [profile.release-min] inherits = "release" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 732499f..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Marco De Araujo - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index bbfca7e..0000000 --- a/README.md +++ /dev/null @@ -1,145 +0,0 @@ -# Uptime Kuma Terminal Dashboard - -![Terminal Dashboard Preview](https://img.shields.io/badge/Rust-1.76+-orange?style=flat-square&logo=rust) - -A terminal-based monitoring dashboard for Uptime Kuma that displays service status, uptime percentages, and historical performance data directly in your terminal. Built with Rust for performance and reliability. - -## Demo - - - -## Overview - -This personal project started as a way to monitor my home lab services without needing a web browser. It connects to your Uptime Kuma instance API and presents a clean, color-coded view of your services' status with real-time updates. The dashboard works great on servers, remote terminals, or anywhere you prefer a lightweight monitoring solution. - -Perfect for DevOps engineers, system administrators, or anyone who prefers terminal workflows over web interfaces for quick status checks. - -## Features - -- Real-time service monitoring with automatic refresh -- Color-coded status indicators (green for up, red for down) -- Historical uptime visualization with compact status history lines -- Responsive terminal UI that adapts to window size -- Internationalization support (English and Portuguese) -- Configurable refresh intervals -- Minimal resource usage (runs efficiently even on low-power devices) -- Keyboard navigation support (arrow keys, home/end) -- Mobile-friendly design for SSH connections - -## Tech Stack - -This project demonstrates proficiency with several modern technologies: - -- **Rust** (core language with async capabilities) -- **ratatui** (terminal UI framework) -- **reqwest** (HTTP client for API communication) -- **serde** (JSON serialization/deserialization) -- **clap** (command-line argument parsing) -- **fluent-templates** (internationalization framework) -- REST API integration patterns -- Error handling and logging best practices -- Cross-platform terminal development - -You can find the latest releases in the [Releases section](https://git.marcodearaujo.com/marcodearaujo/uptime-kuma-dashboard/releases). - -## Installation - -### Option 1: Download built Binary (Recommended) - -1. Go to the [Releases page](https://git.marcodearaujo.com/marcodearaujo/uptime-kuma-dashboard/releases) -2. Download the binary for your operating system: - - **Linux (x86_64)**: `uptime-kuma-dashboard-x86_64-unknown-linux-gnu` - - **Linux (ARM64)**: `uptime-kuma-dashboard-aarch64-unknown-linux-gnu` (Raspberry Pi, ARM servers) - - **Windows**: `uptime-kuma-dashboard-x86_64-pc-windows-gnu.exe` -3. Verify the checksum (recommended for security): - ```bash - sha256sum -c SHA256SUMS.txt - ``` -4. Make it executable (Linux/ARM only): - ```bash - chmod +x uptime-kuma-dashboard-* - ``` -5. Run with your parameters: - ```bash - ./uptime-kuma-dashboard-* --base-url="http://your-kuma:3001/" --slug="your-slug" - ``` - -### Option 2: Build From Source - -### Prerequisites - -- Rust 1.74+ toolchain (install via [rustup](https://rustup.rs/)) -- Uptime Kuma instance with API access - -### Build from Source - -```bash -# Clone the repository -git clone https://git.marcodearaujo.com/marcodearaujo/uptime-kuma-dashboard.git -cd uptime-kuma-dashboard - -# Build the project -cargo build --release - -# Run the dashboard (replace with your Uptime Kuma URL and status page slug) -./target/release/uptime-kuma-dashboard \ - --base-url="http://your-uptime-kuma-instance:3001/" \ - --slug="your-status-page-slug" -``` - -## Usage - -The dashboard requires two parameters: - -- `--base-url`: Your Uptime Kuma instance URL (including port) -- `--slug`: Your status page slug identifier - -Example: - -```bash -./uptime-kuma-dashboard --base-url="http://192.168.1.100:3001/" --slug="home-services" -``` - -**Controls during runtime:** - -- `q` or `ESC`: Exit the dashboard -- `↑`/`↓` or `k`/`j`: Navigate through monitors -- `Home`/`End`: Jump to top/bottom of the list - -## Configuration - -You can set environment variables to avoid typing parameters each time: - -```bash -export UPTIME_KUMA_URL="http://your-uptime-kuma-instance:3001/" -export STATUS_PAGE_SLUG="your-status-page-slug" -``` - -## Development Notes - -This project follows Rust best practices including: - -- Comprehensive error handling with `anyhow` -- Internationalization-ready architecture -- Modular code organization -- Performance optimization for terminal rendering -- Cross-platform compatibility testing - -The architecture separates concerns into: - -- API client layer -- Data processing core -- UI rendering components -- Configuration management -- Internationalization system - -## License - -This project is available under the MIT License - see the [LICENSE](LICENSE) file for details. - ---- - -_Developed as a personal learning project to improve Rust skills and create a useful tool for my home lab monitoring. Feedback welcome!_ diff --git a/assets/demo.webm b/assets/demo.webm deleted file mode 100644 index fb10b24..0000000 Binary files a/assets/demo.webm and /dev/null differ diff --git a/src/api/client.rs b/src/api/client.rs index 6dc4448..acb68ae 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -1,11 +1,9 @@ -use std::time::Duration; - use anyhow::Result; use reqwest::blocking::Client; use crate::{ api::endpoints::UptimeKumaEndpoints, - data::{self, heartbeat::HeartbeatResponse, status_page::model::StatusPageResponse}, i18n::t, + data::{self, heartbeat::HeartbeatResponse, status_page::model::StatusPageResponse}, }; #[derive(Debug, Clone)] @@ -15,37 +13,29 @@ pub struct UptimeKumaClient { impl UptimeKumaClient { pub fn new() -> Self { - let client = Client::builder().connect_timeout(Duration::from_secs(10)).timeout(Duration::from_secs(30)).build().unwrap_or_else(|_| panic!("{}", t("http-build-error"))); Self { - client, + client: Client::new(), } } pub fn fetch_heartbeat(&self, endpoints: &UptimeKumaEndpoints) -> Result { - self.fetch_and_parse(endpoints.heartbeat_url(), data::heartbeat::parse_response) + let response = self.client.get(endpoints.heartbeat_url()).send()?; + + if response.status().is_success() { + let json_text = response.text()?; + data::heartbeat::parse_response(&json_text) + } else { + return Err(anyhow::anyhow!(response.status())); + } } pub fn fetch_status_page(&self, endpoints: &UptimeKumaEndpoints) -> Result { - self.fetch_and_parse( - endpoints.status_page_url(), - data::status_page::parse_response, - ) - } - - fn fetch_and_parse(&self, url: String, parser: F) -> Result - where - F: FnOnce(&str) -> Result, - { - let response = self.client.get(url.clone()).send()?; + let response = self.client.get(endpoints.status_page_url()).send()?; if response.status().is_success() { let json_text = response.text()?; - parser(&json_text) + data::status_page::parse_response(&json_text) } else { - Err(anyhow::anyhow!( - "URL: {}, Error: {}", - url, - response.status() - )) + return Err(anyhow::anyhow!(response.status())); } } } diff --git a/src/api/endpoints.rs b/src/api/endpoints.rs index 9004f3d..884472c 100644 --- a/src/api/endpoints.rs +++ b/src/api/endpoints.rs @@ -16,28 +16,10 @@ impl UptimeKumaEndpoints { } pub fn heartbeat_url(&self) -> String { - let mut url = self.get_url(); - url.path_segments_mut() - .unwrap() - .push("heartbeat") - .push(&self.slug); - url.to_string() + format!("{}api/status-page/heartbeat/{}", self.base_url, self.slug) } pub fn status_page_url(&self) -> String { - let mut url = self.get_url(); - url.path_segments_mut() - .unwrap() - .push(&self.slug); - url.to_string() - } - - fn get_url(&self) -> Url { - let mut url = self.base_url.clone(); - url.path_segments_mut() - .unwrap() - .push("api") - .push("status-page"); - url + format!("{}api/status-page/{}", self.base_url, self.slug) } } diff --git a/src/core/data.rs b/src/core/data.rs index 028515d..bf43969 100644 --- a/src/core/data.rs +++ b/src/core/data.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::collections::HashMap; use crate::core::models::{UnifiedData, UnifiedGroupData, UnifiedMonitorData}; @@ -7,45 +6,32 @@ use crate::data::{ status_page::model::StatusPageResponse, }; -pub fn unify_data<'a>( - status_page: &'a StatusPageResponse, - heartbeat: &'a HeartbeatResponse, -) -> UnifiedData<'a> { +pub fn unify_data(status_page: &StatusPageResponse, heartbeat: &HeartbeatResponse) -> UnifiedData { let mut groups = Vec::with_capacity(status_page.public_group_list.len()); + let heartbeat_map: HashMap> = heartbeat + .monitors + .iter() + .map(|m| (m.monitor_id, m.heartbeats.clone())) + .collect(); - let mut heartbeat_map: HashMap = - HashMap::with_capacity(heartbeat.monitors.len()); - heartbeat_map.extend( - heartbeat - .monitors - .iter() - .map(|m| (m.monitor_id, &m.heartbeats[..])), - ); - - let mut uptime_map: HashMap<(u64, u32), &'a UptimeData> = - HashMap::with_capacity(heartbeat.uptime_data.len()); - uptime_map.extend( - heartbeat - .uptime_data - .iter() - .map(|u| ((u.monitor_id, u.period_hours), u)), - ); + let uptime_map: HashMap<(u64, u32), UptimeData> = heartbeat + .uptime_data + .iter() + .map(|u| ((u.monitor_id, u.period_hours), u.clone())) + .collect(); for group in &status_page.public_group_list { let mut monitors = Vec::with_capacity(group.monitor_list.len()); for monitor_info in &group.monitor_list { - let uptime_data = uptime_map.get(&(monitor_info.id, 24)).copied(); - let heartbeats = heartbeat_map.get(&monitor_info.id).copied().unwrap_or(&[]); - - let name: Cow<'a, str> = if monitor_info.name.len() > 100 { - Cow::Owned(monitor_info.name.clone()) - } else { - Cow::Borrowed(monitor_info.name.as_str()) - }; + let uptime_data = uptime_map.get(&(monitor_info.id, 24)).cloned(); + let heartbeats = heartbeat_map + .get(&monitor_info.id) + .cloned() + .unwrap_or_default(); monitors.push(UnifiedMonitorData { id: monitor_info.id, - name, + name: monitor_info.name.clone(), heartbeats, uptime_data, }); @@ -54,7 +40,7 @@ pub fn unify_data<'a>( monitors.sort_by_key(|m| m.id); groups.push(UnifiedGroupData { - group_info: group, + group_info: group.clone(), monitors, }); } @@ -62,8 +48,8 @@ pub fn unify_data<'a>( groups.sort_by_key(|g| g.group_info.weight); UnifiedData { - title: Cow::Borrowed(&status_page.config.title), - description: status_page.config.description.as_deref().map(Cow::Borrowed), + title: status_page.config.title.clone(), + description: status_page.config.description.clone(), groups, auto_refresh_interval: status_page.config.auto_refresh_interval, } diff --git a/src/core/models.rs b/src/core/models.rs index 441f2dc..d3851a4 100644 --- a/src/core/models.rs +++ b/src/core/models.rs @@ -1,26 +1,24 @@ -use std::borrow::Cow; - use crate::data::heartbeat::model::{HeartbeatEntry, UptimeData}; use crate::data::status_page::model::{StatusPageGroup}; #[derive(Debug, Clone)] -pub struct UnifiedMonitorData<'a> { +pub struct UnifiedMonitorData { pub id: u64, - pub name: Cow<'a, str>, - pub heartbeats: &'a [HeartbeatEntry], - pub uptime_data: Option<&'a UptimeData>, + pub name: String, + pub heartbeats: Vec, + pub uptime_data: Option, } #[derive(Debug, Clone)] -pub struct UnifiedData<'a> { - pub title: Cow<'a, str>, - pub description: Option>, +pub struct UnifiedData { + pub title: String, + pub description: Option, pub auto_refresh_interval: u32, - pub groups: Vec>, + pub groups: Vec, } #[derive(Debug, Clone)] -pub struct UnifiedGroupData<'a> { - pub group_info: &'a StatusPageGroup, - pub monitors: Vec>, +pub struct UnifiedGroupData { + pub group_info: StatusPageGroup, + pub monitors: Vec, } diff --git a/src/data/heartbeat/model.rs b/src/data/heartbeat/model.rs index 042ca91..e2a0107 100644 --- a/src/data/heartbeat/model.rs +++ b/src/data/heartbeat/model.rs @@ -13,7 +13,7 @@ pub struct HeartbeatEntry { #[serde(skip)] pub msg: String, #[serde(default)] - pub ping: Option, + pub ping: Option, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -55,7 +55,7 @@ impl HeartbeatResponse { for (monitor_id_str, heartbeats) in &self.heartbeat_list_raw { let monitor_id = monitor_id_str .parse::() - .with_context(|| t("invalid-monitor-id").to_string())?; + .with_context(|| format!("{}", t("invalid-monitor-id")))?; self.monitors.push(MonitorHeartbeats { monitor_id, @@ -77,11 +77,11 @@ impl HeartbeatResponse { let monitor_id = parts[0] .parse::() - .with_context(|| t("invalid-monitor-id").to_string())?; + .with_context(|| format!("{}", t("invalid-monitor-id")))?; let period_hours = parts[1] .parse::() - .with_context(|| t("invalid-period-hours").to_string())?; + .with_context(|| format!("{}", t("invalid-period-hours")))?; self.uptime_data.push(UptimeData { monitor_id, diff --git a/src/data/heartbeat/parser.rs b/src/data/heartbeat/parser.rs index 72e4bcd..3733065 100644 --- a/src/data/heartbeat/parser.rs +++ b/src/data/heartbeat/parser.rs @@ -3,8 +3,8 @@ use crate::i18n::t; use anyhow::{Context, Ok, Result}; pub fn parse_response(json_text: &str) -> Result { - let mut response: HeartbeatResponse = serde_json::from_slice(json_text.as_bytes()) - .with_context(|| t("invalid-json-heartbeat"))?; + let mut response: HeartbeatResponse = + serde_json::from_str(json_text).with_context(|| t("invalid-json-heartbeat"))?; response.process()?; Ok(response) diff --git a/src/data/status_page/parser.rs b/src/data/status_page/parser.rs index b6b8c07..a765c58 100644 --- a/src/data/status_page/parser.rs +++ b/src/data/status_page/parser.rs @@ -3,7 +3,7 @@ use crate::i18n::t; use anyhow::{Context, Ok, Result}; pub fn parse_response(json_text: &str) -> Result { - let response: StatusPageResponse = serde_json::from_slice(json_text.as_bytes()) - .with_context(|| t("invalid-json-status-page"))?; + let response: StatusPageResponse = + serde_json::from_str(json_text).with_context(|| t("invalid-json-status-page"))?; Ok(response) } diff --git a/src/i18n/translate.rs b/src/i18n/translate.rs index f9a01fc..1207704 100644 --- a/src/i18n/translate.rs +++ b/src/i18n/translate.rs @@ -34,7 +34,7 @@ pub fn t(key: &str) -> String { } } - let result = LOCALES.lookup(get_system_locale(), key); + let result = LOCALES.lookup(&*get_system_locale(), key); let mut cache_write = cache.write().unwrap(); @@ -48,8 +48,12 @@ pub fn t(key: &str) -> String { pub fn t_with_args(key: &str, args: &HashMap<&'static str, String>) -> String { let mut map = HashMap::new(); - for (key, value) in args { - map.insert(Cow::Borrowed(*key), FluentValue::from(value)); - } - LOCALES.lookup_with_args(get_system_locale(), key, &map) + let args_map: &HashMap, FluentValue<'_>>; + args_map = { + for (key, value) in args { + map.insert(Cow::Borrowed(*key), FluentValue::from(value.clone())); + } + &map + }; + LOCALES.lookup_with_args(&get_system_locale(), key, args_map) } diff --git a/src/locales/en-US/main.ftl b/src/locales/en-US/main.ftl index 35172a8..f01d87d 100644 --- a/src/locales/en-US/main.ftl +++ b/src/locales/en-US/main.ftl @@ -6,9 +6,9 @@ Response = Response response = response invalid-json-status-page = ❌ Error parssing status page JSON invalid-json-heartbeat = ❌ Error parssing heartbeat JSON -invalid-uptime-key-format = Invalid format for uptime key. Expected format "monitorID_period". Received key: {$key} -invalid-monitor-id = Invalid monitor ID: {$id} -invalid-period-hours = Invalid period in hours: {$hours} +invalid-uptime-key-format = Invalid format for uptime key. Expected format "monitorID_period". Received key: {key} +invalid-monitor-id = Invalid monitor ID: {id} +invalid-period-hours = Invalid period in hours: {hours} loading = Loading... monitors = Monitors unknown = Unknown @@ -18,11 +18,11 @@ status = Status error = Error dashboard-header = Status Dashboard never = Never -auto-update-failed = Automatic update failed: {$error} -update-fail = Failed to update data: {$error} +auto-update-failed = Automatic update failed: {error} +update-fail = Failed to update data: {error} now = Now uptime = Uptime history = History -auto-update-enabled = Auto-update enabled ({$interval} min) -update-failed = Update failed: {$error} +auto-update-enabled = Auto-update enabled ({interval} min) +update-failed = Update failed: {error} key-to-exit = Press 'q' to exit diff --git a/src/locales/pt-BR/main.ftl b/src/locales/pt-BR/main.ftl index 31a5c98..df72828 100644 --- a/src/locales/pt-BR/main.ftl +++ b/src/locales/pt-BR/main.ftl @@ -5,10 +5,10 @@ missing_url = ❌ URL não fornecida. Use --url ou a variável de ambiente UPTIM Response = Resposta response = resposta invalid-json-status-page = ❌ Falha ao parsear JSON do status page -invalid-json-heartbeat = Falha ao parsear JSON do heartbeat -invalid-uptime-key-format = Formato inválido na chave de uptime. Chave esperada no formato "monitorID_periodo". Chave recebida: {$key} -invalid-monitor-id = ID de monitor inválido: {$id} -invalid-period-hours = Período em horas inválido: {$hours} +invalid-json-heartbeat = ❌ Falha ao parsear JSON do heartbeat +invalid-uptime-key-format = Formato inválido na chave de uptime. Chave esperada no formato "monitorID_periodo". Chave recebida: {key} +invalid-monitor-id = ID de monitor inválido: {id} +invalid-period-hours = Período em horas inválido: {hours} loading = Carregando... monitors = Monitors monitor = Monitor @@ -23,6 +23,6 @@ auto-update-failed = Falha na atualização automática update-fail = Falha ao atualizar dados now = Agora history = Historico -auto-update-enabled = Auto-atualização ativada ({$interval} min) -update-failed = Falha na atualização: {$error} +auto-update-enabled = Auto-atualização ativada ({interval} min) +update-failed = Falha na atualização: {error} key-to-exit = Pressione 'q' para sair diff --git a/src/ui/app.rs b/src/ui/app.rs index 2e19131..18fa818 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,10 +1,12 @@ use crate::api::{UptimeKumaClient, UptimeKumaEndpoints}; use crate::core; +use crate::data::{heartbeat::HeartbeatResponse, status_page::model::StatusPageResponse}; use crate::i18n::{t, t_with_args}; use crate::ui::{ components::{render_footer, render_header, render_monitor_list}, dashboard::model::DashboardViewState, }; +use anyhow::Result; use crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind}, execute, @@ -16,18 +18,23 @@ use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span, Text}, - widgets::{Block, Borders, Padding, Paragraph, Scrollbar, ScrollbarOrientation}, + widgets::{Block, Borders, Padding, Paragraph}, }; use std::{ collections::HashMap, io, + sync::mpsc, + thread, time::{Duration, Instant}, }; const INITIAL_INTERVAL: u32 = 300; -const MAIN_LAYOUT_WITH_SCROLLBAR: [Constraint; 2] = [Constraint::Min(1), Constraint::Length(1)]; -const MAIN_LAYOUT_WITHOUT_SCROLLBAR: [Constraint; 1] = [Constraint::Min(1)]; + +enum FetchResult { + Heartbeat(Result), + StatusPage(Result), +} pub struct App { state: DashboardViewState, @@ -40,26 +47,6 @@ pub struct App { } impl App { - pub fn new(endpoints: UptimeKumaEndpoints) -> io::Result { - let backend = CrosstermBackend::new(io::stdout()); - let mut terminal = Terminal::new(backend)?; - terminal.hide_cursor()?; - - let state = DashboardViewState::new(); - - let initial_interval = Duration::from_secs(INITIAL_INTERVAL as u64); - - Ok(Self { - state, - terminal, - should_quit: false, - last_update: Instant::now(), - update_interval: initial_interval, - endpoints, - client: UptimeKumaClient::new(), - }) - } - fn setup_terminal(&mut self) -> io::Result<()> { enable_raw_mode()?; execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?; @@ -129,101 +116,51 @@ impl App { fn render(&mut self) { let _ = self.terminal.draw(|frame| { let area = frame.area(); - - const HEADER_HEIGHT: u16 = 3; - const FOOTER_HEIGHT: u16 = 3; - - let max_content_height = area.height.saturating_sub(HEADER_HEIGHT + FOOTER_HEIGHT); - let chunks = Layout::default() .direction(Direction::Vertical) .margin(1) .constraints([ - Constraint::Length(HEADER_HEIGHT), - Constraint::Length(max_content_height.max(1)), - Constraint::Length(FOOTER_HEIGHT), + Constraint::Length(3), + Constraint::Min(1), + Constraint::Length(3), ]) .split(area); render_header(frame, chunks[0], &self.state); - let main_constraint = if self.state.show_vertical_scrollbar(chunks[1].height) { - &MAIN_LAYOUT_WITH_SCROLLBAR[..] - } else { - &MAIN_LAYOUT_WITHOUT_SCROLLBAR[..] - }; - - let main_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints(main_constraint) - .split(chunks[1]); - if self.state.is_loading { - Self::render_loading(frame, main_chunks[0]); + Self::render_loading(frame, chunks[1]); } else if let Some(error) = &self.state.error_message { - Self::render_error(frame, main_chunks[0], error); + Self::render_error(frame, chunks[1], error); } else if self.state.groups.is_empty() || self.state.get_all_monitors().is_empty() { - Self::render_no_data(frame, main_chunks[0]); + Self::render_no_data(frame, chunks[1]); } else { - Self::render_main(frame, main_chunks.to_vec(), &mut self.state); + render_monitor_list(frame, chunks[1], &self.state); } let seconds_until_update = self .update_interval .checked_sub(self.last_update.elapsed()) - .map(|d| d.as_secs()) + .map(|d| d.as_secs() as u64) .unwrap_or(30); render_footer(frame, chunks[2], seconds_until_update); }); } - fn render_main(frame: &mut Frame, area: Vec, state: &mut DashboardViewState) { - render_monitor_list(frame, area[0], state); - - if !state.show_vertical_scrollbar(area[0].height) { - return; - } - - if area.len() <= 1 { - dbg!(area[0].height); - return; - } - - let max_scroll = state - .get_total_length() - .saturating_sub(area[0].height as usize) - .saturating_add(1); - - state.scroll_state = state - .scroll_state - .content_length(max_scroll.max(1)) - .viewport_content_length(area[0].height as usize); - - let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) - .begin_symbol(Some("↑")) - .end_symbol(Some("↓")); - - frame.render_stateful_widget(scrollbar, area[1], &mut state.scroll_state); - } - fn handle_events(&mut self) -> io::Result<()> { let timeout = Duration::from_secs(1); - if event::poll(timeout)? - && let Event::Key(key) = event::read()? - { - if key.kind == KeyEventKind::Release { - return Ok(()); - } + if event::poll(timeout)? { + if let Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Release { + return Ok(()); + } - match key.code { - KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true, - KeyCode::Up | KeyCode::Char('k') => self.state.scroll_state.prev(), - KeyCode::Down | KeyCode::Char('j') => self.state.scroll_state.next(), - KeyCode::Home => self.state.scroll_state.first(), - KeyCode::End => self.state.scroll_state.last(), - _ => {} + match key.code { + KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true, + _ => {} + } } } Ok(()) @@ -243,6 +180,7 @@ impl App { fn refresh_data(&mut self) -> io::Result<()> { self.state.is_loading = true; + self.render(); match self.fetch_and_update_data() { Ok(()) => { @@ -259,11 +197,57 @@ impl App { Ok(()) } - fn fetch_and_update_data(&mut self) -> anyhow::Result<()> { - let heartbeat_result = self.client.fetch_heartbeat(&self.endpoints)?; - let status_page_result = self.client.fetch_status_page(&self.endpoints)?; + pub fn new(endpoints: UptimeKumaEndpoints) -> io::Result { + let backend = CrosstermBackend::new(io::stdout()); + let mut terminal = Terminal::new(backend)?; + terminal.hide_cursor()?; - let unified_data = core::unify_data(&status_page_result, &heartbeat_result); + let state = DashboardViewState::new(); + + let initial_interval = Duration::from_secs(INITIAL_INTERVAL as u64); + + Ok(Self { + state, + terminal, + should_quit: false, + last_update: Instant::now(), + update_interval: initial_interval, + endpoints, + client: UptimeKumaClient::new(), + }) + } + + fn fetch_and_update_data(&mut self) -> anyhow::Result<()> { + let (tx, rx) = mpsc::channel(); + + let heartbeat_client = self.client.clone(); + let heartbeat_endpoints = self.endpoints.clone(); + let tx_clone = tx.clone(); + thread::spawn(move || { + let result = heartbeat_client.fetch_heartbeat(&heartbeat_endpoints); + tx.send(FetchResult::Heartbeat(result)).unwrap(); + }); + + let status_page_client = self.client.clone(); + let status_page_endpoints = self.endpoints.clone(); + + thread::spawn(move || { + let result = status_page_client.fetch_status_page(&status_page_endpoints); + tx_clone.send(FetchResult::StatusPage(result)).unwrap(); + }); + + let mut heartbeat_result = None; + let mut status_page_result = None; + + for _ in 0..2 { + match rx.recv()? { + FetchResult::Heartbeat(result) => heartbeat_result = Some(result?), + FetchResult::StatusPage(result) => status_page_result = Some(result?), + } + } + let heartbeat_data = heartbeat_result.unwrap(); + let status_page_data = status_page_result.unwrap(); + let unified_data = core::unify_data(&status_page_data, &heartbeat_data); self.state = DashboardViewState::from_unified_data(unified_data); Ok(()) } diff --git a/src/ui/components/footer.rs b/src/ui/components/footer.rs index 9a79434..806002b 100644 --- a/src/ui/components/footer.rs +++ b/src/ui/components/footer.rs @@ -9,11 +9,7 @@ use ratatui::{ use crate::i18n::t; use chrono::Local; -pub fn render_footer( - frame: &mut Frame, - area: Rect, - seconds_until_update: u64 -) { +pub fn render_footer(frame: &mut Frame, area: Rect, seconds_until_update: u64) { let now = Local::now(); let datatime_str = now.format("%Y-%m-%d %H:%M:%S").to_string(); let countdown_str = format!("↻ {}s", seconds_until_update); diff --git a/src/ui/components/monitor_list.rs b/src/ui/components/monitor_list.rs index ae89733..e0b1c91 100644 --- a/src/ui/components/monitor_list.rs +++ b/src/ui/components/monitor_list.rs @@ -1,19 +1,10 @@ -use std::{ - borrow::Cow, - cmp::min, - collections::{HashMap, hash_map::DefaultHasher}, - hash::{Hash, Hasher}, - sync::{OnceLock, RwLock}, -}; +use std::cmp::min; -use crate::{ - i18n::t, - ui::dashboard::{ - MonitorStatus, MonitorViewState, - model::{BORDER_LINES_VIEW, DashboardViewState, GroupViewState}, - }, +use crate::i18n::t; +use crate::ui::dashboard::{ + MonitorStatus, MonitorViewState, + model::{DashboardViewState, GroupViewState}, }; -use once_cell::sync::Lazy; use ratatui::{ Frame, layout::{Alignment, Constraint, Direction, Layout, Rect}, @@ -25,72 +16,50 @@ use ratatui::{ const STATUS_LINE_LENGTH: usize = 100; const MAX_NAME_LENGTH: usize = 30; -static UP_SPAN: Lazy> = - Lazy::new(|| Span::styled("■", Style::default().fg(Color::Green))); +pub fn render_monitor_list(frame: &mut Frame, area: Rect, state: &DashboardViewState) { + let group_areas = layout_groups(area, &state.groups); -static DOWN_SPAN: Lazy> = - Lazy::new(|| Span::styled("■", Style::default().fg(Color::Red))); - -static UNKNOWN_SPAN: Lazy> = - Lazy::new(|| Span::styled("■", Style::default().fg(Color::Yellow))); - -static STATUS_LINE_CACHE: OnceLock>>> = OnceLock::new(); - -pub fn render_monitor_list(main_frame: &mut Frame, area: Rect, state: &mut DashboardViewState) { - let available_height = area.height as usize; - let max_scroll = state.get_total_length().saturating_sub(available_height); - - if state.scroll_state.get_position() > max_scroll { - state.scroll_state = state.scroll_state.position(max_scroll); - } - - let scroll_pos = state.scroll_state.get_position(); - let mut current_y = area.y as usize; - let mut rendered_height = 0; - let mut lines_skipped = 0; - let half = state.get_total_length().saturating_div(2); - - for group in state.groups.iter() { - let group_height = group.monitors.len() + BORDER_LINES_VIEW; - - if lines_skipped + group_height <= scroll_pos { - lines_skipped += group_height; - continue; - } - - let visible_height = if lines_skipped < scroll_pos { - group_height - (scroll_pos - lines_skipped) - } else { - group_height - } - .min(available_height - rendered_height); - - let group_area = Rect { - x: area.x, - y: current_y as u16, - width: area.width, - height: visible_height as u16, - }; - - render_group(main_frame, group_area, group, half > rendered_height); - - current_y += visible_height; - rendered_height += visible_height; - lines_skipped += group_height; + for (i, (group, &group_area)) in state.groups.iter().zip(group_areas.iter()).enumerate() { + render_group(frame, group_area, group, i == 0); } } -fn render_group(frame: &mut Frame, area: Rect, group: &GroupViewState, is_first_half: bool) { +fn layout_groups(area: Rect, groups: &[GroupViewState]) -> Vec { + let total_lines: usize = groups.iter().map(|g| g.monitors.len() + 1).sum(); + + if total_lines == 0 { + return Vec::new(); + } + + let content_height = area.height.saturating_sub(2); + let line_height = content_height as usize / total_lines; + + let mut current_y = area.y + 1; + let mut areas = Vec::with_capacity(groups.len()); + + for group in groups { + let group_lines = group.monitors.len() + 2; + let group_height = (group_lines + line_height).max(1); + areas.push(Rect { + x: area.x, + y: current_y, + width: area.width, + height: group_height as u16, + }); + + current_y += group_height as u16; + } + + areas +} + +fn render_group(frame: &mut Frame, area: Rect, group: &GroupViewState, is_first: bool) { let chunks = Layout::default() .direction(Direction::Vertical) .margin(0) .constraints([Constraint::Length(1), Constraint::Min(1)]) .split(area); - if chunks[0].height == 0 || chunks[1].height == 0 || group.monitors.is_empty() { - return; - } - let group_title = Line::from(vec![ Span::styled(format!("{} ", group.name), title_style()), Span::styled( @@ -100,7 +69,11 @@ fn render_group(frame: &mut Frame, area: Rect, group: &GroupViewState, is_first_ ]); let title_block = Block::default() - .borders(Borders::ALL) + .borders(if is_first { + Borders::TOP | Borders::LEFT | Borders::RIGHT + } else { + Borders::ALL + }) .border_style(Style::default().fg(Color::Blue)) .title(group_title) .title_alignment(Alignment::Left); @@ -108,24 +81,13 @@ fn render_group(frame: &mut Frame, area: Rect, group: &GroupViewState, is_first_ frame.render_widget(title_block, chunks[0]); if !group.monitors.is_empty() { - render_monitor_table(frame, chunks[1], &group.monitors, is_first_half); + render_monitor_table(frame, chunks[1], &group.monitors); } } -fn render_monitor_table( - frame: &mut Frame, - area: Rect, - monitors: &[MonitorViewState], - is_first_half: bool, -) { +fn render_monitor_table(frame: &mut Frame, area: Rect, monitors: &Vec) { let max_items = area.height as usize; let items_to_show = min(monitors.len(), max_items); - let index = if is_first_half { - monitors.len().saturating_sub(max_items.saturating_sub(2)) // 2 = Table header + botton - } else { - 0 - }; - let monitors = &monitors[index..index + items_to_show.min(monitors.len() - index)]; let header_cells = vec![ "".to_string(), @@ -137,10 +99,11 @@ fn render_monitor_table( let header = Row::new(header_cells).style(title_style()).height(1); - let mut rows: Vec = Vec::with_capacity(items_to_show); - for monitor in monitors.iter().take(items_to_show) { - rows.push(create_monitor_item(monitor)); - } + let rows: Vec = monitors + .iter() + .take(items_to_show) + .map(|monitor| create_monitor_item(monitor)) + .collect(); let widths = vec![ Constraint::Length(3), @@ -164,6 +127,14 @@ fn render_monitor_table( frame.render_widget(table, area); } +pub fn get_status_char(status: &MonitorStatus) -> char { + match status { + MonitorStatus::Up => '■', + MonitorStatus::Down => '■', + MonitorStatus::Unknown => '■', + } +} + pub fn get_status_color(status: &MonitorStatus) -> Color { match status { MonitorStatus::Up => Color::Green, @@ -172,14 +143,11 @@ pub fn get_status_color(status: &MonitorStatus) -> Color { } } -pub fn get_status_emoji(status: &MonitorStatus) -> Cow<'static, str> { - static UP_ICON: &str = "✅"; - static DOWN_ICON: &str = "❌"; - static UNKNOWN_ICON: &str = "❓"; +pub fn get_status_emoji(status: &MonitorStatus) -> &str { match status { - MonitorStatus::Up => Cow::Borrowed(UP_ICON), - MonitorStatus::Down => Cow::Borrowed(DOWN_ICON), - MonitorStatus::Unknown => Cow::Borrowed(UNKNOWN_ICON), + MonitorStatus::Up => "✅", + MonitorStatus::Down => "❌", + MonitorStatus::Unknown => "❓", } } @@ -187,24 +155,20 @@ fn create_monitor_item(monitor: &MonitorViewState) -> Row<'_> { let status_icon = get_status_emoji(&monitor.status); let status_color = get_status_color(&monitor.status); - let display_name: Cow = if monitor.name.len() > MAX_NAME_LENGTH { - Cow::Owned(format!( - "{:.width$}...", - monitor.name, - width = MAX_NAME_LENGTH - 3 - )) + let display_name: String = if monitor.name.len() > MAX_NAME_LENGTH { + format!("{:.width$}...", &monitor.name, width = MAX_NAME_LENGTH - 3) } else { - Cow::Borrowed(&monitor.name) + monitor.name.clone() }; let response_text = format!("{:>7}ms", monitor.response_time); let uptime_text = format!("{:>7}%", monitor.uptime_24h); - let status_line_spans = get_cached_status_line(&monitor.status_history); + let status_line_spans = create_status_line_spans(&monitor.status_history); Row::new(vec![ get_formated_line(format!("{} ", status_icon), status_color, Modifier::empty()), - get_formated_line(display_name.to_string(), Color::White, Modifier::empty()), + get_formated_line(display_name, Color::White, Modifier::empty()), get_formated_line(response_text, Color::Cyan, Modifier::empty()), get_formated_line(uptime_text, Color::Magenta, Modifier::empty()), status_line_spans, @@ -220,12 +184,22 @@ fn get_formated_line(text: String, color: Color, modifier: Modifier) -> Line<'st )]) } -fn get_status_span(status: &MonitorStatus) -> &'static Span<'static> { - match status { - MonitorStatus::Up => &UP_SPAN, - MonitorStatus::Down => &DOWN_SPAN, - MonitorStatus::Unknown => &UNKNOWN_SPAN, +fn create_status_line_spans(status_history: &[MonitorStatus]) -> Line<'_> { + let recent_status: Vec<_> = status_history + .iter() + .rev() + .take(STATUS_LINE_LENGTH) + .collect(); + + let mut spans = Vec::with_capacity(recent_status.len()); + + for status in recent_status.iter().rev() { + let c = get_status_char(status); + let color = get_status_color(status); + + spans.push(Span::styled(c.to_string(), Style::default().fg(color))); } + Line::from(spans) } fn title_style() -> Style { @@ -233,46 +207,3 @@ fn title_style() -> Style { .fg(Color::Yellow) .add_modifier(Modifier::BOLD) } - -fn calculate_history_hash(status_history: &[MonitorStatus]) -> u64 { - let mut hasher = DefaultHasher::new(); - status_history - .iter() - .take(STATUS_LINE_LENGTH) - .for_each(|status| { - status.hash(&mut hasher); - }); - hasher.finish() -} - -fn get_cached_status_line(status_history: &[MonitorStatus]) -> Line<'static> { - let hash = calculate_history_hash(status_history); - let cache = STATUS_LINE_CACHE.get_or_init(|| RwLock::new(HashMap::new())); - { - let read = cache.read().unwrap(); - if let Some(line) = read.get(&hash) { - return line.clone(); - } - } - - let mut spans: Vec> = Vec::with_capacity(STATUS_LINE_LENGTH); - spans.extend( - status_history - .iter() - .take(STATUS_LINE_LENGTH) - .map(|status| get_status_span(status).clone()), - ); - - let new_line = Line::from(spans); - let mut write = cache.write().unwrap(); - if write.len() > 1000 { - let mut keys_to_remove: Vec = Vec::with_capacity(250); - keys_to_remove.extend(write.keys().take(250).copied()); - - for key in keys_to_remove { - write.remove(&key); - } - } - write.insert(hash, new_line.clone()); - new_line -} diff --git a/src/ui/dashboard/model.rs b/src/ui/dashboard/model.rs index 6425d2b..8f24c42 100644 --- a/src/ui/dashboard/model.rs +++ b/src/ui/dashboard/model.rs @@ -1,12 +1,9 @@ use crate::core::models::{UnifiedData, UnifiedGroupData}; use crate::data::heartbeat::model::HeartbeatEntry; use crate::i18n::t; -use ratatui::widgets::ScrollbarState; use rayon::prelude::*; -use std::borrow::Cow; -pub const BORDER_LINES_VIEW: usize = 3; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq)] pub enum MonitorStatus { Up, Down, @@ -15,7 +12,7 @@ pub enum MonitorStatus { #[derive(Debug, Clone)] pub struct MonitorViewState { - pub name: Cow<'static, str>, + pub name: String, pub status: MonitorStatus, pub response_time: String, pub uptime_24h: String, @@ -36,8 +33,6 @@ pub struct DashboardViewState { pub is_loading: bool, pub error_message: Option, pub auto_refresh_interval: u32, - pub scroll_state: ScrollbarState, - total_length: usize, } impl DashboardViewState { @@ -49,8 +44,6 @@ impl DashboardViewState { is_loading: true, error_message: None, auto_refresh_interval: 300, - scroll_state: ScrollbarState::new(0), - total_length: 0, } } @@ -64,43 +57,32 @@ impl DashboardViewState { }); } - let total_length: usize = groups - .iter() - .map(|g| g.monitors.len() + BORDER_LINES_VIEW) - .sum(); - Self { - title: data.title.into_owned(), - descriptions: data.description.map(|d| d.into_owned()), + title: data.title, + descriptions: data.description, groups, is_loading: false, error_message: None, auto_refresh_interval: data.auto_refresh_interval.max(30), - scroll_state: ScrollbarState::new(total_length.saturating_sub(1)), - total_length, } } - pub fn get_total_length(&self) -> usize { - self.total_length - } - pub fn get_all_monitors(&self) -> Vec<&MonitorViewState> { self.groups.iter().flat_map(|g| g.monitors.iter()).collect() } - - pub fn show_vertical_scrollbar(&self, available_height: u16) -> bool { - self.total_length as u16 > available_height - } } fn get_status_history(heartbeats: &[HeartbeatEntry]) -> Vec { - let mut history: Vec<_> = Vec::with_capacity(heartbeats.len()); - history.extend(heartbeats.iter().rev().take(100).map(|h| match h.status { - 0 => MonitorStatus::Down, - 1 => MonitorStatus::Up, - _ => MonitorStatus::Unknown, - })); + let mut history = heartbeats + .iter() + .rev() + .take(100) + .map(|h| match h.status { + 0 => MonitorStatus::Down, + 1 => MonitorStatus::Up, + _ => MonitorStatus::Unknown, + }) + .collect::>(); while history.len() < 100 { history.push(MonitorStatus::Unknown); @@ -112,42 +94,36 @@ fn get_status_history(heartbeats: &[HeartbeatEntry]) -> Vec { fn add_monitor_view_state(group: UnifiedGroupData) -> Vec { let mut monitors = Vec::with_capacity(group.monitors.len()); - group - .monitors - .into_par_iter() - .map(|monitor| { - let status_history = get_status_history(monitor.heartbeats); + group.monitors.into_par_iter().map(|monitor| { - let status = match monitor.heartbeats.last().map(|h| h.status) { - Some(1) => MonitorStatus::Up, - Some(0) => MonitorStatus::Down, - _ => MonitorStatus::Unknown, - }; + let status_history = get_status_history(&monitor.heartbeats); - let response_time = match monitor.heartbeats.last().and_then(|h| h.ping) { - Some(ms) => format!("{}", ms), - None => t("unknown"), - }; + let status = match monitor.heartbeats.last().map(|h| h.status) { + Some(1) => MonitorStatus::Up, + Some(0) => MonitorStatus::Down, + _ => MonitorStatus::Unknown, + }; - let uptime_24h = match monitor.uptime_data.map(|u| u.get_perc_formated()) { - Some(perc) => perc, - None => t("unknown"), - }; + let response_time = monitor + .heartbeats + .last() + .and_then(|h| h.ping) + .map(|ms| format!("{}", ms)) + .unwrap_or_else(|| t("unknown").to_string() + " "); - let name: Cow<'static, str> = match monitor.name { - Cow::Borrowed(borrowed) => Cow::Owned(borrowed.to_string()), - Cow::Owned(owned) => Cow::Owned(owned), - }; + let uptime_24h = monitor + .uptime_data + .map(|u| u.get_perc_formated()) + .unwrap_or_else(|| t("unknown").to_string()); - MonitorViewState { - name, - status, - response_time, - uptime_24h, - status_history, - } - }) - .collect_into_vec(&mut monitors); + MonitorViewState { + name: monitor.name, + status, + response_time, + uptime_24h, + status_history, + } + }).collect_into_vec(&mut monitors); monitors.sort_by_key(|m| m.name.clone());