diff --git a/site/dashboard.html b/site/dashboard.html
index ffc6e0d..c78c48f 100644
--- a/site/dashboard.html
+++ b/site/dashboard.html
@@ -288,6 +288,7 @@ body {
.path-tag.SERVFAIL { background: rgba(181, 68, 58, 0.12); color: var(--rose); }
.path-tag.BLOCKED { background: rgba(163, 152, 136, 0.15); color: var(--text-dim); }
.path-tag.COALESCED { background: rgba(138, 104, 158, 0.12); color: var(--violet-dim); }
+.src-tag { font-size: 0.6rem; color: var(--text-dim); letter-spacing: 0.02em; }
/* Sidebar panels */
.sidebar {
@@ -787,6 +788,13 @@ function formatTime(epoch) {
return d.toLocaleTimeString([], { hour12: false });
}
+function shortSrc(addr) {
+ if (!addr) return '';
+ const ip = addr.replace(/:\d+$/, '');
+ if (ip === '127.0.0.1' || ip === '::1') return 'localhost';
+ return ip;
+}
+
function formatRemaining(secs) {
if (secs == null) return 'permanent';
if (secs < 60) return `${secs}s left`;
@@ -912,8 +920,8 @@ function applyLogFilter() {
? ` `
: '';
return `
-
- | ${formatTime(e.timestamp_epoch)} |
+
+ ${formatTime(e.timestamp_epoch)} ${shortSrc(e.src)} |
${e.query_type} |
${e.domain}${allowBtn} |
${e.path} |
diff --git a/src/main.rs b/src/main.rs
index ebe4aa9..70bc3f9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -94,7 +94,21 @@ async fn main() -> numa::Result<()> {
eprintln!("Config path defaults to numa.toml");
return Ok(());
}
- _ => {}
+ _ => {
+ if !arg1.is_empty()
+ && arg1 != "run"
+ && !arg1.contains('/')
+ && !arg1.contains('\\')
+ && !arg1.ends_with(".toml")
+ {
+ eprintln!(
+ "\x1b[1;38;2;192;98;58mNuma\x1b[0m — unknown command: \x1b[1m{}\x1b[0m\n",
+ arg1
+ );
+ eprintln!("Run \x1b[1mnuma help\x1b[0m for a list of commands.");
+ std::process::exit(1);
+ }
+ }
}
let config_path = if arg1.is_empty() || arg1 == "run" {
diff --git a/src/setup_phone.rs b/src/setup_phone.rs
index 7f9b02a..fd37c84 100644
--- a/src/setup_phone.rs
+++ b/src/setup_phone.rs
@@ -48,6 +48,31 @@ pub async fn run() -> Result<(), String> {
let lan_ip = crate::lan::detect_lan_ip()
.ok_or("could not detect LAN IP — are you connected to a network?")?;
+ let addr = std::net::SocketAddr::from(([127, 0, 0, 1], SETUP_PORT));
+ let api_reachable = tokio::time::timeout(
+ std::time::Duration::from_millis(500),
+ tokio::net::TcpStream::connect(addr),
+ )
+ .await
+ .map(|r| r.is_ok())
+ .unwrap_or(false);
+
+ if !api_reachable {
+ eprintln!();
+ eprintln!(
+ " \x1b[1;38;2;192;98;58mNuma\x1b[0m — mobile API is not reachable on port {}.",
+ SETUP_PORT
+ );
+ eprintln!();
+ eprintln!(" The phone won't be able to download the profile until the mobile");
+ eprintln!(" API is running. Add this to your numa.toml and restart Numa:");
+ eprintln!();
+ eprintln!(" [mobile]");
+ eprintln!(" enabled = true");
+ eprintln!();
+ return Err("mobile API not running".into());
+ }
+
let url = format!("http://{}:{}/mobileconfig", lan_ip, SETUP_PORT);
let qr = render_qr(&url)?;