Repository: Abdenasser/neohtop Branch: main Commit: dc22a9f475b9 Files: 95 Total size: 276.9 KB Directory structure: gitextract_7hzxi6z1/ ├── .github/ │ ├── CODEOWNERS │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature_request.md │ ├── pull_request_template.md │ └── workflows/ │ ├── build-check.yml │ ├── format-check.yml │ ├── linux-aarch64-nightly.yml │ ├── linux-x86_64-nightly.yml │ ├── macos-nightly.yml │ ├── test-release.yml │ └── windows-nightly.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .prettierrc ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── LICENSE ├── README.md ├── docs/ │ ├── index.html │ ├── main.js │ └── styles.css ├── jsconfig.json ├── package.json ├── src/ │ ├── App.svelte │ ├── app.css │ ├── app.html │ ├── lib/ │ │ ├── components/ │ │ │ ├── AppInfo.svelte │ │ │ ├── ThemeSwitcher.svelte │ │ │ ├── TitleBar.svelte │ │ │ ├── index.ts │ │ │ ├── modals/ │ │ │ │ ├── KillProcessModal.svelte │ │ │ │ ├── Modal.svelte │ │ │ │ ├── ProcessDetailsModal.svelte │ │ │ │ └── index.ts │ │ │ ├── process/ │ │ │ │ ├── ActionButtons.svelte │ │ │ │ ├── ProcessIcon.svelte │ │ │ │ ├── ProcessRow.svelte │ │ │ │ ├── ProcessTable.svelte │ │ │ │ ├── TableHeader.svelte │ │ │ │ └── index.ts │ │ │ ├── stats/ │ │ │ │ ├── CpuPanel.svelte │ │ │ │ ├── MemoryPanel.svelte │ │ │ │ ├── NetworkPanel.svelte │ │ │ │ ├── PanelHeader.svelte │ │ │ │ ├── ProgressBar.svelte │ │ │ │ ├── StatItem.svelte │ │ │ │ ├── StatPanel.svelte │ │ │ │ ├── StatsBar.svelte │ │ │ │ ├── StoragePanel.svelte │ │ │ │ ├── SystemPanel.svelte │ │ │ │ └── index.ts │ │ │ └── toolbar/ │ │ │ ├── ColumnToggle.svelte │ │ │ ├── FilterToggle.svelte │ │ │ ├── PaginationControls.svelte │ │ │ ├── RefreshControls.svelte │ │ │ ├── SearchBox.svelte │ │ │ ├── StatusFilter.svelte │ │ │ ├── ToolBar.svelte │ │ │ └── index.ts │ │ ├── constants/ │ │ │ └── index.ts │ │ ├── definitions/ │ │ │ ├── columns.ts │ │ │ ├── index.ts │ │ │ ├── settings.ts │ │ │ └── themes.ts │ │ ├── stores/ │ │ │ ├── index.ts │ │ │ ├── overlay.ts │ │ │ ├── processes.ts │ │ │ ├── settings.ts │ │ │ └── theme.ts │ │ ├── types/ │ │ │ └── index.ts │ │ └── utils/ │ │ └── index.ts │ └── routes/ │ ├── +layout.js │ ├── +layout.svelte │ └── +page.svelte ├── src-tauri/ │ ├── .cargo/ │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ ├── capabilities/ │ │ └── default.json │ ├── icons/ │ │ └── icon.icns │ ├── src/ │ │ ├── commands.rs │ │ ├── main.rs │ │ ├── monitoring/ │ │ │ ├── mod.rs │ │ │ ├── process_monitor.rs │ │ │ ├── system_monitor.rs │ │ │ └── types.rs │ │ ├── state.rs │ │ └── ui/ │ │ ├── mod.rs │ │ └── window.rs │ └── tauri.conf.json ├── svelte.config.js └── vite.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CODEOWNERS ================================================ * @Abdenasser ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing to NeoHtop Thank you for considering contributing to NeoHtop! We welcome contributions from the community. ## How to Contribute 1. Fork the repository. 2. Create a new branch (`git checkout -b feature/YourFeature`). 3. Make your changes. 4. Commit your changes (`git commit -m 'Add some feature'`). 5. Push to the branch (`git push origin feature/YourFeature`). 6. Open a pull request. ## Code of Conduct Please note that this project is released with a [Contributor Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/). By participating in this project you agree to abide by its terms. ## Reporting Bugs Please use the [bug report template](./ISSUE_TEMPLATE/bug_report.md) to report any bugs you find. ## Requesting Features Please use the [feature request template](./ISSUE_TEMPLATE/feature_request.md) to suggest new features. ================================================ FILE: .github/FUNDING.yml ================================================ github: abdenasser ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. macOS] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Support url: https://github.com/Abdenasser/neohtop/discussions about: Please use discussions for questions and support. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/pull_request_template.md ================================================ ## Description Please include a summary of the changes and the related issue. Please also include relevant motivation and context. Fixes # (issue) ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. - [ ] Test A - [ ] Test B ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules ================================================ FILE: .github/workflows/build-check.yml ================================================ name: Build Check on: pull_request: branches: [main] paths: - "src-tauri/**" - ".github/workflows/**" env: CARGO_TERM_COLOR: always CARGO_INCREMENTAL: 1 CARGO_NET_RETRY: 10 RUSTUP_MAX_RETRIES: 10 RUST_BACKTRACE: 1 RUSTC_WRAPPER: sccache CARGO_BUILD_JOBS: 2 jobs: build: name: Build Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "lts/*" cache: "npm" - name: Cache Linux Dependencies id: cache-apt uses: actions/cache@v3 with: path: | /var/cache/apt/archives/*.deb /var/lib/apt/lists/* key: ${{ runner.os }}-apt-${{ hashFiles('**/package.json', '**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-apt- - name: Add Ubuntu Jammy repo for WebKitGTK 4.0 run: | echo "deb http://archive.ubuntu.com/ubuntu jammy main universe" | sudo tee -a /etc/apt/sources.list sudo apt update - name: Install Linux Dependencies run: | sudo rm -rf /var/cache/apt/archives/lock sudo rm -rf /var/cache/apt/archives/partial sudo rm -rf /var/lib/apt/lists/lock sudo rm -rf /var/lib/apt/lists/partial sudo apt-get update sudo apt-get install --no-install-recommends -y \ build-essential \ pkg-config \ libgtk-3-dev \ libayatana-appindicator3-dev \ librsvg2-dev \ libglib2.0-dev \ libjavascriptcoregtk-4.0-dev \ libsoup-3.0-dev \ libwebkit2gtk-4.1-dev - name: Remove Jammy repo run: | sudo sed -i '/jammy main universe/d' /etc/apt/sources.list sudo apt update - name: Install Rust uses: dtolnay/rust-toolchain@stable with: components: cargo target: x86_64-unknown-linux-gnu - name: Install sccache run: | SCCACHE_VERSION=v0.7.7 curl -L "https://github.com/mozilla/sccache/releases/download/${SCCACHE_VERSION}/sccache-${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" | tar xz sudo mv sccache-*/sccache /usr/local/bin/sccache echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV - uses: Swatinem/rust-cache@v2 with: workspaces: "./src-tauri -> target" shared-key: "build" - name: Install Dependencies run: npm ci - name: Build Application run: | npm run tauri build -- \ --target x86_64-unknown-linux-gnu \ --bundles deb \ --ci ================================================ FILE: .github/workflows/format-check.yml ================================================ name: Format Check on: pull_request: branches: [ main ] jobs: format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 'lts/*' - name: Setup Rust uses: dtolnay/rust-toolchain@stable - name: Install dependencies run: npm ci - name: Check formatting run: npm run format:check ================================================ FILE: .github/workflows/linux-aarch64-nightly.yml ================================================ name: Linux (aarch64) Nightly Build on: workflow_dispatch: inputs: release_upload_url: description: "Release upload URL" required: true env: CARGO_TERM_COLOR: always PKG_CONFIG_ALLOW_CROSS: 1 PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig PKG_CONFIG: /usr/bin/aarch64-linux-gnu-pkg-config jobs: build: name: Build Linux aarch64 Packages runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "lts/*" cache: "npm" - name: Install Rust uses: dtolnay/rust-toolchain@stable with: targets: aarch64-unknown-linux-gnu - name: Configure ARM64 repositories run: | sudo dpkg --add-architecture arm64 # Remove all existing sources sudo rm -rf /etc/apt/sources.list.d/* sudo truncate -s 0 /etc/apt/sources.list # Add only ports.ubuntu.com repository sudo tee /etc/apt/sources.list << EOF deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse deb [arch=amd64] http://azure.archive.ubuntu.com/ubuntu jammy main restricted universe multiverse deb [arch=amd64] http://azure.archive.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse deb [arch=amd64] http://azure.archive.ubuntu.com/ubuntu jammy-security main restricted universe multiverse EOF sudo apt-get update # Install required packages including cross-compilation tools sudo apt-get install -y \ build-essential \ pkg-config \ crossbuild-essential-arm64 \ gcc-aarch64-linux-gnu \ g++-aarch64-linux-gnu \ libgtk-3-dev:arm64 \ libayatana-appindicator3-dev:arm64 \ librsvg2-dev:arm64 \ libglib2.0-dev:arm64 \ libjavascriptcoregtk-4.0-dev:arm64 \ libsoup-3.0-dev:arm64 \ libwebkit2gtk-4.1-dev:arm64 \ libssl-dev:arm64 \ libssl-dev \ openssl:arm64 # Configure pkg-config for cross-compilation echo "PKG_CONFIG=/usr/bin/aarch64-linux-gnu-pkg-config" >> $GITHUB_ENV echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV - name: Install Dependencies run: npm install - name: Setup cross-compilation environment run: | sudo apt-get install -y \ crossbuild-essential-arm64 \ pkg-config \ libssl-dev:arm64 \ libssl-dev \ openssl:arm64 \ file \ desktop-file-utils \ libfuse2 \ qemu-user-static # Setup pkg-config sudo tee /usr/bin/aarch64-linux-gnu-pkg-config << 'EOF' #!/bin/sh export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig exec pkg-config "$@" EOF sudo chmod +x /usr/bin/aarch64-linux-gnu-pkg-config # Create .cargo/config mkdir -p .cargo cat > .cargo/config << EOF [target.aarch64-unknown-linux-gnu] linker = "aarch64-linux-gnu-gcc" ar = "aarch64-linux-gnu-ar" EOF # Download and setup appimagetool for ARM64 wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage chmod +x appimagetool-aarch64.AppImage sudo mv appimagetool-aarch64.AppImage /usr/local/bin/appimagetool # Set environment variables echo "PKG_CONFIG=/usr/bin/aarch64-linux-gnu-pkg-config" >> $GITHUB_ENV echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV echo "OPENSSL_DIR=/usr" >> $GITHUB_ENV echo "OPENSSL_INCLUDE_DIR=/usr/include/aarch64-linux-gnu" >> $GITHUB_ENV echo "OPENSSL_LIB_DIR=/usr/lib/aarch64-linux-gnu" >> $GITHUB_ENV echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV echo "APPIMAGE_EXTRACT_AND_RUN=1" >> $GITHUB_ENV - name: Build Frontend run: npm run build - name: Build AppImage run: | echo "Building AppImage for aarch64..." npm run tauri build -- --target aarch64-unknown-linux-gnu --bundles appimage cd src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/appimage/ for f in *.AppImage; do echo "AARCH64_APPIMAGE_PATH=src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/appimage/$f" >> $GITHUB_ENV done - name: Build Debian Package run: | echo "Building Debian package for aarch64..." npm run tauri build -- --target aarch64-unknown-linux-gnu --bundles deb cd src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/deb/ for f in *.deb; do echo "AARCH64_DEB_PATH=src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/deb/$f" >> $GITHUB_ENV done - name: Build RPM Package run: | echo "Building RPM package for aarch64..." npm run tauri build -- --target aarch64-unknown-linux-gnu --bundles rpm cd src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/rpm/ for f in *.rpm; do echo "AARCH64_RPM_PATH=src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/rpm/$f" >> $GITHUB_ENV done - name: Get version from package.json id: version run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - name: Upload AppImage to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.AARCH64_APPIMAGE_PATH }} asset_name: NeoHtop_${{ steps.version.outputs.version }}_aarch64.AppImage asset_content_type: application/x-executable - name: Upload Debian Package to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.AARCH64_DEB_PATH }} asset_name: NeoHtop_${{ steps.version.outputs.version }}_aarch64.deb asset_content_type: application/vnd.debian.binary-package - name: Upload RPM Package to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.AARCH64_RPM_PATH }} asset_name: NeoHtop_${{ steps.version.outputs.version }}_aarch64.rpm asset_content_type: application/x-rpm ================================================ FILE: .github/workflows/linux-x86_64-nightly.yml ================================================ name: Linux (x86_64) Nightly Build on: workflow_dispatch: inputs: release_upload_url: description: "Release upload URL" required: true env: CARGO_TERM_COLOR: always jobs: build: name: Build Linux Packages runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "lts/*" cache: "npm" - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Add Ubuntu Jammy repo for WebKitGTK 4.0 run: | echo "deb http://archive.ubuntu.com/ubuntu jammy main universe" | sudo tee -a /etc/apt/sources.list sudo apt update - name: Install Linux Dependencies run: | sudo apt-get update sudo apt-get install -y \ build-essential \ pkg-config \ libgtk-3-dev \ libayatana-appindicator3-dev \ librsvg2-dev \ libglib2.0-dev \ libjavascriptcoregtk-4.0-dev \ libsoup-3.0-dev \ libwebkit2gtk-4.1-dev - name: Remove Jammy repo run: | sudo sed -i '/jammy main universe/d' /etc/apt/sources.list sudo apt update - name: Install Dependencies run: | npm install - name: Build Frontend run: npm run build - name: Build AppImage (x86_64) run: | echo "Building AppImage for x86_64..." npm run tauri build -- --target x86_64-unknown-linux-gnu --bundles appimage cd src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/ for f in *.AppImage; do echo "APPIMAGE_PATH=src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/$f" >> $GITHUB_ENV done - name: Build Debian Package (x86_64) run: | echo "Building Debian package for x86_64..." npm run tauri build -- --target x86_64-unknown-linux-gnu --bundles deb cd src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/ for f in *.deb; do echo "DEB_PATH=src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/$f" >> $GITHUB_ENV done - name: Build RPM Package (x86_64) run: | echo "Building RPM package for x86_64..." npm run tauri build -- --target x86_64-unknown-linux-gnu --bundles rpm cd src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/ for f in *.rpm; do echo "RPM_PATH=src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/$f" >> $GITHUB_ENV done - name: Get version from package.json id: version run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - name: Upload AppImage to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.APPIMAGE_PATH }} asset_name: NeoHtop_${{ steps.version.outputs.version }}_x86_64.AppImage asset_content_type: application/x-executable - name: Upload Debian Package to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.DEB_PATH }} asset_name: NeoHtop_${{ steps.version.outputs.version }}_x86_64.deb asset_content_type: application/vnd.debian.binary-package - name: Upload RPM Package to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.RPM_PATH }} asset_name: NeoHtop_${{ steps.version.outputs.version }}_x86_64.rpm asset_content_type: application/x-rpm ================================================ FILE: .github/workflows/macos-nightly.yml ================================================ name: MacOS (Intel/Apple Silicon) Nightly Build on: workflow_dispatch: inputs: release_upload_url: description: 'Release upload URL' required: true env: CARGO_TERM_COLOR: always jobs: build: name: Build MacOS Apps runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 'lts/*' cache: 'npm' - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Dependencies run: | rustup target add x86_64-apple-darwin rustup target add aarch64-apple-darwin npm install - name: Set up keychain run: | security create-keychain -p "" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "" build.keychain echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/certificate.p12 security import /tmp/certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain env: MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} - name: Build Frontend run: npm run build - name: Build for Intel Mac run: | echo "Building for Intel Mac..." npm run tauri build -- --target x86_64-apple-darwin --bundles dmg --config "{\"bundle\":{\"macOS\":{\"signingIdentity\": \"Developer ID Application: Abdenasser Elidrissi (785JV74B9Y)\"}}}" # Rename the Intel build and store the filename cd src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/ for f in *.dmg; do mv "$f" "intel-$f" echo "INTEL_DMG_PATH=src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/intel-$f" >> $GITHUB_ENV done env: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Build for Apple Silicon run: | echo "Building for aarch64..." npm run tauri build -- --target aarch64-apple-darwin --bundles dmg --config "{\"bundle\":{\"macOS\":{\"signingIdentity\": \"Developer ID Application: Abdenasser Elidrissi (785JV74B9Y)\"}}}" # Rename the Apple Silicon build and store the filename cd src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/ for f in *.dmg; do mv "$f" "silicon-$f" echo "SILICON_DMG_PATH=src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/silicon-$f" >> $GITHUB_ENV done env: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Get version from package.json id: version run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - name: Upload Intel Build to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.INTEL_DMG_PATH }} asset_name: intel-NeoHtop_${{ steps.version.outputs.version }}_x64.dmg asset_content_type: application/x-apple-diskimage - name: Upload Silicon Build to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.SILICON_DMG_PATH }} asset_name: silicon-NeoHtop_${{ steps.version.outputs.version }}_aarch64.dmg asset_content_type: application/x-apple-diskimage ================================================ FILE: .github/workflows/test-release.yml ================================================ name: Test Release Build on: workflow_dispatch: # Manual trigger jobs: create-draft: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Get version from package.json id: version run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - name: Create Draft Release id: create_release uses: softprops/action-gh-release@v1 with: name: "NeoHtop v${{ steps.version.outputs.version }}" tag_name: "v${{ steps.version.outputs.version }}" draft: true env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} - name: Trigger MacOS Build uses: benc-uk/workflow-dispatch@v1 with: workflow: macos-nightly.yml token: ${{ secrets.PAT_TOKEN }} inputs: '{"release_upload_url": "${{ steps.create_release.outputs.upload_url }}"}' - name: Trigger Windows Build uses: benc-uk/workflow-dispatch@v1 with: workflow: windows-nightly.yml token: ${{ secrets.PAT_TOKEN }} inputs: '{"release_upload_url": "${{ steps.create_release.outputs.upload_url }}"}' - name: Trigger Linux x86_64 Build uses: benc-uk/workflow-dispatch@v1 with: workflow: linux-x86_64-nightly.yml token: ${{ secrets.PAT_TOKEN }} inputs: '{"release_upload_url": "${{ steps.create_release.outputs.upload_url }}"}' ================================================ FILE: .github/workflows/windows-nightly.yml ================================================ name: Windows (x86_64) Nightly Build on: workflow_dispatch: inputs: release_upload_url: description: 'Release upload URL' required: true env: CARGO_TERM_COLOR: always jobs: build: name: Build Windows Executable runs-on: windows-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 'lts/*' cache: 'npm' - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install WebView2 run: | $WebView2InstallPath = "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" Invoke-WebRequest "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile $WebView2InstallPath Start-Process -FilePath $WebView2InstallPath -Args "/silent /install" -Wait - name: Install Dependencies run: | npm install - name: Build Frontend run: npm run build - name: Build Windows Executable shell: bash # Force using bash shell for consistent environment variable setting run: | echo "Building Windows executable..." npm run tauri build echo "WIN_EXE_PATH=src-tauri/target/release/NeoHtop.exe" >> $GITHUB_ENV - name: Get version from package.json id: version shell: bash # Force using bash shell run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - name: Upload to Release if: github.event.inputs.release_upload_url != '' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: upload_url: ${{ github.event.inputs.release_upload_url }} asset_path: ${{ env.WIN_EXE_PATH }} asset_name: NeoHtop_${{ steps.version.outputs.version }}_x64.exe asset_content_type: application/vnd.microsoft.portable-executable ================================================ FILE: .gitignore ================================================ .DS_Store node_modules /build /.svelte-kit /package .env .env.* !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* ## Jetbrains .idea/ .run/ ================================================ FILE: .husky/pre-commit ================================================ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npm exec lint-staged ================================================ FILE: .prettierrc ================================================ { "plugins": ["prettier-plugin-svelte"], "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] } ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "svelte.svelte-vscode", "tauri-apps.tauri-vscode", "rust-lang.rust-analyzer" ] } ================================================ FILE: .vscode/settings.json ================================================ { "svelte.enable-ts-plugin": true } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2024 Abdenasser 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. ================================================ FILE: README.md ================================================
NeoHtop Logo

