29 Commits
Author SHA1 Message Date
mysticmomba b80fff7609 Merge pull request 'Updated version number in code' (#17) from feat/minimizetotray into development
Reviewed-on: #17
2026-10-09 11:50:30 -04:00
mysticmomba a6558576ab Updated version number in code 2026-10-09 11:49:52 -04:00
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
28 changed files with 1491 additions and 291 deletions
+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.
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
it under the terms of the GNU Affero General Public License as published
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]]
name = "alloc-no-stdlib"
version = "2.0.4"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
[[package]]
name = "alloc-stdlib"
version = "0.2.4"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
dependencies = [
"alloc-no-stdlib",
]
@@ -49,7 +49,7 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "argus"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"csv",
"dns-lookup",
@@ -58,13 +58,17 @@ dependencies = [
"libc",
"open",
"ping-async",
"pulldown-cmark",
"reqwest 0.12.28",
"semver",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-dialog",
"tauri-plugin-notification",
"tauri-plugin-opener",
"tauri-plugin-single-instance",
"tokio",
"tracing",
"tracing-subscriber",
@@ -317,9 +321,9 @@ dependencies = [
[[package]]
name = "brotli"
version = "8.0.4"
version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -328,9 +332,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
version = "5.0.3"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -431,12 +435,13 @@ dependencies = [
[[package]]
name = "cargo_toml"
version = "0.22.3"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77"
checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9"
dependencies = [
"semver",
"serde",
"toml 0.9.12+spec-1.1.0",
"toml 1.1.6+spec-1.1.0",
]
[[package]]
@@ -457,13 +462,13 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfb"
version = "0.7.3"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3"
dependencies = [
"byteorder",
"fnv",
"uuid",
"web-time",
]
[[package]]
@@ -624,9 +629,9 @@ dependencies = [
[[package]]
name = "cssparser"
version = "0.36.0"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2"
checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98"
dependencies = [
"cssparser-macros",
"dtoa-short",
@@ -637,12 +642,12 @@ dependencies = [
[[package]]
name = "cssparser-macros"
version = "0.6.1"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a"
dependencies = [
"quote",
"syn 2.0.119",
"syn 3.0.6",
]
[[package]]
@@ -668,19 +673,9 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.8.0"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98"
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"
checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d"
[[package]]
name = "darling"
@@ -800,9 +795,9 @@ dependencies = [
[[package]]
name = "dirs"
version = "6.0.0"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f"
dependencies = [
"dirs-sys",
]
@@ -879,9 +874,9 @@ dependencies = [
[[package]]
name = "dom_query"
version = "0.27.0"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89"
checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc"
dependencies = [
"bit-set",
"cssparser",
@@ -916,21 +911,6 @@ dependencies = [
"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]]
name = "dunce"
version = "1.0.5"
@@ -1367,6 +1347,15 @@ dependencies = [
"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]]
name = "getrandom"
version = "0.2.17"
@@ -1606,9 +1595,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "html5ever"
version = "0.38.0"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2"
checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
dependencies = [
"log",
"markup5ever",
@@ -1899,9 +1888,9 @@ dependencies = [
[[package]]
name = "infer"
version = "0.19.0"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7"
checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4"
dependencies = [
"cfb",
]
@@ -2089,21 +2078,21 @@ dependencies = [
[[package]]
name = "json-patch"
version = "3.0.1"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08"
checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72"
dependencies = [
"jsonptr",
"serde",
"serde_json",
"thiserror 1.0.69",
"thiserror 2.0.20",
]
[[package]]
name = "jsonptr"
version = "0.6.3"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70"
checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe"
dependencies = [
"serde",
"serde_json",
@@ -2111,13 +2100,12 @@ dependencies = [
[[package]]
name = "keyboard-types"
version = "0.7.0"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d"
dependencies = [
"bitflags 2.13.2",
"serde",
"unicode-segmentation",
]
[[package]]
@@ -2212,10 +2200,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "markup5ever"
version = "0.38.0"
name = "mac-notification-sys"
version = "0.6.15"
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 = [
"log",
"tendril",
@@ -2285,9 +2287,9 @@ dependencies = [
[[package]]
name = "muda"
version = "0.19.3"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878"
checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996"
dependencies = [
"crossbeam-channel",
"dpi",
@@ -2363,6 +2365,12 @@ dependencies = [
"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]]
name = "ndk-sys"
version = "0.6.0+11769913"
@@ -2378,6 +2386,20 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "nu-ansi-term"
version = "0.50.3"
@@ -2445,6 +2467,7 @@ dependencies = [
"objc2",
"objc2-core-foundation",
"objc2-foundation",
"objc2-quartz-core",
]
[[package]]
@@ -3006,6 +3029,25 @@ dependencies = [
"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]]
name = "quick-xml"
version = "0.42.0"
@@ -3424,9 +3466,9 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.36.1"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c"
checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d"
dependencies = [
"bitflags 2.13.2",
"cssparser",
@@ -3893,9 +3935,9 @@ dependencies = [
[[package]]
name = "tao"
version = "0.35.3"
version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
checksum = "f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400"
dependencies = [
"bitflags 2.13.2",
"block2",
@@ -3913,20 +3955,20 @@ dependencies = [
"libc",
"log",
"ndk",
"ndk-context",
"ndk-sys",
"objc2",
"objc2-app-kit",
"objc2-foundation",
"objc2-ui-kit",
"once_cell",
"parking_lot",
"percent-encoding",
"raw-window-handle",
"tao-macros",
"unicode-segmentation",
"url",
"windows 0.61.3",
"windows-core 0.61.2",
"windows 0.62.2",
"windows-core 0.62.2",
"windows-version",
"x11-dl",
]
@@ -3950,9 +3992,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.11.6"
version = "2.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fa5bacdb9bbad5954af3d1bd6cf6ae9192cab1b2e270f4a07f904610b9e85f4"
checksum = "d061d042ccd2df9c9f1508c1e63d835278c0a4c8801eb41764080e51f2eaaf34"
dependencies = [
"anyhow",
"bytes",
@@ -3996,14 +4038,14 @@ dependencies = [
"webkit2gtk",
"webview2-com",
"window-vibrancy",
"windows 0.61.3",
"windows 0.62.2",
]
[[package]]
name = "tauri-build"
version = "2.6.3"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632"
checksum = "59563ca5b331fd97f27ee9672ec8d36d33bb3c9e2c9f6710a1a5687256052e45"
dependencies = [
"anyhow",
"cargo_toml",
@@ -4022,16 +4064,16 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.6.3"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5"
checksum = "e50213e463086ecc283d7e9f51c30cc0fd304ad457425158018be26dbf0d0a80"
dependencies = [
"base64 0.22.1",
"brotli",
"ico",
"json-patch",
"plist",
"png 0.17.16",
"png 0.18.1",
"proc-macro2",
"quote",
"semver",
@@ -4049,9 +4091,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.6.3"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45"
checksum = "8129fb43bb6e0d6b73ad3315cd172beaab17cd28899c594b1b32c80edbbfd99c"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -4063,9 +4105,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.6.3"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020"
checksum = "1140cf34a3b3b836a13103dcab17f18831d5cc3534cbd435dc01a5c6daa65aa2"
dependencies = [
"anyhow",
"glob",
@@ -4119,6 +4161,25 @@ dependencies = [
"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]]
name = "tauri-plugin-opener"
version = "2.5.5"
@@ -4142,10 +4203,26 @@ dependencies = [
]
[[package]]
name = "tauri-runtime"
version = "2.11.3"
name = "tauri-plugin-single-instance"
version = "2.4.5"
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 = [
"cookie",
"dpi",
@@ -4163,14 +4240,14 @@ dependencies = [
"url",
"webkit2gtk",
"webview2-com",
"windows 0.61.3",
"windows 0.62.2",
]
[[package]]
name = "tauri-runtime-wry"
version = "2.11.4"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f"
checksum = "7f9e7e0c9f80a5130773cfabb6df4dc924518fb1b2c923726966804634b57d22"
dependencies = [
"gtk",
"http",
@@ -4188,15 +4265,15 @@ dependencies = [
"url",
"webkit2gtk",
"webview2-com",
"windows 0.61.3",
"windows 0.62.2",
"wry",
]
[[package]]
name = "tauri-utils"
version = "2.9.3"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887"
checksum = "2ff55a614843b9a3f010f211df637c94df2607175b8d0f6e0f4a7c19e18621ee"
dependencies = [
"anyhow",
"brotli",
@@ -4241,6 +4318,17 @@ dependencies = [
"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]]
name = "tempfile"
version = "3.27.0"
@@ -4432,21 +4520,6 @@ dependencies = [
"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]]
name = "toml"
version = "1.1.6+spec-1.1.0"
@@ -4471,15 +4544,6 @@ dependencies = [
"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]]
name = "toml_datetime"
version = "1.1.1+spec-1.1.0"
@@ -4648,9 +4712,9 @@ dependencies = [
[[package]]
name = "tray-icon"
version = "0.24.2"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e"
checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72"
dependencies = [
"crossbeam-channel",
"dirs",
@@ -4698,45 +4762,10 @@ dependencies = [
]
[[package]]
name = "unic-char-property"
version = "0.9.0"
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
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",
]
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
@@ -4750,6 +4779,12 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -4771,13 +4806,13 @@ dependencies = [
[[package]]
name = "urlpattern"
version = "0.3.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d"
checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b"
dependencies = [
"icu_properties",
"regex",
"serde",
"unic-ucd-ident",
"url",
]
@@ -4955,6 +4990,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "web_atoms"
version = "0.2.6"
@@ -5013,16 +5058,14 @@ dependencies = [
[[package]]
name = "webview2-com"
version = "0.38.2"
version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6"
dependencies = [
"webview2-com-macros",
"webview2-com-sys",
"windows 0.61.3",
"windows-core 0.61.2",
"windows-implement",
"windows-interface",
"windows 0.62.2",
"windows-core 0.62.2",
]
[[package]]
@@ -5038,13 +5081,13 @@ dependencies = [
[[package]]
name = "webview2-com-sys"
version = "0.38.2"
version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb"
dependencies = [
"thiserror 2.0.20",
"windows 0.61.3",
"windows-core 0.61.2",
"windows 0.62.2",
"windows-core 0.62.2",
]
[[package]]
@@ -5080,16 +5123,17 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "window-vibrancy"
version = "0.6.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580"
dependencies = [
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation",
"objc2-quartz-core",
"raw-window-handle",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
"windows-version",
]
@@ -5552,12 +5596,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
[[package]]
name = "winnow"
version = "1.0.4"
@@ -5591,9 +5629,9 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]]
name = "wry"
version = "0.55.1"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514"
checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0"
dependencies = [
"base64 0.22.1",
"block2",
@@ -5627,8 +5665,8 @@ dependencies = [
"webkit2gtk",
"webkit2gtk-sys",
"webview2-com",
"windows 0.61.3",
"windows-core 0.61.2",
"windows 0.62.2",
"windows-core 0.62.2",
"windows-version",
"x11-dl",
]
+6 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "argus"
version = "0.1.0"
version = "0.4.0"
description = "An All in One Network Utility"
authors = ["James Brotosky <james.brotosky@racooncity.org"]
edition = "2021"
@@ -18,7 +18,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri = { version = "2", features = ["tray-icon"] }
tauri-plugin-single-instance = "2"
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -33,6 +34,9 @@ csv = "1"
open = "5.4.4"
reqwest = { version = "0.12", features = ["json"] }
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]
is_elevated = "0.1"
+4 -1
View File
@@ -6,6 +6,9 @@
"permissions": [
"core: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]
pub async fn export_csv(request: ExportRequest) -> Result<(), String> {
let file = File::create(&request.path)
.map_err(|e| format!("Failed to create file: {}", e))?;
let file = File::create(&request.path).map_err(|e| format!("Failed to create file: {}", e))?;
let mut writer = csv::Writer::from_writer(file);
+1
View File
@@ -1,4 +1,5 @@
pub mod actions;
pub mod changelog;
pub mod export;
pub mod monitor;
pub mod nmap;
+21
View File
@@ -180,3 +180,24 @@ pub async fn monitor_stop() -> Result<(), String> {
}
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
}
+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);
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(())
}
+1
View File
@@ -60,3 +60,4 @@ pub async fn run_traceroute(app: AppHandle, ip: String) -> Result<(), String> {
Ok(())
}
+9 -6
View File
@@ -1,7 +1,8 @@
use serde::{Deserialize, Serialize};
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)]
pub struct UpdateInfo {
@@ -29,10 +30,7 @@ pub async fn check_for_update(app: AppHandle) -> Result<Option<UpdateInfo>, Stri
.as_str()
.ok_or_else(|| "No tag_name in response".to_string())?;
let html_url = body["html_url"]
.as_str()
.unwrap_or("")
.to_string();
let html_url = body["html_url"].as_str().unwrap_or("").to_string();
let notes = body["body"]
.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 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 {
version: remote_version.to_string(),
url: html_url,
+63
View File
@@ -3,6 +3,10 @@ mod models;
mod net;
mod scanner;
use tauri::Manager;
use tauri::menu::{Menu, MenuItem};
use tauri::tray::TrayIconBuilder;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tracing_subscriber::fmt()
@@ -13,8 +17,64 @@ pub fn run() {
.init();
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_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![
commands::scan::start_scan,
commands::scan::stop_scan,
@@ -31,6 +91,9 @@ pub fn run() {
commands::monitor::monitor_stop,
commands::updater::check_for_update,
commands::actions::check_privileges,
commands::changelog::get_changelog,
commands::monitor::monitor_update_label,
commands::monitor::monitor_resolve_host
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
+2 -2
View File
@@ -6,7 +6,7 @@ pub enum HostStatus {
Up,
Down,
Filtered,
Unknown
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -42,7 +42,7 @@ pub struct PortResult {
pub struct NetbiosInfo {
pub computer_name: Option<String>,
pub workgroup: Option<String>,
pub mac_from_netbios: Option<String>
pub mac_from_netbios: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
+1 -33
View File
@@ -1,12 +1,9 @@
use std::net::IpAddr;
use tokio::task::JoinSet;
/// Perform reverse DNS lookup on a single IP.
/// Returns the hostname if found, None otherwise.
pub async fn reverse_lookup(ip: IpAddr) -> Option<String> {
let result = tokio::task::spawn_blocking(move || {
dns_lookup::lookup_addr(&ip)
}).await;
let result = tokio::task::spawn_blocking(move || dns_lookup::lookup_addr(&ip)).await;
match result {
Ok(Ok(hostname)) => {
@@ -19,32 +16,3 @@ pub async fn reverse_lookup(ip: IpAddr) -> Option<String> {
_ => 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 range;
+1 -1
View File
@@ -1,3 +1,3 @@
pub mod netbios;
pub mod ping;
pub mod ports;
pub mod netbios;
+1 -2
View File
@@ -91,8 +91,7 @@ pub async fn ping_sweep(
let nb_fut = query_netbios(enriched.ip, 500);
if port_scan {
let scan_ports = ports2
.unwrap_or_else(|| DEFAULT_PORTS.to_vec());
let scan_ports = ports2.unwrap_or_else(|| DEFAULT_PORTS.to_vec());
let (nb_result, port_results) = tokio::join!(
nb_fut,
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] = &[
21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143,
443, 445, 993, 995, 1433, 1521, 3306, 3389,
21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143, 443, 445, 993, 995, 1433, 1521, 3306, 3389,
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 timeout(banner_timeout, stream.write_all(data)).await.is_err() {
if timeout(banner_timeout, stream.write_all(data))
.await
.is_err()
{
return None;
}
}
@@ -107,8 +109,15 @@ async fn grab_banner(mut stream: TcpStream, port: u16) -> Option<String> {
Ok(Ok(n)) if n > 0 => {
buf.truncate(n);
// Strip to printable ASCII, replace control chars
let banner: String = buf.iter()
.map(|&b| if b >= 0x20 && b < 0x7f { b as char } else { ' ' })
let banner: String = buf
.iter()
.map(|&b| {
if (0x20..0x7f).contains(&b) {
b as char
} else {
' '
}
})
.collect();
let banner = banner.trim().to_string();
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;
pub async fn scan_host_ports(
ip: IpAddr,
ports: &[u16],
timeout_ms: u64,
) -> Vec<PortResult> {
pub async fn scan_host_ports(ip: IpAddr, ports: &[u16], timeout_ms: u64) -> Vec<PortResult> {
let mut results = Vec::with_capacity(ports.len());
for chunk in ports.chunks(MAX_PORT_CONCURRENT) {
+3 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "argus",
"version": "0.1.0",
"version": "0.4.0",
"identifier": "io.argus.app",
"build": {
"frontendDist": "../src"
@@ -10,7 +10,7 @@
"withGlobalTauri": true,
"windows": [
{
"title": "argus",
"title": "Argus",
"width": 800,
"height": 600
}
@@ -22,6 +22,7 @@
"bundle": {
"active": true,
"targets": "all",
"copyright": "Copyright \u00A9 2026 James Brotosky. Licensed under AGPLv3. All rights reserved.",
"windows": {
"webviewInstallMode": {
"type": "embedBootstrapper"
+29 -1
View File
@@ -199,6 +199,24 @@
<!-- Status Bar -->
<div id="status-bar">
<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>
</div>
@@ -212,6 +230,16 @@
<div id="traceroute-output" class="traceroute-output"></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 -->
<div id="context-menu" class="context-menu" hidden>
<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-nmap">Send to Nmap</button>
</div>
<div id="crt-overlay"></div>
<script src="main.js"></script>
</body>
+320 -27
View File
@@ -1,12 +1,104 @@
const { invoke } = window.__TAURI__.core;
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 = (() => {
var dirty = false;
var flushTimer = null;
var ipIndex = {};
let data = [];
let filtered = [];
let sortKey = 'ip';
let sortDir = 'asc';
let filterText = '';
let checkedIps = new Set();
var ROW_HEIGHT = 32;
var BUFFER_ROWS = 20;
var scrollContainer = null;
function ipToNum(ip) {
const parts = String(ip).split('.');
@@ -155,20 +247,61 @@ const table = (() => {
}
function render() {
const tbody = document.getElementById('results-body');
const count = document.getElementById('host-count');
var tbody = document.getElementById('results-body');
var count = document.getElementById('host-count');
if (!tbody) return;
expandedIp = null;
const rows = filtered.map((host) => {
const ip = host.ip;
const status = host.status || 'Unknown';
const cls = 'status-' + status.toLowerCase();
const rtt = host.ping ? host.ping.rtt_ms.toFixed(1) : '-';
const hostname = host.hostname || '-';
if (!scrollContainer) {
scrollContainer = document.getElementById('results-table-container');
scrollContainer.addEventListener('scroll', onScroll);
}
const isChecked = checkedIps.has(ip) ? 'checked' : '';
return '<tr data-ip="' + ip + '">'
renderVisible();
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>' + ip + '</td>'
+ '<td class="' + cls + '">' + status + '</td>'
@@ -176,24 +309,43 @@ const table = (() => {
+ '<td>' + hostname + '</td>'
+ '<td class="ports-cell">' + formatPorts(host) + '</td>'
+ '</tr>';
});
tbody.innerHTML = rows.join('');
if (count) {
const up = data.filter((h) => h.status === 'Up').length;
count.textContent = up + ' up / ' + data.length + ' total';
}
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) {
const idx = data.findIndex((h) => h.ip === host.ip);
var idx = data.findIndex(function (h) { return h.ip === host.ip; });
if (idx >= 0) {
data[idx] = host;
} else {
ipIndex[host.ip] = data.length;
data.push(host);
}
dirty = true;
if (!flushTimer) {
flushTimer = setTimeout(flush, 200);
}
}
function flush() {
flushTimer = null;
if (!dirty) return;
dirty = false;
refresh();
}
@@ -201,6 +353,7 @@ const table = (() => {
data = [];
filtered = [];
checkedIps.clear();
ipIndex = {};
document.getElementById('check-all').checked = false;
refresh();
}
@@ -504,7 +657,7 @@ const sender = (() => {
if (action.startsWith('nmap')) {
nmapTab.setTarget(hosts.map((h) => String(h.ip)));
} 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);
@@ -570,7 +723,8 @@ const ctxMenu = (() => {
traceroute.run(ip);
break;
case 'send-monitor':
monitorTab.setTargets([ip]);
var monHost = table.getAll().find(function (h) { return String(h.ip) === ip; });
monitorTab.setTargets(monHost ? [monHost] : [ip]);
break;
case 'send-nmap':
nmapTab.setTarget([ip]);
@@ -821,7 +975,7 @@ const nmapTab = (() => {
const monitorTab = (() => {
let running = false;
let entries = {};
var editingLabel = false;
var els = {
input: () => document.getElementById('monitor-input'),
interval: () => document.getElementById('monitor-interval'),
@@ -832,6 +986,26 @@ const monitorTab = (() => {
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) {
running = active;
els.btnStart().disabled = active;
@@ -847,6 +1021,7 @@ const monitorTab = (() => {
}
function render() {
if (editingLabel) return;
var body = els.body();
var ips = Object.keys(entries).sort();
@@ -862,7 +1037,7 @@ const monitorTab = (() => {
return '<tr data-ip="' + ip + '">'
+ '<td>' + ip + '</td>'
+ '<td>' + label + '</td>'
+ '<td class="editable-label" data-ip="' + ip + '">' + label + '</td>'
+ '<td class="' + statusClass + '">' + e.status + '</td>'
+ '<td>' + lastRtt + '</td>'
+ '<td>' + minRtt + '</td>'
@@ -889,6 +1064,16 @@ const monitorTab = (() => {
try {
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');
entries = {};
updated.forEach(function (e) { entries[e.ip] = e; });
@@ -917,6 +1102,14 @@ const monitorTab = (() => {
try {
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) {
console.error('Monitor start error:', err);
setRunning(false);
@@ -929,6 +1122,10 @@ const monitorTab = (() => {
} catch (err) {
console.error('Monitor stop error:', err);
}
if (reminderInterval) {
clearInterval(reminderInterval);
reminderInterval = null;
}
setRunning(false);
}
@@ -943,9 +1140,20 @@ const monitorTab = (() => {
}
}
function setTargets(ips) {
els.input().value = ips.join(', ');
// Switch to monitor tab
function setTargets(hosts) {
var items = hosts.map(function (h) {
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('.view').forEach(function (v) { v.classList.remove('active'); });
document.querySelector('[data-tab="monitor"]').classList.add('active');
@@ -961,7 +1169,13 @@ const monitorTab = (() => {
await listen('monitor:state-change', function (e) {
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');
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();
}
@@ -992,7 +1241,6 @@ var updater = (() => {
var msg = document.getElementById('update-message');
var link = document.getElementById('update-link');
var text = 'Argus v' + info.version + ' is available.';
if (info.notes) text += ' ' + info.notes;
msg.textContent = text;
link.href = info.url;
link.addEventListener('click', function (e) {
@@ -1022,6 +1270,42 @@ var updater = (() => {
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', () => {
// Tab switching
document.querySelectorAll('.tab').forEach((tab) => {
@@ -1054,6 +1338,15 @@ window.addEventListener('DOMContentLoaded', () => {
nmapTab.init();
monitorTab.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-up').addEventListener('click', exporter.csvUp);
console.log('[Argus] Ready');
+620 -3
View File
@@ -409,6 +409,7 @@ body {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: 32px;
}
#results-table tbody tr {
@@ -428,6 +429,15 @@ body {
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-up {
@@ -1136,17 +1146,17 @@ body {
text-shadow: 0 0 6px var(--status-up-glow);
}
.monitor-status-Down {
#monitor-table .monitor-status-Down {
color: var(--status-down);
text-shadow: 0 0 6px var(--status-down-glow);
}
.monitor-status-Degraded {
#monitor-table .monitor-status-Degraded {
color: var(--status-filtered);
text-shadow: 0 0 6px var(--status-filtered-glow);
}
.monitor-status-Unknown {
#monitor-table .monitor-status-Unknown {
color: var(--status-unknown);
}
@@ -1260,3 +1270,610 @@ body {
color: var(--text-muted);
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);
}