Compare commits
8
Commits
v0.2.0
...
af5f65f166
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af5f65f166 | ||
|
|
4c72cdab29 | ||
|
|
5b3aa3bf37 | ||
|
|
f3dd13d831 | ||
|
|
4596b93bb6 | ||
|
|
adc061ae26 | ||
|
|
d84902c111 | ||
|
|
5be8e31483 |
@@ -0,0 +1,12 @@
|
|||||||
|
## [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
|
||||||
+94
@@ -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
|
||||||
Generated
+43
-1
@@ -49,7 +49,7 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argus"
|
name = "argus"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv",
|
"csv",
|
||||||
"dns-lookup",
|
"dns-lookup",
|
||||||
@@ -58,7 +58,9 @@ 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",
|
||||||
@@ -1367,6 +1369,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"
|
||||||
@@ -3006,6 +3017,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"
|
||||||
@@ -4738,6 +4768,12 @@ dependencies = [
|
|||||||
"unic-common",
|
"unic-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.26"
|
version = "1.0.26"
|
||||||
@@ -4750,6 +4786,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"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "argus"
|
name = "argus"
|
||||||
version = "0.1.0"
|
version = "0.2.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"
|
||||||
@@ -33,6 +33,8 @@ 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"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
is_elevated = "0.1"
|
is_elevated = "0.1"
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use pulldown_cmark::{Options, Parser, html};
|
||||||
|
|
||||||
|
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,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;
|
||||||
|
|||||||
@@ -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(¤t)
|
||||||
|
.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,
|
||||||
@@ -51,4 +54,4 @@ pub async fn check_for_update(app: AppHandle) -> Result<Option<UpdateInfo>, Stri
|
|||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ 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,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -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.2.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,7 @@
|
|||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
|
"copyright": "Copyright \u00A9 2026 James Brotosky. Licensed under AGPLv3. All rights reserved.",
|
||||||
"windows": {
|
"windows": {
|
||||||
"webviewInstallMode": {
|
"webviewInstallMode": {
|
||||||
"type": "embedBootstrapper"
|
"type": "embedBootstrapper"
|
||||||
|
|||||||
@@ -199,6 +199,14 @@
|
|||||||
<!-- 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>
|
||||||
|
<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>
|
||||||
|
</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 +220,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">×</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>
|
||||||
|
|||||||
+164
-20
@@ -1,12 +1,56 @@
|
|||||||
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) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var saved = get();
|
||||||
|
set(saved);
|
||||||
|
|
||||||
|
var picker = document.getElementById('theme-picker');
|
||||||
|
picker.value = saved;
|
||||||
|
picker.addEventListener('change', function () {
|
||||||
|
set(picker.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 +199,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 +261,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 +305,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();
|
||||||
}
|
}
|
||||||
@@ -992,7 +1097,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 +1126,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 +1194,10 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
nmapTab.init();
|
nmapTab.init();
|
||||||
monitorTab.init();
|
monitorTab.init();
|
||||||
updater.init();
|
updater.init();
|
||||||
|
changelog.init();
|
||||||
|
theme.init();
|
||||||
|
columns.init();
|
||||||
|
sender.init();
|
||||||
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');
|
||||||
|
|||||||
+235
@@ -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 {
|
||||||
@@ -1259,4 +1269,229 @@ body {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
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: #93a1a1;
|
||||||
|
--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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 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);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user