Closes #120. The Tower and band changes panel listed every handover, newest first, growing without bound. On a link that hands over several times a day it reads as a wall of near-identical rows — it answers what happened, but not what the pattern is, which is the question an operator actually has. Dwell totals now sit above it: how long the modem has spent on each band and on each cell, over 24 hours, 7 days or 30 days, ordered by time spent so the answer is the first row rather than something to be worked out. Visit counts sit alongside, because six brief stays and one long one are different situations that share a total. No new collection. History is already written on every change, so dwell is the interval between consecutive events — the same reconstruction used for WiFi roaming sessions. Three decisions worth recording. **The state in force when the window opened is included.** Without it, a link that changed once yesterday would report a few minutes of dwell across seven days, having silently dropped everything before the first change in range. The last event *before* the window is fetched specifically to anchor that first stretch. **Aggregated carriers each count for the whole stretch.** A period on B1 and B3 together credits both with the full duration, so band shares describe how much of the window each band was in use rather than dividing it between them. Totals can exceed 100%, which is the honest description of carrier aggregation rather than a rounding artefact. **Absence is reported as absence.** A segment whose cell was never reported is excluded rather than attributed to "unknown", a device with no history reads as "no changes recorded" rather than an empty panel, and a window longer than our records says so instead of implying the quiet period was real. Logic lives in utils/lteDwell.ts with 17 tests. Verified end to end against seeded history matching the reporter's own pattern: a 24-hour window covered exactly 24 hours, B3 correctly showed 19 of them against a five-hour stretch without it, cell totals summed to the window, and a 30-day request over 9 days of records reported itself truncated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
63 lines
1.7 KiB
JSON
63 lines
1.7 KiB
JSON
{
|
|
"name": "mikrotik-manager-backend",
|
|
"version": "0.23.33-beta",
|
|
"description": "Mikrotik Network Management Platform - Backend API",
|
|
"main": "dist/index.js",
|
|
"scripts": {
|
|
"build": "tsc",
|
|
"start": "node dist/index.js",
|
|
"dev": "nodemon --watch src --ext ts --exec ts-node src/index.ts",
|
|
"lint": "eslint src",
|
|
"migrate": "ts-node src/db/migrate.ts",
|
|
"test": "jest",
|
|
"test:coverage": "jest --coverage"
|
|
},
|
|
"dependencies": {
|
|
"@influxdata/influxdb-client": "^1.33.2",
|
|
"@influxdata/influxdb-client-apis": "^1.35.0",
|
|
"bcryptjs": "^2.4.3",
|
|
"bullmq": "^5.81.2",
|
|
"compression": "^1.7.4",
|
|
"cors": "^2.8.5",
|
|
"dotenv": "^17.4.2",
|
|
"express": "^4.18.2",
|
|
"express-async-errors": "^3.1.1",
|
|
"helmet": "^8.3.0",
|
|
"ioredis": "^5.10.1",
|
|
"jsonwebtoken": "^9.0.2",
|
|
"morgan": "^1.10.0",
|
|
"nodemailer": "^9.0.3",
|
|
"openid-client": "^5.7.1",
|
|
"otpauth": "^9.5.1",
|
|
"pg": "^8.22.0",
|
|
"qrcode": "^1.5.4",
|
|
"socket.io": "^4.6.1",
|
|
"ssh2": "^1.14.0"
|
|
},
|
|
"devDependencies": {
|
|
"@eslint/js": "^10.0.1",
|
|
"@types/bcryptjs": "^2.4.6",
|
|
"@types/compression": "^1.7.5",
|
|
"@types/cors": "^2.8.17",
|
|
"@types/express": "^4.17.21",
|
|
"@types/jest": "^30.0.0",
|
|
"@types/jsonwebtoken": "^9.0.5",
|
|
"@types/morgan": "^1.9.9",
|
|
"@types/node": "^26.1.1",
|
|
"@types/nodemailer": "^6.4.14",
|
|
"@types/pg": "^8.10.9",
|
|
"@types/qrcode": "^1.5.6",
|
|
"@types/ssh2": "^1.11.17",
|
|
"@types/supertest": "^7.2.1",
|
|
"eslint": "^10.7.0",
|
|
"eslint-plugin-security": "^4.0.1",
|
|
"jest": "^30.4.2",
|
|
"nodemon": "^3.0.2",
|
|
"supertest": "^7.2.2",
|
|
"ts-jest": "^29.4.9",
|
|
"ts-node": "^10.9.2",
|
|
"typescript": "^6.0.3",
|
|
"typescript-eslint": "^8.64.0"
|
|
}
|
|
}
|