diff --git a/README.md b/README.md index 932eed8..b86f0e8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

- Version + Version License Docker TypeScript 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[]); - const row = rows.find((r) => r['name'] === name) || rows[0]; - if (!row) return null; + let rows: Record[] | null = await this.client + .execute('/interface/print', { stats: '' }, [`?name=${name}`]) + .catch((err) => { + console.error(`[${this.device.name}] counter read for ${name} failed:`, (err as Error).message); + return null; + }); + + // Some versions answer a filtered print with nothing at all; falling back to + // the full list costs one call and removes a whole class of silent zero. + if (rows && rows.length === 0) { + rows = await this.client.execute('/interface/print', { stats: '' }).catch(() => null); + } + if (!rows) return null; + + const row = rows.find((r) => r['name'] === name); + if (!row) { + console.warn(`[${this.device.name}] interface ${name} not present in counter read`); + return null; + } return { rxBytes: parseInt(row['rx-byte'] || '0', 10), txBytes: parseInt(row['tx-byte'] || '0', 10), @@ -3034,7 +3057,12 @@ export class DeviceCollector { if (!rule) return; const counters = await this.getInterfaceCounters(interfaceName); - if (!counters) return; + if (!counters) { + // A rule that exists but never counts is the failure the user actually + // sees, so it must not be silent. + console.warn(`[DataCap] ${this.device.name}/${interfaceName}: no counters available; usage not updated`); + return; + } const now = new Date(); const key = periodKey(now, rule.reset_hour, rule.reset_minute, rule.timezone); diff --git a/frontend/package.json b/frontend/package.json index 387bb92..b84ede0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mikrotik-manager-frontend", - "version": "0.23.25-beta", + "version": "0.23.26-beta", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/version.ts b/frontend/src/version.ts index 28ccace..4b75adb 100644 --- a/frontend/src/version.ts +++ b/frontend/src/version.ts @@ -1 +1 @@ -export const APP_VERSION = 'v0.23.25 Beta'; +export const APP_VERSION = 'v0.23.26 Beta';