3 Commits
6 changed files with 107 additions and 39 deletions
+6
View File
@@ -1,3 +1,9 @@
## [unreleased]
### 🐛 Bug Fixes
- Status Colors on Monitor tab now reflect status of host. Closes #7
- Removed unused function.
## [0.2.0] - 2026-09-24
### 🚀 Features
+16
View File
@@ -180,3 +180,19 @@ 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)
}
+2
View File
@@ -32,6 +32,8 @@ pub fn run() {
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");
-30
View File
@@ -1,5 +1,4 @@
use std::net::IpAddr;
use tokio::task::JoinSet;
/// Perform reverse DNS lookup on a single IP.
/// Returns the hostname if found, None otherwise.
@@ -19,32 +18,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
}
+60 -6
View File
@@ -609,7 +609,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);
@@ -675,7 +675,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]);
@@ -967,7 +968,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>'
@@ -994,6 +995,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; });
@@ -1048,9 +1059,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');
@@ -1085,6 +1107,38 @@ 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;
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();
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') render();
});
});
setupListeners();
}
+23 -3
View File
@@ -1146,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);
}
@@ -1495,3 +1495,23 @@ body {
: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;
}