Initial Commit

This commit is contained in:
2026-05-20 11:52:18 -04:00
commit ed0f93036d
703 changed files with 81299 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
'use strict';
const { Router } = require('express');
const fs = require('fs');
const path = require('path');
const router = Router();
/** Return available drive letters on Windows. */
function getWindowsRoots() {
const roots = [];
for (let code = 65; code <= 90; code++) {
const drive = `${String.fromCharCode(code)}:\\`;
try { fs.accessSync(drive); roots.push({ name: drive, path: drive }); } catch { /* skip */ }
}
return roots;
}
router.get('/', (req, res) => {
const reqPath = req.query.path ? String(req.query.path) : null;
// No path → return filesystem roots
if (!reqPath) {
const isWindows = process.platform === 'win32';
const dirs = isWindows ? getWindowsRoots() : [{ name: '/', path: '/' }];
return res.json({ path: null, parent: null, dirs });
}
// Validate: must exist and be a directory
let stat;
try { stat = fs.statSync(reqPath); }
catch { return res.status(400).json({ error: 'Path does not exist' }); }
if (!stat.isDirectory()) {
return res.status(400).json({ error: 'Path is not a directory' });
}
// List subdirectories, excluding hidden entries
let dirs;
try {
dirs = fs.readdirSync(reqPath, { withFileTypes: true })
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
.map(e => ({ name: e.name, path: path.join(reqPath, e.name) }))
.sort((a, b) => a.name.localeCompare(b.name));
} catch {
return res.status(403).json({ error: 'Cannot read directory' });
}
// Compute parent; at a drive root on Windows dirname === reqPath
const parent = path.dirname(reqPath);
const atRoot = parent === reqPath;
res.json({ path: reqPath, parent: atRoot ? null : parent, dirs });
});
module.exports = router;
+61
View File
@@ -0,0 +1,61 @@
'use strict';
const { Router } = require('express');
const { getConfig, setConfig, delConfig, getAllTests } = require('../db');
const { fullScan } = require('../scanner');
const { startWatching } = require('../watcher');
const router = Router();
const ALLOWED_KEYS = new Set([
'target_dir', 'results_dir',
'avg_time_coe', 'avg_time_p2p', 'avg_time_p3p',
]);
router.get('/', (req, res) => {
const config = {};
for (const key of ALLOWED_KEYS) {
config[key] = getConfig(key);
}
res.json(config);
});
router.post('/', async (req, res) => {
const updates = req.body;
if (!updates || typeof updates !== 'object') {
return res.status(400).json({ error: 'Request body must be a JSON object' });
}
let dirsChanged = false;
for (const [key, value] of Object.entries(updates)) {
if (!ALLOWED_KEYS.has(key)) continue;
if (value === null || value === '') {
delConfig(key);
} else {
setConfig(key, String(value));
}
if (key === 'target_dir' || key === 'results_dir') dirsChanged = true;
}
if (dirsChanged) {
const targetDir = getConfig('target_dir');
const resultsDir = getConfig('results_dir');
try {
await fullScan(targetDir, resultsDir);
} catch (e) {
console.error('[config] fullScan error:', e.message);
return res.status(500).json({ error: `Scan failed: ${e.message}` });
}
startWatching(targetDir, resultsDir);
const tests = getAllTests();
const completed = tests.filter(t => t.completed).length;
console.log(`[config] Scan complete — ${tests.length} tests found, ${completed} completed`);
return res.json({ ok: true, testCount: tests.length, completedCount: completed });
}
res.json({ ok: true, testCount: null, completedCount: null });
});
module.exports = router;
+11
View File
@@ -0,0 +1,11 @@
'use strict';
const { Router } = require('express');
const { addClient } = require('../sse');
const router = Router();
router.get('/', (req, res) => {
addClient(res);
});
module.exports = router;
+99
View File
@@ -0,0 +1,99 @@
'use strict';
const { Router } = require('express');
const { getAllTests, getConfig } = require('../db');
const router = Router();
router.get('/', (req, res) => {
const tests = getAllTests();
// ── Overall ───────────────────────────────────────────────────────────────
const totalCompleted = tests.filter(t => t.completed).length;
const overall = {
total: tests.length,
completed: totalCompleted,
completionRate: tests.length > 0 ? totalCompleted / tests.length : 0,
};
// ── Per device ────────────────────────────────────────────────────────────
const deviceMap = new Map();
for (const t of tests) {
if (!t.device) continue;
if (!deviceMap.has(t.device)) deviceMap.set(t.device, { total: 0, completed: 0 });
const d = deviceMap.get(t.device);
d.total++;
if (t.completed) d.completed++;
}
const devices = Array.from(deviceMap.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, d]) => ({
name,
total: d.total,
completed: d.completed,
completionRate: d.total > 0 ? d.completed / d.total : 0,
}));
// ── Elapsed = sum of individual test durations ────────────────────────────
const elapsedSeconds = tests.reduce((sum, t) => {
return t.completed && t.duration_seconds != null ? sum + t.duration_seconds : sum;
}, 0);
// ── Per-type averages and remaining counts ────────────────────────────────
const TYPES = ['COE', 'P2P', 'P3P'];
const byType = {};
let estimatedRemainingSeconds = 0;
let estimatePossible = true;
for (const type of TYPES) {
const typeTests = tests.filter(t => t.interference === type);
const completed = typeTests.filter(t => t.completed);
const withDuration = completed.filter(t => t.duration_seconds != null);
const remaining = typeTests.length - completed.length;
const calcAvg = withDuration.length > 0
? withDuration.reduce((s, t) => s + t.duration_seconds, 0) / withDuration.length
: null;
const manualValue = getConfig(`avg_time_${type.toLowerCase()}`);
let avgSeconds = null;
let avgSource = null;
if (manualValue !== null) {
avgSeconds = parseFloat(manualValue);
avgSource = calcAvg !== null ? 'manual_override' : 'manual';
} else if (calcAvg !== null) {
avgSeconds = calcAvg;
avgSource = 'calculated';
}
byType[type] = {
total: typeTests.length,
completed: completed.length,
remaining,
avgSeconds,
avgSource,
};
if (remaining > 0) {
if (avgSeconds !== null) {
estimatedRemainingSeconds += avgSeconds * remaining;
} else {
estimatePossible = false;
}
}
}
res.json({
overall,
devices,
timing: {
elapsedSeconds,
estimatedRemainingSeconds: estimatePossible ? estimatedRemainingSeconds : null,
byType,
},
});
});
module.exports = router;
+36
View File
@@ -0,0 +1,36 @@
'use strict';
const { Router } = require('express');
const { getAllTests } = require('../db');
const router = Router();
router.get('/', (req, res) => {
const { completed, interference, device, rotation, testPoint,
station, band, channel, bandwidth, rssi, direction } = req.query;
let tests = getAllTests();
if (completed !== undefined)
tests = tests.filter(t => t.completed === (completed === 'true' ? 1 : 0));
if (interference) tests = tests.filter(t => t.interference === interference);
if (device) tests = tests.filter(t => t.device === device);
if (rotation) tests = tests.filter(t => t.rotation === rotation);
if (testPoint) tests = tests.filter(t => t.test_point === testPoint);
if (station) tests = tests.filter(t => t.station === station);
if (band) tests = tests.filter(t => t.band === band);
if (channel) tests = tests.filter(t => t.channel === channel);
if (bandwidth) tests = tests.filter(t => t.bandwidth === bandwidth);
if (rssi) tests = tests.filter(t => t.rssi === rssi);
if (direction) tests = tests.filter(t => t.direction === direction);
tests.sort((a, b) => {
const ia = a.interference || '';
const ib = b.interference || '';
return ia.localeCompare(ib) || (a.file_id || '').localeCompare(b.file_id || '');
});
res.json(tests);
});
module.exports = router;