NeoHtop

A modern, cross-platform system monitor built on top of Svelte, Rust, and Tauri.

[![License](https://img.shields.io/github/license/Abdenasser/neohtop)](https://github.com/Abdenasser/neohtop/blob/main/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/Abdenasser/neohtop)](https://github.com/Abdenasser/neohtop/stargazers) [![GitHub issues](https://img.shields.io/github/issues/Abdenasser/neohtop)](https://github.com/Abdenasser/neohtop/issues) [![GitHub release](https://img.shields.io/github/v/release/Abdenasser/neohtop)](https://github.com/Abdenasser/neohtop/releases) [![Notarized by Apple](https://img.shields.io/badge/Release_Notarized_by_Apple-000000?style=flat-square&logo=apple&logoColor=white)](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution)
NeoHtop Screenshot

If you find this project helpful, consider buying me a coffee:

Buy Me A Coffee

Or sponsor me on GitHub:

Sponsor @abdenasser
## Table of Contents - [Why NeoHtop?](#why-neohtop) - [Features](#features) - [Tech Stack](#tech-stack) - [Getting Started](#getting-started) - [Prerequisites](#prerequisites) - [Installation](#installation) - [Running with Sudo](#running-with-sudo) - [Development](#development) - [Setup](#setup) - [Code Formatting](#code-formatting) - [Pull Requests](#pull-requests) - [Contributing](#contributing) - [License](#license) ## Why NeoHtop? [Read about the back story and motivation behind NeoHtop](https://www.abdenasser.com/2024/11/06/oh-boy-neohtop/) ## Features - 🚀 Real-time process monitoring - 💻 CPU and Memory usage tracking - 🎨 Beautiful, modern UI with dark/light themes - 🔍 Advanced process search and filtering - 📌 Pin important processes - 🛠 Process management (kill processes) - 🎯 Sort by any column - 🔄 Auto-refresh system stats ### Search Functionality Search for processes by name, command, or PID. Use commas to search for multiple terms simultaneously. Regular expressions are supported for advanced filtering. Examples: - `arm, x86`: Returns processes with "arm" or "x86" in the name or command - `d$`: Lists daemons (processes ending with 'd') - `^(\w+\.)+\w+$`: Shows processes with reverse domain name notation (e.g., com.docker.vmnetd) ## Tech Stack - **Frontend**: SvelteKit, TypeScript - **Backend**: Rust, Tauri - **Styling**: CSS Variables for theming - **Icons**: FontAwesome ## Getting Started ### Prerequisites - Node.js (v16 or later) - Rust (latest stable) - Xcode Command Line Tools (for macOS) ### Installation #### Manual Download the latest release from the [releases page](https://github.com/Abdenasser/neohtop/releases). #### Package Managers Members of the community have kindly published unofficial packages for various platforms and package managers. Please note, these packages are community-maintained and not officially released, reviewed, or endorsed by NeoHtop. We only provide official builds through the GitHub Releases page. Since these external packages are managed by third parties, we cannot guarantee their security, integrity, or update frequency. Please use them at your own discretion. ##### macOS Using [Homebrew](https://brew.sh/). ```bash brew install --cask neohtop ``` ##### Arch Linux (AUR) Using the [AUR](https://aur.archlinux.org/) and [an AUR helper](https://wiki.archlinux.org/title/AUR_helpers). ```bash yay -S neohtop ``` or ```bash paru -S neohtop ``` ##### Fedora Linux Install the [Terra repository](https://terra.fyralabs.com/). ```bash dnf install neohtop ``` ##### Windows Install the [Scoop repository](https://scoop.sh/), then make sure you have the Scoop extras bucket added: ```bash scoop bucket add extras ``` Then install with: ```bash scoop install extras/neohtop ``` ##### Solus ```bash eopkg install neohtop ``` ### Running with Sudo Some processes require monitoring with sudo privileges. To monitor these processes, launch NeoHtop with sudo: - macOS: `sudo /Applications/NeoHtop.app/Contents/MacOS/NeoHtop` - Linux: `pkexec /path/to/neohtop` (recommended) ## Development ### Setup ```bash # Install dependencies npm install # Run in development mode npm run tauri dev # Build for production npm run tauri build ``` ### Code Formatting We use Prettier for web code and `cargo fmt` for Rust code. ```bash # Format all files npm run format # Check formatting without making changes npm run format:check ``` ### Pull Requests Before submitting a PR, ensure: 1. All code is formatted (`npm run format`) 2. The format check passes (`npm run format:check`) 3. Your commits follow the project's commit message conventions ## Contributing We welcome contributions! Please see our [contributing guidelines](./.github/CONTRIBUTING.md) for more information. ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ================================================ FILE: docs/index.html ================================================ NeoHtop - Blazing-fast system monitoring for your desktop (built with Rust, Tauri & Svelte)

Monitor Your System
With Style

A beautiful, lightning-fast cross-platform system monitor.

NeoHtop Interface light NeoHtop Interface dark

Why Choose NeoHtop?

🚀

Real-time Monitoring

Track system processes in real-time with minimal resource usage

🎨

Modern UI

Beautiful interface with automatic dark/light theme detection

🔍

Smart Search

Quick process search with advanced filtering options

📌

Process Pinning

Keep important processes in view for easy monitoring

⚡️

Resource Efficient

Built with Rust for optimal performance and low memory usage

🛠

Process Management

View and manage processes with detailed information

Installation Guide

1

Download

Choose and download the appropriate version for your Mac

2

Open DMG

Double-click the downloaded .dmg file

3

Install

Drag NeoHtop to your Applications folder

4

First Launch

Right-click and choose Open to bypass Gatekeeper

What People Are Saying

Frequently Asked Questions

NeoHtop offers a modern interface with additional features like process pinning, smart search, and themes while maintaining high performance through its Rust backend. It's designed to be more user-friendly and efficient than traditional system monitors.

No, NeoHtop is built with Rust and optimized for minimal resource usage, typically using less than 1% CPU and minimal memory. It's designed to be lightweight while monitoring your system.

NeoHtop supports macOS 10.15 (Catalina) and newer versions. It's optimized for both Intel and Apple Silicon Macs, with native support for both architectures.

Yes, NeoHtop offers various customization options including:

  • Dark/Light theme switching
  • Customizable columns and metrics
  • Adjustable refresh rates
  • Process grouping options

Yes, NeoHtop is completely open source and available on GitHub. You can contribute to the project, report issues, or suggest new features through our GitHub repository.

================================================ FILE: docs/main.js ================================================ // =============================== // Theme Management // =============================== const themeToggle = document.getElementById('themeToggle'); const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); function setTheme(isDark) { document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light'); themeToggle.querySelector('.theme-icon').textContent = isDark ? '☀️' : '🌙'; localStorage.setItem('theme', isDark ? 'dark' : 'light'); } // Initialize theme const savedTheme = localStorage.getItem('theme'); if (savedTheme) { setTheme(savedTheme === 'dark'); } else { setTheme(prefersDark.matches); } // Theme event listeners themeToggle.addEventListener('click', () => { const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; setTheme(!isDark); }); prefersDark.addEventListener('change', (e) => { if (!localStorage.getItem('theme')) { setTheme(e.matches); } }); // =============================== // Version and Download Management // =============================== async function fetchDownloadStats() { try { const releasesResponse = await fetch('https://api.github.com/repos/abdenasser/neohtop/releases'); const releases = await releasesResponse.json(); const githubDownloads = releases.reduce((total, release) => { const releaseDownloads = release.assets.reduce((sum, asset) => sum + asset.download_count, 0); return total + releaseDownloads; }, 0); const brewResponse = await fetch('https://formulae.brew.sh/api/analytics/install/homebrew-core/365d.json'); const brewData = await brewResponse.json(); const brewInstalls = brewData.formulae?.neohtop?.[0]?.count || 0; const totalDownloads = githubDownloads + brewInstalls; document.getElementById('download-count').textContent = new Intl.NumberFormat().format(totalDownloads); } catch (error) { console.error('Failed to fetch download stats:', error); document.getElementById('download-count').textContent = 'N/A'; } } async function updateVersion() { try { const response = await fetch('https://api.github.com/repos/Abdenasser/neohtop/releases/latest'); const data = await response.json(); const version = data.tag_name; const versionNumber = version.match(/\d+\.\d+\.\d+/)?.[0]; if (versionNumber) { document.getElementById('current-version').textContent = "v" + versionNumber; updateDownloadLinks(versionNumber); } } catch (error) { console.error('Failed to fetch version:', error); } } function updateDownloadLinks(versionNumber) { const platformUrls = { 'macos-intel': `intel-NeoHtop_${versionNumber}_x64.dmg`, 'macos-silicon': `silicon-NeoHtop_${versionNumber}_aarch64.dmg`, 'windows': `NeoHtop_${versionNumber}_x64.exe`, 'linux-deb-x64': `NeoHtop_${versionNumber}_x86_64.deb`, 'linux-appimage-x64': `NeoHtop_${versionNumber}_x86_64.AppImage`, 'linux-rpm-x64': `NeoHtop_${versionNumber}_x86_64.rpm`, 'linux-deb-arm64': `NeoHtop_${versionNumber}_aarch64.deb`, 'linux-appimage-arm64': `NeoHtop_${versionNumber}_aarch64.AppImage`, 'linux-rpm-arm64': `NeoHtop_${versionNumber}_aarch64.rpm` }; document.querySelectorAll('.download-button').forEach(link => { const platform = link.getAttribute('data-type'); if (platformUrls[platform]) { link.href = `https://github.com/Abdenasser/neohtop/releases/download/v${versionNumber}/${platformUrls[platform]}`; } }); } // =============================== // UI Interactions // =============================== // FAQ Accordion document.querySelectorAll('.faq-question').forEach(button => { button.addEventListener('click', () => { const faqItem = button.parentElement; const isActive = faqItem.classList.contains('active'); document.querySelectorAll('.faq-item').forEach(item => item.classList.remove('active')); if (!isActive) faqItem.classList.add('active'); }); }); // Download tracking document.querySelectorAll('.download-button').forEach(button => { button.addEventListener('click', (e) => { gtag('event', 'download', { 'event_category': 'App', 'event_label': button.getAttribute('data-type'), 'value': button.getAttribute('data-version') }); }); }); // Smooth scroll document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // =============================== // Animations // =============================== const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, { threshold: 0.1 } ); document.querySelectorAll('.feature, .step, .download-button').forEach(el => { observer.observe(el); }); // =============================== // Initialization // =============================== document.addEventListener('DOMContentLoaded', () => { updateVersion(); fetchDownloadStats(); }); // =============================== // Mobile Navigation // =============================== document.addEventListener('DOMContentLoaded', () => { const menuButton = document.querySelector('.menu-button'); const navLinks = document.querySelector('.nav-links'); menuButton.addEventListener('click', () => { navLinks.classList.toggle('active'); }); // Close menu when clicking outside document.addEventListener('click', (e) => { if (!navLinks.contains(e.target) && !menuButton.contains(e.target)) { navLinks.classList.remove('active'); } }); }); ================================================ FILE: docs/styles.css ================================================ :root { --primary-color: #6366f1; --secondary-color: #818cf8; --background: #ffffff; --text-primary: #1f2937; --text-secondary: #4b5563; --card-background: rgba(255, 255, 255, 0.8); } [data-theme="dark"] { --background: #0f172a; --text-primary: #f1f5f9; --text-secondary: #cbd5e1; --card-background: rgba(30, 41, 59, 0.8); } * { margin: 0; padding: 0; box-sizing: border-box; scroll-behavior: smooth; } body { font-family: 'Inter', system-ui, sans-serif; background: var(--background); color: var(--text-primary); line-height: 1.6; transition: background-color 0.3s, color 0.3s; margin: 0; padding: 0; min-height: 100vh; width: 100%; } /* Header & Navigation */ header { background: var(--card); position: sticky; top: 0; z-index: 100; backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); } nav { max-width: 1200px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; } .logo-container { display: flex; align-items: center; gap: 0.75rem; } .nav-logo { width: 32px; /* Adjust size as needed */ height: 32px; object-fit: contain; } .nav-brand { font-size: 1.25rem; font-weight: 600; color: var(--text-primary); } .nav-links { display: flex; align-items: center; gap: 2rem; } .nav-links a { color: var(--text-primary); text-decoration: none; font-weight: 500; transition: color 0.2s; } .nav-links a:hover { color: var(--primary-color); } .theme-toggle { background: transparent; border: none; cursor: pointer; padding: 0.5rem; color: var(--text-primary); } /* Theme Toggle */ .theme-toggle { background: none; border: none; color: var(--text); cursor: pointer; padding: 0.5rem; border-radius: 50%; transition: background-color 0.2s; } .theme-toggle:hover { background: var(--hover); } /* Main Content */ main { max-width: 1200px; margin: 0 auto; padding: 2rem; padding-top: 64px; min-height: 100vh; display: flex; flex-direction: column; width: 100%; /* Should match the height of your nav */ } /* Sections */ section { margin: 6rem 0; scroll-margin-top: 5rem; } /* Hero Section */ .hero { height: 600px; width: 100%; display: flex; align-items: center; overflow: hidden; } .hero-content { flex: 0 0 50%; padding: 2rem; z-index: 2; position: relative; } .hero-content::before { content: ''; position: absolute; top: -50px; left: -50px; width: calc(100% + 100px); height: calc(100% + 100px); background: linear-gradient(to right, var(--background) 0%, var(--background) 60%, transparent 100%); z-index: -1; } .hero-background { flex: 0 0 90%; height: 100%; display: flex; align-items: center; justify-content: flex-end; margin-right: -40%; padding-right: 40%; } .hero-background img { max-width: 200%; max-height: 200%; width: auto; height: 100%; object-fit: contain; } .hero-background img[src*="screenshot.png"] { display: none; } .hero-background img[src*="screenshot-light.png"] { display: block; } /* Dark mode */ [data-theme="dark"] .hero-background img[src*="screenshot.png"] { display: block; } [data-theme="dark"] .hero-background img[src*="screenshot-light.png"] { display: none; } .gradient-text { font-size: 3rem; font-weight: 800; line-height: 1.2; margin-bottom: 1rem; } .hero-subtitle { font-size: 1.125rem; color: var(--text-secondary); margin-bottom: 1.5rem; } .hero-cta { display: flex; gap: 1rem; margin-bottom: 1.5rem; } .hero-image { width: 100%; } .floating-screenshot { width: 100%; height: auto; max-width: 600px; border-radius: 12px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); animation: float 6s ease-in-out infinite; } /* Responsive design */ @media (max-width: 1024px) { .hero { flex-direction: column; height: auto; min-height: auto; } .hero-content { flex: 0 0 100%; width: 100%; padding: 3rem 2rem; text-align: center; } .hero-background { flex: 0 0 100%; margin-right: 0; padding-right: 0; } .hero-background img { width: 100%; height: auto; max-height: 400px; object-fit: contain; } .hero-content::before { display: none; /* Remove the gradient since content and image are now stacked */ } .hero-cta { justify-content: center; } .tech-stack { justify-content: center; } } @media (max-width: 640px) { .hero { padding: 2rem 1rem; } .hero-content { padding: 2rem 1rem; } .gradient-text { font-size: 2.5rem; } .hero-cta { flex-direction: column; } } .logo { width: 120px; height: 120px; margin-bottom: 1rem; transition: transform 0.3s; } .logo:hover { transform: scale(1.05); } .badges { display: flex; gap: 0.5rem; justify-content: center; /* Centered only on tablets and smaller screens */ @media (min-width: 1024px) { justify-content: flex-start; /* Align to the start on larger screens */ } margin-top: 1rem; } .badge { background: var(--primary); color: white; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.875rem; } /* Features */ .feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; margin-top: 2rem; } .feature { background: var(--card); padding: 2rem; border-radius: 12px; transition: transform 0.2s; border: 1px solid var(--border); } .feature:hover { transform: translateY(-4px); } /* Download Section */ .download-options { display: flex; gap: 1.5rem; justify-content: center; margin: 2rem 0; flex-wrap: wrap; padding: 0 1rem; } .download-button { background: var(--primary-color); color: white; padding: 1rem 1.5rem; border-radius: 8px; text-decoration: none; display: flex; align-items: center; gap: 1rem; transition: all 0.2s; min-width: 250px; } .download-button .button-text { display: flex; flex-direction: column; gap: 0.25rem; } .download-button .primary { font-weight: 600; font-size: 1rem; } .download-button .secondary { font-size: 0.85rem; opacity: 0.9; } /* New styles for nested links */ .download-button .secondary a { color: inherit; text-decoration: none; padding: 2px 6px; border-radius: 4px; transition: all 0.2s; display: inline-block; } .download-button .secondary a:hover { background-color: rgba(255, 255, 255, 0.2); transform: translateY(-1px); } .download-button:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .download-button .icon { font-size: 1.5rem; } .button-text { display: flex; flex-direction: column; } .button-text .primary { font-weight: 600; } .button-text .secondary { font-size: 0.875rem; opacity: 0.8; } /* Primary button style (can be used for other buttons too) */ .primary-button { background: var(--primary-color); color: white; padding: 0.75rem 1.5rem; border-radius: 8px; text-decoration: none; font-weight: 500; transition: all 0.2s; } .primary-button:hover { background: var(--secondary-color); transform: translateY(-2px); } /* Secondary button style */ .secondary-button { background: rgba(99, 102, 241, 0.1); color: var(--primary-color); padding: 0.75rem 1.5rem; border-radius: 8px; text-decoration: none; font-weight: 500; transition: all 0.2s; } .secondary-button:hover { background: rgba(99, 102, 241, 0.2); transform: translateY(-2px); } @media (max-width: 640px) { .download-options { flex-direction: column; align-items: stretch; } .download-button { justify-content: center; } } /* Installation Steps */ .install-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem; margin-top: 2rem; } .step { background: var(--card-background); padding: 2rem; border-radius: 12px; position: relative; border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); transition: transform 0.2s ease; } .step:hover { transform: translateY(-4px); } .step-number { position: absolute; top: -1rem; left: -1rem; background: var(--primary-color); color: white; width: 2rem; height: 2rem; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); } .step h3 { margin-bottom: 1rem; font-size: 1.25rem; font-weight: 600; color: var(--text-primary); } .step p { color: var(--text-secondary); line-height: 1.5; } /* Dark mode adjustments */ [data-theme="dark"] .step { border-color: rgba(255, 255, 255, 0.1); background: rgba(30, 41, 59, 0.8); } @media (max-width: 768px) { .install-steps { grid-template-columns: 1fr; } } /* Keyboard Shortcuts */ .shortcuts { background: var(--card); border-radius: 12px; padding: 2rem; margin-top: 2rem; border: 1px solid var(--border); } .shortcut-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem; } .shortcut { display: flex; align-items: center; justify-content: space-between; padding: 0.5rem; border-radius: 6px; } .shortcut:hover { background: var(--hover); } .key { background: var(--code-bg); padding: 0.25rem 0.5rem; border-radius: 4px; font-family: monospace; font-size: 0.875rem; } /* FAQ Section */ .faq { max-width: 800px; margin: 4rem auto; padding: 0 2rem; } .faq-list { display: flex; flex-direction: column; gap: 1rem; } .faq-item { background: var(--card-background); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; overflow: hidden; transition: all 0.3s ease; } .faq-question { width: 100%; padding: 1.5rem; display: flex; justify-content: space-between; align-items: center; background: none; border: none; cursor: pointer; text-align: left; color: var(--text-primary); font-size: 1.1rem; font-weight: 500; } .faq-icon { font-size: 1.5rem; transition: transform 0.3s ease; } .faq-item.active .faq-icon { transform: rotate(180deg); } .faq-answer { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; padding: 0 1.5rem; } .faq-item.active .faq-answer { max-height: 500px; /* Adjust based on content */ padding: 0 1.5rem 1.5rem; } .faq-answer p { color: var(--text-secondary); line-height: 1.6; } .faq-answer ul { margin-top: 0.5rem; margin-left: 1.5rem; color: var(--text-secondary); } .faq-answer li { margin-bottom: 0.5rem; } /* Dark mode adjustments */ [data-theme="dark"] .faq-item { border-color: rgba(255, 255, 255, 0.1); background: rgba(30, 41, 59, 0.8); } @media (max-width: 768px) { .faq { margin: 3rem auto; } .faq-question { padding: 1.25rem; font-size: 1rem; } } /* Footer */ footer { border-top: 1px solid var(--border); margin-top: 6rem; padding-top: 3rem; } .footer-content { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 2rem; margin-bottom: 2rem; } .footer-section { display: flex; flex-direction: column; gap: 0.5rem; } .footer-section a { color: var(--text); text-decoration: none; opacity: 0.8; } .footer-section a:hover { opacity: 1; } /* Responsive Design */ @media (max-width: 768px) { nav { flex-wrap: wrap; gap: 0.5rem; } .download-options { flex-direction: column; } .feature-grid { grid-template-columns: 1fr; } .install-steps { grid-template-columns: 1fr; } } /* Animations */ @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .feature, .step, .download-button { animation: fadeIn 0.5s ease-out; } .screenshot { width: 100%; max-width: 1200px; margin: 2rem auto; padding: 0 1rem; } .screenshot-img { width: 100%; height: auto; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .glass-nav { backdrop-filter: blur(12px); background: var(--card-background); position: fixed; width: 100%; top: 0; z-index: 1000; border-bottom: 1px solid rgba(0, 0, 0, 0.1); padding: 1rem 2rem; box-sizing: border-box; box-shadow: none; } [data-theme="dark"] .glass-nav { border-bottom: 1px solid rgba(255, 255, 255, 0.1); background: rgba(30, 41, 59, 0.8); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); } .tech-badge { background: rgba(99, 102, 241, 0.1); color: var(--primary-color); padding: 0.5rem 1rem; border-radius: 9999px; font-weight: 500; margin-right: 0.5rem; } .feature-card { background: var(--card-background); border-radius: 16px; padding: 2rem; transition: transform 0.2s; } .feature-card:hover { transform: translateY(-5px); } @keyframes float { 0% { transform: translateY(0px); } 50% { transform: translateY(-20px); } 100% { transform: translateY(0px); } } /* Features Section */ .features { padding: 4rem 2rem; } .section-title { text-align: center; margin-bottom: 3rem; font-size: 2.5rem; font-weight: 700; } .feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; max-width: 1200px; margin: 0 auto; } .feature-card { background: var(--card-background); padding: 2rem; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); transition: transform 0.2s ease; } .feature-card:hover { transform: translateY(-4px); } .feature-icon { font-size: 2.5rem; margin-bottom: 1rem; } .feature-card h3 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.75rem; color: var(--text-primary); } .feature-card p { color: var(--text-secondary); line-height: 1.5; } /* Dark mode adjustments */ [data-theme="dark"] .feature-card { border-color: rgba(255, 255, 255, 0.1); background: rgba(30, 41, 59, 0.8); } @media (max-width: 768px) { .feature-grid { grid-template-columns: 1fr; } .section-title { font-size: 2rem; } } /* Footer Styles */ .footer { background: var(--card-background); border-top: 1px solid rgba(0, 0, 0, 0.1); padding: 4rem 2rem 2rem; margin-top: 4rem; width: 100%; box-sizing: border-box; box-shadow: none; } [data-theme="dark"] .footer { background: rgba(30, 41, 59, 0.8); border-top: 1px solid rgba(255, 255, 255, 0.1); } .footer-content { max-width: 1200px; margin: 0 auto; display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 3rem; margin-bottom: 3rem; } .footer-section h3 { font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem; color: var(--text-primary); } .footer-section h4 { font-size: 1.1rem; font-weight: 600; margin-bottom: 1rem; color: var(--text-primary); } .footer-description { color: var(--text-secondary); line-height: 1.6; margin-bottom: 1rem; } .footer-links, .tech-stack-list { list-style: none; padding: 0; } .footer-links li, .tech-stack-list li { margin-bottom: 0.75rem; } .footer-links a, .tech-stack-list a { color: var(--text-secondary); text-decoration: none; display: flex; align-items: center; gap: 0.5rem; transition: color 0.2s ease; } .footer-links a:hover, .tech-stack-list a:hover { color: var(--primary-color); } .footer-bottom { max-width: 1200px; margin: 0 auto; padding-top: 2rem; border-top: 1px solid rgba(0, 0, 0, 0.1); text-align: center; color: var(--text-secondary); } [data-theme="dark"] .footer-bottom { border-top: 1px solid rgba(255, 255, 255, 0.1); } .footer-bottom p { margin-bottom: 0.5rem; } .footer-bottom a { color: var(--primary-color); text-decoration: none; } .footer-bottom a:hover { text-decoration: underline; } .heart { color: #ff4b4b; display: inline-block; animation: heartbeat 1.5s ease infinite; } .coffee { display: inline-block; animation: wiggle 1s ease infinite; } @keyframes heartbeat { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } @keyframes wiggle { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-10deg); } 75% { transform: rotate(10deg); } } /* Responsive adjustments */ @media (max-width: 768px) { .footer-content { grid-template-columns: 1fr; gap: 2rem; } .footer { padding: 3rem 1.5rem 1.5rem; } } /* Dark mode adjustments */ [data-theme="dark"] .footer { background: rgba(30, 41, 59, 0.8); border-color: rgba(255, 255, 255, 0.1); } /* Common card styles */ .feature-card, .step, .faq-item { background: var(--card-background); border-radius: 12px; border: 1px solid rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; box-shadow: none; } /* Dark mode specific adjustments */ [data-theme="dark"] .feature-card, [data-theme="dark"] .step, [data-theme="dark"] .faq-item { border-color: rgba(255, 255, 255, 0.1); background: rgba(30, 41, 59, 0.8); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2); } /* Hover states */ .feature-card:hover, .step:hover { transform: translateY(-4px); border-color: var(--primary-color); } .faq-item.active { border-color: var(--primary-color); } /* Optional: Add subtle hover effect to nav links */ .nav-links a { position: relative; padding-bottom: 2px; } .nav-links a::after { content: ''; position: absolute; bottom: 0; left: 0; width: 0; height: 2px; background: var(--primary-color); transition: width 0.3s ease; } .nav-links a:hover::after { width: 100%; } /* Add this to your CSS */ .ph-badge { display: inline-block; transition: transform 0.2s ease; } .ph-badge:hover { transform: translateY(-2px); } /* If you want to adjust for dark mode */ [data-theme="dark"] .ph-badge img { filter: invert(1); /* Optional: if you want to invert colors in dark mode */ } .coming-soon-badge { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; background: #ff6154; /* Product Hunt color */ color: white; border-radius: 8px; text-decoration: none; font-weight: 500; transition: transform 0.2s ease; } .coming-soon-badge:hover { transform: translateY(-2px); } .trust-badge { margin-top: 1.5rem; padding: 0.75rem; background: rgba(255, 255, 255, 0.1); border-radius: 8px; display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; } .verified-icon { color: #34C759; font-weight: bold; } .verified-text { color: var(--text-color); } .learn-more { color: var(--primary-color); text-decoration: none; margin-left: 0.5rem; } .learn-more:hover { text-decoration: underline; } /* Responsive adjustments */ @media (max-width: 768px) { .hero { height: 500px; } .hero-content, .hero-background { flex: 0 0 100%; } } .download-button.windows { background: #00a4ef; } .download-button.windows:hover { background: #0090d5; } .download-button.linux { background: #E95420; } .download-button.linux:hover { background: #C7431B; } .download-options-group { display: flex; flex-direction: column; gap: 0.25rem; } .download-option { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; } .download-option .format { color: rgba(255, 255, 255, 0.8); min-width: 65px; } .download-option .separator { color: rgba(255, 255, 255, 0.4); font-size: 0.7rem; } .download-option a { color: inherit; text-decoration: none; padding: 2px 6px; border-radius: 4px; transition: all 0.2s; } .download-option a:hover { background-color: rgba(255, 255, 255, 0.2); transform: translateY(-1px); } .download-button.linux .secondary { line-height: 1.6; font-size: 0.85rem; } .download-button.linux .secondary a { color: inherit; text-decoration: none; padding: 2px 6px; border-radius: 4px; transition: all 0.2s; white-space: nowrap; } .download-button.linux .secondary a:hover { background-color: rgba(255, 255, 255, 0.2); transform: translateY(-1px); } .download-button.linux .secondary .separator { opacity: 0.7; margin: 0 2px; } .downloads { padding: 2rem 0; } .download-container { max-width: 900px; margin: 0 auto; padding: 2rem; text-align: center; } .download-container h2 { font-size: 2.5rem; margin-bottom: 1rem; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .download-subtitle { color: var(--text-secondary); margin-bottom: 3rem; } .download-options { display: flex; flex-direction: column; gap: 2.5rem; } .download-group { display: flex; flex-direction: column; gap: 1rem; } .platform-title { font-size: 1.2rem; color: var(--text-secondary); text-align: left; margin-bottom: 0.5rem; } .platform-options { display: flex; gap: 1rem; justify-content: center; } .download-button { background: var(--primary-color); color: white; padding: 1rem 1.5rem; border-radius: 12px; text-decoration: none; display: flex; align-items: center; gap: 1rem; transition: all 0.2s; border: 1px solid transparent; min-width: 250px; } .download-button:hover { transform: translateY(-2px); background: var(--secondary-color); box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2); } .download-button .icon { font-size: 1.5rem; } .button-text { display: flex; flex-direction: column; gap: 0.25rem; text-align: left; } .button-text .primary { font-weight: 600; font-size: 1rem; } .button-text .secondary { font-size: 0.85rem; opacity: 0.9; } .linux-options { display: flex; flex-direction: column; gap: 1.5rem; } .format-group { display: flex; flex-direction: column; gap: 0.5rem; } .format-label { font-size: 0.9rem; color: var(--text-secondary); text-align: left; } .format-buttons { display: flex; gap: 1rem; justify-content: center; } .format-buttons .download-button { min-width: 150px; } /* Dark mode adjustments */ [data-theme="dark"] .download-button { background: rgba(30, 41, 59, 0.8); border-color: rgba(255, 255, 255, 0.1); } [data-theme="dark"] .download-button:hover { border-color: var(--primary-color); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } .version-info { text-align: center; margin: 1rem 0; font-size: 0.9rem; color: var(--text-secondary); } #current-version { font-weight: 600; color: var(--primary-color); } /* Download Section Responsive Styles */ @media (max-width: 768px) { .download-options { flex-direction: column; gap: 2rem; } .download-group { width: 100%; } .platform-options { flex-direction: column; } .download-button { width: 100%; margin: 0.5rem 0; } .linux-downloads { width: 100%; margin-top: 1rem; } .linux-downloads h4 { margin-bottom: 0.5rem; } /* Adjust text size for better readability on mobile */ .button-text .primary { font-size: 0.9rem; } .button-text .secondary { font-size: 0.8rem; } } /* Extra small devices */ @media (max-width: 480px) { .download-container { padding: 1rem; } .download-stats { font-size: 0.9rem; } .platform-title { font-size: 1.2rem; } } .download-stats { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; background: rgba(var(--primary-color-rgb), 0.1); border-radius: 6px; margin: 1rem 0; font-size: 0.9rem; } #download-count { font-weight: bold; color: var(--primary-color); font-size: 1.1rem; } /* Adjust the existing download-subtitle margin */ .download-subtitle { color: var(--text-secondary); margin-bottom: 2rem; margin-top: 0.5rem; } /* Testimonials Section */ .testimonials { padding: 4rem 0; background: var(--background); max-width: 1200px; margin: 0 auto; } .testimonial-grid { column-count: 3; column-gap: 2rem; margin-top: 3rem; padding: 0 2rem; } /* Ensure Twitter embeds stack properly */ .twitter-tweet { margin: 0 0 2rem 0 !important; width: 100% !important; break-inside: avoid; display: inline-block; } /* Responsive adjustments */ @media (max-width: 1024px) { .testimonial-grid { column-count: 2; } } @media (max-width: 768px) { .testimonial-grid { column-count: 1; } } /* Mobile Navigation Styles */ @media (max-width: 768px) { .glass-nav { display: flex; justify-content: space-between; align-items: center; padding: 1rem 2rem; } .menu-button { display: block; background: none; border: none; color: var(--text-primary); font-size: 1.5rem; cursor: pointer; padding: 0.5rem; z-index: 101; } .nav-links { position: fixed; top: calc(60px + 1rem); left: 0; right: 0; background: var(--card-background); padding: 1.5rem; flex-direction: column; align-items: center; gap: 1rem; transform: translateY(-150%); transition: transform 0.3s ease; backdrop-filter: blur(12px); visibility: hidden; opacity: 0; z-index: 100; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .nav-links.active { transform: translateY(0); visibility: visible; opacity: 1; } } /* Hide menu button on larger screens */ @media (min-width: 769px) { .menu-button { display: none; } } ================================================ FILE: jsconfig.json ================================================ { "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { "allowJs": true, "checkJs": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, "strict": true, "moduleResolution": "bundler" } // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files // // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // from the referenced tsconfig.json - TypeScript does not merge them in } ================================================ FILE: package.json ================================================ { "name": "neohtop", "version": "1.2.0", "description": "", "type": "module", "scripts": { "dev": "vite dev", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", "tauri": "tauri", "format": "prettier --write ./src && cargo fmt --manifest-path src-tauri/Cargo.toml", "format:check": "prettier --check ./src && cargo fmt --manifest-path src-tauri/Cargo.toml -- --check", "prepare": "husky install" }, "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@tauri-apps/api": "^2.0.3", "@tauri-apps/plugin-os": "^2.0.0", "@tauri-apps/plugin-shell": "^2.2.1", "simple-icons": "^13.15.0", "svelte-fa": "^4.0.3" }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.5", "@sveltejs/kit": "^2.7.0", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tauri-apps/cli": "^2.0.4", "husky": "^8.0.0", "lint-staged": "^15.2.10", "prettier": "^3.3.3", "prettier-plugin-svelte": "^3.2.7", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "^5.5.0", "vite": "^6.3.5" }, "lint-staged": { "src/**/*.{js,ts,jsx,tsx,svelte}": [ "prettier --write" ], "src-tauri/**/*.rs": [ "cargo fmt --manifest-path src-tauri/Cargo.toml --" ] } } ================================================ FILE: src/App.svelte ================================================ ================================================ FILE: src/app.css ================================================ :root { /* Default theme values will be overridden by theme store */ --base: #1e1e2e; --mantle: #181825; --crust: #11111b; --text: #cdd6f4; --subtext0: #a6adc8; --subtext1: #bac2de; --surface0: #313244; --surface1: #45475a; --surface2: #585b70; --overlay0: #6c7086; --overlay1: #7f849c; --blue: #89b4fa; --lavender: #b4befe; --sapphire: #74c7ec; --sky: #89dceb; --red: #f38ba8; --maroon: #eba0ac; --peach: #fab387; --yellow: #f9e2af; --green: #a6e3a1; --teal: #94e2d5; } body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; background-color: var(--base); color: var(--text); -webkit-font-smoothing: antialiased; overflow: hidden; user-select: none; } /* Global scrollbar styles */ * { scrollbar-width: thin; scrollbar-color: var(--surface2) var(--mantle); } ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: var(--mantle); border-radius: 4px; } ::-webkit-scrollbar-thumb { background: var(--surface2); border-radius: 4px; transition: background 0.2s ease; } ::-webkit-scrollbar-thumb:hover { background: var(--surface1); } ::-webkit-scrollbar-corner { background: var(--mantle); } /* Glassy theme */ [data-theme="glassy"] body { background: transparent !important; } [data-theme="glassy"] .toolbar { position: relative; background: rgba(24, 24, 37, 0.5) !important; border: 1px solid rgba(255, 255, 255, 0.1) !important; z-index: 9; } [data-theme="glassy"] .stat-panel { background: rgba(24, 24, 37, 0.2) !important; z-index: 100; } [data-theme="glassy"] .panel-header { border-color: rgba(255, 255, 255, 0.1) !important; } [data-theme="glassy"] .col-actions { background: rgba(24, 24, 37, 0.2) !important; border-bottom: 1px solid rgba(232, 232, 232, 0.1) !important; border-left: 1px solid rgba(232, 232, 232, 0.1) !important; } [data-theme="glassy"] .search-input, [data-theme="glassy"] .btn-toggle, [data-theme="glassy"] .btn-action, [data-theme="glassy"] .info-button, [data-theme="glassy"] .btn-page, [data-theme="glassy"] .theme-button, [data-theme="glassy"] .usage-pill, [data-theme="glassy"] .bar-container, [data-theme="glassy"] .select-input { background: rgba(24, 24, 37, 0.2) !important; z-index: 100; } [data-theme="glassy"] .btn-clear { z-index: 100; } [data-theme="glassy"] tr.pinned, [data-theme="glassy"] tr:hover { background: rgba(24, 24, 37, 0.3) !important; } [data-theme="glassy"] th { background: rgba(24, 24, 37, 0.5) !important; } [data-theme="glassy"] td { border-bottom: 1px solid rgba(232, 232, 232, 0.1) !important; background: rgba(24, 24, 37, 0.2) !important; } ================================================ FILE: src/app.html ================================================ NeoHtop %sveltekit.head%
%sveltekit.body%
================================================ FILE: src/lib/components/AppInfo.svelte ================================================
{#if showInfo}
(showInfo = false)}>
{ASCII_ART}
NeoHtop v{version} {#if hasUpdate} Update to v{latestVersion} {/if}
app :: {APP_INFO.name}
stack :: {APP_INFO.stack.join(", ")}
{/if}
================================================ FILE: src/lib/components/ThemeSwitcher.svelte ================================================
{#if showMenu}
overlayStore.close()} on:keydown={(e) => e.key === "Escape" && overlayStore.close()} role="dialog" aria-label="Theme selection" tabindex="-1" > {#if canScrollLeft} {/if}
{#each themeGroups as group} {#each group.themes as themeName} {@const theme = themes[themeName]} {/each} {/each}
{#if canScrollRight} {/if}
{/if}
================================================ FILE: src/lib/components/TitleBar.svelte ================================================
NeoHtop
NeoHtop
================================================ FILE: src/lib/components/index.ts ================================================ export * from "./toolbar"; export * from "./process"; export * from "./stats"; export * from "./modals"; export { default as AppInfo } from "./AppInfo.svelte"; export { default as TitleBar } from "./TitleBar.svelte"; export { default as ThemeSwitcher } from "./ThemeSwitcher.svelte"; ================================================ FILE: src/lib/components/modals/KillProcessModal.svelte ================================================ {#if process}

Are you sure you want to end this process?

{process.name} (PID: {process.pid})
{/if}
================================================ FILE: src/lib/components/modals/Modal.svelte ================================================ {#if show} {/if} ================================================ FILE: src/lib/components/modals/ProcessDetailsModal.svelte ================================================ {#if process} {/if} ================================================ FILE: src/lib/components/modals/index.ts ================================================ export { default as Modal } from "./Modal.svelte"; export { default as ProcessDetailsModal } from "./ProcessDetailsModal.svelte"; export { default as KillProcessModal } from "./KillProcessModal.svelte"; ================================================ FILE: src/lib/components/process/ActionButtons.svelte ================================================
================================================ FILE: src/lib/components/process/ProcessIcon.svelte ================================================ ================================================ FILE: src/lib/components/process/ProcessRow.svelte ================================================ {#each columns.filter((col) => col.visible) as column} {#if column.id === "name"}
{process.name}
{:else if column.format} {@html column.format(process[column.id])} {:else} {process[column.id]} {/if} {/each} ================================================ FILE: src/lib/components/process/ProcessTable.svelte ================================================
{#each processes as process (process.pid)} 50 || process.memory_usage / (systemStats?.memory_total || 0) > 0.1} {onTogglePin} {onShowDetails} {onKillProcess} /> {/each}
================================================ FILE: src/lib/components/process/TableHeader.svelte ================================================ {#each columns.filter((col) => col.visible) as column} onToggleSort(column.id)}>
{column.label} {getSortIndicator(column.id)}
{/each} Actions ================================================ FILE: src/lib/components/process/index.ts ================================================ export { default as ProcessTable } from "./ProcessTable.svelte"; export { default as ProcessRow } from "./ProcessRow.svelte"; export { default as TableHeader } from "./TableHeader.svelte"; export { default as ActionButtons } from "./ActionButtons.svelte"; export { default as ProcessIcon } from "./ProcessIcon.svelte"; ================================================ FILE: src/lib/components/stats/CpuPanel.svelte ================================================
{#each cpuUsage as usage, i}
{/each}
================================================ FILE: src/lib/components/stats/MemoryPanel.svelte ================================================
================================================ FILE: src/lib/components/stats/NetworkPanel.svelte ================================================
================================================ FILE: src/lib/components/stats/PanelHeader.svelte ================================================

{title}

{#if usageValue}
{usageValue}
{/if}
================================================ FILE: src/lib/components/stats/ProgressBar.svelte ================================================
{label}
{Math.round(value)}%
================================================ FILE: src/lib/components/stats/StatItem.svelte ================================================
{label} {value}
================================================ FILE: src/lib/components/stats/StatPanel.svelte ================================================

{title}

================================================ FILE: src/lib/components/stats/StatsBar.svelte ================================================
{#if systemStats}
{/if}
================================================ FILE: src/lib/components/stats/StoragePanel.svelte ================================================
================================================ FILE: src/lib/components/stats/SystemPanel.svelte ================================================
================================================ FILE: src/lib/components/stats/index.ts ================================================ export { default as StatsBar } from "./StatsBar.svelte"; export { default as CpuPanel } from "./CpuPanel.svelte"; export { default as MemoryPanel } from "./MemoryPanel.svelte"; export { default as StoragePanel } from "./StoragePanel.svelte"; export { default as SystemPanel } from "./SystemPanel.svelte"; export { default as NetworkPanel } from "./NetworkPanel.svelte"; export { default as PanelHeader } from "./PanelHeader.svelte"; export { default as ProgressBar } from "./ProgressBar.svelte"; export { default as StatItem } from "./StatItem.svelte"; ================================================ FILE: src/lib/components/toolbar/ColumnToggle.svelte ================================================
{#if showColumnMenu}
overlayStore.close()} on:keydown={(e) => e.key === "Escape" && overlayStore.close()} role="dialog" aria-label="Column visibility options" tabindex="-1" > {#if canScrollLeft} {/if}
{#each columns as column} {/each}
{#if canScrollRight} {/if}
{/if}
================================================ FILE: src/lib/components/toolbar/FilterToggle.svelte ================================================
{#if showFilters}
overlayStore.close()} on:keydown={(e) => e.key === "Escape" && overlayStore.close()} role="dialog" aria-label="Filter options overlay" tabindex="-1" >
{#each [["cpu", "CPU %"], ["ram", "RAM MB"], ["runtime", "Runtime min"]] as [type, label]} {@const filterKey = type as "cpu" | "ram" | "runtime"}
{#if filters[filterKey].enabled} updateFilter( filterKey, "value", parseInt((e.target as HTMLInputElement).value), )} on:click|stopPropagation on:focus|stopPropagation placeholder={type === "cpu" ? "50" : type === "ram" ? "100" : "60"} /> {#if type === "ram"} MB {:else if type === "runtime"} min {:else} % {/if} {/if}
{/each}
{#each statusOptions as status} {/each}
{#if hasActiveFilters} {/if}
{#if hasActiveFilters} {activeFilterCount} filter{activeFilterCount > 1 ? "s" : ""} active {:else} No filters applied {/if}
{/if}
================================================ FILE: src/lib/components/toolbar/PaginationControls.svelte ================================================
{#if isExpanded}
overlayStore.close()} on:keydown={(e) => e.key === "Escape" && overlayStore.close()} role="dialog" aria-label="Pagination controls" tabindex="-1" >
{#each ITEMS_PER_PAGE_OPTIONS as option} {/each}
{/if}
================================================ FILE: src/lib/components/toolbar/RefreshControls.svelte ================================================
{#if isExpanded}
overlayStore.close()} on:keydown={(e) => e.key === "Escape" && overlayStore.close()} role="dialog" aria-label="Refresh rate controls" tabindex="-1" >
{#each REFRESH_RATE_OPTIONS as option} {/each}
{/if}
================================================ FILE: src/lib/components/toolbar/SearchBox.svelte ================================================ ================================================ FILE: src/lib/components/toolbar/StatusFilter.svelte ================================================
{#if isExpanded}
overlayStore.close()} on:keydown={(e) => e.key === "Escape" && overlayStore.close()} role="dialog" aria-label="Status filter options" tabindex="-1" >
{#each STATUS_OPTIONS as option} {/each}
{/if}
================================================ FILE: src/lib/components/toolbar/ToolBar.svelte ================================================
================================================ FILE: src/lib/components/toolbar/index.ts ================================================ export { default as ToolBar } from "./ToolBar.svelte"; export { default as SearchBox } from "./SearchBox.svelte"; export { default as StatusFilter } from "./StatusFilter.svelte"; export { default as PaginationControls } from "./PaginationControls.svelte"; export { default as ColumnToggle } from "./ColumnToggle.svelte"; export { default as RefreshControls } from "./RefreshControls.svelte"; export { default as FilterToggle } from "./FilterToggle.svelte"; ================================================ FILE: src/lib/constants/index.ts ================================================ export const ASCII_ART = ` ███╗ ██╗███████╗ ██████╗ ██╗ ██╗████████╗ ██████╗ ██████╗ ████╗ ██║██╔════╝██╔═══██╗██║ ██║╚══██╔══╝██╔═══██╗██╔══██╗ ██╔██╗ ██║█████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝ ██║╚██╗██║██╔══╝ ██║ ██║██╔══██║ ██║ ██║ ██║██╔═══╝ ██║ ╚████║███████╗╚██████╔╝██║ ██║ ██║ ╚██████╔╝██║ ╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ `; export const APP_INFO = { name: "NeoHtop", developer: "Abdenasser", github: "https://github.com/Abdenasser/neohtop", stack: ["Tauri", "Rust", "Svelte", "TypeScript"], }; export const ITEMS_PER_PAGE_OPTIONS = [15, 25, 50, 100, 250, 500]; export const REFRESH_RATE_OPTIONS = [ { value: 2000, label: "2s" }, { value: 3000, label: "3s" }, { value: 5000, label: "5s" }, { value: 10000, label: "10s" }, { value: 30000, label: "30s" }, ]; export const STATUS_OPTIONS = [ { value: "all", label: "All Statuses" }, { value: "running", label: "Running" }, { value: "sleeping", label: "Sleeping" }, { value: "idle", label: "Idle" }, { value: "unknown", label: "Unknown" }, ]; export const THEME_GROUPS = [ { label: "Dark", themes: [ "catppuccin", "dracula", "monokaiPro", "tokyoNight", "solarizedDark", "ayuDark", "ayuMirage", ], }, { label: "Light", themes: ["githubLight", "solarizedLight", "oneLight", "ayuLight"], }, { label: "Warm", themes: ["gruvbox"], }, { label: "Cool", themes: ["nord", "oneDark"], }, { label: "Fun", themes: ["bubblegum", "rosePine", "cottonCandy", "synthwave", "candyfloss"], }, { label: "Retro", themes: ["terminal", "amber", "ibmPC"], }, { label: "Accessibility", themes: ["highContrast"], }, ]; ================================================ FILE: src/lib/definitions/columns.ts ================================================ import type { Column } from "$lib/types"; import { formatMemorySize } from "$lib/utils"; export let column_definitions: Column[] = [ { id: "name", label: "Process Name", visible: true, required: true }, { id: "pid", label: "PID", visible: true, required: false }, { id: "status", label: "Status", visible: true, }, { id: "user", label: "User", visible: true }, { id: "cpu_usage", label: "CPU %", visible: true, format: (v) => v.toFixed(1) + "%", }, { id: "memory_usage", label: "RAM", visible: true, format: (v) => (v / (1024 * 1024)).toFixed(1) + " MB", }, { id: "virtual_memory", label: "VIRT", visible: true, format: (v) => formatMemorySize(v), }, { id: "disk_usage", label: "Disk I/O (R/W)", visible: true, format: (v) => { const readMB = (v[0] / (1024 * 1024)).toFixed(1); const writeMB = (v[1] / (1024 * 1024)).toFixed(1); return `${readMB}/${writeMB} MB`; }, }, { id: "ppid", label: "Parent PID", visible: false }, { id: "root", label: "Root", visible: false }, { id: "command", label: "Command", visible: false }, { id: "environ", label: "Environment Variables", visible: false }, { id: "session_id", label: "Session ID", visible: false }, { id: "start_time", label: "Start Time", visible: false, format: (v) => new Date(v * 1000).toLocaleString(), // v is the time where the process was started (in seconds) from epoch }, { id: "run_time", label: "Run Time", visible: true, format: (v) => { const seconds = v; // v is the time the process has been running in seconds const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainingSeconds = seconds % 60; return `${hours}h ${minutes}m ${remainingSeconds}s`; // Format as HH:MM:SS }, }, ]; ================================================ FILE: src/lib/definitions/index.ts ================================================ export * from "./columns"; export * from "./settings"; export * from "./themes"; ================================================ FILE: src/lib/definitions/settings.ts ================================================ import type { AppConfig } from "$lib/types"; export const DEFAULT_CONFIG: AppConfig = { appearance: { columnVisibility: { name: true, pid: true, status: true, user: true, cpu_usage: true, memory_usage: true, virtual_memory: true, disk_usage: true, ppid: false, root: false, command: false, environ: false, session_id: false, start_time: false, run_time: true, }, }, behavior: { itemsPerPage: 15, refreshRate: 3000, defaultStatusFilter: "all", }, }; ================================================ FILE: src/lib/definitions/themes.ts ================================================ import type { Theme } from "$lib/types"; export const themes: Record = { catppuccin: { name: "catppuccin", label: "Catppuccin", colors: { base: "#1e1e2e", mantle: "#181825", crust: "#11111b", text: "#cdd6f4", subtext0: "#a6adc8", subtext1: "#bac2de", surface0: "#313244", surface1: "#45475a", surface2: "#585b70", overlay0: "#6c7086", overlay1: "#7f849c", blue: "#89b4fa", lavender: "#b4befe", sapphire: "#74c7ec", sky: "#89dceb", red: "#f38ba8", maroon: "#eba0ac", peach: "#fab387", yellow: "#f9e2af", green: "#a6e3a1", teal: "#94e2d5", }, }, dracula: { name: "dracula", label: "Dracula", colors: { base: "#282a36", mantle: "#1e1f29", crust: "#191a21", text: "#f8f8f2", subtext0: "#bfbfbf", subtext1: "#e6e6e6", surface0: "#44475a", surface1: "#6272a4", surface2: "#7970a9", overlay0: "#6272a4", overlay1: "#7970a9", blue: "#8be9fd", lavender: "#bd93f9", sapphire: "#62d6e8", sky: "#89ddff", red: "#ff5555", maroon: "#ff6e6e", peach: "#ffb86c", yellow: "#f1fa8c", green: "#50fa7b", teal: "#8be9fd", }, }, monokaiPro: { name: "monokaiPro", label: "Monokai Pro", colors: { base: "#2d2a2e", mantle: "#221f22", crust: "#1b1b1b", text: "#fcfcfa", subtext0: "#939293", subtext1: "#c1c0c0", surface0: "#403e41", surface1: "#565457", surface2: "#69676c", overlay0: "#727072", overlay1: "#848486", blue: "#78dce8", lavender: "#ab9df2", sapphire: "#66d9ef", sky: "#78dce8", red: "#ff6188", maroon: "#ff6188", peach: "#fc9867", yellow: "#ffd866", green: "#a9dc76", teal: "#78dce8", }, }, tokyoNight: { name: "tokyoNight", label: "Tokyo Night", colors: { base: "#1a1b26", mantle: "#16161e", crust: "#13131a", text: "#a9b1d6", subtext0: "#9aa5ce", subtext1: "#9aa5ce", surface0: "#232433", surface1: "#2a2b3d", surface2: "#32344a", overlay0: "#565f89", overlay1: "#6b7089", blue: "#7aa2f7", lavender: "#bb9af7", sapphire: "#7dcfff", sky: "#7dcfff", red: "#f7768e", maroon: "#ff9e64", peach: "#ff9e64", yellow: "#e0af68", green: "#9ece6a", teal: "#2ac3de", }, }, gruvbox: { name: "gruvbox", label: "Gruvbox Dark", colors: { base: "#282828", mantle: "#1d2021", crust: "#1b1b1b", text: "#ebdbb2", subtext0: "#a89984", subtext1: "#bdae93", surface0: "#3c3836", surface1: "#504945", surface2: "#665c54", overlay0: "#7c6f64", overlay1: "#928374", blue: "#83a598", lavender: "#d3869b", sapphire: "#83a598", sky: "#8ec07c", red: "#fb4934", maroon: "#cc241d", peach: "#fe8019", yellow: "#fabd2f", green: "#b8bb26", teal: "#8ec07c", }, }, nord: { name: "nord", label: "Nord", colors: { base: "#2e3440", mantle: "#272c36", crust: "#242933", text: "#eceff4", subtext0: "#d8dee9", subtext1: "#e5e9f0", surface0: "#3b4252", surface1: "#434c5e", surface2: "#4c566a", overlay0: "#616e88", overlay1: "#7b88a1", blue: "#88c0d0", lavender: "#b48ead", sapphire: "#81a1c1", sky: "#88c0d0", red: "#bf616a", maroon: "#d08770", peach: "#d08770", yellow: "#ebcb8b", green: "#a3be8c", teal: "#8fbcbb", }, }, oneDark: { name: "oneDark", label: "One Dark", colors: { base: "#282c34", mantle: "#21252b", crust: "#1b1f23", text: "#abb2bf", subtext0: "#828997", subtext1: "#9da5b4", surface0: "#31353f", surface1: "#393f4a", surface2: "#4b5263", overlay0: "#636d83", overlay1: "#767d8d", blue: "#61afef", lavender: "#c678dd", sapphire: "#56b6c2", sky: "#56b6c2", red: "#e06c75", maroon: "#be5046", peach: "#d19a66", yellow: "#e5c07b", green: "#98c379", teal: "#56b6c2", }, }, highContrast: { name: "highContrast", label: "High Contrast", colors: { base: "#000000", // Pure black background mantle: "#0a0a0a", // Slightly lighter black for layering crust: "#141414", // Even lighter black for depth text: "#ffffff", // Pure white text subtext0: "#e0e0e0", // Very light grey for secondary text subtext1: "#f0f0f0", // Almost white for important secondary text surface0: "#1a1a1a", // Dark surface for contrast surface1: "#2a2a2a", // Lighter surface for hover states surface2: "#3a3a3a", // Even lighter surface for active states overlay0: "#4a4a4a", // Medium grey for overlays overlay1: "#5a5a5a", // Lighter grey for overlay hover states blue: "#00ffff", // Cyan for primary actions lavender: "#ff00ff", // Magenta for accents sapphire: "#00ccff", // Bright blue for links sky: "#00ffee", // Bright cyan for highlights red: "#ff0000", // Pure red for errors/warnings maroon: "#ff3333", // Lighter red for secondary warnings peach: "#ffaa00", // Bright orange for notifications yellow: "#ffff00", // Pure yellow for important highlights green: "#00ff00", // Pure green for success states teal: "#00ffcc", // Bright teal for special actions }, }, githubLight: { name: "githubLight", label: "GitHub Light", colors: { base: "#ffffff", mantle: "#f6f8fa", crust: "#eaeef2", text: "#24292f", subtext0: "#57606a", subtext1: "#6e7781", surface0: "#f3f6fa", surface1: "#eaeef2", surface2: "#d0d7de", overlay0: "#8c959f", overlay1: "#6e7781", blue: "#0969da", lavender: "#8250df", sapphire: "#0550ae", sky: "#218bff", red: "#cf222e", maroon: "#a40e26", peach: "#bc4c00", yellow: "#9a6700", green: "#1a7f37", teal: "#0969da", }, }, solarizedLight: { name: "solarizedLight", label: "Solarized Light", colors: { base: "#fdf6e3", mantle: "#eee8d5", crust: "#e4dcc9", text: "#657b83", subtext0: "#839496", subtext1: "#93a1a1", surface0: "#f7f2e4", surface1: "#eee8d5", surface2: "#dcd4c4", overlay0: "#93a1a1", overlay1: "#839496", blue: "#268bd2", lavender: "#6c71c4", sapphire: "#2aa198", sky: "#2aa198", red: "#dc322f", maroon: "#cb4b16", peach: "#cb4b16", yellow: "#b58900", green: "#859900", teal: "#2aa198", }, }, solarizedDark: { name: "solarizedDark", label: "Solarized Dark", colors: { base: "#002b36", mantle: "#073642", crust: "#0A4C5C", text: "#839496", subtext0: "#657b83", subtext1: "#586e75", surface0: "#00313D", surface1: "#073642", surface2: "#083C49", overlay0: "#586e75", overlay1: "#657b83", blue: "#268bd2", lavender: "#6c71c4", sapphire: "#2aa198", sky: "#2aa198", red: "#dc322f", maroon: "#cb4b16", peach: "#cb4b16", yellow: "#b58900", green: "#859900", teal: "#2aa198", }, }, oneLight: { name: "oneLight", label: "One Light", colors: { base: "#fafafa", mantle: "#f0f0f0", crust: "#e5e5e5", text: "#383a42", subtext0: "#4f525e", subtext1: "#696c77", surface0: "#f2f2f2", surface1: "#e5e5e5", surface2: "#d4d4d4", overlay0: "#a0a1a7", overlay1: "#696c77", blue: "#4078f2", lavender: "#a626a4", sapphire: "#0184bc", sky: "#0997b3", red: "#e45649", maroon: "#ca1243", peach: "#d75f00", yellow: "#c18401", green: "#50a14f", teal: "#0184bc", }, }, bubblegum: { name: "bubblegum", label: "Bubblegum", colors: { base: "#ff9ac1", // Light pink background mantle: "#ffa7cc", // Slightly darker pink crust: "#ffb4d8", // Even darker pink for depth text: "#2d1c2d", // Dark purple text subtext0: "#4b384b", // Medium purple for secondary text subtext1: "#5c465c", // Lighter purple for tertiary text surface0: "#ffc1e0", // Light pink surface surface1: "#ffcee7", // Lighter pink surface surface2: "#ffdaf0", // Even lighter pink surface overlay0: "#7e5c7e", // Muted purple overlay overlay1: "#6e4f6e", // Darker purple overlay blue: "#7287fd", // Soft blue lavender: "#b4befe", // Soft lavender sapphire: "#89dceb", // Soft cyan sky: "#89dceb", // Matching cyan red: "#ff8089", // Soft red maroon: "#ff9999", // Soft maroon peach: "#ffb4a1", // Soft peach yellow: "#ffe5a0", // Soft yellow green: "#a6e3a1", // Soft green teal: "#94e2d5", // Soft teal }, }, rosePine: { name: "rosePine", label: "Rosé Pine", colors: { base: "#191724", // Deep purple base mantle: "#1f1d2e", // Slightly lighter purple crust: "#26233a", // Even lighter purple text: "#e0def4", // Soft white text subtext0: "#908caa", // Muted purple text subtext1: "#6e6a86", // Darker muted text surface0: "#2a2837", // Surface purple surface1: "#343145", // Lighter surface surface2: "#3e3b54", // Even lighter surface overlay0: "#524f67", // Overlay purple overlay1: "#6e6a86", // Lighter overlay blue: "#9ccfd8", // Soft blue lavender: "#c4a7e7", // Soft lavender sapphire: "#31748f", // Deep blue sky: "#9ccfd8", // Light blue red: "#eb6f92", // Soft pink maroon: "#ebbcba", // Soft rose peach: "#f6c177", // Soft peach yellow: "#f6c177", // Gold green: "#31748f", // Sage teal: "#9ccfd8", // Soft teal }, }, cottonCandy: { name: "cottonCandy", label: "Cotton Candy", colors: { base: "#f5d1eb", // Light pink mantle: "#f7d7ee", // Slightly darker pink crust: "#fae1f3", // Even darker pink text: "#2d0c3a", // Deep purple text subtext0: "#4a1259", // Medium purple text subtext1: "#671878", // Light purple text surface0: "#f9def1", // Surface pink surface1: "#fde9f5", // Lighter surface surface2: "#fff2fa", // Even lighter surface overlay0: "#b87dd3", // Purple overlay overlay1: "#9c5fb8", // Darker overlay blue: "#79c7ff", // Baby blue lavender: "#d5a6ff", // Soft purple sapphire: "#7cb8ff", // Light blue sky: "#89dcff", // Bright blue red: "#ff9ed2", // Soft pink maroon: "#ff8ac4", // Darker pink peach: "#ffb2c7", // Peachy pink yellow: "#ffffc2", // Pastel yellow green: "#b6ffd7", // Mint green teal: "#89ffea", // Turquoise }, }, synthwave: { name: "synthwave", label: "Synthwave", colors: { base: "#2b213a", // Deep purple mantle: "#2f2444", // Slightly lighter purple crust: "#33274f", // Even lighter purple text: "#ff7edb", // Neon pink text subtext0: "#e58ee0", // Softer pink text subtext1: "#cb9ee6", // Lavender text surface0: "#392662", // Surface purple surface1: "#443773", // Lighter surface surface2: "#504785", // Even lighter surface overlay0: "#625997", // Purple overlay overlay1: "#7267aa", // Lighter overlay blue: "#36f9f6", // Cyan lavender: "#ff7edb", // Pink sapphire: "#72f1b8", // Mint sky: "#36f9f6", // Bright cyan red: "#fe4450", // Hot red maroon: "#ff558f", // Hot pink peach: "#ff8b39", // Orange yellow: "#fede5d", // Yellow green: "#72f1b8", // Neon green teal: "#36f9f6", // Bright teal }, }, candyfloss: { name: "candyfloss", label: "Candyfloss", colors: { base: "#f8e2ff", // Light purple mantle: "#ffe2f8", // Pink tint crust: "#ffe9f3", // Lighter pink text: "#5c1b99", // Deep purple text subtext0: "#7a3aaf", // Medium purple text subtext1: "#944bc6", // Light purple text surface0: "#ffeaf8", // Surface pink surface1: "#fff2fb", // Lighter surface surface2: "#fff7fd", // Even lighter surface overlay0: "#d59bff", // Purple overlay overlay1: "#c77dff", // Darker overlay blue: "#79baff", // Soft blue lavender: "#cc8fff", // Light purple sapphire: "#85a5ff", // Periwinkle sky: "#8aceff", // Light blue red: "#ff8fab", // Soft red maroon: "#ff7fa6", // Pink peach: "#ffb2c7", // Peach yellow: "#fff3b2", // Soft yellow green: "#b8ffda", // Mint teal: "#8affef", // Aqua }, }, terminal: { name: "terminal", label: "Green Terminal", colors: { base: "#0D1117", // Deep black background mantle: "#161B22", // Slightly lighter black crust: "#1B2127", // Terminal border color text: "#00FF00", // Classic terminal green subtext0: "#00D700", // Dimmer green subtext1: "#00BB00", // Even dimmer green surface0: "#1C2128", // Slightly lifted surface surface1: "#21262D", // Terminal input area surface2: "#282E35", // Selected area overlay0: "#008800", // Darker green for overlays overlay1: "#006600", // Even darker green blue: "#00FF00", // Keep everything in green shades lavender: "#00FF66", // Slight variation sapphire: "#00DD88", // Another variation sky: "#00FFBB", // Lighter green red: "#FF0000", // Error red (keep for errors) maroon: "#AA0000", // Darker error peach: "#00FF99", // Another green variation yellow: "#FFFF00", // Warning yellow (keep for warnings) green: "#00FF00", // Main green teal: "#00FFCC", // Cyan-ish green }, }, amber: { name: "amber", label: "Amber Terminal", colors: { base: "#0D0904", // Deep black with amber tint mantle: "#160E06", // Slightly lighter black crust: "#1B1109", // Terminal border color text: "#FFB000", // Classic amber subtext0: "#CC8800", // Dimmer amber subtext1: "#995500", // Even dimmer amber surface0: "#1C1409", // Slightly lifted surface surface1: "#211909", // Terminal input area surface2: "#281E0A", // Selected area overlay0: "#663300", // Darker amber for overlays overlay1: "#442200", // Even darker amber blue: "#FFB000", // Keep everything in amber shades lavender: "#FFAA00", // Slight variation sapphire: "#FF9500", // Another variation sky: "#FFB000", // Main amber red: "#FF3300", // Error red (keep for errors) maroon: "#CC3300", // Darker error peach: "#FFAA55", // Lighter amber yellow: "#FFDD00", // Warning yellow green: "#FFB000", // Main amber teal: "#FFC000", // Lighter amber }, }, ibmPC: { name: "ibmPC", label: "IBM PC", colors: { base: "#000000", // Classic black background mantle: "#0A0A0A", // Slightly lighter black crust: "#141414", // Border color text: "#AAAAAA", // Light gray text subtext0: "#888888", // Dimmer text subtext1: "#666666", // Even dimmer text surface0: "#1C1C1C", // Slightly lifted surface surface1: "#212121", // Input area surface2: "#282828", // Selected area overlay0: "#444444", // Overlay overlay1: "#333333", // Darker overlay blue: "#5555FF", // CGA blue lavender: "#FF55FF", // CGA magenta sapphire: "#5555FF", // Another blue sky: "#55FFFF", // CGA cyan red: "#FF5555", // CGA red maroon: "#AA0000", // Darker red peach: "#FF5555", // Another red shade yellow: "#FFFF55", // CGA yellow green: "#55FF55", // CGA green teal: "#55FFFF", // Another cyan }, }, glassy: { name: "glassy", label: "Glassy", colors: { base: "#1e1e2e", mantle: "#181825", crust: "#11111b", text: "#cdd6f4", subtext0: "#a6adc8", subtext1: "#bac2de", surface0: "#313244", surface1: "#45475a", surface2: "#585b70", overlay0: "#6c7086", overlay1: "#7f849c", blue: "#89b4fa", lavender: "#b4befe", sapphire: "#74c7ec", sky: "#89dceb", red: "#f38ba8", maroon: "#eba0ac", peach: "#fab387", yellow: "#f9e2af", green: "#a6e3a1", teal: "#94e2d5", }, }, ayuDark: { name: "ayuDark", label: "Ayu Dark", colors: { base: "#0D1017", mantle: "#131721", crust: "#232834", text: "#BFBDB6", subtext0: "#707a8c", subtext1: "#8b939e", surface0: "#0f1419", surface1: "#131721", surface2: "#212733", overlay0: "#E6B450", overlay1: "#434c5e", blue: "#36a3d9", lavender: "#d2a8ff", sapphire: "#0f958a", sky: "#c9d1d9", red: "#D95757", maroon: "#e06c75", peach: "#f07178", yellow: "#ffb454", green: "#b8cc52", teal: "#95e6cb", }, }, ayuMirage: { name: "ayuMirage", label: "Ayu Mirage", colors: { base: "#242936", mantle: "#1A1F29", crust: "#232834", text: "#CCCAC2", subtext0: "#707a8c", subtext1: "#8b939e", surface0: "#0f1419", surface1: "#131721", surface2: "#212733", overlay0: "#FFCC66", overlay1: "#434c5e", blue: "#36a3d9", lavender: "#d2a8ff", sapphire: "#0f958a", sky: "#c9d1d9", red: "#FF6666", maroon: "#e06c75", peach: "#f07178", yellow: "#ffb454", green: "#b8cc52", teal: "#95e6cb", }, }, ayuLight: { name: "ayuLight", label: "Ayu Light", colors: { base: "#FCFCFC", mantle: "#8A91991A", crust: "#eaeef2", text: "#5C6166", subtext0: "#57606a", subtext1: "#6e7781", surface0: "#f3f6fa", surface1: "#eaeef2", surface2: "#d0d7de", overlay0: "#FFAA33", overlay1: "#6e7781", blue: "#0969da", lavender: "#8250df", sapphire: "#0550ae", sky: "#218bff", red: "#E65050", maroon: "#a40e26", peach: "#bc4c00", yellow: "#9a6700", green: "#1a7f37", teal: "#0969da", }, }, }; ================================================ FILE: src/lib/stores/index.ts ================================================ export * from "./processes"; export * from "./theme"; export * from "./settings"; export * from "./overlay"; ================================================ FILE: src/lib/stores/overlay.ts ================================================ import { writable } from "svelte/store"; type OverlayType = | "pagination" | "refresh" | "columns" | "theme" | "searchHelp" | "filters" | "status" | null; function createOverlayStore() { const { subscribe, set } = writable(null); return { subscribe, open: (overlayType: OverlayType) => set(overlayType), close: () => set(null), isOpen: (overlayType: OverlayType) => { let currentValue: OverlayType = null; subscribe((value) => (currentValue = value))(); return currentValue === overlayType; }, }; } export const overlayStore = createOverlayStore(); export default overlayStore; ================================================ FILE: src/lib/stores/processes.ts ================================================ import { writable, derived } from "svelte/store"; import type { Process, SystemStats } from "$lib/types"; import { invoke } from "@tauri-apps/api/core"; interface ProcessStore { processes: Process[]; systemStats: SystemStats | null; error: string | null; isLoading: boolean; searchTerm: string; currentPage: number; pinnedProcesses: Set; selectedProcess: Process | null; showInfoModal: boolean; showConfirmModal: boolean; processToKill: Process | null; isKilling: boolean; isFrozen: boolean; selectedProcessPid: number | null; sortConfig: { field: keyof Process; direction: "asc" | "desc"; }; } const initialState: ProcessStore = { processes: [], systemStats: null, error: null, isLoading: true, searchTerm: "", currentPage: 1, pinnedProcesses: new Set(), selectedProcess: null, showInfoModal: false, showConfirmModal: false, processToKill: null, isKilling: false, isFrozen: false, selectedProcessPid: null, sortConfig: { field: "cpu_usage", direction: "desc", }, }; function createProcessStore() { const { subscribe, set, update } = writable(initialState); // Define all methods first const setIsLoading = (isLoading: boolean) => update((state) => ({ ...state, isLoading })); const getProcesses = async () => { try { const result = await invoke<[Process[], SystemStats]>("get_processes"); update((state) => { let updatedSelectedProcess = state.selectedProcess; if (state.selectedProcessPid) { updatedSelectedProcess = result[0].find((p) => p.pid === state.selectedProcessPid) || null; } return { ...state, processes: result[0], systemStats: result[1], error: null, selectedProcess: updatedSelectedProcess, }; }); } catch (e: unknown) { update((state) => ({ ...state, error: e instanceof Error ? e.message : String(e), })); } }; const killProcess = async (pid: number) => { try { update((state) => ({ ...state, isKilling: true })); const success = await invoke("kill_process", { pid }); if (success) { await getProcesses(); } else { throw new Error("Failed to kill process"); } } catch (e: unknown) { update((state) => ({ ...state, error: e instanceof Error ? e.message : String(e), })); } finally { update((state) => ({ ...state, isKilling: false })); } }; const toggleSort = (field: keyof Process) => { update((state) => ({ ...state, sortConfig: { field, direction: state.sortConfig.field === field ? state.sortConfig.direction === "asc" ? "desc" : "asc" : "desc", }, })); }; const togglePin = (command: string) => { update((state) => { const newPinnedProcesses = new Set(state.pinnedProcesses); if (newPinnedProcesses.has(command)) { newPinnedProcesses.delete(command); } else { newPinnedProcesses.add(command); } return { ...state, pinnedProcesses: newPinnedProcesses }; }); }; const setSearchTerm = (searchTerm: string) => update((state) => ({ ...state, searchTerm, currentPage: 1 })); const setIsFrozen = (isFrozen: boolean) => update((state) => ({ ...state, isFrozen })); const setCurrentPage = (currentPage: number) => update((state) => ({ ...state, currentPage })); const showProcessDetails = (process: Process) => { update((state) => ({ ...state, selectedProcessPid: process.pid, selectedProcess: process, showInfoModal: true, })); }; const closeProcessDetails = () => { update((state) => ({ ...state, showInfoModal: false, selectedProcess: null, selectedProcessPid: null, })); }; const confirmKillProcess = (process: Process) => { update((state) => ({ ...state, processToKill: process, showConfirmModal: true, })); }; const closeConfirmKill = () => { update((state) => ({ ...state, showConfirmModal: false, processToKill: null, })); }; const handleConfirmKill = async () => { let processToKill: Process | null = null; let currentState: ProcessStore | undefined; const unsubscribe = subscribe((state) => { currentState = state; }); unsubscribe(); if (currentState?.processToKill && "pid" in currentState.processToKill) { processToKill = currentState.processToKill; } if (!processToKill?.pid) { return; } try { await killProcess(processToKill.pid); } finally { update((state) => ({ ...state, showConfirmModal: false, processToKill: null, })); } }; // Return all methods return { subscribe, set, update, setIsLoading, getProcesses, killProcess, toggleSort, togglePin, setSearchTerm, setIsFrozen, setCurrentPage, showProcessDetails, closeProcessDetails, confirmKillProcess, closeConfirmKill, handleConfirmKill, }; } export const processStore = createProcessStore(); ================================================ FILE: src/lib/stores/settings.ts ================================================ import { writable } from "svelte/store"; import type { AppConfig } from "$lib/types"; import { DEFAULT_CONFIG } from "$lib/definitions/settings"; function createSettingsStore() { const { subscribe, set, update } = writable(DEFAULT_CONFIG); return { subscribe, init: () => { if (typeof window !== "undefined") { const stored = localStorage.getItem("neohtop_config"); if (stored) { try { const config = JSON.parse(stored); set({ ...DEFAULT_CONFIG, ...config }); } catch (e) { console.error("Failed to parse stored config:", e); set(DEFAULT_CONFIG); } } } }, updateConfig: (newConfig: Partial) => { update((config) => { const updated = { ...config, ...newConfig }; if (typeof window !== "undefined") { localStorage.setItem("neohtop_config", JSON.stringify(updated)); } return updated; }); }, }; } export const settingsStore = createSettingsStore(); ================================================ FILE: src/lib/stores/theme.ts ================================================ import { writable } from "svelte/store"; import { themes } from "$lib/definitions/themes"; import type { Theme } from "$lib/types"; function createThemeStore() { // Default theme const defaultTheme = themes.catppuccin; // Initialize with default theme const { subscribe, set } = writable(defaultTheme); // Initialize theme on client-side only if (typeof window !== "undefined") { const storedTheme = localStorage.getItem("theme"); if (storedTheme && themes[storedTheme]) { set(themes[storedTheme]); } } return { subscribe, setTheme: (themeName: string) => { const theme = themes[themeName]; if (theme) { if (typeof window !== "undefined") { localStorage.setItem("theme", themeName); // Add this line to set the data-theme attribute document.documentElement.setAttribute("data-theme", themeName); } set(theme); applyTheme(theme); } }, init: () => { const storedTheme = typeof window !== "undefined" ? localStorage.getItem("theme") : null; const theme = (storedTheme && themes[storedTheme]) || defaultTheme; if (typeof window !== "undefined") { // Add this line to set the data-theme attribute on init document.documentElement.setAttribute( "data-theme", storedTheme || "catppuccin", ); } applyTheme(theme); }, }; } function applyTheme(theme: Theme) { if (typeof window !== "undefined") { const root = document.documentElement; Object.entries(theme.colors).forEach(([key, value]) => { root.style.setProperty(`--${key}`, value); }); } } export const themeStore = createThemeStore(); ================================================ FILE: src/lib/types/index.ts ================================================ // Create a new types file to centralize interfaces export interface Process { pid: number; ppid: number; name: string; cpu_usage: number; memory_usage: number; status: string; user: string; command: string; threads?: number; environ: string[]; root: string; virtual_memory: number; start_time: number; run_time: number; disk_usage: [number, number]; // [read_bytes, written_bytes] session_id?: number; } export interface SystemStats { cpu_usage: number[]; memory_total: number; memory_used: number; memory_free: number; memory_cached: number; uptime: number; load_avg: [number, number, number]; network_rx_bytes: number; network_tx_bytes: number; disk_total_bytes: number; disk_used_bytes: number; disk_free_bytes: number; } export interface Column { id: keyof Process; label: string; visible: boolean; required?: boolean; format?: (value: any) => string; } export interface Theme { name: string; label: string; colors: { base: string; mantle: string; crust: string; text: string; subtext0: string; subtext1: string; surface0: string; surface1: string; surface2: string; overlay0: string; overlay1: string; blue: string; lavender: string; sapphire: string; sky: string; red: string; maroon: string; peach: string; yellow: string; green: string; teal: string; }; } export interface AppConfig { appearance: { columnVisibility: Record; }; behavior: { itemsPerPage: number; refreshRate: number; defaultStatusFilter: string; }; } export interface ColumnDefinition { id: string; label: string; visible: boolean; required?: boolean; } export interface StatusOption { value: string; label: string; } export interface RefreshRateOption { value: number; label: string; } export interface ToolBarProps { searchTerm: string; statusFilter: string; itemsPerPage: number; currentPage: number; totalPages: number; totalResults: number; columns: ColumnDefinition[]; refreshRate: number; isFrozen: boolean; } export interface SortConfig { field: keyof Process; direction: "asc" | "desc"; } ================================================ FILE: src/lib/utils/index.ts ================================================ import type { Process } from "$lib/types"; import type { SortConfig } from "$lib/types"; export interface ProcessStatus { label: string; emoji: string; color: string; } export function formatMemorySize(bytes: number): string { const gb = bytes / (1024 * 1024 * 1024); return `${gb.toFixed(1)} GB`; } export function formatPercentage(value: number): string { return `${value.toFixed(1)}%`; } export function formatUptime(seconds: number): string { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); return `${days}d ${hours}h ${minutes}m`; } export function getUsageClass(percentage: number): string { if (percentage >= 90) return "critical"; if (percentage >= 60) return "high"; if (percentage >= 30) return "medium"; return "low"; } export function formatBytes(bytes: number): string { const units = ["B", "KB", "MB", "GB", "TB"]; let value = bytes; let unitIndex = 0; while (value >= 1024 && unitIndex < units.length - 1) { value /= 1024; unitIndex++; } return `${value.toFixed(1)} ${units[unitIndex]}`; } export function formatDate(timestamp: number) { return new Date(timestamp * 1000).toLocaleString(); } // Debounce utility to reduce frequency of expensive operations export function debounce any>( func: T, wait: number, ): (...args: Parameters) => void { let timeout: NodeJS.Timeout; return (...args: Parameters) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; } // Cache for compiled regex patterns const regexCache = new Map(); export function filterProcesses( processes: Process[], searchTerm: string, filters: { cpu: { operator: string; value: number; enabled: boolean }; ram: { operator: string; value: number; enabled: boolean }; runtime: { operator: string; value: number; enabled: boolean }; status: { values: string[]; enabled: boolean }; }, ): Process[] { // Early return for empty search and no active filters if ( searchTerm.length === 0 && !Object.values(filters).some((f) => f.enabled) ) { return processes; } // Pre-process search terms once const terms = searchTerm.length > 0 ? searchTerm.split(",").map((term) => term.trim()) : []; return processes.filter((process) => { // Apply status filter if (filters.status.enabled && filters.status.values.length > 0) { if (!filters.status.values.includes(process.status)) { return false; } } // Apply CPU filter if (filters.cpu.enabled) { const cpuValue = process.cpu_usage; if (!compareValue(cpuValue, filters.cpu.operator, filters.cpu.value)) { return false; } } // Apply RAM filter (convert bytes to MB) if (filters.ram.enabled) { const ramMB = process.memory_usage / (1024 * 1024); if (!compareValue(ramMB, filters.ram.operator, filters.ram.value)) { return false; } } // Apply runtime filter (convert to minutes) if (filters.runtime.enabled) { const runtimeMin = process.run_time / 60; if ( !compareValue( runtimeMin, filters.runtime.operator, filters.runtime.value, ) ) { return false; } } // Skip search if no terms if (terms.length === 0) { return true; } // Cache lowercase values const processNameLower = process.name.toLowerCase(); const processCommandLower = process.command.toLowerCase(); const processPidString = process.pid.toString(); // Check each term return terms.some((term) => { const termLower = term.toLowerCase(); // Try exact matches first (faster) if ( processNameLower.includes(termLower) || processCommandLower.includes(termLower) || processPidString.includes(term) ) { return true; } // Try regex match last (slower) try { let regex = regexCache.get(term); if (!regex) { regex = new RegExp(term, "i"); regexCache.set(term, regex); } return regex.test(process.name); } catch { return false; } }); }); } // Helper function to compare values based on operator function compareValue( value: number, operator: string, target: number, ): boolean { switch (operator) { case ">": return value > target; case "<": return value < target; case "=": return value === target; case ">=": return value >= target; case "<=": return value <= target; default: return true; } } // Create a Map for quick pinned status lookup const isPinned = new Map(); export function sortProcesses( processes: Process[], sortConfig: SortConfig, pinnedProcesses: Set, ): Process[] { // Clear the cache before sorting isPinned.clear(); return [...processes].sort((a, b) => { // Cache pinned status let aPin = pinnedProcesses.has(a.command); isPinned.set(a.command, aPin); let bPin = pinnedProcesses.has(b.command); isPinned.set(b.command, bPin); // Quick pin comparison if (aPin !== bPin) { return aPin ? -1 : 1; } // Only compute direction once const direction = sortConfig.direction === "asc" ? 1 : -1; const aValue = a[sortConfig.field]; const bValue = b[sortConfig.field]; // Special handling for disk_usage which is an array [read_bytes, written_bytes] if (sortConfig.field === "disk_usage") { const aRead = (aValue as [number, number])[0]; const aWrite = (aValue as [number, number])[1]; const bRead = (bValue as [number, number])[0]; const bWrite = (bValue as [number, number])[1]; // Smart sorting: analyze if this is a read-heavy or write-heavy comparison const totalReads = aRead + bRead; const totalWrites = aWrite + bWrite; if (totalWrites > totalReads * 1.5) { // Write-heavy scenario: prioritize writes, use reads as tiebreaker if (aWrite !== bWrite) { return direction * (aWrite - bWrite); } return direction * (aRead - bRead); } else if (totalReads > totalWrites * 1.5) { // Read-heavy scenario: prioritize reads, use writes as tiebreaker if (aRead !== bRead) { return direction * (aRead - bRead); } return direction * (aWrite - bWrite); } else { // Balanced I/O: sort by total, use max as tiebreaker const aTotalDisk = aRead + aWrite; const bTotalDisk = bRead + bWrite; if (aTotalDisk !== bTotalDisk) { return direction * (aTotalDisk - bTotalDisk); } // Tiebreaker: use the dominant operation const aMaxDisk = Math.max(aRead, aWrite); const bMaxDisk = Math.max(bRead, bWrite); return direction * (aMaxDisk - bMaxDisk); } } // Type-specific comparisons if (typeof aValue === "string") { return direction * aValue.localeCompare(bValue as string); } return direction * (Number(aValue) - Number(bValue)); }); } ================================================ FILE: src/routes/+layout.js ================================================ // Tauri doesn't have a Node.js server to do proper SSR // so we will use adapter-static to prerender the app (SSG) // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info export const prerender = true; export const ssr = false; ================================================ FILE: src/routes/+layout.svelte ================================================ ================================================ FILE: src/routes/+page.svelte ================================================ {#if isLoading}
{:else}
{#if systemStats} {/if} {#if error}
{error}
{/if}
{/if} ================================================ FILE: src-tauri/.cargo/config.toml ================================================ [target.x86_64-apple-darwin] rustflags = [ "-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup", ] [target.aarch64-apple-darwin] rustflags = [ "-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup", ] ================================================ FILE: src-tauri/.gitignore ================================================ # Generated by Cargo # will have compiled files and executables /target/ # Generated by Tauri # will have schema files for capabilities auto-completion /gen/schemas ================================================ FILE: src-tauri/Cargo.toml ================================================ [package] name = "neohtop" version = "1.2.0" description = "A cross-platform system monitor" authors = ["you"] edition = "2021" [build-dependencies] tauri-build = { version = "2", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "2.1.1", features = ["macos-private-api"] } sysinfo = { version = "0.35.2", features = ["disk", "multithread", "network", "system"] } tauri-plugin-shell = "2" tauri-plugin-os = "2" window-vibrancy = "0.5.2" [features] default = [ "custom-protocol" ] custom-protocol = [ "tauri/custom-protocol" ] [profile.release] panic = "abort" # Strip expensive panic clean-up logic codegen-units = 1 # Compile crates one after another so the compiler can optimize better lto = "fat" # More aggressive link-time optimization opt-level = 3 # Optimize for maximum performance strip = true # Remove debug symbols incremental = false # Disable incremental compilation ================================================ FILE: src-tauri/build.rs ================================================ fn main() { tauri_build::build() } ================================================ FILE: src-tauri/capabilities/default.json ================================================ { "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", "windows": [ "main" ], "permissions": [ "core:default", "core:app:allow-version", "core:window:allow-start-dragging", "core:window:allow-maximize", "core:window:allow-minimize", "core:window:allow-close", "shell:default" ] } ================================================ FILE: src-tauri/src/commands.rs ================================================ //! Tauri command handlers //! //! This module contains the command handlers that are exposed to the frontend //! through Tauri's IPC mechanism. These commands provide the interface between //! the frontend and the system monitoring functionality. use crate::monitoring::{ProcessInfo, ProcessMonitor, SystemStats}; use crate::state::AppState; use tauri::State; /// Retrieves the current list of processes and system statistics /// /// # Arguments /// /// * `state` - The application state containing system monitoring components /// /// # Returns /// /// A tuple containing: /// * A vector of process information /// * Current system statistics /// /// # Errors /// /// Returns an error string if: /// * Failed to acquire locks on system state /// * Failed to collect process information #[tauri::command] pub async fn get_processes( state: State<'_, AppState>, ) -> Result<(Vec, SystemStats), String> { let mut sys = state.sys.lock().map_err(|e| e.to_string())?; let mut disks = state.disks.lock().map_err(|e| e.to_string())?; let mut networks = state.networks.lock().map_err(|e| e.to_string())?; sys.refresh_all(); disks.refresh(true); networks.refresh(true); let mut process_monitor = state.process_monitor.lock().map_err(|e| e.to_string())?; let mut system_monitor = state.system_monitor.lock().map_err(|e| e.to_string())?; let processes = process_monitor.collect_processes(&sys)?; let system_stats = system_monitor.collect_stats(&sys, &networks, &disks); Ok((processes, system_stats)) } /// Attempts to kill a process with the specified PID /// /// # Arguments /// /// * `pid` - Process ID to kill /// * `state` - The application state /// /// # Returns /// /// * `true` if the process was successfully killed /// * `false` if the process couldn't be killed or wasn't found /// /// # Errors /// /// Returns an error string if failed to acquire lock on system state #[tauri::command] pub async fn kill_process(pid: u32, state: State<'_, AppState>) -> Result { let sys = state.sys.lock().map_err(|e| e.to_string())?; Ok(ProcessMonitor::kill_process(&sys, pid)) } ================================================ FILE: src-tauri/src/main.rs ================================================ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] //! NeoHtop - A modern system monitor built with Tauri //! //! This is the main entry point for the application. It sets up the Tauri //! application, initializes plugins, and configures window effects. mod commands; mod monitoring; mod state; mod ui; use state::AppState; use tauri::Manager; /// Main entry point for the application /// /// # Panics /// /// Will panic if: /// - Unable to create the main window /// - Failed to apply window effects /// - Failed to initialize the application state fn main() { #[cfg(target_os = "linux")] if std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland" { std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); } tauri::Builder::default() .setup(|app| { let window = app.get_webview_window("main").unwrap(); ui::setup_window_effects(&window).expect("Failed to apply window effects"); Ok(()) }) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_os::init()) .manage(AppState::new()) .invoke_handler(tauri::generate_handler![ commands::get_processes, commands::kill_process, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ================================================ FILE: src-tauri/src/monitoring/mod.rs ================================================ //! System monitoring functionality //! //! This module provides types and functionality for monitoring system resources //! and processes. It includes process monitoring, system statistics collection, //! and data structures for representing system state. mod process_monitor; mod system_monitor; mod types; pub use process_monitor::ProcessMonitor; pub use system_monitor::SystemMonitor; pub use types::*; // Re-export all types ================================================ FILE: src-tauri/src/monitoring/process_monitor.rs ================================================ //! Process monitoring functionality //! //! This module handles monitoring and managing system processes, including //! collecting process information and managing process lifecycle. use super::{ProcessData, ProcessInfo, ProcessStaticInfo}; use std::collections::HashMap; use std::ffi::OsString; use std::fmt::Debug; use std::time::{SystemTime, UNIX_EPOCH}; use sysinfo::ProcessStatus; fn os_string_vec_to_string_vec(v: &[OsString]) -> Vec { v.iter() .map(|c| c.to_string_lossy().into_owned()) .collect::>() } /// Monitors and manages system processes #[derive(Debug)] pub struct ProcessMonitor { /// Cache for static process information to avoid redundant allocations process_cache: HashMap, } impl ProcessMonitor { /// Creates a new process monitor instance pub fn new() -> Self { Self { process_cache: HashMap::new(), } } /// Collects information about all running processes /// /// # Arguments /// /// * `sys` - System information provider /// /// # Returns /// /// A vector of process information, or an error string if collection failed pub fn collect_processes(&mut self, sys: &sysinfo::System) -> Result, String> { let current_time = Self::get_current_time()?; let processes_data = self.collect_process_data(sys, current_time); Ok(self.build_process_info(processes_data)) } /// Attempts to kill a process /// /// # Arguments /// /// * `sys` - System information provider /// * `pid` - Process ID to kill /// /// # Returns /// /// Boolean indicating whether the process was successfully killed pub fn kill_process(sys: &sysinfo::System, pid: u32) -> bool { sys.process(sysinfo::Pid::from(pid as usize)) .map(|process| process.kill()) .unwrap_or(false) } /// Gets the current system time in seconds since UNIX epoch fn get_current_time() -> Result { SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .map_err(|e| format!("Failed to get system time: {}", e)) } /// Collects raw process data from the system fn collect_process_data(&self, sys: &sysinfo::System, current_time: u64) -> Vec { sys.processes() .iter() .map(|(pid, process)| { let start_time = process.start_time(); ProcessData { pid: pid.as_u32(), name: process.name().to_string_lossy().into_owned(), cmd: os_string_vec_to_string_vec(&process.cmd()), user_id: process.user_id().map(|uid| uid.to_string()), cpu_usage: process.cpu_usage(), memory: process.memory(), status: process.status(), ppid: process.parent().map(|p| p.as_u32()), environ: os_string_vec_to_string_vec(&process.environ()), root: process .root() .map(|p| p.to_string_lossy().into_owned()) .unwrap_or_default(), virtual_memory: process.virtual_memory(), start_time, run_time: if start_time > 0 { current_time.saturating_sub(start_time) } else { 0 }, disk_usage: process.disk_usage(), session_id: process.session_id().map(|id| id.as_u32()), } }) .collect() } /// Builds process information from raw process data fn build_process_info(&mut self, processes: Vec) -> Vec { processes .into_iter() .map(|data| { let cached_info = self.process_cache .entry(data.pid) .or_insert_with(|| ProcessStaticInfo { name: data.name.clone(), command: data.cmd.join(" "), user: data.user_id.unwrap_or_else(|| "-".to_string()), }); ProcessInfo { pid: data.pid, ppid: data.ppid.unwrap_or(0), name: cached_info.name.clone(), cpu_usage: data.cpu_usage, memory_usage: data.memory, status: Self::format_status(data.status), user: cached_info.user.clone(), command: cached_info.command.clone(), threads: None, environ: data.environ, root: data.root, virtual_memory: data.virtual_memory, start_time: data.start_time, run_time: data.run_time, disk_usage: (data.disk_usage.read_bytes, data.disk_usage.written_bytes), session_id: data.session_id, } }) .collect() } /// Formats process status into a human-readable string pub fn format_status(status: ProcessStatus) -> String { match status { ProcessStatus::Run => "Running", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Idle => "Idle", _ => "Unknown", } .to_string() } } #[cfg(test)] mod tests { use super::*; use sysinfo::System; /// Tests creation of a new process monitor #[test] fn test_process_monitor_creation() { let monitor = ProcessMonitor::new(); assert!(monitor.process_cache.is_empty()); } /// Tests process collection functionality #[test] fn test_process_collection() { let mut monitor = ProcessMonitor::new(); let mut sys = System::new(); sys.refresh_all(); let result = monitor.collect_processes(&sys); assert!(result.is_ok()); } } ================================================ FILE: src-tauri/src/monitoring/system_monitor.rs ================================================ //! System statistics monitoring //! //! This module handles collection and monitoring of system-wide statistics //! including CPU, memory, network, and disk usage. use super::SystemStats; use std::fmt::Debug; use std::path::Path; use std::time::Instant; use sysinfo::{Disk, Disks, Networks, System}; /// Monitors system-wide statistics #[derive(Debug)] pub struct SystemMonitor { /// Tracks network usage between updates last_network_update: (Instant, u64, u64), } impl SystemMonitor { /// Creates a new system monitor instance /// /// # Arguments /// /// * `sys` - System information provider for initial readings pub fn new(networks: &Networks) -> Self { let (initial_rx, initial_tx) = networks .iter() .fold((0, 0), |(initial_rx, initial_tx), (_, data)| { ( initial_rx + data.total_received(), initial_tx + data.total_transmitted(), ) }); Self { last_network_update: (Instant::now(), initial_rx, initial_tx), } } /// Collects current system statistics /// /// # Arguments /// /// * `sys` - System information provider pub fn collect_stats( &mut self, sys: &System, networks: &Networks, disks: &Disks, ) -> SystemStats { let (network_rx, network_tx) = self.calculate_network_stats(networks); let (disk_total, disk_used, disk_free) = self.calculate_disk_stats(disks); let load_average = System::load_average(); SystemStats { cpu_usage: sys.cpus().iter().map(|cpu| cpu.cpu_usage()).collect(), memory_total: sys.total_memory(), memory_used: sys.used_memory(), memory_free: sys.total_memory() - sys.used_memory(), memory_cached: sys.total_memory() - (sys.used_memory() + (sys.total_memory() - sys.used_memory())), uptime: System::uptime(), load_avg: [load_average.one, load_average.five, load_average.fifteen], network_rx_bytes: network_rx, network_tx_bytes: network_tx, disk_total_bytes: disk_total, disk_used_bytes: disk_used, disk_free_bytes: disk_free, } } /// Filters disks based on platform-specific criteria #[cfg(not(target_os = "windows"))] fn filter_disks(disks: &[Disk]) -> impl Iterator { disks .iter() .filter(|disk| disk.mount_point() == Path::new("/")) } /// Windows-specific disk filtering #[cfg(target_os = "windows")] fn filter_disks(disks: &[Disk]) -> impl Iterator { disks.iter() } /// Calculates network usage rates fn calculate_network_stats(&mut self, networks: &Networks) -> (u64, u64) { let (current_rx, current_tx) = networks .iter() .fold((0, 0), |(current_rx, current_tx), (_, data)| { ( current_rx + data.total_received(), current_tx + data.total_transmitted(), ) }); let elapsed = self.last_network_update.0.elapsed().as_secs_f64(); let rx_rate = ((current_rx - self.last_network_update.1) as f64 / elapsed) as u64; let tx_rate = ((current_tx - self.last_network_update.2) as f64 / elapsed) as u64; self.last_network_update = (Instant::now(), current_rx, current_tx); (rx_rate, tx_rate) } /// Calculates disk usage statistics and returns `(total, used, free)`. fn calculate_disk_stats(&self, disks: &Disks) -> (u64, u64, u64) { Self::filter_disks(disks).fold((0, 0, 0), |(total, used, free), disk| { ( total + disk.total_space(), used + (disk.total_space() - disk.available_space()), free + disk.available_space(), ) }) } } #[cfg(test)] mod tests { use super::*; use sysinfo::System; /// Tests creation of system monitor #[test] fn test_system_monitor_creation() { let networks = Networks::new(); let monitor = SystemMonitor::new(&networks); assert!(monitor.last_network_update.1 >= 0); assert!(monitor.last_network_update.2 >= 0); } /// Tests system statistics collection #[test] fn test_stats_collection() { let mut networks = Networks::new(); let mut monitor = SystemMonitor::new(&networks); networks.refresh(true); let sys = System::new_all(); let disks = Disks::new_with_refreshed_list(); let stats = monitor.collect_stats(&sys, &networks, &disks); assert!(!stats.cpu_usage.is_empty()); assert!(stats.memory_total > 0); } } ================================================ FILE: src-tauri/src/monitoring/types.rs ================================================ use serde::Serialize; use std::fmt::Debug; use sysinfo::{DiskUsage, ProcessStatus}; /// Internal representation of process data collected from the system /// This struct is used internally and not exposed directly to the frontend #[derive(Clone, Debug)] pub(crate) struct ProcessData { /// Process ID pub pid: u32, /// Name of the process pub name: String, /// Complete command line arguments pub cmd: Vec, /// User ID that owns the process pub user_id: Option, /// CPU usage as percentage (0-100) pub cpu_usage: f32, /// Physical memory usage in bytes pub memory: u64, /// Current process status pub status: ProcessStatus, /// Parent process ID pub ppid: Option, /// Environment variables pub environ: Vec, /// Root directory of the process pub root: String, /// Virtual memory usage in bytes pub virtual_memory: u64, /// Process start time (Unix timestamp) pub start_time: u64, /// Process running time in seconds pub run_time: u64, /// Disk I/O statistics pub disk_usage: DiskUsage, /// Session ID of the process pub session_id: Option, } /// Static information about a process that doesn't change frequently /// Used for caching purposes to avoid frequent updates of stable data #[derive(Clone, Debug)] pub struct ProcessStaticInfo { /// Process name pub name: String, /// Full command string pub command: String, /// Username of the process owner pub user: String, } /// Process information exposed to the frontend via Tauri /// Contains formatted and filtered process data for UI consumption #[derive(Serialize, Debug)] pub struct ProcessInfo { /// Process ID pub pid: u32, /// Parent process ID pub ppid: u32, /// Process name pub name: String, /// CPU usage as percentage (0-100) pub cpu_usage: f32, /// Physical memory usage in bytes pub memory_usage: u64, /// Process status as string pub status: String, /// Username of the process owner pub user: String, /// Full command string pub command: String, /// Number of threads (if available) pub threads: Option, /// Environment variables pub environ: Vec, /// Root directory of the process pub root: String, /// Virtual memory usage in bytes pub virtual_memory: u64, /// Process start time (Unix timestamp) pub start_time: u64, /// Process running time in seconds pub run_time: u64, /// Disk I/O statistics (read bytes, written bytes) pub disk_usage: (u64, u64), /// Session ID of the process pub session_id: Option, } /// System-wide statistics exposed to the frontend /// Provides overall system resource usage and performance metrics #[derive(Serialize, Debug)] pub struct SystemStats { /// CPU usage per core as percentage (0-100) pub cpu_usage: Vec, /// Total physical memory in bytes pub memory_total: u64, /// Used physical memory in bytes pub memory_used: u64, /// Free physical memory in bytes pub memory_free: u64, /// Cached memory in bytes pub memory_cached: u64, /// System uptime in seconds pub uptime: u64, /// Load averages for 1, 5, and 15 minutes pub load_avg: [f64; 3], /// Total bytes received over network pub network_rx_bytes: u64, /// Total bytes transmitted over network pub network_tx_bytes: u64, /// Total disk space in bytes pub disk_total_bytes: u64, /// Used disk space in bytes pub disk_used_bytes: u64, /// Free disk space in bytes pub disk_free_bytes: u64, } ================================================ FILE: src-tauri/src/state.rs ================================================ //! Application state management //! //! This module handles the global application state, including system monitoring //! and process tracking capabilities. use crate::monitoring::{ProcessMonitor, SystemMonitor}; use std::sync::Mutex; use sysinfo::{Disks, Networks, System}; /// Global application state /// /// Maintains thread-safe access to system information and monitoring components #[derive(Debug)] pub struct AppState { /// System information handler pub sys: Mutex, /// Networks information handler pub networks: Mutex, /// Networks information handler pub disks: Mutex, /// Process monitoring component pub process_monitor: Mutex, /// System statistics monitoring component pub system_monitor: Mutex, } impl AppState { /// Creates a new instance of the application state /// /// Initializes system monitoring and process tracking components /// /// # Returns /// /// A new `AppState` instance with initialized monitors pub fn new() -> Self { let sys = System::new_all(); let disks = Disks::new_with_refreshed_list(); let networks = Networks::new_with_refreshed_list(); Self { process_monitor: Mutex::new(ProcessMonitor::new()), system_monitor: Mutex::new(SystemMonitor::new(&networks)), sys: Mutex::new(sys), disks: Mutex::new(disks), networks: Mutex::new(networks), } } } ================================================ FILE: src-tauri/src/ui/mod.rs ================================================ //! User interface functionality //! //! This module handles UI-specific functionality, including window effects //! and platform-specific visual customizations. mod window; pub use window::setup_window_effects; ================================================ FILE: src-tauri/src/ui/window.rs ================================================ //! Window effects and customization //! //! Provides platform-specific window effects like transparency and vibrancy. use tauri::WebviewWindow; #[cfg(target_os = "windows")] use window_vibrancy::apply_acrylic; #[cfg(target_os = "macos")] use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial, NSVisualEffectState}; /// Applies Windows-specific window effects #[cfg(target_os = "windows")] pub fn setup_window_effects(window: &WebviewWindow) -> Result<(), Box> { apply_acrylic(window, Some((0, 0, 25, 125)))?; Ok(()) } /// Applies macOS-specific window effects #[cfg(target_os = "macos")] pub fn setup_window_effects(window: &WebviewWindow) -> Result<(), Box> { apply_vibrancy( window, NSVisualEffectMaterial::HudWindow, Some(NSVisualEffectState::Active), None, )?; Ok(()) } /// No-op for platforms without specific window effects #[cfg(not(any(target_os = "windows", target_os = "macos")))] pub fn setup_window_effects(_window: &WebviewWindow) -> Result<(), Box> { Ok(()) } ================================================ FILE: src-tauri/tauri.conf.json ================================================ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "build": { "beforeBuildCommand": "npm run build", "beforeDevCommand": "npm run dev", "frontendDist": "../build", "devUrl": "http://localhost:1420" }, "bundle": { "active": true, "icon": [ "icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico" ], "macOS": { "hardenedRuntime": true, "entitlements": null, "providerShortName": null }, "targets": ["app", "dmg"] }, "productName": "NeoHtop", "mainBinaryName": "NeoHtop", "version": "1.2.0", "identifier": "com.neohtop.dev", "plugins": { "os": { "all": true } }, "app": { "macOSPrivateApi": true, "windows": [ { "fullscreen": false, "theme": "Dark", "height": 900, "resizable": true, "title": "NeoHtop", "width": 1280, "minWidth": 1120, "minHeight": 700, "titleBarStyle": "Overlay", "hiddenTitle": true, "transparent": true } ], "security": { "csp": null } } } ================================================ FILE: svelte.config.js ================================================ // Tauri doesn't have a Node.js server to do proper SSR // so we will use adapter-static to prerender the app (SSG) // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info import adapter from "@sveltejs/adapter-static"; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter(), alias: { $lib: 'src/lib' } }, preprocess: vitePreprocess() }; export default config; ================================================ FILE: vite.config.js ================================================ import { defineConfig } from "vite"; import { sveltekit } from "@sveltejs/kit/vite"; const host = process.env.TAURI_DEV_HOST; // https://vitejs.dev/config/ export default defineConfig(async () => ({ plugins: [sveltekit()], resolve: { alias: { $lib: "/src/lib", }, }, // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // // 1. prevent vite from obscuring rust errors clearScreen: false, // 2. tauri expects a fixed port, fail if that port is not available server: { port: 1420, strictPort: true, host: host || false, hmr: host ? { protocol: "ws", host, port: 1421, } : undefined, watch: { // 3. tell vite to ignore watching `src-tauri` ignored: ["**/src-tauri/**"], }, }, }));