29 Commits
Author SHA1 Message Date
mysticmomba 5ef796671f Merge pull request 'Feat/minimizetotray' (#16) from feat/minimizetotray into development
Reviewed-on: #16
2026-10-09 11:48:53 -04:00
mysticmomba 20b5c6e798 Updated change log to reflect version 2026-10-09 11:47:53 -04:00
mysticmomba 08eca1aac4 Updated Changelog 2026-10-09 11:47:14 -04:00
mysticmomba 2ce736e10f feat: Added Desktop Notifications While Minimized. This Will Send Notifications to the Desktop if a Host Status Changes While It’s Minimized to the System Tray. Closes #12. 2026-10-09 11:44:39 -04:00
mysticmomba 8f8e77242b feat: Added Minimize to System Tray While Monitor Is Running. Closes #6. 2026-10-09 11:16:32 -04:00
mysticmomba cf2cc9f8ed Added build script 2026-10-09 09:46:32 -04:00
mysticmomba fd8bb16d40 Updated Theme 2026-10-09 09:38:24 -04:00
mysticmomba ed9ed59de9 Updated themes a tad 2026-10-08 15:12:29 -04:00
mysticmomba 56038b7a7e feat: Added more themes 2026-10-08 12:50:48 -04:00
mysticmomba 7a8465b7fb Updated License 2026-10-08 12:25:34 -04:00
mysticmomba 29e0ae9d48 Updated Changelog 2026-10-08 11:23:41 -04:00
mysticmomba 82c07ad81a Updated Version 2026-10-08 11:23:19 -04:00
mysticmomba 1fe24d22ed feat: Added New Theme 2026-10-08 11:22:28 -04:00
mysticmomba aaeb7636a1 feat: Added Always on Top Toggle. Closes #5 2026-10-08 11:00:40 -04:00
mysticmomba 1df9c2f00c fix: Fixed Screen Refresh Bug on Monitor Tab. Now When You Go To Change a Label While the Monitor Is Running. The Page Stops Refreshing Until You’re Done Changing the Name. This Is To Prevent Your Text Box From Being Deselected When the Table Refreshes. 2026-10-08 10:52:50 -04:00
mysticmomba d6ba6270c5 Updated Changelog 2026-10-08 10:43:46 -04:00
mysticmomba c5931c723f feat: Added Auto Labeling to Montior Tab. When sending hosts from the scan tab, it will bring their hostnames over to the label section. When adding a one off host to the montior tab, it will auto query it's hostname. You can also edit labels by double clicking. Closes #3 2026-10-06 16:49:04 -04:00
mysticmomba 8cc8189820 fix: Removed unused function. 2026-10-06 16:36:30 -04:00
mysticmomba dbf98d598d fix: Status Colors on Monitor tab now reflect status of host. Closes #7 2026-10-06 16:27:32 -04:00
mysticmomba 595d73a41d Fixed main.js 2026-09-24 10:21:46 -04:00
mysticmomba 4c72cdab29 Updated Changelog 2026-09-24 10:07:58 -04:00
mysticmomba 5b3aa3bf37 fix: Fixed updater to only check for newer version using proper semver checking. Before it only checked if the current in use version was different than what was available. 2026-09-24 10:07:13 -04:00
mysticmomba f3dd13d831 Updated changelog 2026-09-24 10:04:20 -04:00
mysticmomba 4596b93bb6 Updated Change log and Version 2026-09-24 10:02:09 -04:00
mysticmomba adc061ae26 feat: Added Virtual Scrolling. This allows us to scan a whole /16 subnet without overloading the UI or using a lot of RAM. 2026-09-24 09:43:55 -04:00
mysticmomba d84902c111 feat: Added Themes 2026-09-24 09:12:18 -04:00
mysticmomba 5be8e31483 feat: Added changelog 2026-09-24 09:07:31 -04:00
mysticmomba eb1d0ba0fd Updated README 2026-09-23 10:23:27 -04:00
mysticmomba 293c3b137d Fixed popup window 2026-09-23 10:22:06 -04:00
31 changed files with 1535 additions and 305 deletions
+3
View File
@@ -0,0 +1,3 @@
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
+32
View File
@@ -0,0 +1,32 @@
## [0.4.0] - 2026-10-09
### 🚀 Features
- Added more themes
- Added Minimize to System Tray While Monitor Is Running. Closes #6.
- Added Desktop Notifications While Minimized. This Will Send Notifications to the Desktop if a Host Status Changes While It’s Minimized to the System Tray. Closes #12.
## [0.3.0] - 2026-10-08
### 🚀 Features
- Added Auto Labeling to Montior Tab. When sending hosts from the scan tab, it will bring their hostnames over to the label section. When adding a one off host to the montior tab, it will auto query it's hostname. You can also edit labels by double clicking. Closes #3
- Added Always on Top Toggle. Closes #5
- Added New Theme
### 🐛 Bug Fixes
- Status Colors on Monitor tab now reflect status of host. Closes #7
- Removed unused function.
- Fixed Screen Refresh Bug on Monitor Tab. Now When You Go To Change a Label While the Monitor Is Running. The Page Stops Refreshing Until You’re Done Changing the Name. This Is To Prevent Your Text Box From Being Deselected When the Table Refreshes.
## [0.2.0] - 2026-09-24
### 🚀 Features
- Added changelog
- Added Themes
- Added Virtual Scrolling. This allows us to scan a whole /16 subnet without overloading the UI or using a lot of RAM.
### 🐛 Bug Fixes
- Fixed updater to only check for newer version using proper semver checking. Before it only checked if the current in use version was different than what was available.
## [0.1.0] - 2026-09-23
+1 -1
View File
@@ -625,7 +625,7 @@ state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
Argus, an all in one network utility Argus, an all in one network utility
Copyright (C) 2026 Jamse Brotosky Copyright (C) 2026 James Brotosky
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published it under the terms of the GNU Affero General Public License as published
+3
View File
@@ -61,6 +61,9 @@ cargo tauri dev
# Production build # Production build
cargo tauri build cargo tauri build
# Cross Compiling Windows on Linux
cargo tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc
``` ```
## Privileges ## Privileges
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
cd src-tauri && cargo clean
cargo clippy --fix --all-targets --allow-dirty
cargo fmt
cargo tauri build
cargo tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc
+94
View File
@@ -0,0 +1,94 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[changelog]
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}
"""
# Remove leading and trailing whitespaces from the changelog's body.
trim = true
# Render body even when there are no releases to process.
render_always = true
# An array of regex based postprocessors to modify the changelog.
postprocessors = [
# Replace the placeholder <REPO> with a URL.
#{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# Parse commits according to the conventional commits specification.
# See https://www.conventionalcommits.org
conventional_commits = true
# Exclude commits that do not match the conventional commits specification.
filter_unconventional = true
# Require all commits to be conventional.
# Takes precedence over filter_unconventional.
require_conventional = false
# Split commits on newlines, treating each line as an individual commit.
split_commits = false
# An array of regex based parsers to modify commit messages prior to further processing.
commit_preprocessors = [
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit message using https://github.com/crate-ci/typos.
# If the spelling is incorrect, it will be fixed automatically.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# Prevent commits that are breaking from being excluded by commit parsers.
protect_breaking_commits = false
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = ".*", group = "<!-- 10 -->💼 Other" },
]
# Exclude commits that are not matched by any commit parser.
filter_commits = false
# Fail on a commit that is not matched by any commit parser.
fail_on_unmatched_commit = false
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
link_parsers = []
# Include only the tags that belong to the current branch.
use_branch_tags = false
# Order releases topologically instead of chronologically.
topo_order = false
# Order commits topologically instead of chronologically.
topo_order_commits = true
# Order of commits in each group/release within the changelog.
# Allowed values: newest, oldest
sort_commits = "oldest"
# Process submodules commits
recurse_submodules = false
+224 -186
View File
@@ -19,15 +19,15 @@ dependencies = [
[[package]] [[package]]
name = "alloc-no-stdlib" name = "alloc-no-stdlib"
version = "2.0.4" version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
[[package]] [[package]]
name = "alloc-stdlib" name = "alloc-stdlib"
version = "0.2.4" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
] ]
@@ -49,7 +49,7 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]] [[package]]
name = "argus" name = "argus"
version = "0.1.0" version = "0.3.0"
dependencies = [ dependencies = [
"csv", "csv",
"dns-lookup", "dns-lookup",
@@ -58,13 +58,17 @@ dependencies = [
"libc", "libc",
"open", "open",
"ping-async", "ping-async",
"pulldown-cmark",
"reqwest 0.12.28", "reqwest 0.12.28",
"semver",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-dialog", "tauri-plugin-dialog",
"tauri-plugin-notification",
"tauri-plugin-opener", "tauri-plugin-opener",
"tauri-plugin-single-instance",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@@ -317,9 +321,9 @@ dependencies = [
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "8.0.4" version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
@@ -328,9 +332,9 @@ dependencies = [
[[package]] [[package]]
name = "brotli-decompressor" name = "brotli-decompressor"
version = "5.0.3" version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
@@ -431,12 +435,13 @@ dependencies = [
[[package]] [[package]]
name = "cargo_toml" name = "cargo_toml"
version = "0.22.3" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9"
dependencies = [ dependencies = [
"semver",
"serde", "serde",
"toml 0.9.12+spec-1.1.0", "toml 1.1.6+spec-1.1.0",
] ]
[[package]] [[package]]
@@ -457,13 +462,13 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]] [[package]]
name = "cfb" name = "cfb"
version = "0.7.3" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3"
dependencies = [ dependencies = [
"byteorder",
"fnv", "fnv",
"uuid", "uuid",
"web-time",
] ]
[[package]] [[package]]
@@ -624,9 +629,9 @@ dependencies = [
[[package]] [[package]]
name = "cssparser" name = "cssparser"
version = "0.36.0" version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98"
dependencies = [ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
@@ -637,12 +642,12 @@ dependencies = [
[[package]] [[package]]
name = "cssparser-macros" name = "cssparser-macros"
version = "0.6.1" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.119", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -668,19 +673,9 @@ dependencies = [
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.8.0" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d"
dependencies = [
"ctor-proc-macro",
"dtor",
]
[[package]]
name = "ctor-proc-macro"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
[[package]] [[package]]
name = "darling" name = "darling"
@@ -800,9 +795,9 @@ dependencies = [
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "6.0.0" version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f"
dependencies = [ dependencies = [
"dirs-sys", "dirs-sys",
] ]
@@ -879,9 +874,9 @@ dependencies = [
[[package]] [[package]]
name = "dom_query" name = "dom_query"
version = "0.27.0" version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"cssparser", "cssparser",
@@ -916,21 +911,6 @@ dependencies = [
"dtoa", "dtoa",
] ]
[[package]]
name = "dtor"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4"
dependencies = [
"dtor-proc-macro",
]
[[package]]
name = "dtor-proc-macro"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
[[package]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.5" version = "1.0.5"
@@ -1367,6 +1347,15 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "getopts"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@@ -1606,9 +1595,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "html5ever" name = "html5ever"
version = "0.38.0" version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
dependencies = [ dependencies = [
"log", "log",
"markup5ever", "markup5ever",
@@ -1899,9 +1888,9 @@ dependencies = [
[[package]] [[package]]
name = "infer" name = "infer"
version = "0.19.0" version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4"
dependencies = [ dependencies = [
"cfb", "cfb",
] ]
@@ -2089,21 +2078,21 @@ dependencies = [
[[package]] [[package]]
name = "json-patch" name = "json-patch"
version = "3.0.1" version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72"
dependencies = [ dependencies = [
"jsonptr", "jsonptr",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.69", "thiserror 2.0.20",
] ]
[[package]] [[package]]
name = "jsonptr" name = "jsonptr"
version = "0.6.3" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@@ -2111,13 +2100,12 @@ dependencies = [
[[package]] [[package]]
name = "keyboard-types" name = "keyboard-types"
version = "0.7.0" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d"
dependencies = [ dependencies = [
"bitflags 2.13.2", "bitflags 2.13.2",
"serde", "serde",
"unicode-segmentation",
] ]
[[package]] [[package]]
@@ -2212,10 +2200,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]] [[package]]
name = "markup5ever" name = "mac-notification-sys"
version = "0.38.0" version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca"
dependencies = [
"cc",
"log",
"objc2",
"objc2-foundation",
"time",
"uuid",
]
[[package]]
name = "markup5ever"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
dependencies = [ dependencies = [
"log", "log",
"tendril", "tendril",
@@ -2285,9 +2287,9 @@ dependencies = [
[[package]] [[package]]
name = "muda" name = "muda"
version = "0.19.3" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dpi", "dpi",
@@ -2363,6 +2365,12 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "ndk-context"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]] [[package]]
name = "ndk-sys" name = "ndk-sys"
version = "0.6.0+11769913" version = "0.6.0+11769913"
@@ -2378,6 +2386,20 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "notify-rust"
version = "4.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4587364a9a0074333429b3df75a30a205340c56a536ca3eb6ca0e59b87bbf8af"
dependencies = [
"futures-lite",
"log",
"mac-notification-sys",
"serde",
"tauri-winrt-notification",
"zbus",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.50.3" version = "0.50.3"
@@ -2445,6 +2467,7 @@ dependencies = [
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation", "objc2-foundation",
"objc2-quartz-core",
] ]
[[package]] [[package]]
@@ -3006,6 +3029,25 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "pulldown-cmark"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
dependencies = [
"bitflags 2.13.2",
"getopts",
"memchr",
"pulldown-cmark-escape",
"unicase",
]
[[package]]
name = "pulldown-cmark-escape"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.42.0" version = "0.42.0"
@@ -3424,9 +3466,9 @@ dependencies = [
[[package]] [[package]]
name = "selectors" name = "selectors"
version = "0.36.1" version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d"
dependencies = [ dependencies = [
"bitflags 2.13.2", "bitflags 2.13.2",
"cssparser", "cssparser",
@@ -3893,9 +3935,9 @@ dependencies = [
[[package]] [[package]]
name = "tao" name = "tao"
version = "0.35.3" version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" checksum = "f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400"
dependencies = [ dependencies = [
"bitflags 2.13.2", "bitflags 2.13.2",
"block2", "block2",
@@ -3913,20 +3955,20 @@ dependencies = [
"libc", "libc",
"log", "log",
"ndk", "ndk",
"ndk-context",
"ndk-sys", "ndk-sys",
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
"objc2-foundation", "objc2-foundation",
"objc2-ui-kit", "objc2-ui-kit",
"once_cell",
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",
"raw-window-handle", "raw-window-handle",
"tao-macros", "tao-macros",
"unicode-segmentation", "unicode-segmentation",
"url", "url",
"windows 0.61.3", "windows 0.62.2",
"windows-core 0.61.2", "windows-core 0.62.2",
"windows-version", "windows-version",
"x11-dl", "x11-dl",
] ]
@@ -3950,9 +3992,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "tauri" name = "tauri"
version = "2.11.6" version = "2.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fa5bacdb9bbad5954af3d1bd6cf6ae9192cab1b2e270f4a07f904610b9e85f4" checksum = "d061d042ccd2df9c9f1508c1e63d835278c0a4c8801eb41764080e51f2eaaf34"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -3996,14 +4038,14 @@ dependencies = [
"webkit2gtk", "webkit2gtk",
"webview2-com", "webview2-com",
"window-vibrancy", "window-vibrancy",
"windows 0.61.3", "windows 0.62.2",
] ]
[[package]] [[package]]
name = "tauri-build" name = "tauri-build"
version = "2.6.3" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" checksum = "59563ca5b331fd97f27ee9672ec8d36d33bb3c9e2c9f6710a1a5687256052e45"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cargo_toml", "cargo_toml",
@@ -4022,16 +4064,16 @@ dependencies = [
[[package]] [[package]]
name = "tauri-codegen" name = "tauri-codegen"
version = "2.6.3" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" checksum = "e50213e463086ecc283d7e9f51c30cc0fd304ad457425158018be26dbf0d0a80"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"brotli", "brotli",
"ico", "ico",
"json-patch", "json-patch",
"plist", "plist",
"png 0.17.16", "png 0.18.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"semver", "semver",
@@ -4049,9 +4091,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-macros" name = "tauri-macros"
version = "2.6.3" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" checksum = "8129fb43bb6e0d6b73ad3315cd172beaab17cd28899c594b1b32c80edbbfd99c"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@@ -4063,9 +4105,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin" name = "tauri-plugin"
version = "2.6.3" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" checksum = "1140cf34a3b3b836a13103dcab17f18831d5cc3534cbd435dc01a5c6daa65aa2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"glob", "glob",
@@ -4119,6 +4161,25 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "tauri-plugin-notification"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533770889abb60a0407d5a32096182f0e6463f5a3dfaa675be94580aa14070e1"
dependencies = [
"log",
"notify-rust",
"rand",
"serde",
"serde_json",
"serde_repr",
"tauri",
"tauri-plugin",
"thiserror 2.0.20",
"time",
"url",
]
[[package]] [[package]]
name = "tauri-plugin-opener" name = "tauri-plugin-opener"
version = "2.5.5" version = "2.5.5"
@@ -4142,10 +4203,26 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-plugin-single-instance"
version = "2.11.3" version = "2.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" checksum = "db817fe9295e19b7d8357e900af31edb93703dd9fb6de524b007b47b6afc63b0"
dependencies = [
"serde",
"serde_json",
"tauri",
"thiserror 2.0.20",
"tokio",
"tracing",
"windows-sys 0.60.2",
"zbus",
]
[[package]]
name = "tauri-runtime"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a57a2a9b8c8b7fc00d35b510fe808ded7f9f1b44fc1cdda6aee38632ea5413"
dependencies = [ dependencies = [
"cookie", "cookie",
"dpi", "dpi",
@@ -4163,14 +4240,14 @@ dependencies = [
"url", "url",
"webkit2gtk", "webkit2gtk",
"webview2-com", "webview2-com",
"windows 0.61.3", "windows 0.62.2",
] ]
[[package]] [[package]]
name = "tauri-runtime-wry" name = "tauri-runtime-wry"
version = "2.11.4" version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" checksum = "7f9e7e0c9f80a5130773cfabb6df4dc924518fb1b2c923726966804634b57d22"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
@@ -4188,15 +4265,15 @@ dependencies = [
"url", "url",
"webkit2gtk", "webkit2gtk",
"webview2-com", "webview2-com",
"windows 0.61.3", "windows 0.62.2",
"wry", "wry",
] ]
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "2.9.3" version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" checksum = "2ff55a614843b9a3f010f211df637c94df2607175b8d0f6e0f4a7c19e18621ee"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"brotli", "brotli",
@@ -4241,6 +4318,17 @@ dependencies = [
"toml 1.1.6+spec-1.1.0", "toml 1.1.6+spec-1.1.0",
] ]
[[package]]
name = "tauri-winrt-notification"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37a6c354fd28fc9e322ed9bd47e3959576dad28c9d58ea1cf888cce1c7ccb36"
dependencies = [
"thiserror 2.0.20",
"windows 0.62.2",
"windows-version",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.27.0" version = "3.27.0"
@@ -4432,21 +4520,6 @@ dependencies = [
"toml_edit 0.20.2", "toml_edit 0.20.2",
] ]
[[package]]
name = "toml"
version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
"indexmap 2.14.2",
"serde_core",
"serde_spanned 1.1.1",
"toml_datetime 0.7.5+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 0.7.15",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "1.1.6+spec-1.1.0" version = "1.1.6+spec-1.1.0"
@@ -4471,15 +4544,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "1.1.1+spec-1.1.0" version = "1.1.1+spec-1.1.0"
@@ -4648,9 +4712,9 @@ dependencies = [
[[package]] [[package]]
name = "tray-icon" name = "tray-icon"
version = "0.24.2" version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dirs", "dirs",
@@ -4698,45 +4762,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "unic-char-property" name = "unicase"
version = "0.9.0" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-ucd-ident"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
@@ -4750,6 +4779,12 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@@ -4771,13 +4806,13 @@ dependencies = [
[[package]] [[package]]
name = "urlpattern" name = "urlpattern"
version = "0.3.0" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b"
dependencies = [ dependencies = [
"icu_properties",
"regex", "regex",
"serde", "serde",
"unic-ucd-ident",
"url", "url",
] ]
@@ -4955,6 +4990,16 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "web_atoms" name = "web_atoms"
version = "0.2.6" version = "0.2.6"
@@ -5013,16 +5058,14 @@ dependencies = [
[[package]] [[package]]
name = "webview2-com" name = "webview2-com"
version = "0.38.2" version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6"
dependencies = [ dependencies = [
"webview2-com-macros", "webview2-com-macros",
"webview2-com-sys", "webview2-com-sys",
"windows 0.61.3", "windows 0.62.2",
"windows-core 0.61.2", "windows-core 0.62.2",
"windows-implement",
"windows-interface",
] ]
[[package]] [[package]]
@@ -5038,13 +5081,13 @@ dependencies = [
[[package]] [[package]]
name = "webview2-com-sys" name = "webview2-com-sys"
version = "0.38.2" version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb"
dependencies = [ dependencies = [
"thiserror 2.0.20", "thiserror 2.0.20",
"windows 0.61.3", "windows 0.62.2",
"windows-core 0.61.2", "windows-core 0.62.2",
] ]
[[package]] [[package]]
@@ -5080,16 +5123,17 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "window-vibrancy" name = "window-vibrancy"
version = "0.6.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580"
dependencies = [ dependencies = [
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation", "objc2-foundation",
"objc2-quartz-core",
"raw-window-handle", "raw-window-handle",
"windows-sys 0.59.0", "windows-sys 0.61.2",
"windows-version", "windows-version",
] ]
@@ -5552,12 +5596,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "1.0.4" version = "1.0.4"
@@ -5591,9 +5629,9 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]] [[package]]
name = "wry" name = "wry"
version = "0.55.1" version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"block2", "block2",
@@ -5627,8 +5665,8 @@ dependencies = [
"webkit2gtk", "webkit2gtk",
"webkit2gtk-sys", "webkit2gtk-sys",
"webview2-com", "webview2-com",
"windows 0.61.3", "windows 0.62.2",
"windows-core 0.61.2", "windows-core 0.62.2",
"windows-version", "windows-version",
"x11-dl", "x11-dl",
] ]
+6 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "argus" name = "argus"
version = "0.1.0" version = "0.3.0"
description = "An All in One Network Utility" description = "An All in One Network Utility"
authors = ["James Brotosky <james.brotosky@racooncity.org"] authors = ["James Brotosky <james.brotosky@racooncity.org"]
edition = "2021" edition = "2021"
@@ -18,7 +18,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] } tauri-build = { version = "2", features = [] }
[dependencies] [dependencies]
tauri = { version = "2", features = [] } tauri = { version = "2", features = ["tray-icon"] }
tauri-plugin-single-instance = "2"
tauri-plugin-opener = "2" tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
@@ -33,6 +34,9 @@ csv = "1"
open = "5.4.4" open = "5.4.4"
reqwest = { version = "0.12", features = ["json"] } reqwest = { version = "0.12", features = ["json"] }
libc = "0.2.189" libc = "0.2.189"
pulldown-cmark = { version = "0.13.4", features = ["html"] }
semver = "1.0.28"
tauri-plugin-notification = "2.5.1"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
is_elevated = "0.1" is_elevated = "0.1"
+4 -1
View File
@@ -6,6 +6,9 @@
"permissions": [ "permissions": [
"core:default", "core:default",
"opener:default", "opener:default",
"dialog:default" "dialog:default",
"core:window:allow-set-always-on-top",
"notification:default",
"core:window:allow-is-visible"
] ]
} }
+15
View File
@@ -0,0 +1,15 @@
use pulldown_cmark::{html, Options, Parser};
const CHANGELOG_MD: &str = include_str!("../../../CHANGELOG.md");
#[tauri::command]
pub async fn get_changelog() -> Result<String, String> {
let options =
Options::ENABLE_HEADING_ATTRIBUTES | Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TABLES;
let parser = Parser::new_ext(CHANGELOG_MD, options);
let mut html_output = String::new();
html::push_html(&mut html_output, parser);
Ok(html_output)
}
+1 -2
View File
@@ -11,8 +11,7 @@ pub struct ExportRequest {
#[tauri::command] #[tauri::command]
pub async fn export_csv(request: ExportRequest) -> Result<(), String> { pub async fn export_csv(request: ExportRequest) -> Result<(), String> {
let file = File::create(&request.path) let file = File::create(&request.path).map_err(|e| format!("Failed to create file: {}", e))?;
.map_err(|e| format!("Failed to create file: {}", e))?;
let mut writer = csv::Writer::from_writer(file); let mut writer = csv::Writer::from_writer(file);
+1
View File
@@ -1,4 +1,5 @@
pub mod actions; pub mod actions;
pub mod changelog;
pub mod export; pub mod export;
pub mod monitor; pub mod monitor;
pub mod nmap; pub mod nmap;
+21
View File
@@ -180,3 +180,24 @@ pub async fn monitor_stop() -> Result<(), String> {
} }
Ok(()) Ok(())
} }
#[tauri::command]
pub async fn monitor_update_label(ip: String, label: String) -> Result<(), String> {
let parsed: IpAddr = ip.parse().map_err(|e| format!("Invalid IP: {}", e))?;
let state = get_monitor();
let mut guard = state.lock().await;
if let Some(entry) = guard.entries.get_mut(&parsed) {
entry.label = if label.is_empty() { None } else { Some(label) };
}
Ok(())
}
#[tauri::command]
pub async fn monitor_resolve_host(ip: String) -> Result<Option<String>, String> {
let parsed: IpAddr = ip.parse().map_err(|e| format!("Invalid IP: {}", e))?;
Ok(crate::net::dns::reverse_lookup(parsed).await)
}
pub fn is_monitor_running() -> bool {
let state = get_monitor();
let guard = state.blocking_lock();
guard.running
}
+6 -1
View File
@@ -48,7 +48,12 @@ pub async fn run_nmap(app: AppHandle, config: NmapConfig) -> Result<(), String>
} }
cmd.stdout(Stdio::piped()); cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped()); cmd.stderr(Stdio::piped());
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000;
cmd.creation_flags(CREATE_NO_WINDOW);
}
let mut child = cmd let mut child = cmd
.spawn() .spawn()
.map_err(|e| format!("Failed to start nmap: {}", e))?; .map_err(|e| format!("Failed to start nmap: {}", e))?;
+10 -1
View File
@@ -40,7 +40,16 @@ pub async fn start_scan(app: AppHandle, config: ScanConfig) -> Result<(), String
let port_timeout_ms = config.port_timeout_ms.unwrap_or(1000); let port_timeout_ms = config.port_timeout_ms.unwrap_or(1000);
tokio::spawn(async move { tokio::spawn(async move {
ping_sweep(&app, &ips, timeout_ms, cancel, port_scan, ports, port_timeout_ms).await; ping_sweep(
&app,
&ips,
timeout_ms,
cancel,
port_scan,
ports,
port_timeout_ms,
)
.await;
}); });
Ok(()) Ok(())
} }
+25 -10
View File
@@ -15,18 +15,30 @@ pub async fn run_traceroute(app: AppHandle, ip: String) -> Result<(), String> {
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()
.map_err(|e| format!("Failed to start tracepath: {}. Is iputils-tracepath installed?", e))?; .map_err(|e| {
format!(
"Failed to start tracepath: {}. Is iputils-tracepath installed?",
e
)
})?;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let mut child = Command::new("tracert") let mut child = {
.arg("-d") use std::os::windows::process::CommandExt;
.arg(&ip) const CREATE_NO_WINDOW: u32 = 0x08000000;
.stdout(Stdio::piped()) Command::new("tracert")
.stderr(Stdio::piped()) .arg("-d")
.spawn() .arg(&ip)
.map_err(|e| format!("Failed to start tracert: {}", e))?; .stdout(Stdio::piped())
.stderr(Stdio::piped())
.creation_flags(CREATE_NO_WINDOW)
.spawn()
.map_err(|e| format!("Failed to start tracert: {}", e))?
};
let stdout = child.stdout.take() let stdout = child
.stdout
.take()
.ok_or_else(|| "Failed to capture stdout".to_string())?; .ok_or_else(|| "Failed to capture stdout".to_string())?;
let mut reader = BufReader::new(stdout).lines(); let mut reader = BufReader::new(stdout).lines();
@@ -35,7 +47,9 @@ pub async fn run_traceroute(app: AppHandle, ip: String) -> Result<(), String> {
let _ = app.emit("traceroute:hop", &line); let _ = app.emit("traceroute:hop", &line);
} }
let status = child.wait().await let status = child
.wait()
.await
.map_err(|e| format!("tracepath failed: {}", e))?; .map_err(|e| format!("tracepath failed: {}", e))?;
if status.success() { if status.success() {
@@ -46,3 +60,4 @@ pub async fn run_traceroute(app: AppHandle, ip: String) -> Result<(), String> {
Ok(()) Ok(())
} }
+9 -6
View File
@@ -1,7 +1,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::AppHandle; use tauri::AppHandle;
const UPDATE_URL: &str = "https://git.racooncity.org/api/v1/repos/mysticmomba/argus/releases/latest"; const UPDATE_URL: &str =
"https://git.racooncity.org/api/v1/repos/mysticmomba/argus/releases/latest";
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateInfo { pub struct UpdateInfo {
@@ -29,10 +30,7 @@ pub async fn check_for_update(app: AppHandle) -> Result<Option<UpdateInfo>, Stri
.as_str() .as_str()
.ok_or_else(|| "No tag_name in response".to_string())?; .ok_or_else(|| "No tag_name in response".to_string())?;
let html_url = body["html_url"] let html_url = body["html_url"].as_str().unwrap_or("").to_string();
.as_str()
.unwrap_or("")
.to_string();
let notes = body["body"] let notes = body["body"]
.as_str() .as_str()
@@ -42,7 +40,12 @@ pub async fn check_for_update(app: AppHandle) -> Result<Option<UpdateInfo>, Stri
let remote_version = tag.strip_prefix('v').unwrap_or(tag); let remote_version = tag.strip_prefix('v').unwrap_or(tag);
let current = app.package_info().version.to_string(); let current = app.package_info().version.to_string();
if remote_version != current { let remote_semver = semver::Version::parse(remote_version)
.map_err(|e| format!("Invalid remote version '{}': {}", remote_version, e))?;
let current_semver = semver::Version::parse(&current)
.map_err(|e| format!("Invalid current version '{}': {}", current, e))?;
if remote_semver > current_semver {
Ok(Some(UpdateInfo { Ok(Some(UpdateInfo {
version: remote_version.to_string(), version: remote_version.to_string(),
url: html_url, url: html_url,
+63
View File
@@ -3,6 +3,10 @@ mod models;
mod net; mod net;
mod scanner; mod scanner;
use tauri::Manager;
use tauri::menu::{Menu, MenuItem};
use tauri::tray::TrayIconBuilder;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tracing_subscriber::fmt() tracing_subscriber::fmt()
@@ -13,8 +17,64 @@ pub fn run() {
.init(); .init();
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}))
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_notification::init())
.setup(|app| {
let show = MenuItem::with_id(app, "show", "Show Argus", true, None::<&str>)?;
let quit = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&show, &quit])?;
let _tray = TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.tooltip("Argus - Network Scanner")
.menu(&menu)
.show_menu_on_left_click(false)
.on_menu_event(|app, event| match event.id.as_ref() {
"show" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
"quit" => {
app.exit(0);
}
_ => {}
})
.on_tray_icon_event(|tray, event| {
if let tauri::tray::TrayIconEvent::DoubleClick { .. } = event {
if let Some(window) = tray.app_handle().get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
})
.build(app)?;
// Hide to tray when monitor is running
let app_handle = app.handle().clone();
if let Some(window) = app.get_webview_window("main") {
window.on_window_event(move |event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
if commands::monitor::is_monitor_running() {
api.prevent_close();
if let Some(win) = app_handle.get_webview_window("main") {
let _ = win.hide();
}
}
}
});
}
Ok(())
})
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
commands::scan::start_scan, commands::scan::start_scan,
commands::scan::stop_scan, commands::scan::stop_scan,
@@ -31,6 +91,9 @@ pub fn run() {
commands::monitor::monitor_stop, commands::monitor::monitor_stop,
commands::updater::check_for_update, commands::updater::check_for_update,
commands::actions::check_privileges, commands::actions::check_privileges,
commands::changelog::get_changelog,
commands::monitor::monitor_update_label,
commands::monitor::monitor_resolve_host
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
+2 -2
View File
@@ -6,7 +6,7 @@ pub enum HostStatus {
Up, Up,
Down, Down,
Filtered, Filtered,
Unknown Unknown,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -42,7 +42,7 @@ pub struct PortResult {
pub struct NetbiosInfo { pub struct NetbiosInfo {
pub computer_name: Option<String>, pub computer_name: Option<String>,
pub workgroup: Option<String>, pub workgroup: Option<String>,
pub mac_from_netbios: Option<String> pub mac_from_netbios: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
+1 -33
View File
@@ -1,12 +1,9 @@
use std::net::IpAddr; use std::net::IpAddr;
use tokio::task::JoinSet;
/// Perform reverse DNS lookup on a single IP. /// Perform reverse DNS lookup on a single IP.
/// Returns the hostname if found, None otherwise. /// Returns the hostname if found, None otherwise.
pub async fn reverse_lookup(ip: IpAddr) -> Option<String> { pub async fn reverse_lookup(ip: IpAddr) -> Option<String> {
let result = tokio::task::spawn_blocking(move || { let result = tokio::task::spawn_blocking(move || dns_lookup::lookup_addr(&ip)).await;
dns_lookup::lookup_addr(&ip)
}).await;
match result { match result {
Ok(Ok(hostname)) => { Ok(Ok(hostname)) => {
@@ -19,32 +16,3 @@ pub async fn reverse_lookup(ip: IpAddr) -> Option<String> {
_ => None, _ => None,
} }
} }
/// Perform reverse DNS lookups on a batch of IPs concurrently.
/// Returns a Vec of (IpAddr, Option<hostname>) pairs.
pub async fn reverse_lookup_batch(
ips: &[IpAddr],
max_concurrent: usize,
) -> Vec<(IpAddr, Option<String>)> {
let mut results = Vec::with_capacity(ips.len());
for chunk in ips.chunks(max_concurrent) {
let mut join_set = JoinSet::new();
for &ip in chunk {
join_set.spawn(async move {
let hostname = reverse_lookup(ip).await;
(ip, hostname)
});
}
while let Some(result) = join_set.join_next().await {
match result {
Ok(pair) => results.push(pair),
Err(e) => tracing::warn!("DNS lookup task panicked: {}", e),
}
}
}
results
}
+1 -1
View File
@@ -1,2 +1,2 @@
pub mod range;
pub mod dns; pub mod dns;
pub mod range;
+1 -1
View File
@@ -1,3 +1,3 @@
pub mod netbios;
pub mod ping; pub mod ping;
pub mod ports; pub mod ports;
pub mod netbios;
+2 -3
View File
@@ -76,7 +76,7 @@ pub async fn ping_sweep(
while let Some(result) = join_set.join_next().await { while let Some(result) = join_set.join_next().await {
done += 1; done += 1;
match result { match result {
Ok(mut host_result) => { Ok(host_result) => {
// Emit immediately so the host shows up in the table // Emit immediately so the host shows up in the table
let _ = app.emit("scan:host-found", &host_result); let _ = app.emit("scan:host-found", &host_result);
@@ -91,8 +91,7 @@ pub async fn ping_sweep(
let nb_fut = query_netbios(enriched.ip, 500); let nb_fut = query_netbios(enriched.ip, 500);
if port_scan { if port_scan {
let scan_ports = ports2 let scan_ports = ports2.unwrap_or_else(|| DEFAULT_PORTS.to_vec());
.unwrap_or_else(|| DEFAULT_PORTS.to_vec());
let (nb_result, port_results) = tokio::join!( let (nb_result, port_results) = tokio::join!(
nb_fut, nb_fut,
scan_host_ports(enriched.ip, &scan_ports, port_timeout_ms) scan_host_ports(enriched.ip, &scan_ports, port_timeout_ms)
+15 -10
View File
@@ -42,8 +42,7 @@ fn service_name(port: u16) -> Option<String> {
} }
pub const DEFAULT_PORTS: &[u16] = &[ pub const DEFAULT_PORTS: &[u16] = &[
21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143, 21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143, 443, 445, 993, 995, 1433, 1521, 3306, 3389,
443, 445, 993, 995, 1433, 1521, 3306, 3389,
5432, 5900, 5985, 5986, 8080, 8088, 8443, 5432, 5900, 5985, 5986, 8080, 8088, 8443,
]; ];
@@ -96,7 +95,10 @@ async fn grab_banner(mut stream: TcpStream, port: u16) -> Option<String> {
}; };
if let Some(data) = probe { if let Some(data) = probe {
if timeout(banner_timeout, stream.write_all(data)).await.is_err() { if timeout(banner_timeout, stream.write_all(data))
.await
.is_err()
{
return None; return None;
} }
} }
@@ -107,8 +109,15 @@ async fn grab_banner(mut stream: TcpStream, port: u16) -> Option<String> {
Ok(Ok(n)) if n > 0 => { Ok(Ok(n)) if n > 0 => {
buf.truncate(n); buf.truncate(n);
// Strip to printable ASCII, replace control chars // Strip to printable ASCII, replace control chars
let banner: String = buf.iter() let banner: String = buf
.map(|&b| if b >= 0x20 && b < 0x7f { b as char } else { ' ' }) .iter()
.map(|&b| {
if (0x20..0x7f).contains(&b) {
b as char
} else {
' '
}
})
.collect(); .collect();
let banner = banner.trim().to_string(); let banner = banner.trim().to_string();
if banner.is_empty() { if banner.is_empty() {
@@ -124,11 +133,7 @@ async fn grab_banner(mut stream: TcpStream, port: u16) -> Option<String> {
const MAX_PORT_CONCURRENT: usize = 64; const MAX_PORT_CONCURRENT: usize = 64;
pub async fn scan_host_ports( pub async fn scan_host_ports(ip: IpAddr, ports: &[u16], timeout_ms: u64) -> Vec<PortResult> {
ip: IpAddr,
ports: &[u16],
timeout_ms: u64,
) -> Vec<PortResult> {
let mut results = Vec::with_capacity(ports.len()); let mut results = Vec::with_capacity(ports.len());
for chunk in ports.chunks(MAX_PORT_CONCURRENT) { for chunk in ports.chunks(MAX_PORT_CONCURRENT) {
+8 -2
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "argus", "productName": "argus",
"version": "0.1.0", "version": "0.3.0",
"identifier": "io.argus.app", "identifier": "io.argus.app",
"build": { "build": {
"frontendDist": "../src" "frontendDist": "../src"
@@ -10,7 +10,7 @@
"withGlobalTauri": true, "withGlobalTauri": true,
"windows": [ "windows": [
{ {
"title": "argus", "title": "Argus",
"width": 800, "width": 800,
"height": 600 "height": 600
} }
@@ -22,6 +22,12 @@
"bundle": { "bundle": {
"active": true, "active": true,
"targets": "all", "targets": "all",
"copyright": "Copyright \u00A9 2026 James Brotosky. Licensed under AGPLv3. All rights reserved.",
"windows": {
"webviewInstallMode": {
"type": "embedBootstrapper"
}
},
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
+29 -1
View File
@@ -199,6 +199,24 @@
<!-- Status Bar --> <!-- Status Bar -->
<div id="status-bar"> <div id="status-bar">
<span class="status-bar-item" id="status-bar-version">Argus</span> <span class="status-bar-item" id="status-bar-version">Argus</span>
<button id="btn-changelog" class="status-bar-btn">Changelog</button>
<label class="toggle-label status-bar-toggle">
<input type="checkbox" id="chk-always-on-top" />
<span>Always on Top</span>
</label>
<select id="theme-picker" class="theme-picker">
<option value="cyber">Cyber</option>
<option value="midnight">Midnight</option>
<option value="dracula">Dracula</option>
<option value="nord">Nord</option>
<option value="solarized">Solarized Dark</option>
<option value="mocha">Catppuccin Mocha</option>
<option value="onedark">One Dark</option>
<option value="rosepine">Rosé Pine</option>
<option value="rosepine-moon">Rosé Pine Moon</option>
<option value="tokyonight">Tokyo Night</option>
<option value="iceberg">Iceberg</option>
</select>
<span class="status-bar-item" id="privilege-status">unprivileged</span> <span class="status-bar-item" id="privilege-status">unprivileged</span>
</div> </div>
@@ -212,6 +230,16 @@
<div id="traceroute-output" class="traceroute-output"></div> <div id="traceroute-output" class="traceroute-output"></div>
</div> </div>
</div> </div>
<!-- Changelog Modal -->
<div id="changelog-modal" class="modal-overlay">
<div class="modal-panel" style="width: 700px;">
<div class="modal-header">
<span class="modal-title">Changelog</span>
<button id="changelog-close" class="modal-close">&times;</button>
</div>
<div id="changelog-content" class="changelog-content"></div>
</div>
</div>
<!-- Context Menu --> <!-- Context Menu -->
<div id="context-menu" class="context-menu" hidden> <div id="context-menu" class="context-menu" hidden>
<button class="ctx-item" data-action="copy-ip">Copy IP</button> <button class="ctx-item" data-action="copy-ip">Copy IP</button>
@@ -225,7 +253,7 @@
<button class="ctx-item" data-action="send-monitor">Send to Monitor</button> <button class="ctx-item" data-action="send-monitor">Send to Monitor</button>
<button class="ctx-item" data-action="send-nmap">Send to Nmap</button> <button class="ctx-item" data-action="send-nmap">Send to Nmap</button>
</div> </div>
<div id="crt-overlay"></div>
<script src="main.js"></script> <script src="main.js"></script>
</body> </body>
+321 -28
View File
@@ -1,12 +1,104 @@
const { invoke } = window.__TAURI__.core; const { invoke } = window.__TAURI__.core;
const { listen } = window.__TAURI__.event; const { listen } = window.__TAURI__.event;
var theme = (() => {
var STORAGE_KEY = 'argus-theme';
function get() {
try {
return localStorage.getItem(STORAGE_KEY) || 'cyber';
} catch (e) {
return 'cyber';
}
}
function set(name) {
if (name === 'cyber') {
document.documentElement.removeAttribute('data-theme');
} else {
document.documentElement.setAttribute('data-theme', name);
}
try {
localStorage.setItem(STORAGE_KEY, name);
} catch (e) { }
}
var HACKER_KEY = 'argus-hacker-unlocked';
function isHackerUnlocked() {
try {
return localStorage.getItem(HACKER_KEY) === 'true';
} catch (e) {
return false;
}
}
function unlockHacker() {
try {
localStorage.setItem(HACKER_KEY, 'true');
} catch (e) { }
addHackerOption();
}
function addHackerOption() {
var picker = document.getElementById('theme-picker');
if (!picker.querySelector('option[value="hacker"]')) {
var opt = document.createElement('option');
opt.value = 'hacker';
opt.textContent = '\u2588 H4CK3R';
picker.appendChild(opt);
}
}
function init() {
var saved = get();
set(saved);
// Add hacker option if previously unlocked
if (isHackerUnlocked()) {
addHackerOption();
}
var picker = document.getElementById('theme-picker');
picker.value = saved;
picker.addEventListener('change', function () {
set(picker.value);
});
// Easter egg: listen on the scan input
var scanInput = document.getElementById('ip-range-input');
scanInput.addEventListener('input', function () {
var val = scanInput.value.toLowerCase().trim();
if (val === 'all your base are belong to us') {
if (!isHackerUnlocked()) {
unlockHacker();
set('hacker');
picker.value = 'hacker';
scanInput.value = '';
console.log('%c> ACCESS GRANTED', 'color: #00ff41; font-size: 20px; font-family: monospace;');
console.log('%c> HACKER THEME UNLOCKED', 'color: #00ff41; font-size: 14px; font-family: monospace;');
}
}
});
}
// Apply immediately (before DOMContentLoaded) to prevent flash
set(get());
return { init };
})();
const table = (() => { const table = (() => {
var dirty = false;
var flushTimer = null;
var ipIndex = {};
let data = []; let data = [];
let filtered = []; let filtered = [];
let sortKey = 'ip'; let sortKey = 'ip';
let sortDir = 'asc'; let sortDir = 'asc';
let filterText = ''; let filterText = '';
let checkedIps = new Set(); let checkedIps = new Set();
var ROW_HEIGHT = 32;
var BUFFER_ROWS = 20;
var scrollContainer = null;
function ipToNum(ip) { function ipToNum(ip) {
const parts = String(ip).split('.'); const parts = String(ip).split('.');
@@ -155,20 +247,61 @@ const table = (() => {
} }
function render() { function render() {
const tbody = document.getElementById('results-body'); var tbody = document.getElementById('results-body');
const count = document.getElementById('host-count'); var count = document.getElementById('host-count');
if (!tbody) return; if (!tbody) return;
expandedIp = null; expandedIp = null;
const rows = filtered.map((host) => { if (!scrollContainer) {
const ip = host.ip; scrollContainer = document.getElementById('results-table-container');
const status = host.status || 'Unknown'; scrollContainer.addEventListener('scroll', onScroll);
const cls = 'status-' + status.toLowerCase(); }
const rtt = host.ping ? host.ping.rtt_ms.toFixed(1) : '-';
const hostname = host.hostname || '-';
const isChecked = checkedIps.has(ip) ? 'checked' : ''; renderVisible();
return '<tr data-ip="' + ip + '">'
if (count) {
var up = data.filter(function (h) { return h.status === 'Up'; }).length;
count.textContent = up + ' up / ' + data.length + ' total';
}
if (columns && columns.apply) columns.apply();
}
function renderVisible() {
var tbody = document.getElementById('results-body');
if (!tbody || !scrollContainer) return;
var totalRows = filtered.length;
var viewHeight = scrollContainer.clientHeight;
var scrollTop = scrollContainer.scrollTop;
var headerHeight = 36;
var startIdx = Math.max(0, Math.floor((scrollTop - headerHeight) / ROW_HEIGHT) - BUFFER_ROWS);
var visibleCount = Math.ceil(viewHeight / ROW_HEIGHT) + (BUFFER_ROWS * 2);
var endIdx = Math.min(totalRows, startIdx + visibleCount);
var topSpace = startIdx * ROW_HEIGHT;
var bottomSpace = (totalRows - endIdx) * ROW_HEIGHT;
var colCount = document.querySelectorAll('#results-table th').length;
var html = '';
if (topSpace > 0) {
html += '<tr class="virtual-spacer"><td colspan="' + colCount + '" style="height:' + topSpace + 'px"></td></tr>';
}
for (var i = startIdx; i < endIdx; i++) {
var host = filtered[i];
var ip = host.ip;
var status = host.status || 'Unknown';
var cls = 'status-' + status.toLowerCase();
var rtt = host.ping ? host.ping.rtt_ms.toFixed(1) : '-';
var hostname = host.hostname || '-';
var isChecked = checkedIps.has(ip) ? 'checked' : '';
html += '<tr data-ip="' + ip + '">'
+ '<td class="col-check"><input type="checkbox" class="row-check" ' + isChecked + ' /></td>' + '<td class="col-check"><input type="checkbox" class="row-check" ' + isChecked + ' /></td>'
+ '<td>' + ip + '</td>' + '<td>' + ip + '</td>'
+ '<td class="' + cls + '">' + status + '</td>' + '<td class="' + cls + '">' + status + '</td>'
@@ -176,24 +309,43 @@ const table = (() => {
+ '<td>' + hostname + '</td>' + '<td>' + hostname + '</td>'
+ '<td class="ports-cell">' + formatPorts(host) + '</td>' + '<td class="ports-cell">' + formatPorts(host) + '</td>'
+ '</tr>'; + '</tr>';
});
tbody.innerHTML = rows.join('');
if (count) {
const up = data.filter((h) => h.status === 'Up').length;
count.textContent = up + ' up / ' + data.length + ' total';
} }
if (columns && columns.apply) columns.apply();
if (bottomSpace > 0) {
html += '<tr class="virtual-spacer"><td colspan="' + colCount + '" style="height:' + bottomSpace + 'px"></td></tr>';
}
tbody.innerHTML = html;
}
var scrollTimeout = null;
function onScroll() {
if (scrollTimeout) return;
scrollTimeout = requestAnimationFrame(function () {
scrollTimeout = null;
renderVisible();
if (columns && columns.apply) columns.apply();
});
} }
function upsert(host) { function upsert(host) {
const idx = data.findIndex((h) => h.ip === host.ip); var idx = data.findIndex(function (h) { return h.ip === host.ip; });
if (idx >= 0) { if (idx >= 0) {
data[idx] = host; data[idx] = host;
} else { } else {
ipIndex[host.ip] = data.length;
data.push(host); data.push(host);
} }
dirty = true;
if (!flushTimer) {
flushTimer = setTimeout(flush, 200);
}
}
function flush() {
flushTimer = null;
if (!dirty) return;
dirty = false;
refresh(); refresh();
} }
@@ -201,6 +353,7 @@ const table = (() => {
data = []; data = [];
filtered = []; filtered = [];
checkedIps.clear(); checkedIps.clear();
ipIndex = {};
document.getElementById('check-all').checked = false; document.getElementById('check-all').checked = false;
refresh(); refresh();
} }
@@ -504,7 +657,7 @@ const sender = (() => {
if (action.startsWith('nmap')) { if (action.startsWith('nmap')) {
nmapTab.setTarget(hosts.map((h) => String(h.ip))); nmapTab.setTarget(hosts.map((h) => String(h.ip)));
} else if (action.startsWith('monitor')) { } else if (action.startsWith('monitor')) {
monitorTab.setTargets(hosts.map(function (h) { return String(h.ip); })); monitorTab.setTargets(hosts);
} }
document.querySelectorAll('.send-menu').forEach((m) => m.hidden = true); document.querySelectorAll('.send-menu').forEach((m) => m.hidden = true);
@@ -570,7 +723,8 @@ const ctxMenu = (() => {
traceroute.run(ip); traceroute.run(ip);
break; break;
case 'send-monitor': case 'send-monitor':
monitorTab.setTargets([ip]); var monHost = table.getAll().find(function (h) { return String(h.ip) === ip; });
monitorTab.setTargets(monHost ? [monHost] : [ip]);
break; break;
case 'send-nmap': case 'send-nmap':
nmapTab.setTarget([ip]); nmapTab.setTarget([ip]);
@@ -821,7 +975,7 @@ const nmapTab = (() => {
const monitorTab = (() => { const monitorTab = (() => {
let running = false; let running = false;
let entries = {}; let entries = {};
var editingLabel = false;
var els = { var els = {
input: () => document.getElementById('monitor-input'), input: () => document.getElementById('monitor-input'),
interval: () => document.getElementById('monitor-interval'), interval: () => document.getElementById('monitor-interval'),
@@ -832,6 +986,26 @@ const monitorTab = (() => {
body: () => document.getElementById('monitor-body'), body: () => document.getElementById('monitor-body'),
}; };
var reminderInterval = null;
async function notify(title, body) {
try {
var current = window.__TAURI__.window.getCurrentWindow();
var visible = await current.isVisible();
if (visible) return;
var notif = window.__TAURI__.notification;
var granted = await notif.isPermissionGranted();
if (!granted) {
var perm = await notif.requestPermission();
if (perm !== 'granted') return;
}
notif.sendNotification({ title: title, body: body });
} catch (e) {
console.log('Notification error:', e);
}
}
function setRunning(active) { function setRunning(active) {
running = active; running = active;
els.btnStart().disabled = active; els.btnStart().disabled = active;
@@ -847,6 +1021,7 @@ const monitorTab = (() => {
} }
function render() { function render() {
if (editingLabel) return;
var body = els.body(); var body = els.body();
var ips = Object.keys(entries).sort(); var ips = Object.keys(entries).sort();
@@ -862,7 +1037,7 @@ const monitorTab = (() => {
return '<tr data-ip="' + ip + '">' return '<tr data-ip="' + ip + '">'
+ '<td>' + ip + '</td>' + '<td>' + ip + '</td>'
+ '<td>' + label + '</td>' + '<td class="editable-label" data-ip="' + ip + '">' + label + '</td>'
+ '<td class="' + statusClass + '">' + e.status + '</td>' + '<td class="' + statusClass + '">' + e.status + '</td>'
+ '<td>' + lastRtt + '</td>' + '<td>' + lastRtt + '</td>'
+ '<td>' + minRtt + '</td>' + '<td>' + minRtt + '</td>'
@@ -889,6 +1064,16 @@ const monitorTab = (() => {
try { try {
await invoke('monitor_add_hosts', { hosts: hosts }); await invoke('monitor_add_hosts', { hosts: hosts });
for (var i = 0; i < hosts.length; i++) {
try {
var hostname = await invoke('monitor_resolve_host', { ip: hosts[i].ip });
if (hostname) {
await invoke('monitor_update_label', { ip: hosts[i].ip, label: hostname });
}
} catch (e) { }
}
var updated = await invoke('monitor_get_entries'); var updated = await invoke('monitor_get_entries');
entries = {}; entries = {};
updated.forEach(function (e) { entries[e.ip] = e; }); updated.forEach(function (e) { entries[e.ip] = e; });
@@ -917,6 +1102,14 @@ const monitorTab = (() => {
try { try {
await invoke('monitor_start', { intervalMs: interval }); await invoke('monitor_start', { intervalMs: interval });
// Start 15-minute reminder
if (reminderInterval) clearInterval(reminderInterval);
reminderInterval = setInterval(function () {
var count = Object.keys(entries).length;
notify('Argus Monitor Active',
'The monitor is still running with ' + count + ' hosts. Close the monitor to stop pinging.');
}, 15 * 60 * 1000);
} catch (err) { } catch (err) {
console.error('Monitor start error:', err); console.error('Monitor start error:', err);
setRunning(false); setRunning(false);
@@ -929,6 +1122,10 @@ const monitorTab = (() => {
} catch (err) { } catch (err) {
console.error('Monitor stop error:', err); console.error('Monitor stop error:', err);
} }
if (reminderInterval) {
clearInterval(reminderInterval);
reminderInterval = null;
}
setRunning(false); setRunning(false);
} }
@@ -943,9 +1140,20 @@ const monitorTab = (() => {
} }
} }
function setTargets(ips) { function setTargets(hosts) {
els.input().value = ips.join(', '); var items = hosts.map(function (h) {
// Switch to monitor tab if (typeof h === 'string') return { ip: h, label: null };
return { ip: String(h.ip), label: h.hostname || null };
});
invoke('monitor_add_hosts', { hosts: items }).then(function () {
return invoke('monitor_get_entries');
}).then(function (updated) {
entries = {};
updated.forEach(function (e) { entries[e.ip] = e; });
render();
});
document.querySelectorAll('.tab').forEach(function (t) { t.classList.remove('active'); }); document.querySelectorAll('.tab').forEach(function (t) { t.classList.remove('active'); });
document.querySelectorAll('.view').forEach(function (v) { v.classList.remove('active'); }); document.querySelectorAll('.view').forEach(function (v) { v.classList.remove('active'); });
document.querySelector('[data-tab="monitor"]').classList.add('active'); document.querySelector('[data-tab="monitor"]').classList.add('active');
@@ -961,7 +1169,13 @@ const monitorTab = (() => {
await listen('monitor:state-change', function (e) { await listen('monitor:state-change', function (e) {
var data = e.payload; var data = e.payload;
console.log('State change:', data.ip, data.old, '->', data.new); console.log('State change:', data.ip, data.old, '->', data['new']);
var label = entries[data.ip] ? (entries[data.ip].label || data.ip) : data.ip;
if (data['new'] === 'Down') {
notify('Host Down', label + ' (' + data.ip + ') is no longer responding');
} else if (data['new'] === 'Up' && data.old === 'Down') {
notify('Host Recovered', label + ' (' + data.ip + ') is back online');
}
}); });
} }
@@ -980,6 +1194,41 @@ const monitorTab = (() => {
var btn = e.target.closest('.btn-remove'); var btn = e.target.closest('.btn-remove');
if (btn) removeHost(btn.dataset.ip); if (btn) removeHost(btn.dataset.ip);
}); });
els.body().addEventListener('dblclick', function (e) {
var cell = e.target.closest('.editable-label');
if (!cell) return;
var ip = cell.dataset.ip;
var currentLabel = cell.textContent === '-' ? '' : cell.textContent;
editingLabel = true;
var input = document.createElement('input');
input.type = 'text';
input.value = currentLabel;
input.className = 'label-edit-input';
cell.textContent = '';
cell.appendChild(input);
input.focus();
input.select();
function save() {
var newLabel = input.value.trim();
editingLabel = false;
invoke('monitor_update_label', { ip: ip, label: newLabel }).then(function () {
return invoke('monitor_get_entries');
}).then(function (updated) {
entries = {};
updated.forEach(function (e) { entries[e.ip] = e; });
render();
});
}
input.addEventListener('blur', save);
input.addEventListener('keydown', function (ev) {
if (ev.key === 'Enter') save();
if (ev.key === 'Escape')
editingLabel = false;
render();
});
});
setupListeners(); setupListeners();
} }
@@ -992,7 +1241,6 @@ var updater = (() => {
var msg = document.getElementById('update-message'); var msg = document.getElementById('update-message');
var link = document.getElementById('update-link'); var link = document.getElementById('update-link');
var text = 'Argus v' + info.version + ' is available.'; var text = 'Argus v' + info.version + ' is available.';
if (info.notes) text += ' ' + info.notes;
msg.textContent = text; msg.textContent = text;
link.href = info.url; link.href = info.url;
link.addEventListener('click', function (e) { link.addEventListener('click', function (e) {
@@ -1022,6 +1270,42 @@ var updater = (() => {
return { init }; return { init };
})(); })();
var changelog = (() => {
var modal = function () { return document.getElementById('changelog-modal'); };
var loaded = false;
async function open() {
modal().classList.add('visible');
if (!loaded) {
try {
var html = await invoke('get_changelog');
document.getElementById('changelog-content').innerHTML = html;
loaded = true;
} catch (err) {
document.getElementById('changelog-content').textContent = 'Failed to load changelog.';
}
}
}
function close() {
modal().classList.remove('visible');
}
function init() {
document.getElementById('btn-changelog').addEventListener('click', open);
document.getElementById('changelog-close').addEventListener('click', close);
modal().addEventListener('click', function (e) {
if (e.target === modal()) close();
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modal().classList.contains('visible')) close();
});
}
return { init };
})();
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
// Tab switching // Tab switching
document.querySelectorAll('.tab').forEach((tab) => { document.querySelectorAll('.tab').forEach((tab) => {
@@ -1054,6 +1338,15 @@ window.addEventListener('DOMContentLoaded', () => {
nmapTab.init(); nmapTab.init();
monitorTab.init(); monitorTab.init();
updater.init(); updater.init();
changelog.init();
theme.init();
// Always on top toggle
document.getElementById('chk-always-on-top').addEventListener('change', function (e) {
if (window.__TAURI__ && window.__TAURI__.window) {
var current = window.__TAURI__.window.getCurrentWindow();
current.setAlwaysOnTop(e.target.checked);
}
});
document.getElementById('btn-export-all').addEventListener('click', exporter.csvAll); document.getElementById('btn-export-all').addEventListener('click', exporter.csvAll);
document.getElementById('btn-export-up').addEventListener('click', exporter.csvUp); document.getElementById('btn-export-up').addEventListener('click', exporter.csvUp);
console.log('[Argus] Ready'); console.log('[Argus] Ready');
+620 -3
View File
@@ -409,6 +409,7 @@ body {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
height: 32px;
} }
#results-table tbody tr { #results-table tbody tr {
@@ -428,6 +429,15 @@ body {
box-shadow: inset 3px 0 0 var(--cyan); box-shadow: inset 3px 0 0 var(--cyan);
} }
.virtual-spacer {
border: none !important;
}
.virtual-spacer td {
padding: 0 !important;
border: none !important;
}
/* ===== Status Colors ===== */ /* ===== Status Colors ===== */
.status-up { .status-up {
@@ -1136,17 +1146,17 @@ body {
text-shadow: 0 0 6px var(--status-up-glow); text-shadow: 0 0 6px var(--status-up-glow);
} }
.monitor-status-Down { #monitor-table .monitor-status-Down {
color: var(--status-down); color: var(--status-down);
text-shadow: 0 0 6px var(--status-down-glow); text-shadow: 0 0 6px var(--status-down-glow);
} }
.monitor-status-Degraded { #monitor-table .monitor-status-Degraded {
color: var(--status-filtered); color: var(--status-filtered);
text-shadow: 0 0 6px var(--status-filtered-glow); text-shadow: 0 0 6px var(--status-filtered-glow);
} }
.monitor-status-Unknown { #monitor-table .monitor-status-Unknown {
color: var(--status-unknown); color: var(--status-unknown);
} }
@@ -1260,3 +1270,610 @@ body {
color: var(--text-muted); color: var(--text-muted);
margin-top: 4px; margin-top: 4px;
} }
/* ===== Changelog ===== */
.changelog-content {
flex: 1;
overflow-y: auto;
padding: 20px 24px;
font-family: var(--font-sans);
font-size: 13px;
line-height: 1.7;
color: var(--text-primary);
max-height: 70vh;
}
.changelog-content h1 {
font-size: 20px;
color: var(--cyan);
margin: 0 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.changelog-content h2 {
font-size: 16px;
color: var(--cyan);
margin: 24px 0 12px 0;
}
.changelog-content h3 {
font-size: 14px;
color: var(--text-primary);
margin: 16px 0 8px 0;
}
.changelog-content ul {
padding-left: 20px;
margin: 8px 0;
}
.changelog-content li {
margin: 4px 0;
color: var(--text-secondary);
}
.changelog-content code {
font-family: var(--font-mono);
font-size: 12px;
padding: 2px 5px;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 2px;
color: var(--cyan);
}
.changelog-content a {
color: var(--cyan);
text-decoration: none;
}
.changelog-content a:hover {
text-decoration: underline;
}
.theme-picker {
padding: 1px 6px;
font-size: 11px;
font-family: var(--font-mono);
color: var(--text-muted);
background: transparent;
border: 1px solid var(--border);
border-radius: var(--radius);
cursor: pointer;
-webkit-appearance: none;
appearance: none;
outline: none;
}
.theme-picker:hover {
color: var(--text-secondary);
border-color: var(--border-bright);
}
.theme-picker option {
background: var(--bg-secondary);
color: var(--text-primary);
}
/* ===== Theme: Midnight ===== */
:root[data-theme="midnight"] {
--bg-primary: #000000;
--bg-secondary: #0a0a0a;
--bg-tertiary: #141414;
--bg-hover: #1e1e1e;
--bg-active: #282828;
--border: #1a1a1a;
--border-bright: #2a2a2a;
--text-primary: #e0e0e0;
--text-secondary: #808080;
--text-muted: #4a4a4a;
--cyan: #00bfff;
--cyan-hover: #009ed9;
--cyan-dim: rgba(0, 191, 255, 0.08);
--cyan-glow: rgba(0, 191, 255, 0.25);
--cyan-subtle: rgba(0, 191, 255, 0.03);
}
/* ===== Theme: Dracula ===== */
:root[data-theme="dracula"] {
--bg-primary: #282a36;
--bg-secondary: #21222c;
--bg-tertiary: #2d2f3d;
--bg-hover: #44475a;
--bg-active: #4e5166;
--border: #44475a;
--border-bright: #555869;
--text-primary: #f8f8f2;
--text-secondary: #6272a4;
--text-muted: #4d5b86;
--cyan: #bd93f9;
--cyan-hover: #a87cee;
--cyan-dim: rgba(189, 147, 249, 0.08);
--cyan-glow: rgba(189, 147, 249, 0.25);
--cyan-subtle: rgba(189, 147, 249, 0.03);
--status-up: #50fa7b;
--status-up-glow: rgba(80, 250, 123, 0.15);
--status-down: #ff5555;
--status-down-glow: rgba(255, 85, 85, 0.15);
--status-filtered: #f1fa8c;
--status-filtered-glow: rgba(241, 250, 140, 0.15);
--status-unknown: #6272a4;
--danger: #ff5555;
--danger-hover: #e64545;
}
/* ===== Theme: Nord ===== */
:root[data-theme="nord"] {
--bg-primary: #2e3440;
--bg-secondary: #3b4252;
--bg-tertiary: #434c5e;
--bg-hover: #4c566a;
--bg-active: #556070;
--border: #3b4252;
--border-bright: #4c566a;
--text-primary: #eceff4;
--text-secondary: #d8dee9;
--text-muted: #616e88;
--cyan: #88c0d0;
--cyan-hover: #7bb4c4;
--cyan-dim: rgba(136, 192, 208, 0.08);
--cyan-glow: rgba(136, 192, 208, 0.25);
--cyan-subtle: rgba(136, 192, 208, 0.03);
--status-up: #a3be8c;
--status-up-glow: rgba(163, 190, 140, 0.15);
--status-down: #bf616a;
--status-down-glow: rgba(191, 97, 106, 0.15);
--status-filtered: #ebcb8b;
--status-filtered-glow: rgba(235, 203, 139, 0.15);
--status-unknown: #616e88;
--danger: #bf616a;
--danger-hover: #a9545c;
}
/* ===== Theme: Solarized Dark ===== */
:root[data-theme="solarized"] {
--bg-primary: #002b36;
--bg-secondary: #073642;
--bg-tertiary: #0a3f4c;
--bg-hover: #124a56;
--bg-active: #1a5560;
--border: #0d4450;
--border-bright: #1a5c6a;
--text-primary: #cb4b16;
--text-secondary: #839496;
--text-muted: #586e75;
--cyan: #2aa198;
--cyan-hover: #25908a;
--cyan-dim: rgba(42, 161, 152, 0.1);
--cyan-glow: rgba(42, 161, 152, 0.25);
--cyan-subtle: rgba(42, 161, 152, 0.03);
--status-up: #859900;
--status-up-glow: rgba(133, 153, 0, 0.15);
--status-down: #dc322f;
--status-down-glow: rgba(220, 50, 47, 0.15);
--status-filtered: #b58900;
--status-filtered-glow: rgba(181, 137, 0, 0.15);
--status-unknown: #586e75;
--danger: #dc322f;
--danger-hover: #c62825;
}
/* ===== Theme: Catppuccin Mocha ===== */
:root[data-theme="mocha"] {
--bg-primary: #1e1e2e;
--bg-secondary: #181825;
--bg-tertiary: #313244;
--bg-hover: #45475a;
--bg-active: #585b70;
--border: #313244;
--border-bright: #45475a;
--text-primary: #94e2d5;
--text-secondary: #a6adc8;
--text-muted: #6c7086;
--cyan: #b4befe;
--cyan-hover: #7ba4eb;
--cyan-dim: rgba(137, 180, 250, 0.08);
--cyan-glow: rgba(137, 180, 250, 0.2);
--cyan-subtle: rgba(137, 180, 250, 0.03);
--status-up: #a6e3a1;
--status-up-glow: rgba(166, 227, 161, 0.15);
--status-down: #f38ba8;
--status-down-glow: rgba(243, 139, 168, 0.15);
--status-filtered: #f9e2af;
--status-filtered-glow: rgba(249, 226, 175, 0.15);
--status-unknown: #6c7086;
--danger: #f38ba8;
--danger-hover: #e57a97;
}
:root[data-theme="mocha"] .toolbar-circuit {
filter: hue-rotate(40deg) saturate(0.6) brightness(0.9);
}
:root[data-theme="mocha"] .port-tag {
background: rgba(30, 102, 245, 0.1);
border-color: rgba(30, 102, 245, 0.2);
}
:root[data-theme="mocha"] #privilege-status::before {
background: var(--status-up);
box-shadow: 0 0 4px rgba(64, 160, 43, 0.3);
}
:root[data-theme="mocha"] ::-webkit-scrollbar-track {
background: var(--bg-primary);
}
:root[data-theme="mocha"] ::-webkit-scrollbar-thumb {
background: var(--border-bright);
}
/* ===== Theme: One Dark ===== */
:root[data-theme="onedark"] {
--bg-primary: #282c34;
--bg-secondary: #21252b;
--bg-tertiary: #2c313a;
--bg-hover: #3e4451;
--bg-active: #4b5263;
--border: #3e4451;
--border-bright: #4b5263;
--text-primary: #abb2bf;
--text-secondary: #848b98;
--text-muted: #5c6370;
--cyan: #61afef;
--cyan-hover: #519fdf;
--cyan-dim: rgba(97, 175, 239, 0.08);
--cyan-glow: rgba(97, 175, 239, 0.2);
--cyan-subtle: rgba(97, 175, 239, 0.03);
--status-up: #98c379;
--status-up-glow: rgba(152, 195, 121, 0.15);
--status-down: #e06c75;
--status-down-glow: rgba(224, 108, 117, 0.15);
--status-filtered: #e5c07b;
--status-filtered-glow: rgba(229, 192, 123, 0.15);
--status-unknown: #5c6370;
--danger: #e06c75;
--danger-hover: #d05f68;
}
:root[data-theme="onedark"] .toolbar-circuit {
filter: hue-rotate(30deg) saturate(0.6);
}
/* ===== Theme: Rosé Pine ===== */
:root[data-theme="rosepine"] {
--bg-primary: #191724;
--bg-secondary: #1f1d2e;
--bg-tertiary: #26233a;
--bg-hover: #403d52;
--bg-active: #524f67;
--border: #26233a;
--border-bright: #403d52;
--text-primary: #31748f;
--text-secondary: #908caa;
--text-muted: #6e6a86;
--cyan: #c4a7e7;
--cyan-hover: #b498d7;
--cyan-dim: rgba(196, 167, 231, 0.08);
--cyan-glow: rgba(196, 167, 231, 0.2);
--cyan-subtle: rgba(196, 167, 231, 0.03);
--status-up: #9ccfd8;
--status-up-glow: rgba(156, 207, 216, 0.15);
--status-down: #eb6f92;
--status-down-glow: rgba(235, 111, 146, 0.15);
--status-filtered: #f6c177;
--status-filtered-glow: rgba(246, 193, 119, 0.15);
--status-unknown: #6e6a86;
--danger: #eb6f92;
--danger-hover: #db5f82;
}
:root[data-theme="rosepine"] .toolbar-circuit {
filter: hue-rotate(80deg) saturate(0.5) brightness(0.8);
}
/* ===== Theme: Rosé Pine Moon ===== */
:root[data-theme="rosepine-moon"] {
--bg-primary: #232136;
--bg-secondary: #2a273f;
--bg-tertiary: #393552;
--bg-hover: #44415a;
--bg-active: #56526e;
--border: #393552;
--border-bright: #44415a;
--text-primary: #9ccfd8;
--text-secondary: #908caa;
--text-muted: #6e6a86;
--cyan: #c4a7e7;
--cyan-hover: #b498d7;
--cyan-dim: rgba(196, 167, 231, 0.08);
--cyan-glow: rgba(196, 167, 231, 0.2);
--cyan-subtle: rgba(196, 167, 231, 0.03);
--status-up: #9ccfd8;
--status-up-glow: rgba(156, 207, 216, 0.15);
--status-down: #eb6f92;
--status-down-glow: rgba(235, 111, 146, 0.15);
--status-filtered: #f6c177;
--status-filtered-glow: rgba(246, 193, 119, 0.15);
--status-unknown: #6e6a86;
--danger: #eb6f92;
--danger-hover: #db5f82;
}
:root[data-theme="rosepine-moon"] .toolbar-circuit {
filter: hue-rotate(80deg) saturate(0.5) brightness(0.8);
}
/* ===== Theme: Tokyo Night ===== */
:root[data-theme="tokyonight"] {
--bg-primary: #1a1b26;
--bg-secondary: #16161e;
--bg-tertiary: #1f2335;
--bg-hover: #292e42;
--bg-active: #33384d;
--border: #1f2335;
--border-bright: #292e42;
--text-primary: #bb9af7;
--text-secondary: #a9b1d6;
--text-muted: #565f89;
--cyan: #7aa2f7;
--cyan-hover: #6a92e7;
--cyan-dim: rgba(122, 162, 247, 0.08);
--cyan-glow: rgba(122, 162, 247, 0.2);
--cyan-subtle: rgba(122, 162, 247, 0.03);
--status-up: #9ece6a;
--status-up-glow: rgba(158, 206, 106, 0.15);
--status-down: #f7768e;
--status-down-glow: rgba(247, 118, 142, 0.15);
--status-filtered: #e0af68;
--status-filtered-glow: rgba(224, 175, 104, 0.15);
--status-unknown: #565f89;
--danger: #f7768e;
--danger-hover: #e7667e;
}
:root[data-theme="tokyonight"] .toolbar-circuit {
filter: hue-rotate(40deg) saturate(0.5);
}
/* ===== Theme: Iceberg ===== */
:root[data-theme="iceberg"] {
--bg-primary: #161821;
--bg-secondary: #1e2132;
--bg-tertiary: #252837;
--bg-hover: #33374c;
--bg-active: #3d4259;
--border: #252837;
--border-bright: #33374c;
--text-primary: #c6c8d1;
--text-secondary: #818596;
--text-muted: #444b71;
--cyan: #84a0c6;
--cyan-hover: #7490b6;
--cyan-dim: rgba(132, 160, 198, 0.08);
--cyan-glow: rgba(132, 160, 198, 0.2);
--cyan-subtle: rgba(132, 160, 198, 0.03);
--status-up: #b4be82;
--status-up-glow: rgba(180, 190, 130, 0.15);
--status-down: #e27878;
--status-down-glow: rgba(226, 120, 120, 0.15);
--status-filtered: #e2a478;
--status-filtered-glow: rgba(226, 164, 120, 0.15);
--status-unknown: #444b71;
--danger: #e27878;
--danger-hover: #d26868;
}
:root[data-theme="iceberg"] .toolbar-circuit {
filter: hue-rotate(30deg) saturate(0.4) brightness(0.9);
}
/* ===== Theme: Hacker (Easter Egg) ===== */
:root[data-theme="hacker"] {
--bg-primary: #000000;
--bg-secondary: #0a0a0a;
--bg-tertiary: #0d0d0d;
--bg-hover: #1a1a1a;
--bg-active: #222222;
--border: #0a3a0a;
--border-bright: #0f4f0f;
--text-primary: #00ff41;
--text-secondary: #00cc33;
--text-muted: #006b1d;
--cyan: #00ff41;
--cyan-hover: #00dd38;
--cyan-dim: rgba(0, 255, 65, 0.06);
--cyan-glow: rgba(0, 255, 65, 0.3);
--cyan-subtle: rgba(0, 255, 65, 0.02);
--status-up: #00ff41;
--status-up-glow: rgba(0, 255, 65, 0.2);
--status-down: #ff0040;
--status-down-glow: rgba(255, 0, 64, 0.2);
--status-filtered: #ffff00;
--status-filtered-glow: rgba(255, 255, 0, 0.15);
--status-unknown: #006b1d;
--danger: #ff0040;
--danger-hover: #dd0038;
--font-mono: "Courier New", "Lucida Console", monospace;
--font-sans: "Courier New", "Lucida Console", monospace;
}
:root[data-theme="hacker"] .toolbar-circuit {
filter: hue-rotate(-60deg) saturate(3) brightness(0.8);
}
:root[data-theme="hacker"] body {
font-family: "Courier New", "Lucida Console", monospace;
}
:root[data-theme="hacker"] .app-title {
letter-spacing: 5px;
}
:root[data-theme="hacker"] .btn-primary {
text-shadow: 0 0 8px rgba(0, 255, 65, 0.5);
}
:root[data-theme="hacker"] #results-table td,
:root[data-theme="hacker"] #monitor-table td {
text-shadow: 0 0 3px rgba(0, 255, 65, 0.15);
}
#crt-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 9999;
}
:root[data-theme="hacker"] #crt-overlay {
display: block;
background:
repeating-linear-gradient(0deg,
rgba(0, 0, 0, 0.5) 0px,
rgba(0, 0, 0, 0.5) 1px,
transparent 1px,
transparent 4px),
linear-gradient(180deg,
transparent 0%,
rgba(0, 255, 65, 0.09) 50%,
transparent 100%);
animation: crt-flicker 3s infinite;
}
@keyframes crt-flicker {
0% {
opacity: 1;
}
5% {
opacity: 0.85;
}
10% {
opacity: 1;
}
40% {
opacity: 0.95;
}
45% {
opacity: 1;
}
80% {
opacity: 0.9;
}
85% {
opacity: 1;
}
}
/* ===== Circuit art theme tinting ===== */
:root[data-theme="dracula"] .toolbar-circuit {
filter: hue-rotate(70deg);
}
:root[data-theme="nord"] .toolbar-circuit {
filter: hue-rotate(20deg) saturate(0.5);
}
:root[data-theme="solarized"] .toolbar-circuit {
filter: hue-rotate(160deg) saturate(0.6);
}
:root[data-theme="midnight"] .toolbar-circuit {
filter: brightness(0.8);
}
.editable-label {
cursor: pointer;
}
.editable-label:hover {
background: var(--cyan-dim);
}
.label-edit-input {
width: 100%;
padding: 2px 6px;
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-primary);
background: var(--bg-primary);
border: 1px solid var(--cyan);
border-radius: var(--radius);
outline: none;
}
.status-bar-toggle {
font-size: 11px;
color: var(--text-muted);
}
.status-bar-toggle input:checked+span {
color: var(--cyan);
}