a67815c61a
- Updated `parseFilename` to `parseTargetFilename` and modified its return structure to include `test_id` instead of `file_id`. - Introduced `parseResultFilename` to extract `test_id` and `device` from result file names. - Enhanced `fullScan` to separately handle target and results directories, improving clarity and functionality. - Updated database interactions to use `test_id` instead of `file_id` across various modules. - Added a new `/rescan` endpoint to trigger a full scan of target and results directories. - Improved logging and error handling throughout the scanning process. - Introduced `parseTputRssi` to extract throughput and RSSI data from log files.
148 lines
5.7 KiB
JavaScript
148 lines
5.7 KiB
JavaScript
'use strict';
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const Database = require('better-sqlite3');
|
|
|
|
const DB_PATH = path.join(__dirname, 'dashboard.db');
|
|
const CONFIG_JSON = path.join(__dirname, 'config.json');
|
|
|
|
const db = new Database(DB_PATH);
|
|
|
|
// Enable WAL for better concurrent read performance
|
|
db.pragma('journal_mode = WAL');
|
|
|
|
// ── Schema ───────────────────────────────────────────────────────────────────
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS config (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tests (
|
|
id TEXT PRIMARY KEY,
|
|
test_id TEXT,
|
|
parent_dir TEXT,
|
|
filename TEXT,
|
|
interference TEXT,
|
|
device TEXT,
|
|
rotation TEXT,
|
|
test_point TEXT,
|
|
station TEXT,
|
|
band TEXT,
|
|
channel TEXT,
|
|
bandwidth TEXT,
|
|
rssi TEXT,
|
|
direction TEXT,
|
|
completed INTEGER NOT NULL DEFAULT 0,
|
|
completed_at TEXT,
|
|
duration_seconds REAL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tests_test_id ON tests (test_id);
|
|
CREATE INDEX IF NOT EXISTS idx_tests_device ON tests (device);
|
|
`);
|
|
|
|
// Add new columns to existing databases (idempotent)
|
|
for (const colDef of ['tput_mbps REAL', 'dl_rssi_dbm REAL', 'ul_rssi_dbm REAL', 'tput_results TEXT']) {
|
|
try { db.exec(`ALTER TABLE tests ADD COLUMN ${colDef}`); } catch { /* already exists */ }
|
|
}
|
|
|
|
// ── One-time migration from config.json ──────────────────────────────────────
|
|
{
|
|
const alreadyMigrated = db.prepare("SELECT COUNT(*) AS n FROM config").get().n > 0;
|
|
if (!alreadyMigrated && fs.existsSync(CONFIG_JSON)) {
|
|
try {
|
|
const legacy = JSON.parse(fs.readFileSync(CONFIG_JSON, 'utf8'));
|
|
const insert = db.prepare('INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)');
|
|
const migrate = db.transaction((obj) => {
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
if (v != null) insert.run(k, String(v));
|
|
}
|
|
});
|
|
migrate(legacy);
|
|
console.log('[db] Migrated config.json → SQLite');
|
|
} catch (e) {
|
|
console.warn('[db] Could not migrate config.json:', e.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Config ───────────────────────────────────────────────────────────────────
|
|
const _stmtGetConfig = db.prepare('SELECT value FROM config WHERE key = ?');
|
|
const _stmtSetConfig = db.prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)');
|
|
const _stmtDelConfig = db.prepare('DELETE FROM config WHERE key = ?');
|
|
|
|
function getConfig(key) { return _stmtGetConfig.get(key)?.value ?? null; }
|
|
function setConfig(key, value) { _stmtSetConfig.run(key, value); }
|
|
function delConfig(key) { _stmtDelConfig.run(key); }
|
|
|
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
const _stmtUpsertTest = db.prepare(`
|
|
INSERT INTO tests
|
|
(id, test_id, parent_dir, filename, interference, device, rotation,
|
|
test_point, station, band, channel, bandwidth, rssi, direction)
|
|
VALUES
|
|
(@id, @test_id, @parent_dir, @filename, @interference, @device, @rotation,
|
|
@test_point, @station, @band, @channel, @bandwidth, @rssi, @direction)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
test_id = excluded.test_id,
|
|
parent_dir = excluded.parent_dir,
|
|
filename = excluded.filename,
|
|
interference = excluded.interference,
|
|
device = excluded.device,
|
|
rotation = excluded.rotation,
|
|
test_point = excluded.test_point,
|
|
station = excluded.station,
|
|
band = excluded.band,
|
|
channel = excluded.channel,
|
|
bandwidth = excluded.bandwidth,
|
|
rssi = excluded.rssi,
|
|
direction = excluded.direction
|
|
-- completed / completed_at / duration_seconds intentionally preserved
|
|
`);
|
|
|
|
const _stmtMarkCompleted = db.prepare(`
|
|
UPDATE tests
|
|
SET completed = 1, completed_at = ?, duration_seconds = ?, tput_results = ?
|
|
WHERE test_id = ? AND (? IS NULL OR device = ?)
|
|
`);
|
|
|
|
const _stmtResetTest = db.prepare(`
|
|
UPDATE tests
|
|
SET completed = 0, completed_at = NULL, duration_seconds = NULL, tput_results = NULL
|
|
WHERE test_id = ? AND (? IS NULL OR device = ?)
|
|
`);
|
|
|
|
const _stmtGetStation = db.prepare('SELECT station FROM tests WHERE test_id = ? AND device = ? LIMIT 1');
|
|
|
|
const _stmtClearTests = db.prepare('DELETE FROM tests');
|
|
const _stmtGetAllTests = db.prepare('SELECT * FROM tests');
|
|
const _stmtCountTests = db.prepare('SELECT COUNT(*) AS n FROM tests');
|
|
|
|
function upsertTest(test) {
|
|
_stmtUpsertTest.run(test);
|
|
}
|
|
|
|
function markCompleted(test_id, device, completed_at, duration_seconds, tputResults = null) {
|
|
const json = tputResults && tputResults.length > 0 ? JSON.stringify(tputResults) : null;
|
|
_stmtMarkCompleted.run(completed_at, duration_seconds, json, test_id, device, device);
|
|
}
|
|
|
|
function getStationForTest(test_id, device) {
|
|
return _stmtGetStation.get(test_id, device)?.station ?? null;
|
|
}
|
|
|
|
function resetByFileIdAndDevice(test_id, device) {
|
|
_stmtResetTest.run(test_id, device, device);
|
|
}
|
|
|
|
function clearTests() { _stmtClearTests.run(); }
|
|
function getAllTests() { return _stmtGetAllTests.all(); }
|
|
function countTests() { return _stmtCountTests.get().n; }
|
|
|
|
module.exports = {
|
|
getConfig, setConfig, delConfig,
|
|
upsertTest, markCompleted, resetByFileIdAndDevice, clearTests, getAllTests, countTests,
|
|
getStationForTest,
|
|
};
|