├── .github
├── CODEOWNERS
├── CONTRIBUTING.md
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
├── pull_request_template.md
├── thumbnail.png
└── 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
├── app-icon.png
├── docs
├── favicon.ico
├── index.html
├── main.js
└── styles.css
├── jsconfig.json
├── package-lock.json
├── package.json
├── screenshot-light.png
├── screenshot.png
├── src-tauri
├── .cargo
│ └── config.toml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── capabilities
│ └── default.json
├── icons
│ ├── 128x128.png
│ ├── 128x128@2x.png
│ ├── 32x32.png
│ ├── Square107x107Logo.png
│ ├── Square142x142Logo.png
│ ├── Square150x150Logo.png
│ ├── Square284x284Logo.png
│ ├── Square30x30Logo.png
│ ├── Square310x310Logo.png
│ ├── Square44x44Logo.png
│ ├── Square71x71Logo.png
│ ├── Square89x89Logo.png
│ ├── StoreLogo.png
│ ├── android
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ └── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ ├── icon.icns
│ ├── icon.ico
│ ├── icon.png
│ └── ios
│ │ ├── AppIcon-20x20@1x.png
│ │ ├── AppIcon-20x20@2x-1.png
│ │ ├── AppIcon-20x20@2x.png
│ │ ├── AppIcon-20x20@3x.png
│ │ ├── AppIcon-29x29@1x.png
│ │ ├── AppIcon-29x29@2x-1.png
│ │ ├── AppIcon-29x29@2x.png
│ │ ├── AppIcon-29x29@3x.png
│ │ ├── AppIcon-40x40@1x.png
│ │ ├── AppIcon-40x40@2x-1.png
│ │ ├── AppIcon-40x40@2x.png
│ │ ├── AppIcon-40x40@3x.png
│ │ ├── AppIcon-512@2x.png
│ │ ├── AppIcon-60x60@2x.png
│ │ ├── AppIcon-60x60@3x.png
│ │ ├── AppIcon-76x76@1x.png
│ │ ├── AppIcon-76x76@2x.png
│ │ └── AppIcon-83.5x83.5@2x.png
├── 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
├── 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
│ │ │ ├── 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
│ │ ├── processes.ts
│ │ ├── settings.ts
│ │ └── theme.ts
│ ├── types
│ │ └── index.ts
│ └── utils
│ │ └── index.ts
└── routes
│ ├── +layout.js
│ ├── +layout.svelte
│ └── +page.svelte
├── static
├── 128x128.png
├── 32x32.png
├── favicon.png
├── svelte.svg
├── tauri.svg
└── vite.svg
├── svelte.config.js
└── vite.config.js
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @Abdenasser
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to NeoHtop
2 |
3 | Thank you for considering contributing to NeoHtop! We welcome contributions from the community.
4 |
5 | ## How to Contribute
6 |
7 | 1. Fork the repository.
8 | 2. Create a new branch (`git checkout -b feature/YourFeature`).
9 | 3. Make your changes.
10 | 4. Commit your changes (`git commit -m 'Add some feature'`).
11 | 5. Push to the branch (`git push origin feature/YourFeature`).
12 | 6. Open a pull request.
13 |
14 | ## Code of Conduct
15 |
16 | 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.
17 |
18 | ## Reporting Bugs
19 |
20 | Please use the [bug report template](./ISSUE_TEMPLATE/bug_report.md) to report any bugs you find.
21 |
22 | ## Requesting Features
23 |
24 | Please use the [feature request template](./ISSUE_TEMPLATE/feature_request.md) to suggest new features.
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: abdenasser
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. macOS]
28 | - Version [e.g. 22]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Support
4 | url: https://github.com/Abdenasser/neohtop/discussions
5 | about: Please use discussions for questions and support.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | Please include a summary of the changes and the related issue. Please also include relevant motivation and context.
4 |
5 | Fixes # (issue)
6 |
7 | ## Type of change
8 |
9 | Please delete options that are not relevant.
10 |
11 | - [ ] Bug fix (non-breaking change which fixes an issue)
12 | - [ ] New feature (non-breaking change which adds functionality)
13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
14 | - [ ] This change requires a documentation update
15 |
16 | ## How Has This Been Tested?
17 |
18 | 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.
19 |
20 | - [ ] Test A
21 | - [ ] Test B
22 |
23 | ## Checklist:
24 |
25 | - [ ] My code follows the style guidelines of this project
26 | - [ ] I have performed a self-review of my code
27 | - [ ] I have commented my code, particularly in hard-to-understand areas
28 | - [ ] I have made corresponding changes to the documentation
29 | - [ ] My changes generate no new warnings
30 | - [ ] I have added tests that prove my fix is effective or that my feature works
31 | - [ ] New and existing unit tests pass locally with my changes
32 | - [ ] Any dependent changes have been merged and published in downstream modules
--------------------------------------------------------------------------------
/.github/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/.github/thumbnail.png
--------------------------------------------------------------------------------
/.github/workflows/build-check.yml:
--------------------------------------------------------------------------------
1 | name: Build Check
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 | paths:
7 | - "src-tauri/**"
8 | - ".github/workflows/**"
9 |
10 | env:
11 | CARGO_TERM_COLOR: always
12 | CARGO_INCREMENTAL: 1
13 | CARGO_NET_RETRY: 10
14 | RUSTUP_MAX_RETRIES: 10
15 | RUST_BACKTRACE: 1
16 | RUSTC_WRAPPER: sccache
17 | CARGO_BUILD_JOBS: 2
18 |
19 | jobs:
20 | build:
21 | name: Build Check
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - uses: actions/checkout@v4
26 |
27 | - name: Setup Node.js
28 | uses: actions/setup-node@v4
29 | with:
30 | node-version: "lts/*"
31 | cache: "npm"
32 |
33 | - name: Cache Linux Dependencies
34 | id: cache-apt
35 | uses: actions/cache@v3
36 | with:
37 | path: |
38 | /var/cache/apt/archives/*.deb
39 | /var/lib/apt/lists/*
40 | key: ${{ runner.os }}-apt-${{ hashFiles('**/package.json', '**/Cargo.lock') }}
41 | restore-keys: |
42 | ${{ runner.os }}-apt-
43 |
44 | - name: Add Ubuntu Jammy repo for WebKitGTK 4.0
45 | run: |
46 | echo "deb http://archive.ubuntu.com/ubuntu jammy main universe" | sudo tee -a /etc/apt/sources.list
47 | sudo apt update
48 |
49 | - name: Install Linux Dependencies
50 | run: |
51 | sudo rm -rf /var/cache/apt/archives/lock
52 | sudo rm -rf /var/cache/apt/archives/partial
53 | sudo rm -rf /var/lib/apt/lists/lock
54 | sudo rm -rf /var/lib/apt/lists/partial
55 | sudo apt-get update
56 | sudo apt-get install --no-install-recommends -y \
57 | build-essential \
58 | pkg-config \
59 | libgtk-3-dev \
60 | libayatana-appindicator3-dev \
61 | librsvg2-dev \
62 | libglib2.0-dev \
63 | libjavascriptcoregtk-4.0-dev \
64 | libsoup-3.0-dev \
65 | libwebkit2gtk-4.1-dev
66 |
67 | - name: Remove Jammy repo
68 | run: |
69 | sudo sed -i '/jammy main universe/d' /etc/apt/sources.list
70 | sudo apt update
71 |
72 | - name: Install Rust
73 | uses: dtolnay/rust-toolchain@stable
74 | with:
75 | components: cargo
76 | target: x86_64-unknown-linux-gnu
77 |
78 | - name: Install sccache
79 | run: |
80 | SCCACHE_VERSION=v0.7.7
81 | curl -L "https://github.com/mozilla/sccache/releases/download/${SCCACHE_VERSION}/sccache-${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" | tar xz
82 | sudo mv sccache-*/sccache /usr/local/bin/sccache
83 | echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
84 |
85 | - uses: Swatinem/rust-cache@v2
86 | with:
87 | workspaces: "./src-tauri -> target"
88 | shared-key: "build"
89 |
90 | - name: Install Dependencies
91 | run: npm ci
92 |
93 | - name: Build Application
94 | run: |
95 | npm run tauri build -- \
96 | --target x86_64-unknown-linux-gnu \
97 | --bundles deb \
98 | --ci
99 |
--------------------------------------------------------------------------------
/.github/workflows/format-check.yml:
--------------------------------------------------------------------------------
1 | name: Format Check
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | jobs:
8 | format:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 |
13 | - name: Setup Node
14 | uses: actions/setup-node@v4
15 | with:
16 | node-version: 'lts/*'
17 |
18 | - name: Setup Rust
19 | uses: dtolnay/rust-toolchain@stable
20 |
21 | - name: Install dependencies
22 | run: npm ci
23 |
24 | - name: Check formatting
25 | run: npm run format:check
26 |
--------------------------------------------------------------------------------
/.github/workflows/linux-aarch64-nightly.yml:
--------------------------------------------------------------------------------
1 | name: Linux (aarch64) Nightly Build
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | release_upload_url:
7 | description: "Release upload URL"
8 | required: true
9 |
10 | env:
11 | CARGO_TERM_COLOR: always
12 | PKG_CONFIG_ALLOW_CROSS: 1
13 | PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig
14 | PKG_CONFIG: /usr/bin/aarch64-linux-gnu-pkg-config
15 |
16 | jobs:
17 | build:
18 | name: Build Linux aarch64 Packages
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 |
24 | - name: Setup Node.js
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version: "lts/*"
28 | cache: "npm"
29 |
30 | - name: Install Rust
31 | uses: dtolnay/rust-toolchain@stable
32 | with:
33 | targets: aarch64-unknown-linux-gnu
34 |
35 | - name: Configure ARM64 repositories
36 | run: |
37 | sudo dpkg --add-architecture arm64
38 | # Remove all existing sources
39 | sudo rm -rf /etc/apt/sources.list.d/*
40 | sudo truncate -s 0 /etc/apt/sources.list
41 | # Add only ports.ubuntu.com repository
42 | sudo tee /etc/apt/sources.list << EOF
43 | deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse
44 | deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse
45 | deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse
46 | deb [arch=amd64] http://azure.archive.ubuntu.com/ubuntu jammy main restricted universe multiverse
47 | deb [arch=amd64] http://azure.archive.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse
48 | deb [arch=amd64] http://azure.archive.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
49 | EOF
50 | sudo apt-get update
51 | # Install required packages including cross-compilation tools
52 | sudo apt-get install -y \
53 | build-essential \
54 | pkg-config \
55 | crossbuild-essential-arm64 \
56 | gcc-aarch64-linux-gnu \
57 | g++-aarch64-linux-gnu \
58 | libgtk-3-dev:arm64 \
59 | libayatana-appindicator3-dev:arm64 \
60 | librsvg2-dev:arm64 \
61 | libglib2.0-dev:arm64 \
62 | libjavascriptcoregtk-4.0-dev:arm64 \
63 | libsoup-3.0-dev:arm64 \
64 | libwebkit2gtk-4.1-dev:arm64 \
65 | libssl-dev:arm64 \
66 | libssl-dev \
67 | openssl:arm64
68 | # Configure pkg-config for cross-compilation
69 | echo "PKG_CONFIG=/usr/bin/aarch64-linux-gnu-pkg-config" >> $GITHUB_ENV
70 | echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV
71 |
72 | - name: Install Dependencies
73 | run: npm install
74 |
75 | - name: Setup cross-compilation environment
76 | run: |
77 | sudo apt-get install -y \
78 | crossbuild-essential-arm64 \
79 | pkg-config \
80 | libssl-dev:arm64 \
81 | libssl-dev \
82 | openssl:arm64 \
83 | file \
84 | desktop-file-utils \
85 | libfuse2 \
86 | qemu-user-static
87 |
88 | # Setup pkg-config
89 | sudo tee /usr/bin/aarch64-linux-gnu-pkg-config << 'EOF'
90 | #!/bin/sh
91 | export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
92 | exec pkg-config "$@"
93 | EOF
94 | sudo chmod +x /usr/bin/aarch64-linux-gnu-pkg-config
95 |
96 | # Create .cargo/config
97 | mkdir -p .cargo
98 | cat > .cargo/config << EOF
99 | [target.aarch64-unknown-linux-gnu]
100 | linker = "aarch64-linux-gnu-gcc"
101 | ar = "aarch64-linux-gnu-ar"
102 | EOF
103 |
104 | # Download and setup appimagetool for ARM64
105 | wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage
106 | chmod +x appimagetool-aarch64.AppImage
107 | sudo mv appimagetool-aarch64.AppImage /usr/local/bin/appimagetool
108 |
109 | # Set environment variables
110 | echo "PKG_CONFIG=/usr/bin/aarch64-linux-gnu-pkg-config" >> $GITHUB_ENV
111 | echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV
112 | echo "OPENSSL_DIR=/usr" >> $GITHUB_ENV
113 | echo "OPENSSL_INCLUDE_DIR=/usr/include/aarch64-linux-gnu" >> $GITHUB_ENV
114 | echo "OPENSSL_LIB_DIR=/usr/lib/aarch64-linux-gnu" >> $GITHUB_ENV
115 | echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
116 | echo "APPIMAGE_EXTRACT_AND_RUN=1" >> $GITHUB_ENV
117 |
118 | - name: Build Frontend
119 | run: npm run build
120 |
121 | - name: Build AppImage
122 | run: |
123 | echo "Building AppImage for aarch64..."
124 | npm run tauri build -- --target aarch64-unknown-linux-gnu --bundles appimage
125 | cd src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/appimage/
126 | for f in *.AppImage; do
127 | echo "AARCH64_APPIMAGE_PATH=src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/appimage/$f" >> $GITHUB_ENV
128 | done
129 |
130 | - name: Build Debian Package
131 | run: |
132 | echo "Building Debian package for aarch64..."
133 | npm run tauri build -- --target aarch64-unknown-linux-gnu --bundles deb
134 | cd src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/deb/
135 | for f in *.deb; do
136 | echo "AARCH64_DEB_PATH=src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/deb/$f" >> $GITHUB_ENV
137 | done
138 |
139 | - name: Build RPM Package
140 | run: |
141 | echo "Building RPM package for aarch64..."
142 | npm run tauri build -- --target aarch64-unknown-linux-gnu --bundles rpm
143 | cd src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/rpm/
144 | for f in *.rpm; do
145 | echo "AARCH64_RPM_PATH=src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/rpm/$f" >> $GITHUB_ENV
146 | done
147 |
148 | - name: Get version from package.json
149 | id: version
150 | run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
151 |
152 | - name: Upload AppImage to Release
153 | if: github.event.inputs.release_upload_url != ''
154 | uses: actions/upload-release-asset@v1
155 | env:
156 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
157 | with:
158 | upload_url: ${{ github.event.inputs.release_upload_url }}
159 | asset_path: ${{ env.AARCH64_APPIMAGE_PATH }}
160 | asset_name: NeoHtop_${{ steps.version.outputs.version }}_aarch64.AppImage
161 | asset_content_type: application/x-executable
162 |
163 | - name: Upload Debian Package to Release
164 | if: github.event.inputs.release_upload_url != ''
165 | uses: actions/upload-release-asset@v1
166 | env:
167 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
168 | with:
169 | upload_url: ${{ github.event.inputs.release_upload_url }}
170 | asset_path: ${{ env.AARCH64_DEB_PATH }}
171 | asset_name: NeoHtop_${{ steps.version.outputs.version }}_aarch64.deb
172 | asset_content_type: application/vnd.debian.binary-package
173 |
174 | - name: Upload RPM Package to Release
175 | if: github.event.inputs.release_upload_url != ''
176 | uses: actions/upload-release-asset@v1
177 | env:
178 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
179 | with:
180 | upload_url: ${{ github.event.inputs.release_upload_url }}
181 | asset_path: ${{ env.AARCH64_RPM_PATH }}
182 | asset_name: NeoHtop_${{ steps.version.outputs.version }}_aarch64.rpm
183 | asset_content_type: application/x-rpm
184 |
--------------------------------------------------------------------------------
/.github/workflows/linux-x86_64-nightly.yml:
--------------------------------------------------------------------------------
1 | name: Linux (x86_64) Nightly Build
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | release_upload_url:
7 | description: "Release upload URL"
8 | required: true
9 |
10 | env:
11 | CARGO_TERM_COLOR: always
12 |
13 | jobs:
14 | build:
15 | name: Build Linux Packages
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 |
21 | - name: Setup Node.js
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: "lts/*"
25 | cache: "npm"
26 |
27 | - name: Install Rust
28 | uses: dtolnay/rust-toolchain@stable
29 |
30 | - name: Add Ubuntu Jammy repo for WebKitGTK 4.0
31 | run: |
32 | echo "deb http://archive.ubuntu.com/ubuntu jammy main universe" | sudo tee -a /etc/apt/sources.list
33 | sudo apt update
34 |
35 | - name: Install Linux Dependencies
36 | run: |
37 | sudo apt-get update
38 | sudo apt-get install -y \
39 | build-essential \
40 | pkg-config \
41 | libgtk-3-dev \
42 | libayatana-appindicator3-dev \
43 | librsvg2-dev \
44 | libglib2.0-dev \
45 | libjavascriptcoregtk-4.0-dev \
46 | libsoup-3.0-dev \
47 | libwebkit2gtk-4.1-dev
48 |
49 | - name: Remove Jammy repo
50 | run: |
51 | sudo sed -i '/jammy main universe/d' /etc/apt/sources.list
52 | sudo apt update
53 |
54 | - name: Install Dependencies
55 | run: |
56 | npm install
57 |
58 | - name: Build Frontend
59 | run: npm run build
60 |
61 | - name: Build AppImage (x86_64)
62 | run: |
63 | echo "Building AppImage for x86_64..."
64 | npm run tauri build -- --target x86_64-unknown-linux-gnu --bundles appimage
65 | cd src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/
66 | for f in *.AppImage; do
67 | echo "APPIMAGE_PATH=src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/$f" >> $GITHUB_ENV
68 | done
69 |
70 | - name: Build Debian Package (x86_64)
71 | run: |
72 | echo "Building Debian package for x86_64..."
73 | npm run tauri build -- --target x86_64-unknown-linux-gnu --bundles deb
74 | cd src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/
75 | for f in *.deb; do
76 | echo "DEB_PATH=src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/$f" >> $GITHUB_ENV
77 | done
78 |
79 | - name: Build RPM Package (x86_64)
80 | run: |
81 | echo "Building RPM package for x86_64..."
82 | npm run tauri build -- --target x86_64-unknown-linux-gnu --bundles rpm
83 | cd src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/
84 | for f in *.rpm; do
85 | echo "RPM_PATH=src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/$f" >> $GITHUB_ENV
86 | done
87 |
88 | - name: Get version from package.json
89 | id: version
90 | run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
91 |
92 | - name: Upload AppImage to Release
93 | if: github.event.inputs.release_upload_url != ''
94 | uses: actions/upload-release-asset@v1
95 | env:
96 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
97 | with:
98 | upload_url: ${{ github.event.inputs.release_upload_url }}
99 | asset_path: ${{ env.APPIMAGE_PATH }}
100 | asset_name: NeoHtop_${{ steps.version.outputs.version }}_x86_64.AppImage
101 | asset_content_type: application/x-executable
102 |
103 | - name: Upload Debian Package to Release
104 | if: github.event.inputs.release_upload_url != ''
105 | uses: actions/upload-release-asset@v1
106 | env:
107 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
108 | with:
109 | upload_url: ${{ github.event.inputs.release_upload_url }}
110 | asset_path: ${{ env.DEB_PATH }}
111 | asset_name: NeoHtop_${{ steps.version.outputs.version }}_x86_64.deb
112 | asset_content_type: application/vnd.debian.binary-package
113 |
114 | - name: Upload RPM Package to Release
115 | if: github.event.inputs.release_upload_url != ''
116 | uses: actions/upload-release-asset@v1
117 | env:
118 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
119 | with:
120 | upload_url: ${{ github.event.inputs.release_upload_url }}
121 | asset_path: ${{ env.RPM_PATH }}
122 | asset_name: NeoHtop_${{ steps.version.outputs.version }}_x86_64.rpm
123 | asset_content_type: application/x-rpm
124 |
--------------------------------------------------------------------------------
/.github/workflows/macos-nightly.yml:
--------------------------------------------------------------------------------
1 | name: MacOS (Intel/Apple Silicon) Nightly Build
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | release_upload_url:
7 | description: 'Release upload URL'
8 | required: true
9 |
10 | env:
11 | CARGO_TERM_COLOR: always
12 |
13 | jobs:
14 | build:
15 | name: Build MacOS Apps
16 | runs-on: macos-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 |
21 | - name: Setup Node.js
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: 'lts/*'
25 | cache: 'npm'
26 |
27 | - name: Install Rust
28 | uses: dtolnay/rust-toolchain@stable
29 |
30 | - name: Install Dependencies
31 | run: |
32 | rustup target add x86_64-apple-darwin
33 | rustup target add aarch64-apple-darwin
34 | npm install
35 |
36 | - name: Set up keychain
37 | run: |
38 | security create-keychain -p "" build.keychain
39 | security default-keychain -s build.keychain
40 | security unlock-keychain -p "" build.keychain
41 | echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/certificate.p12
42 | security import /tmp/certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
43 | security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
44 | env:
45 | MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
46 | MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
47 |
48 | - name: Build Frontend
49 | run: npm run build
50 |
51 | - name: Build for Intel Mac
52 | run: |
53 | echo "Building for Intel Mac..."
54 | npm run tauri build -- --target x86_64-apple-darwin --bundles dmg --config "{\"bundle\":{\"macOS\":{\"signingIdentity\": \"Developer ID Application: Abdenasser Elidrissi (785JV74B9Y)\"}}}"
55 | # Rename the Intel build and store the filename
56 | cd src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/
57 | for f in *.dmg; do
58 | mv "$f" "intel-$f"
59 | echo "INTEL_DMG_PATH=src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/intel-$f" >> $GITHUB_ENV
60 | done
61 | env:
62 | APPLE_ID: ${{ secrets.APPLE_ID }}
63 | APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
64 | APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
65 |
66 | - name: Build for Apple Silicon
67 | run: |
68 | echo "Building for aarch64..."
69 | npm run tauri build -- --target aarch64-apple-darwin --bundles dmg --config "{\"bundle\":{\"macOS\":{\"signingIdentity\": \"Developer ID Application: Abdenasser Elidrissi (785JV74B9Y)\"}}}"
70 | # Rename the Apple Silicon build and store the filename
71 | cd src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/
72 | for f in *.dmg; do
73 | mv "$f" "silicon-$f"
74 | echo "SILICON_DMG_PATH=src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/silicon-$f" >> $GITHUB_ENV
75 | done
76 | env:
77 | APPLE_ID: ${{ secrets.APPLE_ID }}
78 | APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
79 | APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
80 |
81 | - name: Get version from package.json
82 | id: version
83 | run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
84 |
85 | - name: Upload Intel Build to Release
86 | if: github.event.inputs.release_upload_url != ''
87 | uses: actions/upload-release-asset@v1
88 | env:
89 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
90 | with:
91 | upload_url: ${{ github.event.inputs.release_upload_url }}
92 | asset_path: ${{ env.INTEL_DMG_PATH }}
93 | asset_name: intel-NeoHtop_${{ steps.version.outputs.version }}_x64.dmg
94 | asset_content_type: application/x-apple-diskimage
95 |
96 | - name: Upload Silicon Build to Release
97 | if: github.event.inputs.release_upload_url != ''
98 | uses: actions/upload-release-asset@v1
99 | env:
100 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
101 | with:
102 | upload_url: ${{ github.event.inputs.release_upload_url }}
103 | asset_path: ${{ env.SILICON_DMG_PATH }}
104 | asset_name: silicon-NeoHtop_${{ steps.version.outputs.version }}_aarch64.dmg
105 | asset_content_type: application/x-apple-diskimage
--------------------------------------------------------------------------------
/.github/workflows/test-release.yml:
--------------------------------------------------------------------------------
1 | name: Test Release Build
2 |
3 | on:
4 | workflow_dispatch: # Manual trigger
5 |
6 | jobs:
7 | create-draft:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 |
12 | - name: Get version from package.json
13 | id: version
14 | run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
15 |
16 | - name: Create Draft Release
17 | id: create_release
18 | uses: softprops/action-gh-release@v1
19 | with:
20 | name: "NeoHtop v${{ steps.version.outputs.version }}"
21 | tag_name: "v${{ steps.version.outputs.version }}"
22 | draft: true
23 | env:
24 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
25 |
26 | - name: Trigger MacOS Build
27 | uses: benc-uk/workflow-dispatch@v1
28 | with:
29 | workflow: macos-nightly.yml
30 | token: ${{ secrets.PAT_TOKEN }}
31 | inputs: '{"release_upload_url": "${{ steps.create_release.outputs.upload_url }}"}'
32 |
33 | - name: Trigger Windows Build
34 | uses: benc-uk/workflow-dispatch@v1
35 | with:
36 | workflow: windows-nightly.yml
37 | token: ${{ secrets.PAT_TOKEN }}
38 | inputs: '{"release_upload_url": "${{ steps.create_release.outputs.upload_url }}"}'
39 |
40 | - name: Trigger Linux x86_64 Build
41 | uses: benc-uk/workflow-dispatch@v1
42 | with:
43 | workflow: linux-x86_64-nightly.yml
44 | token: ${{ secrets.PAT_TOKEN }}
45 | inputs: '{"release_upload_url": "${{ steps.create_release.outputs.upload_url }}"}'
46 |
--------------------------------------------------------------------------------
/.github/workflows/windows-nightly.yml:
--------------------------------------------------------------------------------
1 | name: Windows (x86_64) Nightly Build
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | release_upload_url:
7 | description: 'Release upload URL'
8 | required: true
9 |
10 | env:
11 | CARGO_TERM_COLOR: always
12 |
13 | jobs:
14 | build:
15 | name: Build Windows Executable
16 | runs-on: windows-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 |
21 | - name: Setup Node.js
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: 'lts/*'
25 | cache: 'npm'
26 |
27 | - name: Install Rust
28 | uses: dtolnay/rust-toolchain@stable
29 |
30 | - name: Install WebView2
31 | run: |
32 | $WebView2InstallPath = "$env:TEMP\MicrosoftEdgeWebview2Setup.exe"
33 | Invoke-WebRequest "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile $WebView2InstallPath
34 | Start-Process -FilePath $WebView2InstallPath -Args "/silent /install" -Wait
35 |
36 | - name: Install Dependencies
37 | run: |
38 | npm install
39 |
40 | - name: Build Frontend
41 | run: npm run build
42 |
43 | - name: Build Windows Executable
44 | shell: bash # Force using bash shell for consistent environment variable setting
45 | run: |
46 | echo "Building Windows executable..."
47 | npm run tauri build
48 | echo "WIN_EXE_PATH=src-tauri/target/release/NeoHtop.exe" >> $GITHUB_ENV
49 |
50 | - name: Get version from package.json
51 | id: version
52 | shell: bash # Force using bash shell
53 | run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
54 |
55 | - name: Upload to Release
56 | if: github.event.inputs.release_upload_url != ''
57 | uses: actions/upload-release-asset@v1
58 | env:
59 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
60 | with:
61 | upload_url: ${{ github.event.inputs.release_upload_url }}
62 | asset_path: ${{ env.WIN_EXE_PATH }}
63 | asset_name: NeoHtop_${{ steps.version.outputs.version }}_x64.exe
64 | asset_content_type: application/vnd.microsoft.portable-executable
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 |
12 | ## Jetbrains
13 | .idea/
14 | .run/
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npm exec lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["prettier-plugin-svelte"],
3 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "svelte.svelte-vscode",
4 | "tauri-apps.tauri-vscode",
5 | "rust-lang.rust-analyzer"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "svelte.enable-ts-plugin": true
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Abdenasser
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
NeoHtop
5 |
A modern, cross-platform system monitor built on top of Svelte, Rust, and Tauri.
6 |
7 | [](https://github.com/Abdenasser/neohtop/blob/main/LICENSE)
8 | [](https://github.com/Abdenasser/neohtop/stargazers)
9 | [](https://github.com/Abdenasser/neohtop/issues)
10 | [](https://github.com/Abdenasser/neohtop/releases)
11 | [](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution)
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
If you find this project helpful, consider buying me a coffee:
24 |

25 |
Or sponsor me on GitHub:
26 |

27 |
28 |
29 |
30 | ## Table of Contents
31 | - [Why NeoHtop?](#why-neohtop)
32 | - [Features](#features)
33 | - [Tech Stack](#tech-stack)
34 | - [Getting Started](#getting-started)
35 | - [Prerequisites](#prerequisites)
36 | - [Installation](#installation)
37 | - [Running with Sudo](#running-with-sudo)
38 | - [Development](#development)
39 | - [Setup](#setup)
40 | - [Code Formatting](#code-formatting)
41 | - [Pull Requests](#pull-requests)
42 | - [Contributing](#contributing)
43 | - [License](#license)
44 |
45 | ## Why NeoHtop?
46 | [Read about the back story and motivation behind NeoHtop](https://www.abdenasser.com/2024/11/06/oh-boy-neohtop/)
47 |
48 | ## Features
49 | - 🚀 Real-time process monitoring
50 | - 💻 CPU and Memory usage tracking
51 | - 🎨 Beautiful, modern UI with dark/light themes
52 | - 🔍 Advanced process search and filtering
53 | - 📌 Pin important processes
54 | - 🛠 Process management (kill processes)
55 | - 🎯 Sort by any column
56 | - 🔄 Auto-refresh system stats
57 |
58 | ### Search Functionality
59 | Search for processes by name, command, or PID. Use commas to search for multiple terms simultaneously. Regular expressions are supported for advanced filtering.
60 |
61 | Examples:
62 | - `arm, x86`: Returns processes with "arm" or "x86" in the name or command
63 | - `d$`: Lists daemons (processes ending with 'd')
64 | - `^(\w+\.)+\w+$`: Shows processes with reverse domain name notation (e.g., com.docker.vmnetd)
65 |
66 | ## Tech Stack
67 | - **Frontend**: SvelteKit, TypeScript
68 | - **Backend**: Rust, Tauri
69 | - **Styling**: CSS Variables for theming
70 | - **Icons**: FontAwesome
71 |
72 | ## Getting Started
73 |
74 | ### Prerequisites
75 | - Node.js (v16 or later)
76 | - Rust (latest stable)
77 | - Xcode Command Line Tools (for macOS)
78 |
79 | ### Installation
80 | Download the latest release from the [releases page](https://github.com/Abdenasser/neohtop/releases).
81 |
82 | ### Running with Sudo
83 | Some processes require monitoring with sudo privileges. To monitor these processes, launch NeoHtop with sudo:
84 |
85 | - macOS: `sudo /Applications/NeoHtop.app/Contents/MacOS/NeoHtop`
86 | - Linux: `pkexec /path/to/neohtop` (recommended)
87 |
88 | ## Development
89 |
90 | ### Setup
91 | ```bash
92 | # Install dependencies
93 | npm install
94 |
95 | # Run in development mode
96 | npm run tauri dev
97 |
98 | # Build for production
99 | npm run tauri build
100 | ```
101 |
102 | ### Code Formatting
103 | We use Prettier for web code and `cargo fmt` for Rust code.
104 |
105 | ```bash
106 | # Format all files
107 | npm run format
108 |
109 | # Check formatting without making changes
110 | npm run format:check
111 | ```
112 |
113 | ### Pull Requests
114 | Before submitting a PR, ensure:
115 | 1. All code is formatted (`npm run format`)
116 | 2. The format check passes (`npm run format:check`)
117 | 3. Your commits follow the project's commit message conventions
118 |
119 | ## Contributing
120 | We welcome contributions! Please see our [contributing guidelines](./.github/CONTRIBUTING.md) for more information.
121 |
122 | ## License
123 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
124 |
--------------------------------------------------------------------------------
/app-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/app-icon.png
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/main.js:
--------------------------------------------------------------------------------
1 | // ===============================
2 | // Theme Management
3 | // ===============================
4 | const themeToggle = document.getElementById('themeToggle');
5 | const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
6 |
7 | function setTheme(isDark) {
8 | document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
9 | themeToggle.querySelector('.theme-icon').textContent = isDark ? '☀️' : '🌙';
10 | localStorage.setItem('theme', isDark ? 'dark' : 'light');
11 | }
12 |
13 | // Initialize theme
14 | const savedTheme = localStorage.getItem('theme');
15 | if (savedTheme) {
16 | setTheme(savedTheme === 'dark');
17 | } else {
18 | setTheme(prefersDark.matches);
19 | }
20 |
21 | // Theme event listeners
22 | themeToggle.addEventListener('click', () => {
23 | const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
24 | setTheme(!isDark);
25 | });
26 |
27 | prefersDark.addEventListener('change', (e) => {
28 | if (!localStorage.getItem('theme')) {
29 | setTheme(e.matches);
30 | }
31 | });
32 |
33 | // ===============================
34 | // Version and Download Management
35 | // ===============================
36 | async function fetchDownloadStats() {
37 | try {
38 | const releasesResponse = await fetch('https://api.github.com/repos/abdenasser/neohtop/releases');
39 | const releases = await releasesResponse.json();
40 | const githubDownloads = releases.reduce((total, release) => {
41 | const releaseDownloads = release.assets.reduce((sum, asset) =>
42 | sum + asset.download_count, 0);
43 | return total + releaseDownloads;
44 | }, 0);
45 |
46 | const brewResponse = await fetch('https://formulae.brew.sh/api/analytics/install/homebrew-core/365d.json');
47 | const brewData = await brewResponse.json();
48 | const brewInstalls = brewData.formulae?.neohtop?.[0]?.count || 0;
49 |
50 | const totalDownloads = githubDownloads + brewInstalls;
51 | document.getElementById('download-count').textContent = new Intl.NumberFormat().format(totalDownloads);
52 | } catch (error) {
53 | console.error('Failed to fetch download stats:', error);
54 | document.getElementById('download-count').textContent = 'N/A';
55 | }
56 | }
57 |
58 | async function updateVersion() {
59 | try {
60 | const response = await fetch('https://api.github.com/repos/Abdenasser/neohtop/releases/latest');
61 | const data = await response.json();
62 | const version = data.tag_name;
63 | const versionNumber = version.match(/\d+\.\d+\.\d+/)?.[0];
64 |
65 | if (versionNumber) {
66 | document.getElementById('current-version').textContent = "v" + versionNumber;
67 | updateDownloadLinks(versionNumber);
68 | }
69 | } catch (error) {
70 | console.error('Failed to fetch version:', error);
71 | }
72 | }
73 |
74 | function updateDownloadLinks(versionNumber) {
75 | const platformUrls = {
76 | 'macos-intel': `intel-NeoHtop_${versionNumber}_x64.dmg`,
77 | 'macos-silicon': `silicon-NeoHtop_${versionNumber}_aarch64.dmg`,
78 | 'windows': `NeoHtop_${versionNumber}_x64.exe`,
79 | 'linux-deb-x64': `NeoHtop_${versionNumber}_x86_64.deb`,
80 | 'linux-appimage-x64': `NeoHtop_${versionNumber}_x86_64.AppImage`,
81 | 'linux-rpm-x64': `NeoHtop_${versionNumber}_x86_64.rpm`,
82 | 'linux-deb-arm64': `NeoHtop_${versionNumber}_aarch64.deb`,
83 | 'linux-appimage-arm64': `NeoHtop_${versionNumber}_aarch64.AppImage`,
84 | 'linux-rpm-arm64': `NeoHtop_${versionNumber}_aarch64.rpm`
85 | };
86 |
87 | document.querySelectorAll('.download-button').forEach(link => {
88 | const platform = link.getAttribute('data-type');
89 | if (platformUrls[platform]) {
90 | link.href = `https://github.com/Abdenasser/neohtop/releases/download/v${versionNumber}/${platformUrls[platform]}`;
91 | }
92 | });
93 | }
94 |
95 | // ===============================
96 | // UI Interactions
97 | // ===============================
98 | // FAQ Accordion
99 | document.querySelectorAll('.faq-question').forEach(button => {
100 | button.addEventListener('click', () => {
101 | const faqItem = button.parentElement;
102 | const isActive = faqItem.classList.contains('active');
103 | document.querySelectorAll('.faq-item').forEach(item => item.classList.remove('active'));
104 | if (!isActive) faqItem.classList.add('active');
105 | });
106 | });
107 |
108 | // Download tracking
109 | document.querySelectorAll('.download-button').forEach(button => {
110 | button.addEventListener('click', (e) => {
111 | gtag('event', 'download', {
112 | 'event_category': 'App',
113 | 'event_label': button.getAttribute('data-type'),
114 | 'value': button.getAttribute('data-version')
115 | });
116 | });
117 | });
118 |
119 | // Smooth scroll
120 | document.querySelectorAll('a[href^="#"]').forEach(anchor => {
121 | anchor.addEventListener('click', function (e) {
122 | e.preventDefault();
123 | const target = document.querySelector(this.getAttribute('href'));
124 | if (target) {
125 | target.scrollIntoView({ behavior: 'smooth', block: 'start' });
126 | }
127 | });
128 | });
129 | // ===============================
130 | // Animations
131 | // ===============================
132 | const observer = new IntersectionObserver(
133 | (entries) => {
134 | entries.forEach(entry => {
135 | if (entry.isIntersecting) {
136 | entry.target.classList.add('visible');
137 | }
138 | });
139 | },
140 | { threshold: 0.1 }
141 | );
142 |
143 | document.querySelectorAll('.feature, .step, .download-button').forEach(el => {
144 | observer.observe(el);
145 | });
146 |
147 | // ===============================
148 | // Initialization
149 | // ===============================
150 | document.addEventListener('DOMContentLoaded', () => {
151 | updateVersion();
152 | fetchDownloadStats();
153 | });
154 |
155 |
156 | // ===============================
157 | // Mobile Navigation
158 | // ===============================
159 |
160 | document.addEventListener('DOMContentLoaded', () => {
161 | const menuButton = document.querySelector('.menu-button');
162 | const navLinks = document.querySelector('.nav-links');
163 |
164 | menuButton.addEventListener('click', () => {
165 | navLinks.classList.toggle('active');
166 | });
167 |
168 | // Close menu when clicking outside
169 | document.addEventListener('click', (e) => {
170 | if (!navLinks.contains(e.target) && !menuButton.contains(e.target)) {
171 | navLinks.classList.remove('active');
172 | }
173 | });
174 | });
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
16 | //
17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
18 | // from the referenced tsconfig.json - TypeScript does not merge them in
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "neohtop",
3 | "version": "1.1.3",
4 | "description": "",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite dev",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
12 | "tauri": "tauri",
13 | "format": "prettier --write ./src && cargo fmt --manifest-path src-tauri/Cargo.toml",
14 | "format:check": "prettier --check ./src && cargo fmt --manifest-path src-tauri/Cargo.toml -- --check",
15 | "prepare": "husky install"
16 | },
17 | "license": "MIT",
18 | "dependencies": {
19 | "@fortawesome/fontawesome-svg-core": "^6.6.0",
20 | "@fortawesome/free-solid-svg-icons": "^6.6.0",
21 | "@tauri-apps/api": "^2.0.3",
22 | "@tauri-apps/plugin-os": "^2.0.0",
23 | "@tauri-apps/plugin-shell": "^2.2.1",
24 | "simple-icons": "^13.15.0",
25 | "svelte-fa": "^4.0.3"
26 | },
27 | "devDependencies": {
28 | "@sveltejs/adapter-static": "^3.0.5",
29 | "@sveltejs/kit": "^2.7.0",
30 | "@sveltejs/vite-plugin-svelte": "^5.0.3",
31 | "@tauri-apps/cli": "^2.0.4",
32 | "husky": "^8.0.0",
33 | "lint-staged": "^15.2.10",
34 | "prettier": "^3.3.3",
35 | "prettier-plugin-svelte": "^3.2.7",
36 | "svelte": "^5.0.0",
37 | "svelte-check": "^4.0.0",
38 | "typescript": "^5.5.0",
39 | "vite": "^6.3.5"
40 | },
41 | "lint-staged": {
42 | "src/**/*.{js,ts,jsx,tsx,svelte}": [
43 | "prettier --write"
44 | ],
45 | "src-tauri/**/*.rs": [
46 | "cargo fmt --manifest-path src-tauri/Cargo.toml --"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/screenshot-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/screenshot-light.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/screenshot.png
--------------------------------------------------------------------------------
/src-tauri/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.x86_64-apple-darwin]
2 | rustflags = [
3 | "-C", "link-arg=-undefined",
4 | "-C", "link-arg=dynamic_lookup",
5 | ]
6 |
7 | [target.aarch64-apple-darwin]
8 | rustflags = [
9 | "-C", "link-arg=-undefined",
10 | "-C", "link-arg=dynamic_lookup",
11 | ]
12 |
--------------------------------------------------------------------------------
/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Generated by Tauri
6 | # will have schema files for capabilities auto-completion
7 | /gen/schemas
8 |
--------------------------------------------------------------------------------
/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "neohtop"
3 | version = "1.1.3"
4 | description = "A cross-platform system monitor"
5 | authors = ["you"]
6 | edition = "2021"
7 |
8 | [build-dependencies]
9 | tauri-build = { version = "2", features = [] }
10 |
11 | [dependencies]
12 | serde_json = "1.0"
13 | serde = { version = "1.0", features = ["derive"] }
14 | tauri = { version = "2", features = ["macos-private-api"] }
15 | sysinfo = "0.29.0"
16 | tauri-plugin-shell = "2"
17 | tauri-plugin-os = "2"
18 | window-vibrancy = "0.5.2"
19 |
20 | [features]
21 | default = [ "custom-protocol" ]
22 | custom-protocol = [ "tauri/custom-protocol" ]
23 |
24 | [profile.release]
25 | panic = "abort" # Strip expensive panic clean-up logic
26 | codegen-units = 1 # Compile crates one after another so the compiler can optimize better
27 | lto = "fat" # More aggressive link-time optimization
28 | opt-level = 3 # Optimize for maximum performance
29 | strip = true # Remove debug symbols
30 | incremental = false # Disable incremental compilation
31 |
--------------------------------------------------------------------------------
/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/src-tauri/capabilities/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../gen/schemas/desktop-schema.json",
3 | "identifier": "default",
4 | "description": "Capability for the main window",
5 | "windows": [
6 | "main"
7 | ],
8 | "permissions": [
9 | "core:default",
10 | "core:app:allow-version",
11 | "core:window:allow-start-dragging",
12 | "core:window:allow-maximize",
13 | "core:window:allow-minimize",
14 | "core:window:allow-close",
15 | "shell:default"
16 | ]
17 | }
--------------------------------------------------------------------------------
/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-20x20@1x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-20x20@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-20x20@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-20x20@3x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-29x29@1x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-29x29@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-29x29@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-29x29@3x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-40x40@1x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-40x40@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-40x40@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-40x40@3x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-512@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-60x60@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-60x60@3x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-76x76@1x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-76x76@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/src-tauri/src/commands.rs:
--------------------------------------------------------------------------------
1 | //! Tauri command handlers
2 | //!
3 | //! This module contains the command handlers that are exposed to the frontend
4 | //! through Tauri's IPC mechanism. These commands provide the interface between
5 | //! the frontend and the system monitoring functionality.
6 |
7 | use crate::monitoring::{ProcessInfo, ProcessMonitor, SystemStats};
8 | use crate::state::AppState;
9 | use sysinfo::SystemExt;
10 | use tauri::State;
11 |
12 | /// Retrieves the current list of processes and system statistics
13 | ///
14 | /// # Arguments
15 | ///
16 | /// * `state` - The application state containing system monitoring components
17 | ///
18 | /// # Returns
19 | ///
20 | /// A tuple containing:
21 | /// * A vector of process information
22 | /// * Current system statistics
23 | ///
24 | /// # Errors
25 | ///
26 | /// Returns an error string if:
27 | /// * Failed to acquire locks on system state
28 | /// * Failed to collect process information
29 | #[tauri::command]
30 | pub async fn get_processes(
31 | state: State<'_, AppState>,
32 | ) -> Result<(Vec, SystemStats), String> {
33 | let mut sys = state.sys.lock().map_err(|e| e.to_string())?;
34 | sys.refresh_all();
35 | sys.refresh_networks_list();
36 | sys.refresh_disks_list();
37 |
38 | let mut process_monitor = state.process_monitor.lock().map_err(|e| e.to_string())?;
39 | let mut system_monitor = state.system_monitor.lock().map_err(|e| e.to_string())?;
40 |
41 | let processes = process_monitor.collect_processes(&sys)?;
42 | let system_stats = system_monitor.collect_stats(&sys);
43 |
44 | Ok((processes, system_stats))
45 | }
46 |
47 | /// Attempts to kill a process with the specified PID
48 | ///
49 | /// # Arguments
50 | ///
51 | /// * `pid` - Process ID to kill
52 | /// * `state` - The application state
53 | ///
54 | /// # Returns
55 | ///
56 | /// * `true` if the process was successfully killed
57 | /// * `false` if the process couldn't be killed or wasn't found
58 | ///
59 | /// # Errors
60 | ///
61 | /// Returns an error string if failed to acquire lock on system state
62 | #[tauri::command]
63 | pub async fn kill_process(pid: u32, state: State<'_, AppState>) -> Result {
64 | let sys = state.sys.lock().map_err(|e| e.to_string())?;
65 | Ok(ProcessMonitor::kill_process(&sys, pid))
66 | }
67 |
--------------------------------------------------------------------------------
/src-tauri/src/main.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
2 | //! NeoHtop - A modern system monitor built with Tauri
3 | //!
4 | //! This is the main entry point for the application. It sets up the Tauri
5 | //! application, initializes plugins, and configures window effects.
6 |
7 | mod commands;
8 | mod monitoring;
9 | mod state;
10 | mod ui;
11 |
12 | use state::AppState;
13 | use tauri::Manager;
14 |
15 | /// Main entry point for the application
16 | ///
17 | /// # Panics
18 | ///
19 | /// Will panic if:
20 | /// - Unable to create the main window
21 | /// - Failed to apply window effects
22 | /// - Failed to initialize the application state
23 | fn main() {
24 | tauri::Builder::default()
25 | .setup(|app| {
26 | let window = app.get_webview_window("main").unwrap();
27 | ui::setup_window_effects(&window).expect("Failed to apply window effects");
28 | Ok(())
29 | })
30 | .plugin(tauri_plugin_shell::init())
31 | .plugin(tauri_plugin_os::init())
32 | .manage(AppState::new())
33 | .invoke_handler(tauri::generate_handler![
34 | commands::get_processes,
35 | commands::kill_process,
36 | ])
37 | .run(tauri::generate_context!())
38 | .expect("error while running tauri application");
39 | }
40 |
--------------------------------------------------------------------------------
/src-tauri/src/monitoring/mod.rs:
--------------------------------------------------------------------------------
1 | //! System monitoring functionality
2 | //!
3 | //! This module provides types and functionality for monitoring system resources
4 | //! and processes. It includes process monitoring, system statistics collection,
5 | //! and data structures for representing system state.
6 |
7 | mod process_monitor;
8 | mod system_monitor;
9 | mod types;
10 |
11 | pub use process_monitor::ProcessMonitor;
12 | pub use system_monitor::SystemMonitor;
13 | pub use types::*; // Re-export all types
14 |
--------------------------------------------------------------------------------
/src-tauri/src/monitoring/process_monitor.rs:
--------------------------------------------------------------------------------
1 | //! Process monitoring functionality
2 | //!
3 | //! This module handles monitoring and managing system processes, including
4 | //! collecting process information and managing process lifecycle.
5 |
6 | use super::{ProcessData, ProcessInfo, ProcessStaticInfo};
7 | use std::collections::HashMap;
8 | use std::fmt::Debug;
9 | use std::time::{SystemTime, UNIX_EPOCH};
10 | use sysinfo::{PidExt, ProcessExt, ProcessStatus, SystemExt};
11 |
12 | /// Monitors and manages system processes
13 | #[derive(Debug)]
14 | pub struct ProcessMonitor {
15 | /// Cache for static process information to avoid redundant allocations
16 | process_cache: HashMap,
17 | }
18 |
19 | impl ProcessMonitor {
20 | /// Creates a new process monitor instance
21 | pub fn new() -> Self {
22 | Self {
23 | process_cache: HashMap::new(),
24 | }
25 | }
26 |
27 | /// Collects information about all running processes
28 | ///
29 | /// # Arguments
30 | ///
31 | /// * `sys` - System information provider
32 | ///
33 | /// # Returns
34 | ///
35 | /// A vector of process information, or an error string if collection failed
36 | pub fn collect_processes(&mut self, sys: &sysinfo::System) -> Result, String> {
37 | let current_time = Self::get_current_time()?;
38 | let processes_data = self.collect_process_data(sys, current_time);
39 | Ok(self.build_process_info(processes_data))
40 | }
41 |
42 | /// Attempts to kill a process
43 | ///
44 | /// # Arguments
45 | ///
46 | /// * `sys` - System information provider
47 | /// * `pid` - Process ID to kill
48 | ///
49 | /// # Returns
50 | ///
51 | /// Boolean indicating whether the process was successfully killed
52 | pub fn kill_process(sys: &sysinfo::System, pid: u32) -> bool {
53 | sys.process(sysinfo::Pid::from(pid as usize))
54 | .map(|process| process.kill())
55 | .unwrap_or(false)
56 | }
57 |
58 | /// Gets the current system time in seconds since UNIX epoch
59 | fn get_current_time() -> Result {
60 | SystemTime::now()
61 | .duration_since(UNIX_EPOCH)
62 | .map(|d| d.as_secs())
63 | .map_err(|e| format!("Failed to get system time: {}", e))
64 | }
65 |
66 | /// Collects raw process data from the system
67 | fn collect_process_data(&self, sys: &sysinfo::System, current_time: u64) -> Vec {
68 | sys.processes()
69 | .iter()
70 | .map(|(pid, process)| {
71 | let start_time = process.start_time();
72 | ProcessData {
73 | pid: pid.as_u32(),
74 | name: process.name().to_string(),
75 | cmd: process.cmd().to_vec(),
76 | user_id: process.user_id().map(|uid| uid.to_string()),
77 | cpu_usage: process.cpu_usage(),
78 | memory: process.memory(),
79 | status: process.status(),
80 | ppid: process.parent().map(|p| p.as_u32()),
81 | environ: process.environ().to_vec(),
82 | root: process.root().to_string_lossy().into_owned(),
83 | virtual_memory: process.virtual_memory(),
84 | start_time,
85 | run_time: if start_time > 0 {
86 | current_time.saturating_sub(start_time)
87 | } else {
88 | 0
89 | },
90 | disk_usage: process.disk_usage(),
91 | session_id: process.session_id().map(|id| id.as_u32()),
92 | }
93 | })
94 | .collect()
95 | }
96 |
97 | /// Builds process information from raw process data
98 | fn build_process_info(&mut self, processes: Vec) -> Vec {
99 | processes
100 | .into_iter()
101 | .map(|data| {
102 | let cached_info =
103 | self.process_cache
104 | .entry(data.pid)
105 | .or_insert_with(|| ProcessStaticInfo {
106 | name: data.name.clone(),
107 | command: data.cmd.join(" "),
108 | user: data.user_id.unwrap_or_else(|| "-".to_string()),
109 | });
110 |
111 | ProcessInfo {
112 | pid: data.pid,
113 | ppid: data.ppid.unwrap_or(0),
114 | name: cached_info.name.clone(),
115 | cpu_usage: data.cpu_usage,
116 | memory_usage: data.memory,
117 | status: Self::format_status(data.status),
118 | user: cached_info.user.clone(),
119 | command: cached_info.command.clone(),
120 | threads: None,
121 | environ: data.environ,
122 | root: data.root,
123 | virtual_memory: data.virtual_memory,
124 | start_time: data.start_time,
125 | run_time: data.run_time,
126 | disk_usage: (data.disk_usage.read_bytes, data.disk_usage.written_bytes),
127 | session_id: data.session_id,
128 | }
129 | })
130 | .collect()
131 | }
132 |
133 | /// Formats process status into a human-readable string
134 | pub fn format_status(status: ProcessStatus) -> String {
135 | match status {
136 | ProcessStatus::Run => "Running",
137 | ProcessStatus::Sleep => "Sleeping",
138 | ProcessStatus::Idle => "Idle",
139 | _ => "Unknown",
140 | }
141 | .to_string()
142 | }
143 | }
144 |
145 | #[cfg(test)]
146 | mod tests {
147 | use super::*;
148 | use sysinfo::System;
149 |
150 | /// Tests creation of a new process monitor
151 | #[test]
152 | fn test_process_monitor_creation() {
153 | let monitor = ProcessMonitor::new();
154 | assert!(monitor.process_cache.is_empty());
155 | }
156 |
157 | /// Tests process collection functionality
158 | #[test]
159 | fn test_process_collection() {
160 | let mut monitor = ProcessMonitor::new();
161 | let mut sys = System::new();
162 | sys.refresh_all();
163 |
164 | let result = monitor.collect_processes(&sys);
165 | assert!(result.is_ok());
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src-tauri/src/monitoring/system_monitor.rs:
--------------------------------------------------------------------------------
1 | //! System statistics monitoring
2 | //!
3 | //! This module handles collection and monitoring of system-wide statistics
4 | //! including CPU, memory, network, and disk usage.
5 |
6 | use super::SystemStats;
7 | use std::fmt::Debug;
8 | use std::path::Path;
9 | use std::time::Instant;
10 | use sysinfo::{CpuExt, Disk, DiskExt, NetworkExt, NetworksExt, SystemExt};
11 |
12 | /// Monitors system-wide statistics
13 | #[derive(Debug)]
14 | pub struct SystemMonitor {
15 | /// Tracks network usage between updates
16 | last_network_update: (Instant, u64, u64),
17 | }
18 |
19 | impl SystemMonitor {
20 | /// Creates a new system monitor instance
21 | ///
22 | /// # Arguments
23 | ///
24 | /// * `sys` - System information provider for initial readings
25 | pub fn new(sys: &sysinfo::System) -> Self {
26 | let initial_rx: u64 = sys
27 | .networks()
28 | .iter()
29 | .map(|(_, data)| data.total_received())
30 | .sum();
31 | let initial_tx: u64 = sys
32 | .networks()
33 | .iter()
34 | .map(|(_, data)| data.total_transmitted())
35 | .sum();
36 |
37 | Self {
38 | last_network_update: (Instant::now(), initial_rx, initial_tx),
39 | }
40 | }
41 |
42 | /// Collects current system statistics
43 | ///
44 | /// # Arguments
45 | ///
46 | /// * `sys` - System information provider
47 | pub fn collect_stats(&mut self, sys: &sysinfo::System) -> SystemStats {
48 | let (network_rx, network_tx) = self.calculate_network_stats(sys);
49 | let (disk_total, disk_used, disk_free) = self.calculate_disk_stats(sys);
50 |
51 | SystemStats {
52 | cpu_usage: sys.cpus().iter().map(|cpu| cpu.cpu_usage()).collect(),
53 | memory_total: sys.total_memory(),
54 | memory_used: sys.used_memory(),
55 | memory_free: sys.total_memory() - sys.used_memory(),
56 | memory_cached: sys.total_memory()
57 | - (sys.used_memory() + (sys.total_memory() - sys.used_memory())),
58 | uptime: sys.uptime(),
59 | load_avg: [
60 | sys.load_average().one,
61 | sys.load_average().five,
62 | sys.load_average().fifteen,
63 | ],
64 | network_rx_bytes: network_rx,
65 | network_tx_bytes: network_tx,
66 | disk_total_bytes: disk_total,
67 | disk_used_bytes: disk_used,
68 | disk_free_bytes: disk_free,
69 | }
70 | }
71 |
72 | /// Filters disks based on platform-specific criteria
73 | #[cfg(not(target_os = "windows"))]
74 | fn filter_disks(disks: &[Disk]) -> Vec<&Disk> {
75 | disks
76 | .iter()
77 | .filter(|disk| disk.mount_point() == Path::new("/"))
78 | .collect()
79 | }
80 |
81 | /// Windows-specific disk filtering
82 | #[cfg(target_os = "windows")]
83 | fn filter_disks(disks: &[Disk]) -> Vec<&Disk> {
84 | disks.iter().collect()
85 | }
86 |
87 | /// Calculates network usage rates
88 | fn calculate_network_stats(&mut self, sys: &sysinfo::System) -> (u64, u64) {
89 | let current_rx: u64 = sys
90 | .networks()
91 | .iter()
92 | .map(|(_, data)| data.total_received())
93 | .sum();
94 | let current_tx: u64 = sys
95 | .networks()
96 | .iter()
97 | .map(|(_, data)| data.total_transmitted())
98 | .sum();
99 |
100 | let elapsed = self.last_network_update.0.elapsed().as_secs_f64();
101 | let rx_rate = ((current_rx - self.last_network_update.1) as f64 / elapsed) as u64;
102 | let tx_rate = ((current_tx - self.last_network_update.2) as f64 / elapsed) as u64;
103 |
104 | self.last_network_update = (Instant::now(), current_rx, current_tx);
105 | (rx_rate, tx_rate)
106 | }
107 |
108 | /// Calculates disk usage statistics
109 | fn calculate_disk_stats(&self, sys: &sysinfo::System) -> (u64, u64, u64) {
110 | let disks = Self::filter_disks(sys.disks());
111 | let total: u64 = disks.iter().map(|disk| disk.total_space()).sum();
112 | let used: u64 = disks
113 | .iter()
114 | .map(|disk| disk.total_space() - disk.available_space())
115 | .sum();
116 | let free: u64 = disks.iter().map(|disk| disk.available_space()).sum();
117 | (total, used, free)
118 | }
119 | }
120 |
121 | #[cfg(test)]
122 | mod tests {
123 | use super::*;
124 | use sysinfo::System;
125 |
126 | /// Tests creation of system monitor
127 | #[test]
128 | fn test_system_monitor_creation() {
129 | let sys = System::new();
130 | let monitor = SystemMonitor::new(&sys);
131 | assert!(monitor.last_network_update.1 >= 0);
132 | assert!(monitor.last_network_update.2 >= 0);
133 | }
134 |
135 | /// Tests system statistics collection
136 | #[test]
137 | fn test_stats_collection() {
138 | let mut sys = System::new();
139 | let mut monitor = SystemMonitor::new(&sys);
140 | sys.refresh_all();
141 |
142 | let stats = monitor.collect_stats(&sys);
143 | assert!(!stats.cpu_usage.is_empty());
144 | assert!(stats.memory_total > 0);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src-tauri/src/monitoring/types.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 | use std::fmt::Debug;
3 | use sysinfo::{DiskUsage, ProcessStatus};
4 |
5 | /// Internal representation of process data collected from the system
6 | /// This struct is used internally and not exposed directly to the frontend
7 | #[derive(Clone, Debug)]
8 | pub(crate) struct ProcessData {
9 | /// Process ID
10 | pub pid: u32,
11 | /// Name of the process
12 | pub name: String,
13 | /// Complete command line arguments
14 | pub cmd: Vec,
15 | /// User ID that owns the process
16 | pub user_id: Option,
17 | /// CPU usage as percentage (0-100)
18 | pub cpu_usage: f32,
19 | /// Physical memory usage in bytes
20 | pub memory: u64,
21 | /// Current process status
22 | pub status: ProcessStatus,
23 | /// Parent process ID
24 | pub ppid: Option,
25 | /// Environment variables
26 | pub environ: Vec,
27 | /// Root directory of the process
28 | pub root: String,
29 | /// Virtual memory usage in bytes
30 | pub virtual_memory: u64,
31 | /// Process start time (Unix timestamp)
32 | pub start_time: u64,
33 | /// Process running time in seconds
34 | pub run_time: u64,
35 | /// Disk I/O statistics
36 | pub disk_usage: DiskUsage,
37 | /// Session ID of the process
38 | pub session_id: Option,
39 | }
40 |
41 | /// Static information about a process that doesn't change frequently
42 | /// Used for caching purposes to avoid frequent updates of stable data
43 | #[derive(Clone, Debug)]
44 | pub struct ProcessStaticInfo {
45 | /// Process name
46 | pub name: String,
47 | /// Full command string
48 | pub command: String,
49 | /// Username of the process owner
50 | pub user: String,
51 | }
52 |
53 | /// Process information exposed to the frontend via Tauri
54 | /// Contains formatted and filtered process data for UI consumption
55 | #[derive(Serialize, Debug)]
56 | pub struct ProcessInfo {
57 | /// Process ID
58 | pub pid: u32,
59 | /// Parent process ID
60 | pub ppid: u32,
61 | /// Process name
62 | pub name: String,
63 | /// CPU usage as percentage (0-100)
64 | pub cpu_usage: f32,
65 | /// Physical memory usage in bytes
66 | pub memory_usage: u64,
67 | /// Process status as string
68 | pub status: String,
69 | /// Username of the process owner
70 | pub user: String,
71 | /// Full command string
72 | pub command: String,
73 | /// Number of threads (if available)
74 | pub threads: Option,
75 | /// Environment variables
76 | pub environ: Vec,
77 | /// Root directory of the process
78 | pub root: String,
79 | /// Virtual memory usage in bytes
80 | pub virtual_memory: u64,
81 | /// Process start time (Unix timestamp)
82 | pub start_time: u64,
83 | /// Process running time in seconds
84 | pub run_time: u64,
85 | /// Disk I/O statistics (read bytes, written bytes)
86 | pub disk_usage: (u64, u64),
87 | /// Session ID of the process
88 | pub session_id: Option,
89 | }
90 |
91 | /// System-wide statistics exposed to the frontend
92 | /// Provides overall system resource usage and performance metrics
93 | #[derive(Serialize, Debug)]
94 | pub struct SystemStats {
95 | /// CPU usage per core as percentage (0-100)
96 | pub cpu_usage: Vec,
97 | /// Total physical memory in bytes
98 | pub memory_total: u64,
99 | /// Used physical memory in bytes
100 | pub memory_used: u64,
101 | /// Free physical memory in bytes
102 | pub memory_free: u64,
103 | /// Cached memory in bytes
104 | pub memory_cached: u64,
105 | /// System uptime in seconds
106 | pub uptime: u64,
107 | /// Load averages for 1, 5, and 15 minutes
108 | pub load_avg: [f64; 3],
109 | /// Total bytes received over network
110 | pub network_rx_bytes: u64,
111 | /// Total bytes transmitted over network
112 | pub network_tx_bytes: u64,
113 | /// Total disk space in bytes
114 | pub disk_total_bytes: u64,
115 | /// Used disk space in bytes
116 | pub disk_used_bytes: u64,
117 | /// Free disk space in bytes
118 | pub disk_free_bytes: u64,
119 | }
120 |
--------------------------------------------------------------------------------
/src-tauri/src/state.rs:
--------------------------------------------------------------------------------
1 | //! Application state management
2 | //!
3 | //! This module handles the global application state, including system monitoring
4 | //! and process tracking capabilities.
5 |
6 | use crate::monitoring::{ProcessMonitor, SystemMonitor};
7 | use std::sync::Mutex;
8 | use sysinfo::{System, SystemExt};
9 |
10 | /// Global application state
11 | ///
12 | /// Maintains thread-safe access to system information and monitoring components
13 | #[derive(Debug)]
14 | pub struct AppState {
15 | /// System information handler
16 | pub sys: Mutex,
17 | /// Process monitoring component
18 | pub process_monitor: Mutex,
19 | /// System statistics monitoring component
20 | pub system_monitor: Mutex,
21 | }
22 |
23 | impl AppState {
24 | /// Creates a new instance of the application state
25 | ///
26 | /// Initializes system monitoring and process tracking components
27 | ///
28 | /// # Returns
29 | ///
30 | /// A new `AppState` instance with initialized monitors
31 | pub fn new() -> Self {
32 | let mut sys = System::new();
33 | sys.refresh_all();
34 |
35 | Self {
36 | process_monitor: Mutex::new(ProcessMonitor::new()),
37 | system_monitor: Mutex::new(SystemMonitor::new(&sys)),
38 | sys: Mutex::new(sys),
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src-tauri/src/ui/mod.rs:
--------------------------------------------------------------------------------
1 | //! User interface functionality
2 | //!
3 | //! This module handles UI-specific functionality, including window effects
4 | //! and platform-specific visual customizations.
5 |
6 | mod window;
7 | pub use window::setup_window_effects;
8 |
--------------------------------------------------------------------------------
/src-tauri/src/ui/window.rs:
--------------------------------------------------------------------------------
1 | //! Window effects and customization
2 | //!
3 | //! Provides platform-specific window effects like transparency and vibrancy.
4 |
5 | use tauri::WebviewWindow;
6 |
7 | #[cfg(target_os = "windows")]
8 | use window_vibrancy::apply_acrylic;
9 | #[cfg(target_os = "macos")]
10 | use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial, NSVisualEffectState};
11 |
12 | /// Applies Windows-specific window effects
13 | #[cfg(target_os = "windows")]
14 | pub fn setup_window_effects(window: &WebviewWindow) -> Result<(), Box> {
15 | apply_acrylic(window, Some((0, 0, 25, 125)))?;
16 | Ok(())
17 | }
18 |
19 | /// Applies macOS-specific window effects
20 | #[cfg(target_os = "macos")]
21 | pub fn setup_window_effects(window: &WebviewWindow) -> Result<(), Box> {
22 | apply_vibrancy(
23 | window,
24 | NSVisualEffectMaterial::HudWindow,
25 | Some(NSVisualEffectState::Active),
26 | None,
27 | )?;
28 | Ok(())
29 | }
30 |
31 | /// No-op for platforms without specific window effects
32 | #[cfg(not(any(target_os = "windows", target_os = "macos")))]
33 | pub fn setup_window_effects(_window: &WebviewWindow) -> Result<(), Box> {
34 | Ok(())
35 | }
36 |
--------------------------------------------------------------------------------
/src-tauri/tauri.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
3 | "build": {
4 | "beforeBuildCommand": "npm run build",
5 | "beforeDevCommand": "npm run dev",
6 | "frontendDist": "../build",
7 | "devUrl": "http://localhost:1420"
8 | },
9 | "bundle": {
10 | "active": true,
11 | "icon": [
12 | "icons/32x32.png",
13 | "icons/128x128.png",
14 | "icons/128x128@2x.png",
15 | "icons/icon.icns",
16 | "icons/icon.ico"
17 | ],
18 | "macOS": {
19 | "hardenedRuntime": true,
20 | "entitlements": null,
21 | "providerShortName": null
22 | },
23 | "targets": ["app", "dmg"]
24 | },
25 | "productName": "NeoHtop",
26 | "mainBinaryName": "NeoHtop",
27 | "version": "1.1.3",
28 | "identifier": "com.neohtop.dev",
29 | "plugins": {
30 | "os": {
31 | "all": true
32 | }
33 | },
34 | "app": {
35 | "macOSPrivateApi": true,
36 | "windows": [
37 | {
38 | "fullscreen": false,
39 | "theme": "Dark",
40 | "height": 900,
41 | "resizable": true,
42 | "title": "NeoHtop",
43 | "width": 1280,
44 | "minWidth": 1120,
45 | "minHeight": 700,
46 | "titleBarStyle": "Overlay",
47 | "hiddenTitle": true,
48 | "transparent": true
49 | }
50 | ],
51 | "security": {
52 | "csp": null
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/App.svelte:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/src/App.svelte
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | :root {
2 | /* Default theme values will be overridden by theme store */
3 | --base: #1e1e2e;
4 | --mantle: #181825;
5 | --crust: #11111b;
6 | --text: #cdd6f4;
7 | --subtext0: #a6adc8;
8 | --subtext1: #bac2de;
9 | --surface0: #313244;
10 | --surface1: #45475a;
11 | --surface2: #585b70;
12 | --overlay0: #6c7086;
13 | --overlay1: #7f849c;
14 | --blue: #89b4fa;
15 | --lavender: #b4befe;
16 | --sapphire: #74c7ec;
17 | --sky: #89dceb;
18 | --red: #f38ba8;
19 | --maroon: #eba0ac;
20 | --peach: #fab387;
21 | --yellow: #f9e2af;
22 | --green: #a6e3a1;
23 | --teal: #94e2d5;
24 | }
25 |
26 | body {
27 | margin: 0;
28 | padding: 0;
29 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
30 | sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
31 | background-color: var(--base);
32 | color: var(--text);
33 | -webkit-font-smoothing: antialiased;
34 | overflow: hidden;
35 | user-select: none;
36 | }
37 |
38 | /* Global scrollbar styles */
39 | * {
40 | scrollbar-width: thin;
41 | scrollbar-color: var(--surface2) var(--mantle);
42 | }
43 |
44 | ::-webkit-scrollbar {
45 | width: 8px;
46 | height: 8px;
47 | }
48 |
49 | ::-webkit-scrollbar-track {
50 | background: var(--mantle);
51 | border-radius: 4px;
52 | }
53 |
54 | ::-webkit-scrollbar-thumb {
55 | background: var(--surface2);
56 | border-radius: 4px;
57 | transition: background 0.2s ease;
58 | }
59 |
60 | ::-webkit-scrollbar-thumb:hover {
61 | background: var(--surface1);
62 | }
63 |
64 | ::-webkit-scrollbar-corner {
65 | background: var(--mantle);
66 | }
67 |
68 | /* Glassy theme */
69 |
70 | [data-theme="glassy"] body {
71 | background: transparent !important;
72 | }
73 |
74 | [data-theme="glassy"] .toolbar {
75 | position: relative;
76 | background: rgba(24, 24, 37, 0.5) !important;
77 | border: 1px solid rgba(255, 255, 255, 0.1) !important;
78 | z-index: 9;
79 | }
80 |
81 | [data-theme="glassy"] .stat-panel {
82 | background: rgba(24, 24, 37, 0.2) !important;
83 | z-index: 100;
84 | }
85 |
86 | [data-theme="glassy"] .panel-header {
87 | border-color: rgba(255, 255, 255, 0.1) !important;
88 | }
89 |
90 | [data-theme="glassy"] .col-actions {
91 | background: rgba(24, 24, 37, 0.2) !important;
92 | border-bottom: 1px solid rgba(232, 232, 232, 0.1) !important;
93 | border-left: 1px solid rgba(232, 232, 232, 0.1) !important;
94 | }
95 |
96 | [data-theme="glassy"] .search-input,
97 | [data-theme="glassy"] .btn-toggle,
98 | [data-theme="glassy"] .btn-action,
99 | [data-theme="glassy"] .info-button,
100 | [data-theme="glassy"] .btn-page,
101 | [data-theme="glassy"] .theme-button,
102 | [data-theme="glassy"] .usage-pill,
103 | [data-theme="glassy"] .bar-container,
104 | [data-theme="glassy"] .select-input {
105 | background: rgba(24, 24, 37, 0.2) !important;
106 | z-index: 100;
107 | }
108 |
109 | [data-theme="glassy"] .btn-clear {
110 | z-index: 100;
111 | }
112 |
113 | [data-theme="glassy"] tr.pinned,
114 | [data-theme="glassy"] tr:hover {
115 | background: rgba(24, 24, 37, 0.3) !important;
116 | }
117 |
118 | [data-theme="glassy"] th {
119 | background: rgba(24, 24, 37, 0.5) !important;
120 | }
121 |
122 | [data-theme="glassy"] td {
123 | border-bottom: 1px solid rgba(232, 232, 232, 0.1) !important;
124 | background: rgba(24, 24, 37, 0.2) !important;
125 | }
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | NeoHtop
8 | %sveltekit.head%
9 |
10 |
11 |
12 | %sveltekit.body%
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/lib/components/AppInfo.svelte:
--------------------------------------------------------------------------------
1 |
59 |
60 |
61 |
62 |
72 |
73 | {#if showInfo}
74 |
75 |
(showInfo = false)}>
76 |
77 |
{ASCII_ART}
78 |
79 |
92 |
93 | app
94 | ::
95 | {APP_INFO.name}
96 |
97 |
109 |
110 | stack
111 | ::
112 | {APP_INFO.stack.join(", ")}
113 |
114 |
115 |
116 |
117 | {/if}
118 |
119 |
120 |
251 |
--------------------------------------------------------------------------------
/src/lib/components/ThemeSwitcher.svelte:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
58 |
59 | {#if showMenu}
60 |
61 |
62 |
100 | {/if}
101 |
102 |
103 |
227 |
--------------------------------------------------------------------------------
/src/lib/components/TitleBar.svelte:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |

7 |
NeoHtop
8 |
9 |
10 |
11 |
47 |
--------------------------------------------------------------------------------
/src/lib/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./toolbar";
2 | export * from "./process";
3 | export * from "./stats";
4 | export * from "./modals";
5 | export { default as AppInfo } from "./AppInfo.svelte";
6 | export { default as TitleBar } from "./TitleBar.svelte";
7 | export { default as ThemeSwitcher } from "./ThemeSwitcher.svelte";
8 |
--------------------------------------------------------------------------------
/src/lib/components/modals/KillProcessModal.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 | {#if process}
18 |
19 |
Are you sure you want to end this process?
20 |
21 | {process.name}
22 | (PID: {process.pid})
23 |
24 |
25 |
28 |
36 |
37 |
38 | {/if}
39 |
40 |
41 |
120 |
--------------------------------------------------------------------------------
/src/lib/components/modals/Modal.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | {#if show}
9 |
10 |
11 |
22 | {/if}
23 |
24 |
79 |
--------------------------------------------------------------------------------
/src/lib/components/modals/ProcessDetailsModal.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
31 | {#if process}
32 |
33 |
34 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
68 |
69 |
70 |
71 | Name
72 | {process.name}
73 |
74 |
75 | User
76 | {process.user}
77 |
78 |
79 | Parent PID
80 |
81 |
82 | {
85 | const parent = processes.find(
86 | (p) => p.pid === process.ppid,
87 | );
88 | if (parent) onShowDetails(parent);
89 | }}
90 | >
91 | {process.ppid}
92 |
93 |
94 |
95 | Session ID
96 | {process.session_id}
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
108 |
109 |
110 |
111 |
117 |
118 |
50}
122 | class:critical={process.cpu_usage > 80}
123 | >
124 |
125 |
126 |
127 |
130 |
131 |
Physical: {formatBytes(process.memory_usage)}
132 |
Virtual: {formatBytes(process.virtual_memory)}
133 |
134 |
135 |
136 |
139 |
140 |
Read: {formatBytes(process.disk_usage[0])}
141 |
Written: {formatBytes(process.disk_usage[1])}
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
157 |
158 |
{process.command}
159 |
{process.root}
160 |
161 |
162 |
163 |
164 | {#if childProcesses.length > 0}
165 |
166 |
170 |
171 |
172 |
173 |
174 | Name |
175 | PID |
176 | CPU |
177 | Memory |
178 |
179 |
180 |
181 | {#each childProcesses as child}
182 | onShowDetails(child)}
185 | >
186 | {child.name} |
187 | {child.pid} |
188 | {child.cpu_usage.toFixed(1)}% |
189 | {formatBytes(child.memory_usage)} |
190 |
191 | {/each}
192 |
193 |
194 |
195 |
196 | {/if}
197 |
198 |
199 | {#if process.environ.length > 0}
200 |
201 |
205 |
206 |
207 | {#each process.environ as env}
208 |
{env}
209 | {/each}
210 |
211 |
212 |
213 | {/if}
214 |
215 |
216 |
217 | {/if}
218 |
219 |
220 |
515 |
--------------------------------------------------------------------------------
/src/lib/components/modals/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Modal } from "./Modal.svelte";
2 | export { default as ProcessDetailsModal } from "./ProcessDetailsModal.svelte";
3 | export { default as KillProcessModal } from "./KillProcessModal.svelte";
4 |
--------------------------------------------------------------------------------
/src/lib/components/process/ActionButtons.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
27 |
34 |
41 |
42 | |
43 |
44 |
165 |
--------------------------------------------------------------------------------
/src/lib/components/process/ProcessIcon.svelte:
--------------------------------------------------------------------------------
1 |
68 |
69 |
77 |
78 |
85 |
--------------------------------------------------------------------------------
/src/lib/components/process/ProcessRow.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 | {#each columns.filter((col) => col.visible) as column}
17 |
18 | {#if column.id === "name"}
19 |
20 |
21 | {process.name}
22 |
23 | {:else if column.format}
24 | {@html column.format(process[column.id])}
25 | {:else}
26 | {process[column.id]}
27 | {/if}
28 | |
29 | {/each}
30 |
37 |
38 |
39 |
80 |
--------------------------------------------------------------------------------
/src/lib/components/process/ProcessTable.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | {#each processes as process (process.pid)}
22 | 50 ||
27 | process.memory_usage / (systemStats?.memory_total || 0) > 0.1}
28 | {onTogglePin}
29 | {onShowDetails}
30 | {onKillProcess}
31 | />
32 | {/each}
33 |
34 |
35 |
36 |
37 |
78 |
--------------------------------------------------------------------------------
/src/lib/components/process/TableHeader.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 | {#each columns.filter((col) => col.visible) as column}
17 | onToggleSort(column.id)}>
18 |
19 | {column.label}
20 |
24 | {getSortIndicator(column.id)}
25 |
26 |
27 | |
28 | {/each}
29 | Actions |
30 |
31 |
32 |
33 |
80 |
--------------------------------------------------------------------------------
/src/lib/components/process/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ProcessTable } from "./ProcessTable.svelte";
2 | export { default as ProcessRow } from "./ProcessRow.svelte";
3 | export { default as TableHeader } from "./TableHeader.svelte";
4 | export { default as ActionButtons } from "./ActionButtons.svelte";
5 | export { default as ProcessIcon } from "./ProcessIcon.svelte";
6 |
--------------------------------------------------------------------------------
/src/lib/components/stats/CpuPanel.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 | {#each cpuUsage as usage, i}
17 |
25 | {/each}
26 |
27 |
28 |
29 |
60 |
--------------------------------------------------------------------------------
/src/lib/components/stats/MemoryPanel.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
33 |
34 |
57 |
--------------------------------------------------------------------------------
/src/lib/components/stats/NetworkPanel.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
35 |
--------------------------------------------------------------------------------
/src/lib/components/stats/PanelHeader.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
17 |
18 |
51 |
--------------------------------------------------------------------------------
/src/lib/components/stats/ProgressBar.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
19 |
{label}
20 |
26 |
{Math.round(value)}%
27 |
28 |
29 |
81 |
--------------------------------------------------------------------------------
/src/lib/components/stats/StatItem.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 | {label}
8 | {value}
9 |
10 |
11 |
31 |
--------------------------------------------------------------------------------
/src/lib/components/stats/StatPanel.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
{title}
8 |
9 |
10 |
11 |
12 |
13 |
35 |
--------------------------------------------------------------------------------
/src/lib/components/stats/StatsBar.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | {#if systemStats}
16 |
17 |
18 |
19 |
24 |
25 |
30 |
31 |
32 |
33 |
37 |
38 | {/if}
39 |
40 |
41 |
53 |
--------------------------------------------------------------------------------
/src/lib/components/stats/StoragePanel.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
43 |
--------------------------------------------------------------------------------
/src/lib/components/stats/SystemPanel.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
38 |
--------------------------------------------------------------------------------
/src/lib/components/stats/index.ts:
--------------------------------------------------------------------------------
1 | export { default as StatsBar } from "./StatsBar.svelte";
2 | export { default as CpuPanel } from "./CpuPanel.svelte";
3 | export { default as MemoryPanel } from "./MemoryPanel.svelte";
4 | export { default as StoragePanel } from "./StoragePanel.svelte";
5 | export { default as SystemPanel } from "./SystemPanel.svelte";
6 | export { default as NetworkPanel } from "./NetworkPanel.svelte";
7 | export { default as PanelHeader } from "./PanelHeader.svelte";
8 | export { default as ProgressBar } from "./ProgressBar.svelte";
9 | export { default as StatItem } from "./StatItem.svelte";
10 |
--------------------------------------------------------------------------------
/src/lib/components/toolbar/ColumnToggle.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
45 |
46 | {#if showColumnMenu}
47 |
48 |
(showColumnMenu = false)}>
49 | {#each columns as column}
50 |
60 | {/each}
61 |
62 | {/if}
63 |
64 |
65 |
173 |
--------------------------------------------------------------------------------
/src/lib/components/toolbar/PaginationControls.svelte:
--------------------------------------------------------------------------------
1 |
26 |
27 |
74 |
75 |
149 |
--------------------------------------------------------------------------------
/src/lib/components/toolbar/RefreshControls.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
31 |
43 |
44 |
45 |
110 |
--------------------------------------------------------------------------------
/src/lib/components/toolbar/SearchBox.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
13 | {#if searchTerm}
14 |
17 | {/if}
18 |
19 |
20 |
21 |
91 |
--------------------------------------------------------------------------------
/src/lib/components/toolbar/StatusFilter.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
28 |
29 |
30 |
61 |
--------------------------------------------------------------------------------
/src/lib/components/toolbar/ToolBar.svelte:
--------------------------------------------------------------------------------
1 |
26 |
27 |
49 |
50 |
69 |
--------------------------------------------------------------------------------
/src/lib/components/toolbar/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ToolBar } from "./ToolBar.svelte";
2 | export { default as SearchBox } from "./SearchBox.svelte";
3 | export { default as StatusFilter } from "./StatusFilter.svelte";
4 | export { default as PaginationControls } from "./PaginationControls.svelte";
5 | export { default as ColumnToggle } from "./ColumnToggle.svelte";
6 | export { default as RefreshControls } from "./RefreshControls.svelte";
7 |
--------------------------------------------------------------------------------
/src/lib/constants/index.ts:
--------------------------------------------------------------------------------
1 | export const ASCII_ART = `
2 | ███╗ ██╗███████╗ ██████╗ ██╗ ██╗████████╗ ██████╗ ██████╗
3 | ████╗ ██║██╔════╝██╔═══██╗██║ ██║╚══██╔══╝██╔═══██╗██╔══██╗
4 | ██╔██╗ ██║█████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝
5 | ██║╚██╗██║██╔══╝ ██║ ██║██╔══██║ ██║ ██║ ██║██╔═══╝
6 | ██║ ╚████║███████╗╚██████╔╝██║ ██║ ██║ ╚██████╔╝██║
7 | ╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝
8 | `;
9 |
10 | export const APP_INFO = {
11 | name: "NeoHtop",
12 | developer: "Abdenasser",
13 | github: "https://github.com/Abdenasser/neohtop",
14 | stack: ["Tauri", "Rust", "Svelte", "TypeScript"],
15 | };
16 |
17 | export const ITEMS_PER_PAGE_OPTIONS = [15, 25, 50, 100, 250, 500];
18 |
19 | export const REFRESH_RATE_OPTIONS = [
20 | { value: 1000, label: "1s" },
21 | { value: 2000, label: "2s" },
22 | { value: 5000, label: "5s" },
23 | { value: 10000, label: "10s" },
24 | { value: 30000, label: "30s" },
25 | ];
26 |
27 | export const STATUS_OPTIONS = [
28 | { value: "all", label: "All Statuses" },
29 | { value: "running", label: "Running" },
30 | { value: "sleeping", label: "Sleeping" },
31 | { value: "idle", label: "Idle" },
32 | { value: "unknown", label: "Unknown" },
33 | ];
34 |
35 | export const THEME_GROUPS = [
36 | {
37 | label: "Dark",
38 | themes: [
39 | "catppuccin",
40 | "dracula",
41 | "monokaiPro",
42 | "tokyoNight",
43 | "solarizedDark",
44 | "ayuDark",
45 | "ayuMirage",
46 | ],
47 | },
48 | {
49 | label: "Light",
50 | themes: ["githubLight", "solarizedLight", "oneLight", "ayuLight"],
51 | },
52 | {
53 | label: "Warm",
54 | themes: ["gruvbox"],
55 | },
56 | {
57 | label: "Cool",
58 | themes: ["nord", "oneDark"],
59 | },
60 | {
61 | label: "Fun",
62 | themes: ["bubblegum", "rosePine", "cottonCandy", "synthwave", "candyfloss"],
63 | },
64 | {
65 | label: "Retro",
66 | themes: ["terminal", "amber", "ibmPC"],
67 | },
68 | {
69 | label: "Accessibility",
70 | themes: ["highContrast"],
71 | },
72 | ];
73 |
--------------------------------------------------------------------------------
/src/lib/definitions/columns.ts:
--------------------------------------------------------------------------------
1 | import type { Column } from "$lib/types";
2 | import { formatMemorySize } from "$lib/utils";
3 |
4 | export let column_definitions: Column[] = [
5 | { id: "name", label: "Process Name", visible: true, required: true },
6 | { id: "pid", label: "PID", visible: true, required: false },
7 | {
8 | id: "status",
9 | label: "Status",
10 | visible: true,
11 | },
12 | { id: "user", label: "User", visible: true },
13 | {
14 | id: "cpu_usage",
15 | label: "CPU %",
16 | visible: true,
17 | format: (v) => v.toFixed(1) + "%",
18 | },
19 | {
20 | id: "memory_usage",
21 | label: "RAM",
22 | visible: true,
23 | format: (v) => (v / (1024 * 1024)).toFixed(1) + " MB",
24 | },
25 | {
26 | id: "virtual_memory",
27 | label: "VIRT",
28 | visible: true,
29 | format: (v) => formatMemorySize(v),
30 | },
31 | {
32 | id: "disk_usage",
33 | label: "Disk I/O (R/W)",
34 | visible: true,
35 | format: (v) => {
36 | const readMB = (v[0] / (1024 * 1024)).toFixed(1);
37 | const writeMB = (v[1] / (1024 * 1024)).toFixed(1);
38 | return `${readMB}/${writeMB} MB`;
39 | },
40 | },
41 | { id: "ppid", label: "Parent PID", visible: false },
42 | { id: "root", label: "Root", visible: false },
43 | { id: "command", label: "Command", visible: false },
44 | { id: "environ", label: "Environment Variables", visible: false },
45 | { id: "session_id", label: "Session ID", visible: false },
46 | {
47 | id: "start_time",
48 | label: "Start Time",
49 | visible: false,
50 | format: (v) => new Date(v * 1000).toLocaleString(), // v is the time where the process was started (in seconds) from epoch
51 | },
52 | {
53 | id: "run_time",
54 | label: "Run Time",
55 | visible: true,
56 | format: (v) => {
57 | const seconds = v; // v is the time the process has been running in seconds
58 | const hours = Math.floor(seconds / 3600);
59 | const minutes = Math.floor((seconds % 3600) / 60);
60 | const remainingSeconds = seconds % 60;
61 | return `${hours}h ${minutes}m ${remainingSeconds}s`; // Format as HH:MM:SS
62 | },
63 | },
64 | ];
65 |
--------------------------------------------------------------------------------
/src/lib/definitions/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./columns";
2 | export * from "./settings";
3 | export * from "./themes";
4 |
--------------------------------------------------------------------------------
/src/lib/definitions/settings.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig } from "$lib/types";
2 |
3 | export const DEFAULT_CONFIG: AppConfig = {
4 | appearance: {
5 | columnVisibility: {
6 | name: true,
7 | pid: true,
8 | status: true,
9 | user: true,
10 | cpu_usage: true,
11 | memory_usage: true,
12 | virtual_memory: true,
13 | disk_usage: true,
14 | ppid: false,
15 | root: false,
16 | command: false,
17 | environ: false,
18 | session_id: false,
19 | start_time: false,
20 | run_time: true,
21 | },
22 | },
23 | behavior: {
24 | itemsPerPage: 15,
25 | refreshRate: 1000,
26 | defaultStatusFilter: "all",
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/src/lib/stores/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./processes";
2 | export * from "./theme";
3 | export * from "./settings";
4 |
--------------------------------------------------------------------------------
/src/lib/stores/processes.ts:
--------------------------------------------------------------------------------
1 | import { writable, derived } from "svelte/store";
2 | import type { Process, SystemStats } from "$lib/types";
3 | import { invoke } from "@tauri-apps/api/core";
4 |
5 | interface ProcessStore {
6 | processes: Process[];
7 | systemStats: SystemStats | null;
8 | error: string | null;
9 | isLoading: boolean;
10 | searchTerm: string;
11 | currentPage: number;
12 | pinnedProcesses: Set;
13 | selectedProcess: Process | null;
14 | showInfoModal: boolean;
15 | showConfirmModal: boolean;
16 | processToKill: Process | null;
17 | isKilling: boolean;
18 | isFrozen: boolean;
19 | selectedProcessPid: number | null;
20 | sortConfig: {
21 | field: keyof Process;
22 | direction: "asc" | "desc";
23 | };
24 | }
25 |
26 | const initialState: ProcessStore = {
27 | processes: [],
28 | systemStats: null,
29 | error: null,
30 | isLoading: true,
31 | searchTerm: "",
32 | currentPage: 1,
33 | pinnedProcesses: new Set(),
34 | selectedProcess: null,
35 | showInfoModal: false,
36 | showConfirmModal: false,
37 | processToKill: null,
38 | isKilling: false,
39 | isFrozen: false,
40 | selectedProcessPid: null,
41 | sortConfig: {
42 | field: "cpu_usage",
43 | direction: "desc",
44 | },
45 | };
46 |
47 | function createProcessStore() {
48 | const { subscribe, set, update } = writable(initialState);
49 |
50 | // Define all methods first
51 | const setIsLoading = (isLoading: boolean) =>
52 | update((state) => ({ ...state, isLoading }));
53 |
54 | const getProcesses = async () => {
55 | try {
56 | const result = await invoke<[Process[], SystemStats]>("get_processes");
57 | update((state) => {
58 | let updatedSelectedProcess = state.selectedProcess;
59 | if (state.selectedProcessPid) {
60 | updatedSelectedProcess =
61 | result[0].find((p) => p.pid === state.selectedProcessPid) || null;
62 | }
63 |
64 | return {
65 | ...state,
66 | processes: result[0],
67 | systemStats: result[1],
68 | error: null,
69 | selectedProcess: updatedSelectedProcess,
70 | };
71 | });
72 | } catch (e: unknown) {
73 | update((state) => ({
74 | ...state,
75 | error: e instanceof Error ? e.message : String(e),
76 | }));
77 | }
78 | };
79 |
80 | const killProcess = async (pid: number) => {
81 | try {
82 | update((state) => ({ ...state, isKilling: true }));
83 | const success = await invoke("kill_process", { pid });
84 | if (success) {
85 | await getProcesses();
86 | } else {
87 | throw new Error("Failed to kill process");
88 | }
89 | } catch (e: unknown) {
90 | update((state) => ({
91 | ...state,
92 | error: e instanceof Error ? e.message : String(e),
93 | }));
94 | } finally {
95 | update((state) => ({ ...state, isKilling: false }));
96 | }
97 | };
98 |
99 | const toggleSort = (field: keyof Process) => {
100 | update((state) => ({
101 | ...state,
102 | sortConfig: {
103 | field,
104 | direction:
105 | state.sortConfig.field === field
106 | ? state.sortConfig.direction === "asc"
107 | ? "desc"
108 | : "asc"
109 | : "desc",
110 | },
111 | }));
112 | };
113 |
114 | const togglePin = (command: string) => {
115 | update((state) => {
116 | const newPinnedProcesses = new Set(state.pinnedProcesses);
117 | if (newPinnedProcesses.has(command)) {
118 | newPinnedProcesses.delete(command);
119 | } else {
120 | newPinnedProcesses.add(command);
121 | }
122 | return { ...state, pinnedProcesses: newPinnedProcesses };
123 | });
124 | };
125 |
126 | const setSearchTerm = (searchTerm: string) =>
127 | update((state) => ({ ...state, searchTerm, currentPage: 1 }));
128 |
129 | const setIsFrozen = (isFrozen: boolean) =>
130 | update((state) => ({ ...state, isFrozen }));
131 |
132 | const setCurrentPage = (currentPage: number) =>
133 | update((state) => ({ ...state, currentPage }));
134 |
135 | const showProcessDetails = (process: Process) => {
136 | update((state) => ({
137 | ...state,
138 | selectedProcessPid: process.pid,
139 | selectedProcess: process,
140 | showInfoModal: true,
141 | }));
142 | };
143 |
144 | const closeProcessDetails = () => {
145 | update((state) => ({
146 | ...state,
147 | showInfoModal: false,
148 | selectedProcess: null,
149 | selectedProcessPid: null,
150 | }));
151 | };
152 |
153 | const confirmKillProcess = (process: Process) => {
154 | update((state) => ({
155 | ...state,
156 | processToKill: process,
157 | showConfirmModal: true,
158 | }));
159 | };
160 |
161 | const closeConfirmKill = () => {
162 | update((state) => ({
163 | ...state,
164 | showConfirmModal: false,
165 | processToKill: null,
166 | }));
167 | };
168 |
169 | const handleConfirmKill = async () => {
170 | let processToKill: Process | null = null;
171 |
172 | let currentState: ProcessStore | undefined;
173 | const unsubscribe = subscribe((state) => {
174 | currentState = state;
175 | });
176 | unsubscribe();
177 |
178 | if (currentState?.processToKill && "pid" in currentState.processToKill) {
179 | processToKill = currentState.processToKill;
180 | }
181 |
182 | if (!processToKill?.pid) {
183 | return;
184 | }
185 |
186 | try {
187 | await killProcess(processToKill.pid);
188 | } finally {
189 | update((state) => ({
190 | ...state,
191 | showConfirmModal: false,
192 | processToKill: null,
193 | }));
194 | }
195 | };
196 |
197 | // Return all methods
198 | return {
199 | subscribe,
200 | set,
201 | update,
202 | setIsLoading,
203 | getProcesses,
204 | killProcess,
205 | toggleSort,
206 | togglePin,
207 | setSearchTerm,
208 | setIsFrozen,
209 | setCurrentPage,
210 | showProcessDetails,
211 | closeProcessDetails,
212 | confirmKillProcess,
213 | closeConfirmKill,
214 | handleConfirmKill,
215 | };
216 | }
217 |
218 | export const processStore = createProcessStore();
219 |
--------------------------------------------------------------------------------
/src/lib/stores/settings.ts:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 | import type { AppConfig } from "$lib/types";
3 | import { DEFAULT_CONFIG } from "$lib/definitions/settings";
4 |
5 | function createSettingsStore() {
6 | const { subscribe, set, update } = writable(DEFAULT_CONFIG);
7 |
8 | return {
9 | subscribe,
10 | init: () => {
11 | if (typeof window !== "undefined") {
12 | const stored = localStorage.getItem("neohtop_config");
13 | if (stored) {
14 | try {
15 | const config = JSON.parse(stored);
16 | set({ ...DEFAULT_CONFIG, ...config });
17 | } catch (e) {
18 | console.error("Failed to parse stored config:", e);
19 | set(DEFAULT_CONFIG);
20 | }
21 | }
22 | }
23 | },
24 | updateConfig: (newConfig: Partial) => {
25 | update((config) => {
26 | const updated = { ...config, ...newConfig };
27 | if (typeof window !== "undefined") {
28 | localStorage.setItem("neohtop_config", JSON.stringify(updated));
29 | }
30 | return updated;
31 | });
32 | },
33 | };
34 | }
35 |
36 | export const settingsStore = createSettingsStore();
37 |
--------------------------------------------------------------------------------
/src/lib/stores/theme.ts:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 | import { themes } from "$lib/definitions/themes";
3 | import type { Theme } from "$lib/types";
4 | function createThemeStore() {
5 | // Default theme
6 | const defaultTheme = themes.catppuccin;
7 |
8 | // Initialize with default theme
9 | const { subscribe, set } = writable(defaultTheme);
10 |
11 | // Initialize theme on client-side only
12 | if (typeof window !== "undefined") {
13 | const storedTheme = localStorage.getItem("theme");
14 | if (storedTheme && themes[storedTheme]) {
15 | set(themes[storedTheme]);
16 | }
17 | }
18 |
19 | return {
20 | subscribe,
21 | setTheme: (themeName: string) => {
22 | const theme = themes[themeName];
23 | if (theme) {
24 | if (typeof window !== "undefined") {
25 | localStorage.setItem("theme", themeName);
26 | // Add this line to set the data-theme attribute
27 | document.documentElement.setAttribute("data-theme", themeName);
28 | }
29 | set(theme);
30 | applyTheme(theme);
31 | }
32 | },
33 | init: () => {
34 | const storedTheme =
35 | typeof window !== "undefined" ? localStorage.getItem("theme") : null;
36 | const theme = (storedTheme && themes[storedTheme]) || defaultTheme;
37 | if (typeof window !== "undefined") {
38 | // Add this line to set the data-theme attribute on init
39 | document.documentElement.setAttribute(
40 | "data-theme",
41 | storedTheme || "catppuccin",
42 | );
43 | }
44 | applyTheme(theme);
45 | },
46 | };
47 | }
48 |
49 | function applyTheme(theme: Theme) {
50 | if (typeof window !== "undefined") {
51 | const root = document.documentElement;
52 | Object.entries(theme.colors).forEach(([key, value]) => {
53 | root.style.setProperty(`--${key}`, value);
54 | });
55 | }
56 | }
57 |
58 | export const themeStore = createThemeStore();
59 |
--------------------------------------------------------------------------------
/src/lib/types/index.ts:
--------------------------------------------------------------------------------
1 | // Create a new types file to centralize interfaces
2 | export interface Process {
3 | pid: number;
4 | ppid: number;
5 | name: string;
6 | cpu_usage: number;
7 | memory_usage: number;
8 | status: string;
9 | user: string;
10 | command: string;
11 | threads?: number;
12 | environ: string[];
13 | root: string;
14 | virtual_memory: number;
15 | start_time: number;
16 | run_time: number;
17 | disk_usage: [number, number]; // [read_bytes, written_bytes]
18 | session_id?: number;
19 | }
20 |
21 | export interface SystemStats {
22 | cpu_usage: number[];
23 | memory_total: number;
24 | memory_used: number;
25 | memory_free: number;
26 | memory_cached: number;
27 | uptime: number;
28 | load_avg: [number, number, number];
29 | network_rx_bytes: number;
30 | network_tx_bytes: number;
31 | disk_total_bytes: number;
32 | disk_used_bytes: number;
33 | disk_free_bytes: number;
34 | }
35 |
36 | export interface Column {
37 | id: keyof Process;
38 | label: string;
39 | visible: boolean;
40 | required?: boolean;
41 | format?: (value: any) => string;
42 | }
43 |
44 | export interface Theme {
45 | name: string;
46 | label: string;
47 | colors: {
48 | base: string;
49 | mantle: string;
50 | crust: string;
51 | text: string;
52 | subtext0: string;
53 | subtext1: string;
54 | surface0: string;
55 | surface1: string;
56 | surface2: string;
57 | overlay0: string;
58 | overlay1: string;
59 | blue: string;
60 | lavender: string;
61 | sapphire: string;
62 | sky: string;
63 | red: string;
64 | maroon: string;
65 | peach: string;
66 | yellow: string;
67 | green: string;
68 | teal: string;
69 | };
70 | }
71 |
72 | export interface AppConfig {
73 | appearance: {
74 | columnVisibility: Record;
75 | };
76 | behavior: {
77 | itemsPerPage: number;
78 | refreshRate: number;
79 | defaultStatusFilter: string;
80 | };
81 | }
82 |
83 | export interface ColumnDefinition {
84 | id: string;
85 | label: string;
86 | visible: boolean;
87 | required?: boolean;
88 | }
89 |
90 | export interface StatusOption {
91 | value: string;
92 | label: string;
93 | }
94 |
95 | export interface RefreshRateOption {
96 | value: number;
97 | label: string;
98 | }
99 |
100 | export interface ToolBarProps {
101 | searchTerm: string;
102 | statusFilter: string;
103 | itemsPerPage: number;
104 | currentPage: number;
105 | totalPages: number;
106 | totalResults: number;
107 | columns: ColumnDefinition[];
108 | refreshRate: number;
109 | isFrozen: boolean;
110 | }
111 |
112 | export interface SortConfig {
113 | field: keyof Process;
114 | direction: "asc" | "desc";
115 | }
116 |
--------------------------------------------------------------------------------
/src/lib/utils/index.ts:
--------------------------------------------------------------------------------
1 | import type { Process } from "$lib/types";
2 | import type { SortConfig } from "$lib/types";
3 |
4 | export interface ProcessStatus {
5 | label: string;
6 | emoji: string;
7 | color: string;
8 | }
9 |
10 | export function formatMemorySize(bytes: number): string {
11 | const gb = bytes / (1024 * 1024 * 1024);
12 | return `${gb.toFixed(1)} GB`;
13 | }
14 |
15 | export function formatPercentage(value: number): string {
16 | return `${value.toFixed(1)}%`;
17 | }
18 |
19 | export function formatUptime(seconds: number): string {
20 | const days = Math.floor(seconds / 86400);
21 | const hours = Math.floor((seconds % 86400) / 3600);
22 | const minutes = Math.floor((seconds % 3600) / 60);
23 | return `${days}d ${hours}h ${minutes}m`;
24 | }
25 |
26 | export function getUsageClass(percentage: number): string {
27 | if (percentage >= 90) return "critical";
28 | if (percentage >= 60) return "high";
29 | if (percentage >= 30) return "medium";
30 | return "low";
31 | }
32 |
33 | export function formatBytes(bytes: number): string {
34 | const units = ["B", "KB", "MB", "GB", "TB"];
35 | let value = bytes;
36 | let unitIndex = 0;
37 |
38 | while (value >= 1024 && unitIndex < units.length - 1) {
39 | value /= 1024;
40 | unitIndex++;
41 | }
42 |
43 | return `${value.toFixed(1)} ${units[unitIndex]}`;
44 | }
45 |
46 | export function formatDate(timestamp: number) {
47 | return new Date(timestamp * 1000).toLocaleString();
48 | }
49 |
50 | // Cache for compiled regex patterns
51 | const regexCache = new Map();
52 |
53 | export function filterProcesses(
54 | processes: Process[],
55 | searchTerm: string,
56 | statusFilter: string,
57 | ): Process[] {
58 | // Early return for empty search and "all" status
59 | if (searchTerm.length === 0 && statusFilter === "all") {
60 | return processes;
61 | }
62 |
63 | // Pre-process search terms once
64 | const terms =
65 | searchTerm.length > 0
66 | ? searchTerm.split(",").map((term) => term.trim())
67 | : [];
68 |
69 | return processes.filter((process) => {
70 | // Early status check
71 | if (
72 | statusFilter !== "all" &&
73 | process.status.toLowerCase() !== statusFilter
74 | ) {
75 | return false;
76 | }
77 |
78 | // Skip search if no terms
79 | if (terms.length === 0) {
80 | return true;
81 | }
82 |
83 | // Cache lowercase values
84 | const processNameLower = process.name.toLowerCase();
85 | const processCommandLower = process.command.toLowerCase();
86 | const processPidString = process.pid.toString();
87 |
88 | // Check each term
89 | return terms.some((term) => {
90 | const termLower = term.toLowerCase();
91 |
92 | // Try exact matches first (faster)
93 | if (
94 | processNameLower.includes(termLower) ||
95 | processCommandLower.includes(termLower) ||
96 | processPidString.includes(term)
97 | ) {
98 | return true;
99 | }
100 |
101 | // Try regex match last (slower)
102 | try {
103 | let regex = regexCache.get(term);
104 | if (!regex) {
105 | regex = new RegExp(term, "i");
106 | regexCache.set(term, regex);
107 | }
108 | return regex.test(process.name);
109 | } catch {
110 | return false;
111 | }
112 | });
113 | });
114 | }
115 |
116 | // Create a Map for quick pinned status lookup
117 | const isPinned = new Map();
118 |
119 | export function sortProcesses(
120 | processes: Process[],
121 | sortConfig: SortConfig,
122 | pinnedProcesses: Set,
123 | ): Process[] {
124 | // Clear the cache before sorting
125 | isPinned.clear();
126 |
127 | return [...processes].sort((a, b) => {
128 | // Cache pinned status
129 | let aPin = pinnedProcesses.has(a.command);
130 | isPinned.set(a.command, aPin);
131 |
132 | let bPin = pinnedProcesses.has(b.command);
133 | isPinned.set(b.command, bPin);
134 |
135 | // Quick pin comparison
136 | if (aPin !== bPin) {
137 | return aPin ? -1 : 1;
138 | }
139 |
140 | // Only compute direction once
141 | const direction = sortConfig.direction === "asc" ? 1 : -1;
142 | const aValue = a[sortConfig.field];
143 | const bValue = b[sortConfig.field];
144 |
145 | // Special handling for disk_usage which is an array [read_bytes, written_bytes]
146 | if (sortConfig.field === "disk_usage") {
147 | const aRead = (aValue as [number, number])[0];
148 | const aWrite = (aValue as [number, number])[1];
149 | const bRead = (bValue as [number, number])[0];
150 | const bWrite = (bValue as [number, number])[1];
151 |
152 | // Smart sorting: analyze if this is a read-heavy or write-heavy comparison
153 | const totalReads = aRead + bRead;
154 | const totalWrites = aWrite + bWrite;
155 |
156 | if (totalWrites > totalReads * 1.5) {
157 | // Write-heavy scenario: prioritize writes, use reads as tiebreaker
158 | if (aWrite !== bWrite) {
159 | return direction * (aWrite - bWrite);
160 | }
161 | return direction * (aRead - bRead);
162 | } else if (totalReads > totalWrites * 1.5) {
163 | // Read-heavy scenario: prioritize reads, use writes as tiebreaker
164 | if (aRead !== bRead) {
165 | return direction * (aRead - bRead);
166 | }
167 | return direction * (aWrite - bWrite);
168 | } else {
169 | // Balanced I/O: sort by total, use max as tiebreaker
170 | const aTotalDisk = aRead + aWrite;
171 | const bTotalDisk = bRead + bWrite;
172 | if (aTotalDisk !== bTotalDisk) {
173 | return direction * (aTotalDisk - bTotalDisk);
174 | }
175 | // Tiebreaker: use the dominant operation
176 | const aMaxDisk = Math.max(aRead, aWrite);
177 | const bMaxDisk = Math.max(bRead, bWrite);
178 | return direction * (aMaxDisk - bMaxDisk);
179 | }
180 | }
181 |
182 | // Type-specific comparisons
183 | if (typeof aValue === "string") {
184 | return direction * aValue.localeCompare(bValue as string);
185 | }
186 | return direction * (Number(aValue) - Number(bValue));
187 | });
188 | }
189 |
--------------------------------------------------------------------------------
/src/routes/+layout.js:
--------------------------------------------------------------------------------
1 | // Tauri doesn't have a Node.js server to do proper SSR
2 | // so we will use adapter-static to prerender the app (SSG)
3 | // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
4 | export const prerender = true;
5 | export const ssr = false;
6 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
90 |
91 | {#if isLoading}
92 |
93 |
94 |

95 |
96 |
97 | {:else}
98 |
99 |
100 |
101 | {#if systemStats}
102 |
103 | {/if}
104 |
105 |
116 |
117 | {#if error}
118 | {error}
119 | {/if}
120 |
121 |
132 |
133 |
134 | {/if}
135 |
136 |
143 |
144 |
151 |
152 |
252 |
--------------------------------------------------------------------------------
/static/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/static/128x128.png
--------------------------------------------------------------------------------
/static/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/static/32x32.png
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdenasser/neohtop/3ea16752d6e79b00c7d505e55257727f3814dcc0/static/favicon.png
--------------------------------------------------------------------------------
/static/svelte.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/tauri.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/static/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | // Tauri doesn't have a Node.js server to do proper SSR
2 | // so we will use adapter-static to prerender the app (SSG)
3 | // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
4 | import adapter from "@sveltejs/adapter-static";
5 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
6 |
7 | /** @type {import('@sveltejs/kit').Config} */
8 | const config = {
9 | kit: {
10 | adapter: adapter(),
11 | alias: {
12 | $lib: 'src/lib'
13 | }
14 | },
15 | preprocess: vitePreprocess()
16 | };
17 |
18 | export default config;
19 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { sveltekit } from "@sveltejs/kit/vite";
3 |
4 | const host = process.env.TAURI_DEV_HOST;
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig(async () => ({
8 | plugins: [sveltekit()],
9 | resolve: {
10 | alias: {
11 | $lib: "/src/lib",
12 | },
13 | },
14 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
15 | //
16 | // 1. prevent vite from obscuring rust errors
17 | clearScreen: false,
18 | // 2. tauri expects a fixed port, fail if that port is not available
19 | server: {
20 | port: 1420,
21 | strictPort: true,
22 | host: host || false,
23 | hmr: host
24 | ? {
25 | protocol: "ws",
26 | host,
27 | port: 1421,
28 | }
29 | : undefined,
30 | watch: {
31 | // 3. tell vite to ignore watching `src-tauri`
32 | ignored: ["**/src-tauri/**"],
33 | },
34 | },
35 | }));
36 |
--------------------------------------------------------------------------------