@usex/mikrotik-mcp 3.22.0 → 3.23.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 +41 -9
- package/dist/index.d.ts +15 -4
- package/dist/index.js +41 -9
- package/dist/ui/dashboard.html +1 -0
- package/dist/ui/firewall-audit.html +1 -0
- package/dist/ui/firewall.html +1 -0
- package/dist/ui/interfaces.html +1 -0
- package/dist/ui/records.html +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -239,13 +239,38 @@ function getConfig() {
|
|
|
239
239
|
function listDevices() {
|
|
240
240
|
return { names: Object.keys(active.devices), default: active.defaultDevice };
|
|
241
241
|
}
|
|
242
|
+
function deviceKeyForLabel(name) {
|
|
243
|
+
const target = name.trim().toLowerCase();
|
|
244
|
+
for (const [key, dc] of Object.entries(active.devices)) {
|
|
245
|
+
if (dc.description && dc.description.trim().toLowerCase() === target)
|
|
246
|
+
return key;
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
function deviceLabels() {
|
|
251
|
+
const seen = new Set;
|
|
252
|
+
const out = [];
|
|
253
|
+
for (const [key, dc] of Object.entries(active.devices)) {
|
|
254
|
+
const label = dc.description?.trim();
|
|
255
|
+
if (label && label !== key && !(label in active.devices) && !seen.has(label)) {
|
|
256
|
+
seen.add(label);
|
|
257
|
+
out.push(label);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
242
262
|
function resolveDeviceName(name) {
|
|
243
|
-
if (name
|
|
244
|
-
|
|
263
|
+
if (name) {
|
|
264
|
+
if (name in active.devices)
|
|
265
|
+
return name;
|
|
266
|
+
const byLabel = deviceKeyForLabel(name);
|
|
267
|
+
if (byLabel)
|
|
268
|
+
return byLabel;
|
|
269
|
+
}
|
|
245
270
|
return active.defaultDevice in active.devices ? active.defaultDevice : Object.keys(active.devices)[0] ?? active.defaultDevice;
|
|
246
271
|
}
|
|
247
272
|
function getDevice(name) {
|
|
248
|
-
if (name && !(name in active.devices)) {
|
|
273
|
+
if (name && !(name in active.devices) && !deviceKeyForLabel(name)) {
|
|
249
274
|
throw new Error(`Unknown device '${name}'. Configured devices: ${Object.keys(active.devices).join(", ")}`);
|
|
250
275
|
}
|
|
251
276
|
const key = resolveDeviceName(name);
|
|
@@ -1647,6 +1672,7 @@ function recordToolCall(raw) {
|
|
|
1647
1672
|
|
|
1648
1673
|
// src/core/registry.ts
|
|
1649
1674
|
var AUTO_RECORDS_VERB = /^(list|get|show|print)_/;
|
|
1675
|
+
var UI_OUTPUT_SCHEMA = z2.object({}).passthrough();
|
|
1650
1676
|
function effectiveUi(def) {
|
|
1651
1677
|
if (def.ui)
|
|
1652
1678
|
return { ui: def.ui, auto: false };
|
|
@@ -1687,12 +1713,13 @@ function defineTool(def) {
|
|
|
1687
1713
|
inputSchema: def.inputSchema,
|
|
1688
1714
|
ui: def.ui,
|
|
1689
1715
|
register(server, opts = {}) {
|
|
1690
|
-
const { sendLog, deviceNames } = opts;
|
|
1716
|
+
const { sendLog, deviceNames, deviceAliases } = opts;
|
|
1691
1717
|
const multiDevice = !!deviceNames && deviceNames.length > 1;
|
|
1692
1718
|
const { ui, auto } = effectiveUi(def);
|
|
1719
|
+
const selectorNames = multiDevice && deviceNames ? [...new Set([...deviceNames, ...deviceAliases ?? []])] : [];
|
|
1693
1720
|
const inputSchema = multiDevice ? {
|
|
1694
1721
|
...def.inputSchema,
|
|
1695
|
-
device: z2.enum(
|
|
1722
|
+
device: z2.enum(selectorNames).optional().describe(`Which configured MikroTik device to run this on. One of: ${selectorNames.join(", ")} (a config key or its label). Omit to use the default device.`)
|
|
1696
1723
|
} : def.inputSchema;
|
|
1697
1724
|
const risk = riskOf(def.annotations);
|
|
1698
1725
|
const callback = async (args) => {
|
|
@@ -1715,7 +1742,10 @@ function defineTool(def) {
|
|
|
1715
1742
|
return { content: [{ type: "text", text: out.text }], isError: true };
|
|
1716
1743
|
}
|
|
1717
1744
|
if (auto && !out.structuredContent) {
|
|
1718
|
-
|
|
1745
|
+
const view = buildRecordsView(def.name, def.title, out.text, new Date().toISOString());
|
|
1746
|
+
if (view.rows.length > 0) {
|
|
1747
|
+
out.structuredContent = view;
|
|
1748
|
+
}
|
|
1719
1749
|
}
|
|
1720
1750
|
const result = {
|
|
1721
1751
|
content: [{ type: "text", text: out.text }]
|
|
@@ -1758,7 +1788,8 @@ function defineTool(def) {
|
|
|
1758
1788
|
description: def.description,
|
|
1759
1789
|
inputSchema,
|
|
1760
1790
|
annotations: { ...def.annotations, title: def.title },
|
|
1761
|
-
...ui ? { _meta: toolUiMeta(ui) } : {}
|
|
1791
|
+
...ui ? { _meta: toolUiMeta(ui) } : {},
|
|
1792
|
+
...ui && !auto ? { outputSchema: UI_OUTPUT_SCHEMA } : {}
|
|
1762
1793
|
}, callback);
|
|
1763
1794
|
}
|
|
1764
1795
|
};
|
|
@@ -13522,7 +13553,7 @@ ${redactSecrets(result)}`;
|
|
|
13522
13553
|
|
|
13523
13554
|
// src/tools/poe.ts
|
|
13524
13555
|
import { z as z69 } from "zod";
|
|
13525
|
-
var NO_POE = "This device
|
|
13556
|
+
var NO_POE = "This device has no PoE-OUT hardware, so the `/interface ethernet poe` menu " + "(used to SUPPLY power to other devices) is not available \u2014 this is normal, not an error. " + "Note: PoE-OUT (this router powering other devices) is different from PoE-IN (this router being " + "powered over Ethernet). If your device is PoE-powered, read its PoE-IN voltage/state with " + "get_system_health (`/system health`), not the PoE tools.";
|
|
13526
13557
|
var poeTools = [
|
|
13527
13558
|
defineTool({
|
|
13528
13559
|
name: "get_poe_monitor",
|
|
@@ -23961,7 +23992,7 @@ function registerPrompts(server) {
|
|
|
23961
23992
|
// package.json
|
|
23962
23993
|
var package_default = {
|
|
23963
23994
|
name: "@usex/mikrotik-mcp",
|
|
23964
|
-
version: "3.
|
|
23995
|
+
version: "3.23.0",
|
|
23965
23996
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
23966
23997
|
keywords: [
|
|
23967
23998
|
"ai",
|
|
@@ -24165,6 +24196,7 @@ function createServer(opts = {}) {
|
|
|
24165
24196
|
const toolCount = registerTools(server, toolModules, {
|
|
24166
24197
|
sendLog,
|
|
24167
24198
|
deviceNames: names,
|
|
24199
|
+
deviceAliases: deviceLabels(),
|
|
24168
24200
|
readOnly
|
|
24169
24201
|
});
|
|
24170
24202
|
const promptCount = registerPrompts(server);
|
package/dist/index.d.ts
CHANGED
|
@@ -49,9 +49,16 @@ interface UiLink {
|
|
|
49
49
|
/** Options threaded into every tool registration. */
|
|
50
50
|
interface RegisterOptions {
|
|
51
51
|
sendLog?: SendLog;
|
|
52
|
-
/** Configured device
|
|
52
|
+
/** Configured device KEYS; when more than one, a `device` selector is injected. */
|
|
53
53
|
deviceNames?: string[];
|
|
54
54
|
/**
|
|
55
|
+
* Extra accepted values for the `device` selector — a device's free-text
|
|
56
|
+
* label (`description`, e.g. "Ali Home") so the AI can target it by the
|
|
57
|
+
* friendly name as well as its config key. Resolved back to the key by
|
|
58
|
+
* {@link resolveDeviceName}. Does NOT affect the single-vs-multi decision.
|
|
59
|
+
*/
|
|
60
|
+
deviceAliases?: string[];
|
|
61
|
+
/**
|
|
55
62
|
* Read-only mode: register only tools annotated `readOnlyHint`. Used to
|
|
56
63
|
* withhold every write/destructive tool from a publicly-exposed surface (e.g.
|
|
57
64
|
* a ChatGPT Apps connector) until authentication is in place.
|
|
@@ -110,11 +117,15 @@ declare function listDevices(): {
|
|
|
110
117
|
names: string[];
|
|
111
118
|
default: string;
|
|
112
119
|
};
|
|
113
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* Resolve a (possibly undefined) device name to a concrete, existing config key.
|
|
122
|
+
* Accepts a config key OR a device's free-text label; falls back to the default.
|
|
123
|
+
*/
|
|
114
124
|
declare function resolveDeviceName(name?: string): string;
|
|
115
125
|
/**
|
|
116
|
-
* Return the connection config for a device by
|
|
117
|
-
* `name` is undefined). Throws if an explicit
|
|
126
|
+
* Return the connection config for a device by key or label (or the default when
|
|
127
|
+
* `name` is undefined). Throws if an explicit name matches neither a key nor a
|
|
128
|
+
* label.
|
|
118
129
|
*/
|
|
119
130
|
declare function getDevice(name?: string): DeviceConfig;
|
|
120
131
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
package/dist/index.js
CHANGED
|
@@ -231,13 +231,38 @@ function getConfig() {
|
|
|
231
231
|
function listDevices() {
|
|
232
232
|
return { names: Object.keys(active.devices), default: active.defaultDevice };
|
|
233
233
|
}
|
|
234
|
+
function deviceKeyForLabel(name) {
|
|
235
|
+
const target = name.trim().toLowerCase();
|
|
236
|
+
for (const [key, dc] of Object.entries(active.devices)) {
|
|
237
|
+
if (dc.description && dc.description.trim().toLowerCase() === target)
|
|
238
|
+
return key;
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
function deviceLabels() {
|
|
243
|
+
const seen = new Set;
|
|
244
|
+
const out = [];
|
|
245
|
+
for (const [key, dc] of Object.entries(active.devices)) {
|
|
246
|
+
const label = dc.description?.trim();
|
|
247
|
+
if (label && label !== key && !(label in active.devices) && !seen.has(label)) {
|
|
248
|
+
seen.add(label);
|
|
249
|
+
out.push(label);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
234
254
|
function resolveDeviceName(name) {
|
|
235
|
-
if (name
|
|
236
|
-
|
|
255
|
+
if (name) {
|
|
256
|
+
if (name in active.devices)
|
|
257
|
+
return name;
|
|
258
|
+
const byLabel = deviceKeyForLabel(name);
|
|
259
|
+
if (byLabel)
|
|
260
|
+
return byLabel;
|
|
261
|
+
}
|
|
237
262
|
return active.defaultDevice in active.devices ? active.defaultDevice : Object.keys(active.devices)[0] ?? active.defaultDevice;
|
|
238
263
|
}
|
|
239
264
|
function getDevice(name) {
|
|
240
|
-
if (name && !(name in active.devices)) {
|
|
265
|
+
if (name && !(name in active.devices) && !deviceKeyForLabel(name)) {
|
|
241
266
|
throw new Error(`Unknown device '${name}'. Configured devices: ${Object.keys(active.devices).join(", ")}`);
|
|
242
267
|
}
|
|
243
268
|
const key = resolveDeviceName(name);
|
|
@@ -1553,6 +1578,7 @@ function recordToolCall(raw) {
|
|
|
1553
1578
|
|
|
1554
1579
|
// src/core/registry.ts
|
|
1555
1580
|
var AUTO_RECORDS_VERB = /^(list|get|show|print)_/;
|
|
1581
|
+
var UI_OUTPUT_SCHEMA = z2.object({}).passthrough();
|
|
1556
1582
|
function effectiveUi(def) {
|
|
1557
1583
|
if (def.ui)
|
|
1558
1584
|
return { ui: def.ui, auto: false };
|
|
@@ -1593,12 +1619,13 @@ function defineTool(def) {
|
|
|
1593
1619
|
inputSchema: def.inputSchema,
|
|
1594
1620
|
ui: def.ui,
|
|
1595
1621
|
register(server, opts = {}) {
|
|
1596
|
-
const { sendLog, deviceNames } = opts;
|
|
1622
|
+
const { sendLog, deviceNames, deviceAliases } = opts;
|
|
1597
1623
|
const multiDevice = !!deviceNames && deviceNames.length > 1;
|
|
1598
1624
|
const { ui, auto } = effectiveUi(def);
|
|
1625
|
+
const selectorNames = multiDevice && deviceNames ? [...new Set([...deviceNames, ...deviceAliases ?? []])] : [];
|
|
1599
1626
|
const inputSchema = multiDevice ? {
|
|
1600
1627
|
...def.inputSchema,
|
|
1601
|
-
device: z2.enum(
|
|
1628
|
+
device: z2.enum(selectorNames).optional().describe(`Which configured MikroTik device to run this on. One of: ${selectorNames.join(", ")} (a config key or its label). Omit to use the default device.`)
|
|
1602
1629
|
} : def.inputSchema;
|
|
1603
1630
|
const risk = riskOf(def.annotations);
|
|
1604
1631
|
const callback = async (args) => {
|
|
@@ -1621,7 +1648,10 @@ function defineTool(def) {
|
|
|
1621
1648
|
return { content: [{ type: "text", text: out.text }], isError: true };
|
|
1622
1649
|
}
|
|
1623
1650
|
if (auto && !out.structuredContent) {
|
|
1624
|
-
|
|
1651
|
+
const view = buildRecordsView(def.name, def.title, out.text, new Date().toISOString());
|
|
1652
|
+
if (view.rows.length > 0) {
|
|
1653
|
+
out.structuredContent = view;
|
|
1654
|
+
}
|
|
1625
1655
|
}
|
|
1626
1656
|
const result = {
|
|
1627
1657
|
content: [{ type: "text", text: out.text }]
|
|
@@ -1664,7 +1694,8 @@ function defineTool(def) {
|
|
|
1664
1694
|
description: def.description,
|
|
1665
1695
|
inputSchema,
|
|
1666
1696
|
annotations: { ...def.annotations, title: def.title },
|
|
1667
|
-
...ui ? { _meta: toolUiMeta(ui) } : {}
|
|
1697
|
+
...ui ? { _meta: toolUiMeta(ui) } : {},
|
|
1698
|
+
...ui && !auto ? { outputSchema: UI_OUTPUT_SCHEMA } : {}
|
|
1668
1699
|
}, callback);
|
|
1669
1700
|
}
|
|
1670
1701
|
};
|
|
@@ -13534,7 +13565,7 @@ ${redactSecrets(result)}`;
|
|
|
13534
13565
|
|
|
13535
13566
|
// src/tools/poe.ts
|
|
13536
13567
|
import { z as z70 } from "zod";
|
|
13537
|
-
var NO_POE = "This device
|
|
13568
|
+
var NO_POE = "This device has no PoE-OUT hardware, so the `/interface ethernet poe` menu " + "(used to SUPPLY power to other devices) is not available \u2014 this is normal, not an error. " + "Note: PoE-OUT (this router powering other devices) is different from PoE-IN (this router being " + "powered over Ethernet). If your device is PoE-powered, read its PoE-IN voltage/state with " + "get_system_health (`/system health`), not the PoE tools.";
|
|
13538
13569
|
var poeTools = [
|
|
13539
13570
|
defineTool({
|
|
13540
13571
|
name: "get_poe_monitor",
|
|
@@ -22361,7 +22392,7 @@ function selectToolModules(filter = {}, catalog = moduleCatalog) {
|
|
|
22361
22392
|
// package.json
|
|
22362
22393
|
var package_default = {
|
|
22363
22394
|
name: "@usex/mikrotik-mcp",
|
|
22364
|
-
version: "3.
|
|
22395
|
+
version: "3.23.0",
|
|
22365
22396
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
22366
22397
|
keywords: [
|
|
22367
22398
|
"ai",
|
|
@@ -22565,6 +22596,7 @@ function createServer(opts = {}) {
|
|
|
22565
22596
|
const toolCount = registerTools(server, toolModules, {
|
|
22566
22597
|
sendLog,
|
|
22567
22598
|
deviceNames: names,
|
|
22599
|
+
deviceAliases: deviceLabels(),
|
|
22568
22600
|
readOnly
|
|
22569
22601
|
});
|
|
22570
22602
|
const promptCount = registerPrompts(server);
|
package/dist/ui/dashboard.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
6
7
|
<title>MikroTik Device Dashboard</title>
|
|
7
8
|
<script type="module">
|
|
8
9
|
import{a as e,i as t,n,r,t as i}from"./app-DU6NBjf_.js";var a=`show_system_dashboard`,o=document.getElementById(`app`);function s(e,t={},...n){let r=document.createElement(e);for(let[e,n]of Object.entries(t))e===`class`?r.className=n:r.setAttribute(e,n);for(let e of n.flat())e===!1||e==null||r.append(e instanceof Node?e:document.createTextNode(String(e)));return r}function c(e){if(e==null)return`—`;let t=[`B`,`KiB`,`MiB`,`GiB`,`TiB`],n=0,r=e;for(;r>=1024&&n<t.length-1;)r/=1024,n++;return`${r.toFixed(+(r<10&&n>0))} ${t[n]}`}function l(e){return e==null?``:e>=90?`is-bad`:e>=70?`is-warn`:`is-good`}var u=`http://www.w3.org/2000/svg`;function d(e,t){let n=document.createElementNS(u,e);for(let[e,r]of Object.entries(t))n.setAttribute(e,String(r));return n}function f(e,t,n){let r=2*Math.PI*26,i=t==null?0:Math.max(0,Math.min(100,t))/100,a=d(`svg`,{width:64,height:64,viewBox:`0 0 64 64`}),o=d(`circle`,{cx:32,cy:32,r:26,fill:`none`,"stroke-width":7});o.setAttribute(`class`,`gauge__track`);let c=d(`circle`,{cx:32,cy:32,r:26,fill:`none`,"stroke-width":7,"stroke-dasharray":r,"stroke-dashoffset":r*(1-i)});c.setAttribute(`class`,`gauge__bar ${l(t)}`);let u=d(`text`,{x:32,y:37,"text-anchor":`middle`});return u.setAttribute(`class`,`gauge__pct`),u.textContent=t==null?`—`:`${Math.round(t)}%`,a.replaceChildren(o,c,u),s(`div`,{class:`card gauge`},a,s(`div`,{class:`gauge__meta`},s(`p`,{class:`card__label`},e),s(`small`,{},n)))}function p(e,t){return s(`div`,{class:`card`},s(`p`,{class:`card__label`},e),s(`div`,{class:`card__value`},t))}function m(e,t){let n=Object.entries(t);if(n.length===0)return null;let r=s(`div`,{class:`kv__body`},...n.flatMap(([e,t])=>[s(`div`,{class:`kv__k`},e),s(`div`,{class:`kv__v`},t||`—`)]));return s(`details`,{class:`kv`},s(`summary`,{},`${e} (${n.length})`),r)}var h=null,g=!1;function _(){if(!h){o.replaceChildren(s(`div`,{class:`skeleton`},`Waiting for device data…`));return}let e=h,t=e.derived,n=e.resource.version??`?`,r=e.routerboard.model??e.resource[`board-name`]??`?`,i=[f(`CPU load`,t.cpuLoadPct,`${e.resource[`cpu-count`]??`?`} cores`),f(`Memory`,t.memUsedPct,`${c(t.memUsedBytes)} / ${c(t.memTotalBytes)}`),t.hddUsedPct!=null&&f(`Disk`,t.hddUsedPct,`${c(t.hddUsedBytes)} / ${c(t.hddTotalBytes)}`)].filter(Boolean),a=[p(`Uptime`,e.resource.uptime??`—`),t.temperatureC!=null&&p(`Temperature`,`${t.temperatureC} °C`),t.voltageV!=null&&p(`Voltage`,`${t.voltageV} V`),p(`Architecture`,e.resource[`architecture-name`]??`—`)].filter(Boolean),l=s(`button`,{class:`btn`},g?`Refreshing…`:`↻ Refresh`);g&&l.setAttribute(`disabled`,`true`),l.addEventListener(`click`,b);let u=s(`span`,{class:`pill`},`device `,s(`b`,{},e.device)),d=s(`header`,{class:`hd`},s(`span`,{class:`hd__dot`}),s(`div`,{},s(`h1`,{class:`hd__title`},e.identity),s(`p`,{class:`hd__sub`},`${r} · RouterOS ${n}`)),s(`span`,{class:`hd__spacer`}),u),_=s(`footer`,{class:`foot`},l,s(`span`,{},`updated ${new Date(e.generatedAt).toLocaleTimeString()}`)),v=[d,s(`section`,{class:`grid`},...i),s(`section`,{class:`grid`},...a),m(`System resource`,e.resource),m(`RouterBOARD`,e.routerboard),_].filter(e=>e!=null);o.replaceChildren(...v)}var v=new t({name:`mikrotik-dashboard`,version:`1.0.0`});function y(e){e&&typeof e==`object`&&`device`in e&&(h=e,_())}async function b(){if(!g){g=!0,_();try{y((await v.callServerTool({name:a,arguments:{}})).structuredContent)}catch(e){console.error(`[dashboard] refresh failed`,e)}finally{g=!1,_()}}}v.ontoolresult=e=>y(e.structuredContent),v.ontoolinput=()=>{h||_()},v.onhostcontextchanged=t=>{if(t.theme&&e(t.theme),t.styles?.variables&&r(t.styles.variables),t.styles?.css?.fonts&&n(t.styles.css.fonts),t.safeAreaInsets){let{top:e,right:n,bottom:r,left:i}=t.safeAreaInsets;document.body.style.padding=`${e+16}px ${n+16}px ${r+16}px ${i+16}px`}},v.onteardown=async()=>({}),e(i()),_(),v.connect().catch(e=>console.error(`[dashboard] connect failed`,e));
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
6
7
|
<title>MikroTik Firewall Audit</title>
|
|
7
8
|
<script type="module">
|
|
8
9
|
import{i as e}from"./app-DU6NBjf_.js";import{i as t,o as n,t as r}from"./kit-DAVkZl3Q.js";var i=`firewall_audit`,a=document.getElementById(`app`),o=null,s=!1,c=null,l=new e({name:`mikrotik-firewall-audit`,version:`1.0.0`});function u(e){return e===`clean`||e===`good`?`is-good`:e===`fair`?`is-warn`:`is-bad`}function d(e){return e===`high`?`is-bad`:e===`medium`?`is-warn`:`is-low`}function f(e,t){let n=2*Math.PI*34,r=Math.max(0,Math.min(100,e))/100,i=document.createElementNS(`http://www.w3.org/2000/svg`,`svg`);i.setAttribute(`viewBox`,`0 0 88 88`),i.setAttribute(`class`,`gauge ${u(t)}`);let a=(e,t)=>{let n=document.createElementNS(`http://www.w3.org/2000/svg`,`circle`);n.setAttribute(`class`,e),n.setAttribute(`cx`,`44`),n.setAttribute(`cy`,`44`),n.setAttribute(`r`,`34`);for(let[e,r]of Object.entries(t))n.setAttribute(e,r);return n};i.append(a(`gauge__bg`,{})),i.append(a(`gauge__fg`,{"stroke-dasharray":`${n*r} ${n}`,transform:`rotate(-90 44 44)`}));let o=document.createElementNS(`http://www.w3.org/2000/svg`,`text`);return o.setAttribute(`x`,`44`),o.setAttribute(`y`,`48`),o.setAttribute(`text-anchor`,`middle`),o.setAttribute(`class`,`gauge__num`),o.textContent=String(e),i.append(o),i}async function p(e,t){if(!(!e.action||s)){c=t,h();try{await l.callServerTool({name:e.action.tool,arguments:e.action.args}),await _()}catch(e){console.error(`[firewall-audit] action failed`,e)}finally{c=null,h()}}}function m(e,n){let i=`${e.kind}-${e.table}-${e.ruleIndex??e.chain}-${n}`,a=t(`div`,{class:`f-head`},t(`span`,{class:`chip ${d(e.severity)}`},e.severity),t(`span`,{class:`f-title`},e.title),t(`span`,{class:`f-where`},`${e.table}/${e.chain}`)),o=t(`div`,{class:`f-body`},t(`p`,{class:`f-detail`},e.detail),t(`p`,{class:`f-fix`},t(`b`,{},`Fix: `),e.suggestion)),l=t(`div`,{class:`f-card ${d(e.severity)}`},a,o);if(e.action){let n=c===i?`Working…`:e.action.label;l.append(t(`div`,{class:`f-actions`},r(n,()=>void p(e,i),{class:`btn-danger`,disabled:s||c===i,title:`Calls ${e.action.tool}`})))}return l}function h(){if(!o){a.replaceChildren(t(`div`,{class:`skeleton`},`Running firewall audit…`));return}let e=o,n=t(`header`,{class:`hd`},f(e.riskScore,e.grade),t(`div`,{class:`hd__meta`},t(`h1`,{class:`hd__title`},`Firewall audit`),t(`p`,{class:`hd__sub`},`device `,t(`b`,{},e.device),` · ${e.ruleCount} rules · grade `,t(`b`,{class:u(e.grade)},e.grade)),t(`div`,{class:`counts`},t(`span`,{class:`chip is-bad`},`${e.counts.high} high`),t(`span`,{class:`chip is-warn`},`${e.counts.medium} medium`),t(`span`,{class:`chip is-low`},`${e.counts.low} low`))),t(`span`,{class:`hd__spacer`}),r(s?`Auditing…`:`↻ Re-audit`,_,{disabled:s})),i;i=e.findings.length===0?t(`div`,{class:`empty`},`No issues found — the ruleset looks clean. ✓`):t(`div`,{class:`f-list`},...e.findings.map(m));let c=t(`footer`,{class:`foot`},t(`span`,{class:`grow`}),t(`span`,{},`updated ${new Date(e.generatedAt).toLocaleTimeString()}`));a.replaceChildren(n,i,c)}function g(e){e&&typeof e==`object`&&e.__mikrotikView===`firewall-audit`&&(o=e,h())}async function _(){if(!s){s=!0,h();try{g((await l.callServerTool({name:i,arguments:{}})).structuredContent)}catch(e){console.error(`[firewall-audit] refresh failed`,e)}finally{s=!1,h()}}}l.ontoolresult=e=>g(e.structuredContent),l.ontoolinput=()=>{o||h()},n(l),l.onteardown=async()=>({}),h(),l.connect().catch(e=>console.error(`[firewall-audit] connect failed`,e));
|
package/dist/ui/firewall.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
6
7
|
<title>MikroTik Firewall Rules</title>
|
|
7
8
|
<script type="module">
|
|
8
9
|
import{i as e}from"./app-DU6NBjf_.js";import{a as t,i as n,o as r,r as i,t as a}from"./kit-DAVkZl3Q.js";var o=`show_firewall_filter`,s=document.getElementById(`app`),c=null,l=``,u=!1,d=[`protocol`,`src-address`,`dst-address`,`src-port`,`dst-port`,`in-interface`,`out-interface`,`in-interface-list`,`out-interface-list`,`connection-state`,`src-address-list`,`dst-address-list`];function f(e){return e===`accept`?`act is-accept`:e===`drop`||e===`reject`?`act is-drop`:e===`jump`||e===`return`?`act is-jump`:`act`}function p(e){let t=Number(e);if(!e||Number.isNaN(t))return e??`—`;if(t<1e3)return String(t);let n=[`k`,`M`,`G`,`T`],r=-1,i=t;for(;i>=1e3&&r<n.length-1;)i/=1e3,r++;return`${i.toFixed(1)}${n[r]}`}function m(e){let t=[];for(let r of d)e[r]&&t.push(n(`span`,{},`${r}=`,n(`b`,{},e[r])));let r=n(`td`,{class:`matchers`});return t.forEach((e,t)=>{t&&r.append(document.createTextNode(` `)),r.append(e)}),t.length||r.append(document.createTextNode(e.comment??`—`)),r}function h(e){let t=l.trim().toLowerCase();return t?e.rows.filter(e=>Object.values(e).some(e=>e.toLowerCase().includes(t))):e.rows}function g(e){let t=(e.flags??``).includes(`X`),r=e.action??`—`,i=n(`tr`,t?{class:`is-disabled`}:{});return i.append(n(`td`,{class:`col-num`},e[`#`]??`—`),n(`td`,{},e.chain??`—`),n(`td`,{},n(`span`,{class:f(r)},r)),m(e),n(`td`,{class:`num`},p(e.packets)),n(`td`,{class:`num`},p(e.bytes))),i}function _(){if(!c){s.replaceChildren(n(`div`,{class:`skeleton`},`Waiting for firewall rules…`));return}let e=c,r=h(e),o=e.rows.filter(e=>(e.flags??``).includes(`X`)).length,f=n(`input`,{class:`search`,type:`search`,placeholder:`Search ${e.rows.length} rule(s)…`,value:l});f.addEventListener(`input`,()=>{l=f.value,_()});let p=n(`header`,{class:`hd`},n(`span`,{class:`hd__dot`}),n(`div`,{},n(`h1`,{class:`hd__title`},`Firewall — Filter`),n(`p`,{class:`hd__sub`},`${e.rows.length} rules · ${o} disabled`)),n(`span`,{class:`hd__spacer`}),n(`span`,{class:`pill`},`device `,n(`b`,{},e.device))),m=n(`div`,{class:`toolbar`},n(`div`,{class:`grow`},f),a(u?`Refreshing…`:`↻ Refresh`,b,{disabled:u}),a(`CSV`,()=>{let n=[`#`,`chain`,`action`,...d,`packets`,`bytes`];i(`firewall-${e.chain}.csv`,t(n,h(e)),`text/csv`)},{title:`Export visible rules as CSV`})),v;v=e.rows.length?r.length?n(`div`,{class:`tablewrap`},n(`table`,{class:`tbl`},n(`thead`,{},n(`tr`,{},n(`th`,{class:`col-num`},`#`),n(`th`,{},`chain`),n(`th`,{},`action`),n(`th`,{},`matchers`),n(`th`,{class:`num`},`packets`),n(`th`,{class:`num`},`bytes`))),n(`tbody`,{},...r.map(g)))):n(`div`,{class:`empty`},`No rules match the search.`):n(`div`,{class:`empty`},`No filter rules configured.`);let y=n(`footer`,{class:`foot`},n(`span`,{class:`grow`}),n(`span`,{},`updated ${new Date(e.generatedAt).toLocaleTimeString()}`));s.replaceChildren(p,m,v,y)}var v=new e({name:`mikrotik-firewall`,version:`1.0.0`});function y(e){e&&typeof e==`object`&&e.__mikrotikView===`firewall`&&(c=e,_())}async function b(){if(!u){u=!0,_();try{y((await v.callServerTool({name:o,arguments:{}})).structuredContent)}catch(e){console.error(`[firewall] refresh failed`,e)}finally{u=!1,_()}}}v.ontoolresult=e=>y(e.structuredContent),v.ontoolinput=()=>{c||_()},r(v),v.onteardown=async()=>({}),_(),v.connect().catch(e=>console.error(`[firewall] connect failed`,e));
|
package/dist/ui/interfaces.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
6
7
|
<title>MikroTik Interfaces</title>
|
|
7
8
|
<script type="module">
|
|
8
9
|
import{i as e}from"./app-DU6NBjf_.js";import{i as t,o as n,t as r}from"./kit-DAVkZl3Q.js";var i=`show_interfaces`,a=document.getElementById(`app`),o=null,s=``,c=!1;function l(e){return(e.flags??``).includes(`R`)||e.running===`true`}function u(e){return(e.flags??``).includes(`X`)||e.disabled===`true`}function d(e,n){return!!n&&t(`div`,{class:`contents`},t(`span`,{},e),t(`b`,{},n))}function f(e){let n=u(e),r=n?`dot is-down`:l(e)?`dot is-up`:`dot`,i=t(`div`,{class:`if-card__meta`},d(`MTU`,e.mtu??e[`actual-mtu`]),d(`MAC`,e[`mac-address`]),d(`comment`,e.comment));return t(`div`,{class:`card if-card${n?` is-disabled`:``}`},t(`div`,{class:`if-card__top`},t(`span`,{class:r}),t(`span`,{class:`if-card__name`},e.name??`?`),t(`span`,{class:`badge`},e.type??`—`)),i)}function p(e){let t=s.trim().toLowerCase();return t?e.rows.filter(e=>Object.values(e).some(e=>e.toLowerCase().includes(t))):e.rows}function m(){if(!o){a.replaceChildren(t(`div`,{class:`skeleton`},`Waiting for interfaces…`));return}let e=o,n=p(e),i=e.rows.filter(l).length,d=e.rows.filter(u).length,h=t(`input`,{class:`search`,type:`search`,placeholder:`Search ${e.rows.length} interface(s)…`,value:s});h.addEventListener(`input`,()=>{s=h.value,m()});let g=t(`header`,{class:`hd`},t(`span`,{class:`hd__dot`}),t(`div`,{},t(`h1`,{class:`hd__title`},`Interfaces`),t(`p`,{class:`hd__sub`},`${i} running · ${d} disabled · ${e.rows.length} total`)),t(`span`,{class:`hd__spacer`}),t(`span`,{class:`pill`},`device `,t(`b`,{},e.device))),v=t(`div`,{class:`toolbar`},t(`div`,{class:`grow`},h),r(c?`Refreshing…`:`↻ Refresh`,_,{disabled:c})),y=n.length?t(`section`,{class:`if-grid`},...n.map(f)):t(`div`,{class:`empty`},`No interfaces match the search.`),b=t(`footer`,{class:`foot`},t(`span`,{class:`grow`}),t(`span`,{},`updated ${new Date(e.generatedAt).toLocaleTimeString()}`));a.replaceChildren(g,v,y,b)}var h=new e({name:`mikrotik-interfaces`,version:`1.0.0`});function g(e){e&&typeof e==`object`&&e.__mikrotikView===`interfaces`&&(o=e,m())}async function _(){if(!c){c=!0,m();try{g((await h.callServerTool({name:i,arguments:{}})).structuredContent)}catch(e){console.error(`[interfaces] refresh failed`,e)}finally{c=!1,m()}}}h.ontoolresult=e=>g(e.structuredContent),h.ontoolinput=()=>{o||m()},n(h),h.onteardown=async()=>({}),m(),h.connect().catch(e=>console.error(`[interfaces] connect failed`,e));
|
package/dist/ui/records.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
6
7
|
<title>MikroTik Records Viewer</title>
|
|
7
8
|
<script type="module">
|
|
8
9
|
import{i as e}from"./app-DU6NBjf_.js";import{a as t,i as n,n as r,o as i,r as a,t as o}from"./kit-DAVkZl3Q.js";var s=document.getElementById(`app`),c=null,l=``,u=null,d=1,f=new Set,p=null,m=!1,h=0,g=null,_={};function v(e){let t=[`#`,`flags`].filter(t=>e.columns.includes(t));return[...t,...e.columns.filter(e=>!t.includes(e))]}function y(e){let t=l.trim().toLowerCase(),n=e.rows;if(t&&(n=n.filter(e=>Object.values(e).some(e=>e.toLowerCase().includes(t)))),f.size&&(n=n.filter(e=>{let t=e.flags??``;return[...f].every(e=>t.includes(e))})),u){let e=u;n=[...n].sort((t,n)=>{let r=t[e]??``,i=n[e]??``,a=Number(r),o=Number(i);return(r!==``&&i!==``&&!Number.isNaN(a)&&!Number.isNaN(o)?a-o:r.localeCompare(i))*d})}return n}function b(e){let t=new Set;for(let n of e.rows)for(let e of n.flags??``)t.add(e);return[...t].sort()}function x(e){return Object.entries(e).filter(([e])=>e!==`#`&&e!==`flags`).map(([e,t])=>/\s/.test(t)?`${e}="${t}"`:`${e}=${t}`).join(` `)}function S(e){let t=b(e);return t.length?n(`div`,{class:`toolbar`},...t.map(t=>{let n=e.flags[t]??t,r=f.has(t);return o(`${t} · ${n}`,()=>{r?f.delete(t):f.add(t),j()},{class:r?`is-active`:``,title:`Filter rows flagged "${n}"`})})):!1}function C(e){let t=n(`th`,e===`#`?{class:`col-num`}:{},e);return u===e&&t.append(n(`span`,{class:`arrow`},d===1?`▲`:`▼`)),t.addEventListener(`click`,()=>{u===e?d=d===1?-1:1:(u=e,d=1),j()}),t}function w(e,t){let r=v(e),i=n(`thead`,{},n(`tr`,{},...r.map(C))),a=n(`tbody`);return t.forEach(t=>{let i=n(`tr`,(t.flags??``).includes(`X`)?{class:`is-disabled`}:{});for(let e of r)i.append(n(`td`,e===`#`?{class:`col-num`}:{},t[e]??`—`));i.addEventListener(`click`,()=>{p=e.rows.indexOf(t),j()}),a.append(i)}),n(`div`,{class:`tablewrap`},n(`table`,{class:`tbl`},i,a))}function T(e){let t=n(`div`,{class:`kv__body`});for(let[r,i]of Object.entries(e))t.append(n(`div`,{class:`kv__k`},r),n(`div`,{class:`kv__v`},i||`—`));return t}function E(e){if(p==null||!e.rows[p])return!1;let t=e.rows[p],i=t.name??t[`#`]??`record`;return n(`div`,{class:`drawer`},n(`div`,{class:`drawer__hd`},n(`span`,{},`Row `),n(`b`,{},String(i)),n(`span`,{class:`hd__spacer`}),o(`Copy as CLI`,()=>void r(x(t)),{title:`Copy key=value pairs`}),o(`Close`,()=>{p=null,j()})),T(t))}function D(e,i){let s=n(`input`,{class:`search`,type:`search`,placeholder:`Search ${e.count} row${e.count===1?``:`s`}…`,value:l});s.addEventListener(`input`,()=>{l=s.value,p=null,k()});let c=n(`select`,{class:`btn`,title:`Auto-refresh interval`});for(let[e,t]of[[`Auto: off`,`0`],[`Auto: 5s`,`5000`],[`Auto: 15s`,`15000`],[`Auto: 60s`,`60000`]]){let r=n(`option`,{value:t},e);Number(t)===h&&(r.selected=!0),c.append(r)}c.addEventListener(`change`,()=>F(Number(c.value)));let u=()=>y(e),d=v(e);return n(`div`,{class:`toolbar`},n(`div`,{class:`grow`},s),o(m?`Refreshing…`:`↻ Refresh`,P,{disabled:m}),c,o(`CSV`,()=>a(`${e.tool}.csv`,t(d,u()),`text/csv`),{title:`Export visible rows as CSV`}),o(`JSON`,()=>a(`${e.tool}.json`,JSON.stringify(u(),null,2),`application/json`),{title:`Export visible rows as JSON`}),o(`Copy`,()=>void r(JSON.stringify(u(),null,2)),{title:`Copy visible rows as JSON`}),n(`span`,{class:`pill count-pill`},`showing `,n(`b`,{},`${i}/${e.count}`)))}function O(e){return n(`header`,{class:`hd`},n(`span`,{class:`hd__dot`}),n(`div`,{},n(`h1`,{class:`hd__title`},e.title),n(`p`,{class:`hd__sub`},e.tool)),n(`span`,{class:`hd__spacer`}))}function k(){if(!c)return;let e=document.getElementById(`results`);if(!e){j();return}e.replaceChildren(A(c));let t=document.querySelector(`.count-pill b`);t&&(t.textContent=`${y(c).length}/${c.count}`)}function A(e){let t=y(e);return e.rows.length===0?e.raw?n(`pre`,{class:`raw`},e.raw):n(`div`,{class:`empty`},`No records returned.`):e.kind===`record`&&e.rows.length===1?T(e.rows[0]):t.length===0?n(`div`,{class:`empty`},`No rows match the current filter.`):w(e,t)}function j(){if(!c){s.replaceChildren(n(`div`,{class:`skeleton`},`Waiting for data…`));return}let e=c,t=y(e).length,r=e.kind===`list`&&e.rows.length>0,i=[O(e),r&&D(e,t),r&&S(e),n(`div`,{id:`results`},A(e)),E(e),n(`footer`,{class:`foot`},n(`span`,{},`format: ${e.format}`),n(`span`,{class:`grow`}),n(`span`,{},`updated ${new Date(e.generatedAt).toLocaleTimeString()}`))];s.replaceChildren(...i.filter(Boolean))}var M=new e({name:`mikrotik-records`,version:`1.0.0`});function N(e){e&&typeof e==`object`&&e.__mikrotikView===`records`&&(c=e,p=null,j())}async function P(){if(!(m||!c)){m=!0,j();try{N((await M.callServerTool({name:c.tool,arguments:_})).structuredContent)}catch(e){console.error(`[records] refresh failed`,e)}finally{m=!1,j()}}}function F(e){h=e,g&&=(clearInterval(g),null),e>0&&(g=setInterval(()=>void P(),e)),j()}M.ontoolresult=e=>N(e.structuredContent),M.ontoolinput=e=>{e&&typeof e==`object`&&`arguments`in e&&(_=e.arguments??{}),c||j()},i(M),M.onteardown=async()=>(g&&clearInterval(g),{}),j(),M.connect().catch(e=>console.error(`[records] connect failed`,e));
|
package/package.json
CHANGED