@usex/mikrotik-mcp 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -17641,13 +17641,58 @@ import { join as join4 } from "path";
17641
17641
  var {serve } = globalThis.Bun;
17642
17642
 
17643
17643
  // src/observability/health.ts
17644
+ var HISTORY_CAP = 60;
17644
17645
  var statuses = new Map;
17646
+ var histories = new Map;
17645
17647
  var timer = null;
17646
17648
  var inFlight = false;
17647
17649
  var UNKNOWN = { reachable: null, checkedAt: null, latencyMs: null };
17648
17650
  function getDeviceStatus(name) {
17649
17651
  return statuses.get(name) ?? UNKNOWN;
17650
17652
  }
17653
+ function getDeviceHistory(name) {
17654
+ return histories.get(name) ?? [];
17655
+ }
17656
+ function pushHistory(name, sample) {
17657
+ const arr = histories.get(name) ?? [];
17658
+ arr.push(sample);
17659
+ if (arr.length > HISTORY_CAP)
17660
+ arr.splice(0, arr.length - HISTORY_CAP);
17661
+ histories.set(name, arr);
17662
+ }
17663
+ function parseSize(s) {
17664
+ if (!s)
17665
+ return;
17666
+ const m = s.trim().match(/^([\d.]+)\s*([KMGT]i?B|B)?$/i);
17667
+ if (!m)
17668
+ return;
17669
+ const val = Number.parseFloat(m[1]);
17670
+ if (!Number.isFinite(val))
17671
+ return;
17672
+ const mult = {
17673
+ B: 1,
17674
+ KIB: 1024,
17675
+ MIB: 1024 ** 2,
17676
+ GIB: 1024 ** 3,
17677
+ TIB: 1024 ** 4,
17678
+ KB: 1000,
17679
+ MB: 1e6,
17680
+ GB: 1e9,
17681
+ TB: 1000000000000
17682
+ };
17683
+ return val * (mult[(m[2] ?? "B").toUpperCase()] ?? 1);
17684
+ }
17685
+ function parsePercent(s) {
17686
+ if (!s)
17687
+ return;
17688
+ const m = s.trim().match(/^([\d.]+)\s*%?$/);
17689
+ return m ? Number.parseFloat(m[1]) : undefined;
17690
+ }
17691
+ function usedPct(total, free) {
17692
+ if (total == null || free == null || total <= 0)
17693
+ return;
17694
+ return Math.max(0, Math.min(100, (total - free) / total * 100));
17695
+ }
17651
17696
  async function probeDevice(name, dc) {
17652
17697
  if (dc.mac) {
17653
17698
  const status2 = {
@@ -17676,14 +17721,42 @@ async function probeDevice(name, dc) {
17676
17721
  };
17677
17722
  } else {
17678
17723
  const identity = parseKeyValues(await client.run("/system identity print")).name;
17679
- const version = parseKeyValues(await client.run("/system resource print")).version;
17724
+ const r = parseKeyValues(await client.run("/system resource print"));
17725
+ const checkedAt = Date.now();
17726
+ const latencyMs = checkedAt - t0;
17727
+ const totalMemory = parseSize(r["total-memory"]);
17728
+ const freeMemory = parseSize(r["free-memory"]);
17729
+ const totalHdd = parseSize(r["total-hdd-space"]);
17730
+ const freeHdd = parseSize(r["free-hdd-space"]);
17731
+ const cpuLoad = parsePercent(r["cpu-load"]);
17732
+ const memUsedPct = usedPct(totalMemory, freeMemory);
17733
+ const hddUsedPct = usedPct(totalHdd, freeHdd);
17734
+ const cpuCount = Number.parseInt(r["cpu-count"] ?? "", 10);
17680
17735
  status = {
17681
17736
  reachable: true,
17682
- checkedAt: Date.now(),
17683
- latencyMs: Date.now() - t0,
17737
+ checkedAt,
17738
+ latencyMs,
17684
17739
  identity,
17685
- version
17740
+ version: r.version,
17741
+ boardName: r["board-name"] || undefined,
17742
+ architecture: r["architecture-name"] || undefined,
17743
+ cpuCount: Number.isFinite(cpuCount) ? cpuCount : undefined,
17744
+ cpuLoad,
17745
+ freeMemory,
17746
+ totalMemory,
17747
+ memUsedPct,
17748
+ freeHdd,
17749
+ totalHdd,
17750
+ hddUsedPct,
17751
+ uptime: r.uptime || undefined
17686
17752
  };
17753
+ pushHistory(name, {
17754
+ ts: checkedAt,
17755
+ cpuLoad: cpuLoad ?? null,
17756
+ memUsedPct: memUsedPct ?? null,
17757
+ hddUsedPct: hddUsedPct ?? null,
17758
+ latencyMs
17759
+ });
17687
17760
  }
17688
17761
  } catch (e) {
17689
17762
  status = {
@@ -17842,6 +17915,28 @@ class SqliteEventStore {
17842
17915
  const r = this.db.query("SELECT COUNT(*) AS n FROM events").get();
17843
17916
  return r.n;
17844
17917
  }
17918
+ delete(ids) {
17919
+ if (ids.length === 0)
17920
+ return 0;
17921
+ let removed = 0;
17922
+ const CHUNK = 500;
17923
+ for (let i = 0;i < ids.length; i += CHUNK) {
17924
+ const chunk = ids.slice(i, i + CHUNK);
17925
+ const placeholders = chunk.map((_, j) => `$id${j}`).join(",");
17926
+ const params = {};
17927
+ chunk.forEach((id, j) => {
17928
+ params[`$id${j}`] = id;
17929
+ });
17930
+ const res = this.db.query(`DELETE FROM events WHERE id IN (${placeholders})`).run(params);
17931
+ removed += Number(res.changes ?? 0);
17932
+ }
17933
+ return removed;
17934
+ }
17935
+ clear() {
17936
+ const n = this.total();
17937
+ this.db.run("DELETE FROM events");
17938
+ return n;
17939
+ }
17845
17940
  prune(maxEvents2) {
17846
17941
  const n = this.total();
17847
17942
  if (n <= maxEvents2)
@@ -18013,7 +18108,12 @@ function deviceActivity(store2) {
18013
18108
  for (const e of recent) {
18014
18109
  if (!e.device)
18015
18110
  continue;
18016
- const a = map.get(e.device) ?? { calls: 0, errors: 0, lastSeen: 0, sumMs: 0 };
18111
+ const a = map.get(e.device) ?? {
18112
+ calls: 0,
18113
+ errors: 0,
18114
+ lastSeen: 0,
18115
+ sumMs: 0
18116
+ };
18017
18117
  a.calls++;
18018
18118
  if (e.isError)
18019
18119
  a.errors++;
@@ -18047,7 +18147,13 @@ function devicesPayload(store2) {
18047
18147
  isDefault: name === cfg.defaultDevice,
18048
18148
  description: dc.description,
18049
18149
  status: getDeviceStatus(name),
18050
- activity: activity.get(name) ?? { calls: 0, errors: 0, lastSeen: 0, avgMs: 0 }
18150
+ history: getDeviceHistory(name),
18151
+ activity: activity.get(name) ?? {
18152
+ calls: 0,
18153
+ errors: 0,
18154
+ lastSeen: 0,
18155
+ avgMs: 0
18156
+ }
18051
18157
  }));
18052
18158
  return { server: SERVER_TAG, defaultDevice: cfg.defaultDevice, devices };
18053
18159
  }
@@ -18122,7 +18228,7 @@ async function runDashboard(cfg, transportLabel) {
18122
18228
  hostname: cfg.host,
18123
18229
  port: cfg.port,
18124
18230
  idleTimeout: 0,
18125
- fetch(req, srv) {
18231
+ async fetch(req, srv) {
18126
18232
  const url = new URL(req.url);
18127
18233
  if (url.pathname === "/health")
18128
18234
  return new Response("OK");
@@ -18140,6 +18246,15 @@ async function runDashboard(cfg, transportLabel) {
18140
18246
  const db = getEventStore();
18141
18247
  if (!db)
18142
18248
  return json({ error: "recorder not active" }, 503);
18249
+ if (url.pathname === "/api/events" && req.method === "DELETE") {
18250
+ let body = {};
18251
+ try {
18252
+ body = await req.json();
18253
+ } catch {}
18254
+ const ids = Array.isArray(body.ids) ? body.ids.filter((x) => typeof x === "string") : [];
18255
+ const removed = body.all === true ? db.clear() : db.delete(ids);
18256
+ return json({ removed, total: db.total() });
18257
+ }
18143
18258
  if (url.pathname === "/api/devices") {
18144
18259
  return json(devicesPayload(db));
18145
18260
  }
@@ -18358,7 +18473,7 @@ function registerPrompts(server) {
18358
18473
  // package.json
18359
18474
  var package_default = {
18360
18475
  name: "@usex/mikrotik-mcp",
18361
- version: "2.3.0",
18476
+ version: "2.4.0",
18362
18477
  description: "Bun-native MCP server for MikroTik RouterOS \u2014 200+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
18363
18478
  keywords: [
18364
18479
  "ai",
package/dist/index.js CHANGED
@@ -17711,7 +17711,7 @@ var allToolModules = moduleCatalog.map((m) => m.tools);
17711
17711
  // package.json
17712
17712
  var package_default = {
17713
17713
  name: "@usex/mikrotik-mcp",
17714
- version: "2.3.0",
17714
+ version: "2.4.0",
17715
17715
  description: "Bun-native MCP server for MikroTik RouterOS \u2014 200+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
17716
17716
  keywords: [
17717
17717
  "ai",