Full health check: fix startup crash, add subnet input to network discovery
Server fixes: - database.js: auto-create data/ directory on startup — was crashing on fresh clone because the gitignored directory didn't exist - better-sqlite3 upgraded to latest (13.x) for Node 26 compatibility Network discovery: - discoverNetwork() accepts optional subnet param; scans that range instead of auto-detecting when provided - New GET /api/network-subnets endpoint returns detected local interfaces so UI can offer quick-select buttons - Discovery modal: subnet text input + Scan button + auto-detect quick-select; local subnet buttons populate once the modal is open - server/index.js: pass subnet from request body to discoverNetwork() Infra: - React client runs on PORT=3456 in dev to avoid port conflicts - .claude/launch.json added for one-command preview via Express (serves the pre-built client, no port conflict with the preview tool) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
59078362da
commit
f119168399
14 files changed
+119
-427
No files matched your search
+5
-1
@@ -1,7 +1,11 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const db = new Database(path.join(__dirname, '../data/monitor.db'));
|
||||
const dataDir = path.join(__dirname, '../data');
|
||||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
||||
|
||||
const db = new Database(path.join(dataDir, 'monitor.db'));
|
||||
|
||||
// Create tables
|
||||
db.exec(`
|
||||
|
||||
+12
-2
@@ -13,7 +13,7 @@ const notificationService = require('./notificationService');
|
||||
const cache = require('./cache');
|
||||
const security = require('./securityMiddleware');
|
||||
const metrics = require('./metrics');
|
||||
const { discoverNetwork } = require('./networkDiscovery');
|
||||
const { discoverNetwork, getLocalSubnets } = require('./networkDiscovery');
|
||||
const auditLog = require('./auditLog');
|
||||
|
||||
// Initialize notifications table if it doesn't exist
|
||||
@@ -1541,7 +1541,8 @@ app.post('/api/test-ntfy', async (req, res) => {
|
||||
// Network discovery — scan local network for monitorable hosts
|
||||
app.post('/api/network-discovery', async (req, res) => {
|
||||
try {
|
||||
const hosts = await discoverNetwork();
|
||||
const { subnet } = req.body || {};
|
||||
const hosts = await discoverNetwork(subnet || null);
|
||||
res.json({ hosts });
|
||||
} catch (error) {
|
||||
console.error('Network discovery error:', error);
|
||||
@@ -1549,6 +1550,15 @@ app.post('/api/network-discovery', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Return detected local subnets so the UI can offer them as quick-select options
|
||||
app.get('/api/network-subnets', (req, res) => {
|
||||
try {
|
||||
res.json({ subnets: getLocalSubnets() });
|
||||
} catch (e) {
|
||||
res.json({ subnets: [] });
|
||||
}
|
||||
});
|
||||
|
||||
// Clear cache endpoint for debugging
|
||||
app.post('/api/cache/clear', (req, res) => {
|
||||
cache.clear();
|
||||
|
||||
@@ -101,20 +101,29 @@ async function getArpHosts() {
|
||||
});
|
||||
}
|
||||
|
||||
async function discoverNetwork() {
|
||||
const subnets = getLocalSubnets();
|
||||
async function discoverNetwork(customSubnet = null) {
|
||||
const arpHosts = await getArpHosts();
|
||||
const arpIps = new Set(arpHosts.map(h => h.ip));
|
||||
|
||||
// Collect all IPs to scan: ARP table + subnet range
|
||||
const allIps = new Map();
|
||||
for (const h of arpHosts) allIps.set(h.ip, { ip: h.ip, hostname: h.hostname });
|
||||
|
||||
for (const subnet of subnets) {
|
||||
if (!subnet.prefix) continue;
|
||||
const range = subnetToRange(subnet.prefix);
|
||||
for (const ip of range) {
|
||||
if (!allIps.has(ip)) allIps.set(ip, { ip, hostname: null });
|
||||
if (customSubnet) {
|
||||
// Scan the user-specified subnet only (no ARP seeding, still probe all IPs in range)
|
||||
const range = subnetToRange(customSubnet);
|
||||
for (const ip of range) allIps.set(ip, { ip, hostname: null });
|
||||
// Supplement with ARP hostnames for any IPs that happen to be in the ARP table
|
||||
for (const h of arpHosts) {
|
||||
if (allIps.has(h.ip) && h.hostname) allIps.get(h.ip).hostname = h.hostname;
|
||||
}
|
||||
} else {
|
||||
// Auto-detect: ARP table + all local subnets
|
||||
for (const h of arpHosts) allIps.set(h.ip, { ip: h.ip, hostname: h.hostname });
|
||||
for (const subnet of getLocalSubnets()) {
|
||||
if (!subnet.prefix) continue;
|
||||
const range = subnetToRange(subnet.prefix);
|
||||
for (const ip of range) {
|
||||
if (!allIps.has(ip)) allIps.set(ip, { ip, hostname: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user