fix: setup-phone probe, unknown command error, query source in dashboard
- setup-phone now probes the mobile API before printing the QR code and shows an actionable error if [mobile] is not enabled - Unknown CLI subcommands print an error instead of silently attempting to start a full server - Dashboard query log shows source IP under timestamp (localhost for loopback, full IP for LAN devices) with full addr on hover Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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() {
|
||||
? ` <button class="btn-delete" onclick="allowDomain('${e.domain}')" title="Allow this domain" style="color:var(--emerald);font-size:0.65rem;">allow</button>`
|
||||
: '';
|
||||
return `
|
||||
<tr>
|
||||
<td>${formatTime(e.timestamp_epoch)}</td>
|
||||
<tr title="Source: ${e.src || 'unknown'}">
|
||||
<td>${formatTime(e.timestamp_epoch)}<br><span class="src-tag">${shortSrc(e.src)}</span></td>
|
||||
<td>${e.query_type}</td>
|
||||
<td class="domain-cell" title="${e.domain}">${e.domain}${allowBtn}</td>
|
||||
<td><span class="path-tag ${e.path}">${e.path}</span></td>
|
||||
|
||||
16
src/main.rs
16
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" {
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user