12 Commits
Author SHA1 Message Date
mysticmomba 518f8cfe36 Merge pull request 'Development' (#8) from development into master
Reviewed-on: #8
2026-10-08 11:25:29 -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 dbe8f2fb68 Merge pull request 'Fixed main.js' (#2) from development into master
Reviewed-on: #2
2026-09-24 10:28:40 -04:00
mysticmomba af5f65f166 Merge pull request '[0.2.0] - 2026-09-24' (#1) from development into master
Reviewed-on: #1
2026-09-24 10:18:36 -04:00
11 changed files with 362 additions and 44 deletions
+13
View File
@@ -1,3 +1,16 @@
## [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 ## [0.2.0] - 2026-09-24
### 🚀 Features ### 🚀 Features
+1 -1
View File
@@ -49,7 +49,7 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]] [[package]]
name = "argus" name = "argus"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"csv", "csv",
"dns-lookup", "dns-lookup",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "argus" name = "argus"
version = "0.2.0" version = "0.3.0"
description = "An All in One Network Utility" description = "An All in One Network Utility"
authors = ["James Brotosky <james.brotosky@racooncity.org"] authors = ["James Brotosky <james.brotosky@racooncity.org"]
edition = "2021" edition = "2021"
+2 -1
View File
@@ -6,6 +6,7 @@
"permissions": [ "permissions": [
"core:default", "core:default",
"opener:default", "opener:default",
"dialog:default" "dialog:default",
"core:window:allow-set-always-on-top"
] ]
} }
+16
View File
@@ -180,3 +180,19 @@ pub async fn monitor_stop() -> Result<(), String> {
} }
Ok(()) Ok(())
} }
#[tauri::command]
pub async fn monitor_update_label(ip: String, label: String) -> Result<(), String> {
let parsed: IpAddr = ip.parse().map_err(|e| format!("Invalid IP: {}", e))?;
let state = get_monitor();
let mut guard = state.lock().await;
if let Some(entry) = guard.entries.get_mut(&parsed) {
entry.label = if label.is_empty() { None } else { Some(label) };
}
Ok(())
}
#[tauri::command]
pub async fn monitor_resolve_host(ip: String) -> Result<Option<String>, String> {
let parsed: IpAddr = ip.parse().map_err(|e| format!("Invalid IP: {}", e))?;
Ok(crate::net::dns::reverse_lookup(parsed).await)
}
+2
View File
@@ -32,6 +32,8 @@ pub fn run() {
commands::updater::check_for_update, commands::updater::check_for_update,
commands::actions::check_privileges, commands::actions::check_privileges,
commands::changelog::get_changelog, commands::changelog::get_changelog,
commands::monitor::monitor_update_label,
commands::monitor::monitor_resolve_host
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
-30
View File
@@ -1,5 +1,4 @@
use std::net::IpAddr; use std::net::IpAddr;
use tokio::task::JoinSet;
/// Perform reverse DNS lookup on a single IP. /// Perform reverse DNS lookup on a single IP.
/// Returns the hostname if found, None otherwise. /// Returns the hostname if found, None otherwise.
@@ -18,33 +17,4 @@ pub async fn reverse_lookup(ip: IpAddr) -> Option<String> {
} }
_ => None, _ => None,
} }
}
/// Perform reverse DNS lookups on a batch of IPs concurrently.
/// Returns a Vec of (IpAddr, Option<hostname>) pairs.
pub async fn reverse_lookup_batch(
ips: &[IpAddr],
max_concurrent: usize,
) -> Vec<(IpAddr, Option<String>)> {
let mut results = Vec::with_capacity(ips.len());
for chunk in ips.chunks(max_concurrent) {
let mut join_set = JoinSet::new();
for &ip in chunk {
join_set.spawn(async move {
let hostname = reverse_lookup(ip).await;
(ip, hostname)
});
}
while let Some(result) = join_set.join_next().await {
match result {
Ok(pair) => results.push(pair),
Err(e) => tracing::warn!("DNS lookup task panicked: {}", e),
}
}
}
results
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "argus", "productName": "argus",
"version": "0.2.0", "version": "0.3.0",
"identifier": "io.argus.app", "identifier": "io.argus.app",
"build": { "build": {
"frontendDist": "../src" "frontendDist": "../src"
+6 -1
View File
@@ -200,12 +200,17 @@
<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> <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"> <select id="theme-picker" class="theme-picker">
<option value="cyber">Cyber</option> <option value="cyber">Cyber</option>
<option value="midnight">Midnight</option> <option value="midnight">Midnight</option>
<option value="dracula">Dracula</option> <option value="dracula">Dracula</option>
<option value="nord">Nord</option> <option value="nord">Nord</option>
<option value="solarized">Solarized Dark</option> <option value="solarized">Solarized Dark</option>
<option value="mocha">Catppuccin Mocha</option>
</select> </select>
<span class="status-bar-item" id="privilege-status">unprivileged</span> <span class="status-bar-item" id="privilege-status">unprivileged</span>
</div> </div>
@@ -243,7 +248,7 @@
<button class="ctx-item" data-action="send-monitor">Send to Monitor</button> <button class="ctx-item" data-action="send-monitor">Send to Monitor</button>
<button class="ctx-item" data-action="send-nmap">Send to Nmap</button> <button class="ctx-item" data-action="send-nmap">Send to Nmap</button>
</div> </div>
<div id="crt-overlay"></div>
<script src="main.js"></script> <script src="main.js"></script>
</body> </body>
+120 -6
View File
@@ -22,15 +22,63 @@ var theme = (() => {
} catch (e) { } } 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() { function init() {
var saved = get(); var saved = get();
set(saved); set(saved);
// Add hacker option if previously unlocked
if (isHackerUnlocked()) {
addHackerOption();
}
var picker = document.getElementById('theme-picker'); var picker = document.getElementById('theme-picker');
picker.value = saved; picker.value = saved;
picker.addEventListener('change', function () { picker.addEventListener('change', function () {
set(picker.value); 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 // Apply immediately (before DOMContentLoaded) to prevent flash
@@ -609,7 +657,7 @@ const sender = (() => {
if (action.startsWith('nmap')) { if (action.startsWith('nmap')) {
nmapTab.setTarget(hosts.map((h) => String(h.ip))); nmapTab.setTarget(hosts.map((h) => String(h.ip)));
} else if (action.startsWith('monitor')) { } else if (action.startsWith('monitor')) {
monitorTab.setTargets(hosts.map(function (h) { return String(h.ip); })); monitorTab.setTargets(hosts);
} }
document.querySelectorAll('.send-menu').forEach((m) => m.hidden = true); document.querySelectorAll('.send-menu').forEach((m) => m.hidden = true);
@@ -675,7 +723,8 @@ const ctxMenu = (() => {
traceroute.run(ip); traceroute.run(ip);
break; break;
case 'send-monitor': case 'send-monitor':
monitorTab.setTargets([ip]); var monHost = table.getAll().find(function (h) { return String(h.ip) === ip; });
monitorTab.setTargets(monHost ? [monHost] : [ip]);
break; break;
case 'send-nmap': case 'send-nmap':
nmapTab.setTarget([ip]); nmapTab.setTarget([ip]);
@@ -926,6 +975,7 @@ const nmapTab = (() => {
const monitorTab = (() => { const monitorTab = (() => {
let running = false; let running = false;
let entries = {}; let entries = {};
var editingLabel = false;
var els = { var els = {
input: () => document.getElementById('monitor-input'), input: () => document.getElementById('monitor-input'),
@@ -952,6 +1002,7 @@ const monitorTab = (() => {
} }
function render() { function render() {
if (editingLabel) return;
var body = els.body(); var body = els.body();
var ips = Object.keys(entries).sort(); var ips = Object.keys(entries).sort();
@@ -967,7 +1018,7 @@ const monitorTab = (() => {
return '<tr data-ip="' + ip + '">' return '<tr data-ip="' + ip + '">'
+ '<td>' + ip + '</td>' + '<td>' + ip + '</td>'
+ '<td>' + label + '</td>' + '<td class="editable-label" data-ip="' + ip + '">' + label + '</td>'
+ '<td class="' + statusClass + '">' + e.status + '</td>' + '<td class="' + statusClass + '">' + e.status + '</td>'
+ '<td>' + lastRtt + '</td>' + '<td>' + lastRtt + '</td>'
+ '<td>' + minRtt + '</td>' + '<td>' + minRtt + '</td>'
@@ -994,6 +1045,16 @@ const monitorTab = (() => {
try { try {
await invoke('monitor_add_hosts', { hosts: hosts }); await invoke('monitor_add_hosts', { hosts: hosts });
for (var i = 0; i < hosts.length; i++) {
try {
var hostname = await invoke('monitor_resolve_host', { ip: hosts[i].ip });
if (hostname) {
await invoke('monitor_update_label', { ip: hosts[i].ip, label: hostname });
}
} catch (e) { }
}
var updated = await invoke('monitor_get_entries'); var updated = await invoke('monitor_get_entries');
entries = {}; entries = {};
updated.forEach(function (e) { entries[e.ip] = e; }); updated.forEach(function (e) { entries[e.ip] = e; });
@@ -1048,9 +1109,20 @@ const monitorTab = (() => {
} }
} }
function setTargets(ips) { function setTargets(hosts) {
els.input().value = ips.join(', '); var items = hosts.map(function (h) {
// Switch to monitor tab if (typeof h === 'string') return { ip: h, label: null };
return { ip: String(h.ip), label: h.hostname || null };
});
invoke('monitor_add_hosts', { hosts: items }).then(function () {
return invoke('monitor_get_entries');
}).then(function (updated) {
entries = {};
updated.forEach(function (e) { entries[e.ip] = e; });
render();
});
document.querySelectorAll('.tab').forEach(function (t) { t.classList.remove('active'); }); document.querySelectorAll('.tab').forEach(function (t) { t.classList.remove('active'); });
document.querySelectorAll('.view').forEach(function (v) { v.classList.remove('active'); }); document.querySelectorAll('.view').forEach(function (v) { v.classList.remove('active'); });
document.querySelector('[data-tab="monitor"]').classList.add('active'); document.querySelector('[data-tab="monitor"]').classList.add('active');
@@ -1085,6 +1157,41 @@ const monitorTab = (() => {
var btn = e.target.closest('.btn-remove'); var btn = e.target.closest('.btn-remove');
if (btn) removeHost(btn.dataset.ip); if (btn) removeHost(btn.dataset.ip);
}); });
els.body().addEventListener('dblclick', function (e) {
var cell = e.target.closest('.editable-label');
if (!cell) return;
var ip = cell.dataset.ip;
var currentLabel = cell.textContent === '-' ? '' : cell.textContent;
editingLabel = true;
var input = document.createElement('input');
input.type = 'text';
input.value = currentLabel;
input.className = 'label-edit-input';
cell.textContent = '';
cell.appendChild(input);
input.focus();
input.select();
function save() {
var newLabel = input.value.trim();
editingLabel = false;
invoke('monitor_update_label', { ip: ip, label: newLabel }).then(function () {
return invoke('monitor_get_entries');
}).then(function (updated) {
entries = {};
updated.forEach(function (e) { entries[e.ip] = e; });
render();
});
}
input.addEventListener('blur', save);
input.addEventListener('keydown', function (ev) {
if (ev.key === 'Enter') save();
if (ev.key === 'Escape')
editingLabel = false;
render();
});
});
setupListeners(); setupListeners();
} }
@@ -1196,6 +1303,13 @@ window.addEventListener('DOMContentLoaded', () => {
updater.init(); updater.init();
changelog.init(); changelog.init();
theme.init(); theme.init();
// Always on top toggle
document.getElementById('chk-always-on-top').addEventListener('change', function (e) {
if (window.__TAURI__ && window.__TAURI__.window) {
var current = window.__TAURI__.window.getCurrentWindow();
current.setAlwaysOnTop(e.target.checked);
}
});
document.getElementById('btn-export-all').addEventListener('click', exporter.csvAll); document.getElementById('btn-export-all').addEventListener('click', exporter.csvAll);
document.getElementById('btn-export-up').addEventListener('click', exporter.csvUp); document.getElementById('btn-export-up').addEventListener('click', exporter.csvUp);
console.log('[Argus] Ready'); console.log('[Argus] Ready');
+200 -3
View File
@@ -1146,17 +1146,17 @@ body {
text-shadow: 0 0 6px var(--status-up-glow); text-shadow: 0 0 6px var(--status-up-glow);
} }
.monitor-status-Down { #monitor-table .monitor-status-Down {
color: var(--status-down); color: var(--status-down);
text-shadow: 0 0 6px var(--status-down-glow); text-shadow: 0 0 6px var(--status-down-glow);
} }
.monitor-status-Degraded { #monitor-table .monitor-status-Degraded {
color: var(--status-filtered); color: var(--status-filtered);
text-shadow: 0 0 6px var(--status-filtered-glow); text-shadow: 0 0 6px var(--status-filtered-glow);
} }
.monitor-status-Unknown { #monitor-table .monitor-status-Unknown {
color: var(--status-unknown); color: var(--status-unknown);
} }
@@ -1478,6 +1478,174 @@ body {
--danger-hover: #c62825; --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: #cdd6f4;
--text-secondary: #a6adc8;
--text-muted: #6c7086;
--cyan: #89b4fa;
--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="latte"] .port-tag {
background: rgba(30, 102, 245, 0.1);
border-color: rgba(30, 102, 245, 0.2);
}
:root[data-theme="latte"] #privilege-status::before {
background: var(--status-up);
box-shadow: 0 0 4px rgba(64, 160, 43, 0.3);
}
:root[data-theme="latte"] ::-webkit-scrollbar-track {
background: var(--bg-primary);
}
:root[data-theme="latte"] ::-webkit-scrollbar-thumb {
background: var(--border-bright);
}
/* ===== 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.4) 0px,
rgba(0, 0, 0, 0.4) 1px,
transparent 1px,
transparent 4px),
linear-gradient(180deg,
transparent 0%,
rgba(0, 255, 65, 0.03) 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 ===== */ /* ===== Circuit art theme tinting ===== */
:root[data-theme="dracula"] .toolbar-circuit { :root[data-theme="dracula"] .toolbar-circuit {
@@ -1494,4 +1662,33 @@ body {
:root[data-theme="midnight"] .toolbar-circuit { :root[data-theme="midnight"] .toolbar-circuit {
filter: brightness(0.8); 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);
} }