From 19e2d914f5eccbea265a54431f60f9eca7d926ee Mon Sep 17 00:00:00 2001
From: 2GT-Rich <107219997+2GT-Rich@users.noreply.github.com>
Date: Wed, 2 Sep 2026 13:31:52 -0700
Subject: [PATCH] fix(lte): data-cap usage was never counted (v0.23.26-beta)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Reported from the field: manual SMS send works, but no traffic is accumulated.
Correct on both counts, and the cause is a single wrong call shape made invisible
by a swallowed error.
The interface filter was passed as a parameter rather than a RouterOS query
word, so the request went out as `=?name=lte1` and the device answered "unknown
parameter ?name". That threw on every poll, a `.catch(() => [])` turned it into
an empty result, and the empty result made the tracker return early — every
time, in silence. The rule looked configured, the usage bar sat at zero, and
nothing anywhere said why.
Proved against hardware rather than reasoned about:
as-shipped -> THREW: unknown parameter ?name
query-word -> rows: 1 ether1 rx=4997765728
The filter now goes through the client's query-word argument, falls back to an
unfiltered read if a filtered one comes back empty, and — the part that matters
more — logs instead of discarding. A counter read that fails, or an interface
that is not in the result, now says so. The tracker also warns when it has no
counters, because a rule that exists and never counts is precisely the failure
the user experiences.
Accumulation verified end to end on real hardware afterwards: first poll
establishes a baseline and counts nothing, second and third accumulate 118 KB
then 144 KB, with the period keyed to the configured local date.
Worth recording why this shipped. The pure accounting logic has eighteen tests
and all of them pass — reboot handling, midnight rollover across zones, margin,
cooldown. None of it ran, because the one integration point those tests cannot
reach was wrong. Verifying the API paths and the configuration flow, as was done
before release, exercised everything except the thing the feature is for.
Co-Authored-By: Claude Opus 5
-
+
diff --git a/backend/package.json b/backend/package.json
index be0c8f4..e349ba8 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "mikrotik-manager-backend",
- "version": "0.23.25-beta",
+ "version": "0.23.26-beta",
"description": "Mikrotik Network Management Platform - Backend API",
"main": "dist/index.js",
"scripts": {
diff --git a/backend/src/services/mikrotik/DeviceCollector.ts b/backend/src/services/mikrotik/DeviceCollector.ts
index 0192bdf..eed8ab4 100644
--- a/backend/src/services/mikrotik/DeviceCollector.ts
+++ b/backend/src/services/mikrotik/DeviceCollector.ts
@@ -2865,13 +2865,36 @@ export class DeviceCollector {
});
}
- /** Current byte counters for one interface, for data-cap accounting. */
+ /**
+ * Current byte counters for one interface, for data-cap accounting.
+ *
+ * The filter is a RouterOS *query word* and has to be passed as one. Smuggled
+ * in as a parameter it becomes `=?name=lte1`, which the device rejects with
+ * "unknown parameter ?name" — and because that error was being swallowed, the
+ * data-cap counter silently read nothing on every poll while the rest of the
+ * feature looked healthy. Errors here are logged rather than discarded for
+ * exactly that reason.
+ */
async getInterfaceCounters(name: string): Promise<{ rxBytes: number; txBytes: number } | null> {
- const rows = await this.client
- .execute('/interface/print', { stats: '', '?name': name })
- .catch(() => [] as Record