Initial Commit
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user