@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 +123 -8
- package/dist/index.js +1 -1
- package/dist/ui/observability.html +7 -7
- package/package.json +1 -1
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
|
|
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
|
|
17683
|
-
latencyMs
|
|
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) ?? {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|