@usex/mikrotik-mcp 3.45.0 → 3.47.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 +122 -11
- package/dist/index.js +8 -5
- package/dist/shared/{cli-k2ghtqd0.js → cli-b6nehpgn.js} +14 -5
- package/dist/shared/{cli-1e5sj65w.js → cli-je6h62bk.js} +1 -1
- package/dist/shared/{library-98nh7hz6.js → library-fnp716x3.js} +1 -1
- package/dist/shared/{library-nb4wss02.js → library-wppv8kdx.js} +14 -5
- package/dist/ui/aaa.html +3 -3
- package/dist/ui/connected-devices.html +3 -3
- package/dist/ui/dashboard.html +2 -2
- package/dist/ui/firewall-audit.html +3 -3
- package/dist/ui/firewall.html +3 -3
- package/dist/ui/interfaces.html +3 -3
- package/dist/ui/observability.html +52 -46
- package/dist/ui/records.html +3 -3
- package/package.json +7 -4
package/dist/cli.js
CHANGED
|
@@ -89,7 +89,7 @@ import {
|
|
|
89
89
|
toggleAaaEntity,
|
|
90
90
|
updateAaaEntity,
|
|
91
91
|
writeBackup
|
|
92
|
-
} from "./shared/cli-
|
|
92
|
+
} from "./shared/cli-b6nehpgn.js";
|
|
93
93
|
|
|
94
94
|
// src/cli.ts
|
|
95
95
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -652,7 +652,8 @@ function rowToEvent(r) {
|
|
|
652
652
|
output: r.output,
|
|
653
653
|
outputBytes: r.output_bytes,
|
|
654
654
|
hasStructured: r.has_structured === 1,
|
|
655
|
-
truncated: r.truncated === 1
|
|
655
|
+
truncated: r.truncated === 1,
|
|
656
|
+
reason: r.reason ?? undefined
|
|
656
657
|
};
|
|
657
658
|
}
|
|
658
659
|
var SCHEMA_STATEMENTS = [
|
|
@@ -671,12 +672,14 @@ var SCHEMA_STATEMENTS = [
|
|
|
671
672
|
output TEXT NOT NULL,
|
|
672
673
|
output_bytes INTEGER NOT NULL,
|
|
673
674
|
has_structured INTEGER NOT NULL,
|
|
674
|
-
truncated INTEGER NOT NULL
|
|
675
|
+
truncated INTEGER NOT NULL,
|
|
676
|
+
reason TEXT
|
|
675
677
|
)`,
|
|
676
678
|
"CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts)",
|
|
677
679
|
"CREATE INDEX IF NOT EXISTS idx_events_tool ON events(tool)",
|
|
678
680
|
"CREATE INDEX IF NOT EXISTS idx_events_error ON events(is_error)"
|
|
679
681
|
];
|
|
682
|
+
var MIGRATIONS = ["ALTER TABLE events ADD COLUMN reason TEXT"];
|
|
680
683
|
|
|
681
684
|
class SqliteEventStore {
|
|
682
685
|
db;
|
|
@@ -686,13 +689,18 @@ class SqliteEventStore {
|
|
|
686
689
|
db.run("PRAGMA synchronous = NORMAL");
|
|
687
690
|
for (const stmt of SCHEMA_STATEMENTS)
|
|
688
691
|
db.run(stmt);
|
|
692
|
+
for (const m of MIGRATIONS) {
|
|
693
|
+
try {
|
|
694
|
+
db.run(m);
|
|
695
|
+
} catch {}
|
|
696
|
+
}
|
|
689
697
|
}
|
|
690
698
|
insert(e) {
|
|
691
699
|
this.db.query(`INSERT OR REPLACE INTO events
|
|
692
700
|
(id, ts, tool, title, risk, device, transport, duration_ms, is_error, error,
|
|
693
|
-
input, output, output_bytes, has_structured, truncated)
|
|
701
|
+
input, output, output_bytes, has_structured, truncated, reason)
|
|
694
702
|
VALUES ($id,$ts,$tool,$title,$risk,$device,$transport,$dur,$err,$errmsg,
|
|
695
|
-
$input,$output,$obytes,$structured,$trunc)`).run({
|
|
703
|
+
$input,$output,$obytes,$structured,$trunc,$reason)`).run({
|
|
696
704
|
$id: e.id,
|
|
697
705
|
$ts: e.ts,
|
|
698
706
|
$tool: e.tool,
|
|
@@ -707,7 +715,8 @@ class SqliteEventStore {
|
|
|
707
715
|
$output: e.output,
|
|
708
716
|
$obytes: e.outputBytes,
|
|
709
717
|
$structured: e.hasStructured ? 1 : 0,
|
|
710
|
-
$trunc: e.truncated ? 1 : 0
|
|
718
|
+
$trunc: e.truncated ? 1 : 0,
|
|
719
|
+
$reason: e.reason ?? null
|
|
711
720
|
});
|
|
712
721
|
}
|
|
713
722
|
query(filter) {
|
|
@@ -738,7 +747,7 @@ class SqliteEventStore {
|
|
|
738
747
|
params.$until = filter.until;
|
|
739
748
|
}
|
|
740
749
|
if (filter.q) {
|
|
741
|
-
where.push("(tool LIKE $q OR title LIKE $q OR input LIKE $q OR output LIKE $q OR error LIKE $q)");
|
|
750
|
+
where.push("(tool LIKE $q OR title LIKE $q OR input LIKE $q OR output LIKE $q OR error LIKE $q OR reason LIKE $q)");
|
|
742
751
|
params.$q = `%${filter.q}%`;
|
|
743
752
|
}
|
|
744
753
|
const clause = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
@@ -1134,7 +1143,7 @@ function computeStats(events, opts) {
|
|
|
1134
1143
|
// package.json
|
|
1135
1144
|
var package_default = {
|
|
1136
1145
|
name: "@usex/mikrotik-mcp",
|
|
1137
|
-
version: "3.
|
|
1146
|
+
version: "3.47.0",
|
|
1138
1147
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
1139
1148
|
keywords: [
|
|
1140
1149
|
"ai",
|
|
@@ -1210,6 +1219,8 @@ var package_default = {
|
|
|
1210
1219
|
"@modelcontextprotocol/ext-apps": "^1.7.4",
|
|
1211
1220
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
1212
1221
|
"@tikoci/centrs": "^0.1.0",
|
|
1222
|
+
figlet: "^1.11.0",
|
|
1223
|
+
"gradient-string": "^3.0.0",
|
|
1213
1224
|
"ipaddr.js": "^2.4.0",
|
|
1214
1225
|
ssh2: "^1.17.0",
|
|
1215
1226
|
zod: "^4.4.3"
|
|
@@ -1218,7 +1229,8 @@ var package_default = {
|
|
|
1218
1229
|
"@resvg/resvg-js": "^2.6.2",
|
|
1219
1230
|
"@tailwindcss/vite": "^4.3.2",
|
|
1220
1231
|
"@types/bun": "latest",
|
|
1221
|
-
"@types/
|
|
1232
|
+
"@types/figlet": "^1.7.0",
|
|
1233
|
+
"@types/node": "^26.1.0",
|
|
1222
1234
|
"@types/react": "^19.2.17",
|
|
1223
1235
|
"@types/react-dom": "^19.2.3",
|
|
1224
1236
|
"@types/ssh2": "^1.15.5",
|
|
@@ -1230,12 +1242,12 @@ var package_default = {
|
|
|
1230
1242
|
prettier: "^3.9.4",
|
|
1231
1243
|
react: "^19.2.7",
|
|
1232
1244
|
"react-dom": "^19.2.7",
|
|
1233
|
-
recharts: "^3.9.
|
|
1245
|
+
recharts: "^3.9.1",
|
|
1234
1246
|
"release-it": "^20.2.1",
|
|
1235
1247
|
"tailwind-merge": "^3.6.0",
|
|
1236
1248
|
tailwindcss: "^4.3.2",
|
|
1237
1249
|
vite: "npm:@voidzero-dev/vite-plus-core@latest",
|
|
1238
|
-
"vite-plus": "
|
|
1250
|
+
"vite-plus": "^0.2.2"
|
|
1239
1251
|
},
|
|
1240
1252
|
peerDependencies: {
|
|
1241
1253
|
typescript: "^6.0.3"
|
|
@@ -1274,6 +1286,44 @@ var SERVER_NAME = "mcp-mikrotik";
|
|
|
1274
1286
|
|
|
1275
1287
|
// src/observability/dashboard.ts
|
|
1276
1288
|
var SERVER_TAG2 = "mikrotik-mcp";
|
|
1289
|
+
var releaseCache = null;
|
|
1290
|
+
var RELEASE_CACHE_TTL = 15 * 60 * 1000;
|
|
1291
|
+
function compareVersions(a, b) {
|
|
1292
|
+
const pa = a.replace(/^v/, "").split(".").map(Number);
|
|
1293
|
+
const pb = b.replace(/^v/, "").split(".").map(Number);
|
|
1294
|
+
for (let i = 0;i < Math.max(pa.length, pb.length); i++) {
|
|
1295
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1296
|
+
if (diff !== 0)
|
|
1297
|
+
return diff;
|
|
1298
|
+
}
|
|
1299
|
+
return 0;
|
|
1300
|
+
}
|
|
1301
|
+
async function fetchLatestRelease() {
|
|
1302
|
+
if (releaseCache && Date.now() - releaseCache.fetchedAt < RELEASE_CACHE_TTL) {
|
|
1303
|
+
return releaseCache.data;
|
|
1304
|
+
}
|
|
1305
|
+
const res = await fetch("https://api.github.com/repos/ali-master/mikrotik-mcp/releases/latest", {
|
|
1306
|
+
headers: {
|
|
1307
|
+
accept: "application/vnd.github+json",
|
|
1308
|
+
"user-agent": "mikrotik-mcp-dashboard"
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
if (!res.ok)
|
|
1312
|
+
throw new Error(`GitHub API ${res.status}`);
|
|
1313
|
+
const gh = await res.json();
|
|
1314
|
+
const latestVersion = gh.tag_name.replace(/^v/, "");
|
|
1315
|
+
const data = {
|
|
1316
|
+
version: latestVersion,
|
|
1317
|
+
name: gh.name || `v${latestVersion}`,
|
|
1318
|
+
body: gh.body || "",
|
|
1319
|
+
publishedAt: gh.published_at,
|
|
1320
|
+
url: gh.html_url,
|
|
1321
|
+
isNewer: compareVersions(latestVersion, VERSION) > 0,
|
|
1322
|
+
currentVersion: VERSION
|
|
1323
|
+
};
|
|
1324
|
+
releaseCache = { data, fetchedAt: Date.now() };
|
|
1325
|
+
return data;
|
|
1326
|
+
}
|
|
1277
1327
|
var JSON_HEADERS = { "content-type": "application/json; charset=utf-8" };
|
|
1278
1328
|
function json(body, status = 200) {
|
|
1279
1329
|
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
@@ -2189,6 +2239,13 @@ async function runDashboard(cfg, transportLabel) {
|
|
|
2189
2239
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
2190
2240
|
});
|
|
2191
2241
|
}
|
|
2242
|
+
if (url.pathname === "/api/releases/latest") {
|
|
2243
|
+
try {
|
|
2244
|
+
return json(await fetchLatestRelease());
|
|
2245
|
+
} catch (e) {
|
|
2246
|
+
return json({ error: e instanceof Error ? e.message : "fetch failed" }, 502);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2192
2249
|
if (url.pathname === "/api/meta") {
|
|
2193
2250
|
const f = facets(db);
|
|
2194
2251
|
return json({
|
|
@@ -2624,6 +2681,59 @@ async function runStdio() {
|
|
|
2624
2681
|
logger.info(`MCP server ready on stdio \u2014 ${toolCount} tools, ${promptCount} prompts, ${uiViewCount} app views${readOnly ? " [READ-ONLY]" : ""}`);
|
|
2625
2682
|
}
|
|
2626
2683
|
|
|
2684
|
+
// src/cli-logo.ts
|
|
2685
|
+
import figlet from "figlet";
|
|
2686
|
+
import gradient from "gradient-string";
|
|
2687
|
+
var BRAND = gradient(["#9d7bff", "#3291ff", "#2dd4bf"]);
|
|
2688
|
+
var ESC = "\x1B[";
|
|
2689
|
+
var R = `${ESC}0m`;
|
|
2690
|
+
var DIM = `${ESC}90m`;
|
|
2691
|
+
var BOLD = `${ESC}1m`;
|
|
2692
|
+
var ITALIC = `${ESC}3m`;
|
|
2693
|
+
var rgb = (r, g, b) => `${ESC}38;2;${r};${g};${b}m`;
|
|
2694
|
+
var CV = rgb(157, 123, 255);
|
|
2695
|
+
var CS = rgb(50, 145, 255);
|
|
2696
|
+
var CT = rgb(45, 212, 191);
|
|
2697
|
+
var CA = rgb(245, 166, 35);
|
|
2698
|
+
async function printBanner() {
|
|
2699
|
+
if (!process.stderr.isTTY)
|
|
2700
|
+
return;
|
|
2701
|
+
try {
|
|
2702
|
+
const art = figlet.textSync("MikroTik MCP", { font: "Small" });
|
|
2703
|
+
const colored = BRAND.multiline(art);
|
|
2704
|
+
process.stderr.write(`${colored}
|
|
2705
|
+
`);
|
|
2706
|
+
const author = typeof package_default.author === "object" ? package_default.author : { name: String(package_default.author) };
|
|
2707
|
+
const site = (package_default.homepage ?? "").replace(/^https?:\/\//, "");
|
|
2708
|
+
const w = 56;
|
|
2709
|
+
const hr = "\u2500".repeat(w);
|
|
2710
|
+
const pad = (s, vis) => `${s}${" ".repeat(Math.max(0, w - 2 - vis))}`;
|
|
2711
|
+
const r1Label = `\u25CF v${VERSION}`;
|
|
2712
|
+
const r1Mid = "RouterOS over SSH";
|
|
2713
|
+
const r1End = "MCP Protocol";
|
|
2714
|
+
const r1Text = ` ${CV}\u25CF${R} ${BOLD}v${VERSION}${R}${DIM} \u2502 ${CS}${r1Mid}${R}${DIM} \u2502 ${CT}${r1End}${R}`;
|
|
2715
|
+
const r1Vis = r1Label.length + 2 + r1Mid.length + 4 + r1End.length + 4;
|
|
2716
|
+
const r2Name = author.name ?? "Unknown";
|
|
2717
|
+
const r2License = package_default.license ?? "MIT";
|
|
2718
|
+
const r2Text = ` ${CA}${r2Name}${R}${DIM} \xB7 ${ITALIC}${r2License}${R}${DIM} \xB7 ${CS}${site}${R}`;
|
|
2719
|
+
const r2Vis = r2Name.length + 2 + r2License.length + 4 + site.length + 4;
|
|
2720
|
+
const box = [
|
|
2721
|
+
`${DIM} \u250C${hr}\u2510${R}`,
|
|
2722
|
+
`${DIM} \u2502${R}${pad(r1Text, r1Vis)}${DIM}\u2502${R}`,
|
|
2723
|
+
`${DIM} \u251C${hr}\u2524${R}`,
|
|
2724
|
+
`${DIM} \u2502${R}${pad(r2Text, r2Vis)}${DIM}\u2502${R}`,
|
|
2725
|
+
`${DIM} \u2514${hr}\u2518${R}`
|
|
2726
|
+
].join(`
|
|
2727
|
+
`);
|
|
2728
|
+
process.stderr.write(`${box}
|
|
2729
|
+
|
|
2730
|
+
`);
|
|
2731
|
+
} catch {
|
|
2732
|
+
process.stderr.write(`${DIM}MikroTik MCP v${VERSION}${R}
|
|
2733
|
+
`);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2627
2737
|
// src/cli.ts
|
|
2628
2738
|
var HELP = `${SERVER_NAME} v${VERSION} \u2014 MikroTik RouterOS MCP server
|
|
2629
2739
|
|
|
@@ -2766,6 +2876,7 @@ async function main() {
|
|
|
2766
2876
|
}
|
|
2767
2877
|
const cfg = loadConfig();
|
|
2768
2878
|
setConfig(cfg);
|
|
2879
|
+
await printBanner();
|
|
2769
2880
|
warnIfPlaintextPasswordInContainer(Object.values(cfg.devices).some((d) => !!d.password));
|
|
2770
2881
|
const deviceNames = Object.keys(cfg.devices);
|
|
2771
2882
|
logger.info(`Starting ${SERVER_NAME} v${VERSION} (transport=${cfg.mcp.transport}, ` + `devices=${deviceNames.length === 1 ? deviceNames[0] : deviceNames.join("/")}, ` + `ssh-pool=${cfg.ssh.keepAlive ? "on" : "off"})`);
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
resolveDeviceName,
|
|
22
22
|
selectToolModules,
|
|
23
23
|
setConfig
|
|
24
|
-
} from "./shared/library-
|
|
24
|
+
} from "./shared/library-wppv8kdx.js";
|
|
25
25
|
// src/server.ts
|
|
26
26
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
27
27
|
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -131,7 +131,7 @@ function registerPrompts(server) {
|
|
|
131
131
|
// package.json
|
|
132
132
|
var package_default = {
|
|
133
133
|
name: "@usex/mikrotik-mcp",
|
|
134
|
-
version: "3.
|
|
134
|
+
version: "3.47.0",
|
|
135
135
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
136
136
|
keywords: [
|
|
137
137
|
"ai",
|
|
@@ -207,6 +207,8 @@ var package_default = {
|
|
|
207
207
|
"@modelcontextprotocol/ext-apps": "^1.7.4",
|
|
208
208
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
209
209
|
"@tikoci/centrs": "^0.1.0",
|
|
210
|
+
figlet: "^1.11.0",
|
|
211
|
+
"gradient-string": "^3.0.0",
|
|
210
212
|
"ipaddr.js": "^2.4.0",
|
|
211
213
|
ssh2: "^1.17.0",
|
|
212
214
|
zod: "^4.4.3"
|
|
@@ -215,7 +217,8 @@ var package_default = {
|
|
|
215
217
|
"@resvg/resvg-js": "^2.6.2",
|
|
216
218
|
"@tailwindcss/vite": "^4.3.2",
|
|
217
219
|
"@types/bun": "latest",
|
|
218
|
-
"@types/
|
|
220
|
+
"@types/figlet": "^1.7.0",
|
|
221
|
+
"@types/node": "^26.1.0",
|
|
219
222
|
"@types/react": "^19.2.17",
|
|
220
223
|
"@types/react-dom": "^19.2.3",
|
|
221
224
|
"@types/ssh2": "^1.15.5",
|
|
@@ -227,12 +230,12 @@ var package_default = {
|
|
|
227
230
|
prettier: "^3.9.4",
|
|
228
231
|
react: "^19.2.7",
|
|
229
232
|
"react-dom": "^19.2.7",
|
|
230
|
-
recharts: "^3.9.
|
|
233
|
+
recharts: "^3.9.1",
|
|
231
234
|
"release-it": "^20.2.1",
|
|
232
235
|
"tailwind-merge": "^3.6.0",
|
|
233
236
|
tailwindcss: "^4.3.2",
|
|
234
237
|
vite: "npm:@voidzero-dev/vite-plus-core@latest",
|
|
235
|
-
"vite-plus": "
|
|
238
|
+
"vite-plus": "^0.2.2"
|
|
236
239
|
},
|
|
237
240
|
peerDependencies: {
|
|
238
241
|
typescript: "^6.0.3"
|
|
@@ -1990,7 +1990,8 @@ function buildEvent(raw, id, opts) {
|
|
|
1990
1990
|
output,
|
|
1991
1991
|
outputBytes,
|
|
1992
1992
|
hasStructured: raw.hasStructured,
|
|
1993
|
-
truncated
|
|
1993
|
+
truncated,
|
|
1994
|
+
reason: raw.reason
|
|
1994
1995
|
};
|
|
1995
1996
|
}
|
|
1996
1997
|
|
|
@@ -2102,13 +2103,20 @@ function defineTool(def) {
|
|
|
2102
2103
|
const multiDevice = !!deviceNames && deviceNames.length > 1;
|
|
2103
2104
|
const { ui, auto } = appViews === false ? { ui: undefined, auto: false } : effectiveUi(def);
|
|
2104
2105
|
const selectorNames = multiDevice && deviceNames ? [...new Set([...deviceNames, ...deviceAliases ?? []])] : [];
|
|
2105
|
-
|
|
2106
|
+
let inputSchema = multiDevice ? {
|
|
2106
2107
|
...def.inputSchema,
|
|
2107
2108
|
device: z2.enum(selectorNames).optional().describe(deviceSelectorDescription(selectorNames, deviceDirectory2))
|
|
2108
2109
|
} : def.inputSchema;
|
|
2109
2110
|
const risk = riskOf(def.annotations);
|
|
2111
|
+
const requiresReason = risk === "DANGEROUS";
|
|
2112
|
+
if (requiresReason) {
|
|
2113
|
+
inputSchema = {
|
|
2114
|
+
...inputSchema,
|
|
2115
|
+
reason: z2.string().describe("Your rationale for performing this high-impact action \u2014 explain in one concise " + "sentence why this operation is necessary. Logged for audit and accountability.")
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2110
2118
|
const callback = async (args) => {
|
|
2111
|
-
const { device, ...rest } = args;
|
|
2119
|
+
const { device, reason, ...rest } = args;
|
|
2112
2120
|
const deviceName = typeof device === "string" ? device : undefined;
|
|
2113
2121
|
const ctx = createContext(sendLog, deviceName);
|
|
2114
2122
|
const deviceStamp = multiDevice && risk !== "READ" ? (() => {
|
|
@@ -2174,7 +2182,8 @@ function defineTool(def) {
|
|
|
2174
2182
|
error: errMsg,
|
|
2175
2183
|
args: rest,
|
|
2176
2184
|
output: outText,
|
|
2177
|
-
hasStructured
|
|
2185
|
+
hasStructured,
|
|
2186
|
+
reason: typeof reason === "string" ? reason : undefined
|
|
2178
2187
|
});
|
|
2179
2188
|
}
|
|
2180
2189
|
}
|
|
@@ -6310,7 +6319,7 @@ var cache = null;
|
|
|
6310
6319
|
async function gateway() {
|
|
6311
6320
|
if (cache)
|
|
6312
6321
|
return cache;
|
|
6313
|
-
const { moduleCatalog } = await import("./cli-
|
|
6322
|
+
const { moduleCatalog } = await import("./cli-je6h62bk.js");
|
|
6314
6323
|
const forIndex = [];
|
|
6315
6324
|
const byName = new Map;
|
|
6316
6325
|
for (const mod of moduleCatalog) {
|
|
@@ -1971,7 +1971,8 @@ function buildEvent(raw, id, opts) {
|
|
|
1971
1971
|
output,
|
|
1972
1972
|
outputBytes,
|
|
1973
1973
|
hasStructured: raw.hasStructured,
|
|
1974
|
-
truncated
|
|
1974
|
+
truncated,
|
|
1975
|
+
reason: raw.reason
|
|
1975
1976
|
};
|
|
1976
1977
|
}
|
|
1977
1978
|
|
|
@@ -2065,13 +2066,20 @@ function defineTool(def) {
|
|
|
2065
2066
|
const multiDevice = !!deviceNames && deviceNames.length > 1;
|
|
2066
2067
|
const { ui, auto } = appViews === false ? { ui: undefined, auto: false } : effectiveUi(def);
|
|
2067
2068
|
const selectorNames = multiDevice && deviceNames ? [...new Set([...deviceNames, ...deviceAliases ?? []])] : [];
|
|
2068
|
-
|
|
2069
|
+
let inputSchema = multiDevice ? {
|
|
2069
2070
|
...def.inputSchema,
|
|
2070
2071
|
device: z2.enum(selectorNames).optional().describe(deviceSelectorDescription(selectorNames, deviceDirectory2))
|
|
2071
2072
|
} : def.inputSchema;
|
|
2072
2073
|
const risk = riskOf(def.annotations);
|
|
2074
|
+
const requiresReason = risk === "DANGEROUS";
|
|
2075
|
+
if (requiresReason) {
|
|
2076
|
+
inputSchema = {
|
|
2077
|
+
...inputSchema,
|
|
2078
|
+
reason: z2.string().describe("Your rationale for performing this high-impact action \u2014 explain in one concise " + "sentence why this operation is necessary. Logged for audit and accountability.")
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2073
2081
|
const callback = async (args) => {
|
|
2074
|
-
const { device, ...rest } = args;
|
|
2082
|
+
const { device, reason, ...rest } = args;
|
|
2075
2083
|
const deviceName = typeof device === "string" ? device : undefined;
|
|
2076
2084
|
const ctx = createContext(sendLog, deviceName);
|
|
2077
2085
|
const deviceStamp = multiDevice && risk !== "READ" ? (() => {
|
|
@@ -2137,7 +2145,8 @@ function defineTool(def) {
|
|
|
2137
2145
|
error: errMsg,
|
|
2138
2146
|
args: rest,
|
|
2139
2147
|
output: outText,
|
|
2140
|
-
hasStructured
|
|
2148
|
+
hasStructured,
|
|
2149
|
+
reason: typeof reason === "string" ? reason : undefined
|
|
2141
2150
|
});
|
|
2142
2151
|
}
|
|
2143
2152
|
}
|
|
@@ -6251,7 +6260,7 @@ var cache = null;
|
|
|
6251
6260
|
async function gateway() {
|
|
6252
6261
|
if (cache)
|
|
6253
6262
|
return cache;
|
|
6254
|
-
const { moduleCatalog } = await import("./library-
|
|
6263
|
+
const { moduleCatalog } = await import("./library-fnp716x3.js");
|
|
6255
6264
|
const forIndex = [];
|
|
6256
6265
|
const byName = new Map;
|
|
6257
6266
|
for (const mod of moduleCatalog) {
|
package/dist/ui/aaa.html
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
<meta name="color-scheme" content="light dark" />
|
|
7
7
|
<title>MikroTik RADIUS & User Manager</title>
|
|
8
8
|
<script type="module">
|
|
9
|
-
import{i as e}from"./app-DU6NBjf_.js";import{a as t,s as n,t as r}from"./kit-CoebAMdu.js";var i=[{slug:`radius`,label:`RADIUS Servers`,idKey:`.id`,toggle:!0,columns:[{key:`address`,label:`Address`},{key:`service`,label:`Service`},{key:`authentication-port`,label:`Auth`},{key:`accounting-port`,label:`Acct`},{key:`_status`,label:`Status`}],fields:[{key:`address`,label:`Address`,required:!0},{key:`secret`,label:`Secret`,type:`password`,required:!0},{key:`service`,label:`Service`,required:!0,placeholder:`login,ppp,hotspot,…`},{key:`authentication-port`,label:`Auth port`,type:`number`,placeholder:`1812`},{key:`accounting-port`,label:`Acct port`,type:`number`,placeholder:`1813`},{key:`timeout`,label:`Timeout`,placeholder:`300ms`},{key:`src-address`,label:`Src address`},{key:`realm`,label:`Realm`},{key:`called-id`,label:`Called ID`},{key:`domain`,label:`Domain`},{key:`protocol`,label:`Protocol`,type:`select`,options:[`udp`,`radsec`]},{key:`certificate`,label:`Certificate`},{key:`accounting-backup`,label:`Acct backup`,type:`bool`},{key:`comment`,label:`Comment`},{key:`disabled`,label:`Disabled`,type:`bool`}]},{slug:`um-users`,label:`Users`,idKey:`name`,toggle:!0,columns:[{key:`name`,label:`Name`},{key:`group`,label:`Group`},{key:`shared-users`,label:`Shared`},{key:`comment`,label:`Comment`},{key:`_status`,label:`Status`}],fields:[{key:`name`,label:`Name`,required:!0},{key:`password`,label:`Password`,type:`password`,required:!0},{key:`group`,label:`Group`},{key:`shared-users`,label:`Shared users`,type:`number`},{key:`attributes`,label:`Attributes`},{key:`caller-id`,label:`Caller ID`},{key:`otp-secret`,label:`OTP secret`,type:`password`},{key:`comment`,label:`Comment`},{key:`disabled`,label:`Disabled`,type:`bool`}]},{slug:`um-profiles`,label:`Profiles`,idKey:`name`,columns:[{key:`name`,label:`Name`},{key:`validity`,label:`Validity`},{key:`price`,label:`Price`},{key:`starts-when`,label:`Starts`}],fields:[{key:`name`,label:`Name`,required:!0},{key:`name-for-users`,label:`Display name`},{key:`validity`,label:`Validity`,placeholder:`30d`},{key:`price`,label:`Price`,type:`number`},{key:`starts-when`,label:`Starts when`,type:`select`,options:[`assigned`,`first-auth`]},{key:`override-shared-users`,label:`Override shared`},{key:`comment`,label:`Comment`}]},{slug:`um-limitations`,label:`Limitations`,idKey:`name`,columns:[{key:`name`,label:`Name`},{key:`rate-limit-rx`,label:`Rate ↓`},{key:`rate-limit-tx`,label:`Rate ↑`},{key:`transfer-limit`,label:`Transfer`},{key:`uptime-limit`,label:`Uptime`}],fields:[{key:`name`,label:`Name`,required:!0},{key:`rate-limit-rx`,label:`Download rate`,placeholder:`10M`},{key:`rate-limit-tx`,label:`Upload rate`,placeholder:`10M`},{key:`rate-limit-min-rx`,label:`Min ↓ (CIR)`},{key:`rate-limit-min-tx`,label:`Min ↑ (CIR)`},{key:`rate-limit-burst-rx`,label:`Burst ↓`},{key:`rate-limit-burst-tx`,label:`Burst ↑`},{key:`rate-limit-priority`,label:`Priority`,type:`number`},{key:`transfer-limit`,label:`Transfer cap`,placeholder:`10G`},{key:`uptime-limit`,label:`Uptime cap`,placeholder:`1d`},{key:`comment`,label:`Comment`}]},{slug:`um-routers`,label:`NAS Clients`,idKey:`name`,toggle:!0,columns:[{key:`name`,label:`Name`},{key:`address`,label:`Address`},{key:`coa-port`,label:`CoA`},{key:`_status`,label:`Status`}],fields:[{key:`name`,label:`Name`,required:!0},{key:`address`,label:`Address`,required:!0},{key:`shared-secret`,label:`Shared secret`,type:`password`,required:!0},{key:`coa-port`,label:`CoA port`,type:`number`},{key:`protocol`,label:`Protocol`},{key:`comment`,label:`Comment`},{key:`disabled`,label:`Disabled`,type:`bool`}]},{slug:`um-user-profiles`,label:`Assignments`,idKey:`.id`,addOnly:!0,columns:[{key:`user`,label:`User`},{key:`profile`,label:`Profile`},{key:`state`,label:`State`}],fields:[{key:`user`,label:`User`,required:!0},{key:`profile`,label:`Profile`,required:!0}]},{slug:`um-sessions`,label:`Sessions`,idKey:`.id`,readonly:!0,columns:[{key:`user`,label:`User`},{key:`calling-station-id`,label:`Caller`},{key:`started`,label:`Started`},{key:`uptime`,label:`Uptime`},{key:`status`,label:`Status`}],fields:[]}],a={slug:`settings`,label:`Settings`},o=e=>i.find(t=>t.slug===e),s=e=>(e.flags??``).includes(`X`)||e.disabled===`yes`,c=document.getElementById(`app`),l=new e({name:`mikrotik-aaa`,version:`1.0.0`}),u=`radius`,d=!0,f=[],p=null,m={},h=null,g=!1,_=null;function v(e){return e?.structuredContent}function y(e){if(!e||e.__mikrotikView!==`aaa-section`)return;typeof e.slug==`string`&&(u=e.slug),d=e.available!==!1,f=e.rows??[];let t=e.lastOp;_=t&&!t.ok?t.message:null}function b(e){if(!e||e.__mikrotikView!==`aaa-settings`)return;h={radiusIncoming:e.radiusIncoming??{},umAvailable:e.umAvailable!==!1,umSettings:e.umSettings??{}};let t=e.lastOp;_=t&&!t.ok?t.message:null}async function x(e){u=e,p=null,_=null,g=!0,j();try{y(v(await l.callServerTool({name:`get_aaa_section`,arguments:{slug:e}})))}catch(e){_=String(e)}finally{g=!1,j()}}async function S(){u=`settings`,_=null,g=!0,j();try{b(v(await l.callServerTool({name:`get_aaa_settings`,arguments:{}})))}catch(e){_=String(e)}finally{g=!1,j()}}async function C(e,t){if(!g){g=!0,_=null,j();try{y(v(await l.callServerTool({name:`aaa_mutate`,arguments:{op:e,slug:u,...t}}))),p=null}catch(e){_=String(e)}finally{g=!1,j()}}}async function w(e,t){if(!g){g=!0,_=null,j();try{b(v(await l.callServerTool({name:`set_aaa_settings`,arguments:{target:e,fields:t}})))}catch(e){_=String(e)}finally{g=!1,j()}}}function T(e){if(e.type===`bool`||e.type===`select`){let n=t(`select`,{class:`aaa-input`}),r=e.type===`bool`?[``,`no`,`yes`]:[``,...e.options??[]];for(let e of r){let r=t(`option`,{value:e},e||`—`);n.append(r)}return n.value=m[e.key]??``,n.addEventListener(`change`,()=>{m[e.key]=n.value}),n}let n=t(`input`,{class:`aaa-input`,type:e.type===`password`?`password`:e.type===`number`?`number`:`text`,placeholder:e.placeholder??(e.type===`password`?`(unchanged)`:``)});return n.value=m[e.key]??``,n.addEventListener(`input`,()=>{m[e.key]=n.value}),n}function E(e){let n=t(`div`,{class:`aaa-form-grid`},...e.fields.map(e=>t(`label`,{class:`aaa-field`},t(`span`,{class:`aaa-field-label`},`${e.label}${e.required&&p===`new`?` *`:``}`),T(e)))),i=r(p===`new`?`Create`:`Save`,()=>{p===`new`?C(`add`,{fields:{...m}}):C(`update`,{id:p,fields:{...m}})},{class:`primary`}),a=r(`Cancel`,()=>{p=null,j()});return t(`div`,{class:`aaa-form`},t(`div`,{class:`aaa-form-title`},p===`new`?`New ${e.label}`:`Edit ${p}`),n,t(`div`,{class:`aaa-form-actions`},i,a))}function D(e,t){p=t[e.idKey],m={};for(let n of e.fields)n.type!==`password`&&(n.key===`disabled`?m.disabled=s(t)?`yes`:`no`:t[n.key]!=null&&(m[n.key]=t[n.key]));j()}function O(e){let n=t(`div`,{class:`aaa-row aaa-head`},...e.columns.map(e=>t(`span`,{},e.label)),t(`span`,{class:`aaa-actions`},e.readonly?``:`Actions`)),i=f.map(n=>{let i=n[e.idKey],a=s(n),o=e.columns.map(e=>e.key===`_status`?t(`span`,{},t(`span`,{class:`aaa-badge ${a?`off`:`on`}`},a?`disabled`:`enabled`)):t(`span`,{class:`aaa-cell`,title:n[e.key]??``},n[e.key]??``)),c=[];return e.readonly||(e.toggle&&c.push(r(a?`Enable`:`Disable`,()=>void C(`toggle`,{id:i,enable:a}))),e.addOnly||c.push(r(`Edit`,()=>D(e,n))),c.push(r(`Remove`,()=>void C(`remove`,{id:i}),{class:`danger`}))),t(`div`,{class:`aaa-row${a?` is-off`:``}`},...o,t(`span`,{class:`aaa-actions`},...c))});return t(`div`,{class:`aaa-table`,style:`--cols:${e.columns.length}`},n,...i)}function k(e,n,i,a,o){let s={},c=t(`div`,{class:`aaa-form-grid`},...i.map(e=>{let r=t(`label`,{class:`aaa-field`},t(`span`,{class:`aaa-field-label`},e.label)),i;if(e.type===`bool`){let n=t(`select`,{class:`aaa-input`});for(let e of[``,`no`,`yes`])n.append(t(`option`,{value:e},e||`(unchanged)`));n.addEventListener(`change`,()=>{s[e.key]=n.value}),i=n}else{let r=t(`input`,{class:`aaa-input`,type:e.type===`number`?`number`:`text`,placeholder:n[e.key]??e.placeholder??``});r.addEventListener(`input`,()=>{s[e.key]=r.value}),i=r}return r.append(i),r})),l=t(`div`,{class:`aaa-current`},i.map(e=>`${e.label}: ${n[e.key]??`—`}`).join(` · `)),u=t(`div`,{class:`aaa-form-actions`},r(`Save`,()=>void w(a,s),{class:`primary`}),o??null);return t(`div`,{class:`aaa-card`},t(`div`,{class:`aaa-form-title`},e),l,c,u)}function A(){return h?t(`div`,{class:`aaa-settings`},k(`RADIUS Incoming (CoA listener)`,h.radiusIncoming,[{key:`accept`,label:`Accept CoA`,type:`bool`},{key:`port`,label:`CoA port`,type:`number`,placeholder:`3799`}],`radius-incoming`,r(`Reset counters`,()=>void w(`radius-reset-counters`,{}))),h.umAvailable?k(`User Manager (built-in RADIUS server)`,h.umSettings,[{key:`enabled`,label:`Enabled`,type:`bool`},{key:`use-profiles`,label:`Use profiles`,type:`bool`},{key:`certificate`,label:`Certificate`},{key:`authentication-port`,label:`Auth port`,type:`number`},{key:`accounting-port`,label:`Acct port`,type:`number`}],`um-settings`):t(`div`,{class:`aaa-card`},t(`div`,{class:`aaa-form-title`},`User Manager`),t(`div`,{class:`muted`},`The user-manager package is not installed on this device.`))):t(`div`,{class:`muted`},`Loading settings…`)}function j(){let e=t(`div`,{class:`aaa-tabs`},...[...i,a].map(e=>t(`button`,{class:`aaa-tab${u===e.slug?` is-active`:``}`},e.label)));[...e.children].forEach((e,t)=>{let n=[...i,a][t];e.addEventListener(`click`,()=>{n.slug===`settings`?S():x(n.slug)})});let n=[t(`div`,{class:`aaa-titlebar`},t(`div`,{class:`aaa-title`},`RADIUS & User Manager`)),e];if(_&&n.push(t(`div`,{class:`aaa-error`},_)),u===`settings`)n.push(A());else{let e=o(u);if(!e)n.push(t(`div`,{class:`muted`},`Unknown section.`));else if(!d)n.push(t(`div`,{class:`aaa-notice`},`User Manager is not installed on this device. Install the user-manager package (System → Packages) and reboot.`));else{let i=t(`div`,{class:`aaa-bar`});i.append(t(`span`,{class:`muted`},`${f.length} row(s)`)),i.append(t(`span`,{class:`spacer`})),i.append(r(`↻ Refresh`,()=>void x(u))),e.readonly||i.append(r(`+ Add`,()=>{p=`new`,m={},j()},{class:`primary`})),n.push(i),p&&n.push(E(e)),n.push(f.length||!e?O(e):t(`div`,{class:`muted`},`No rows.`))}}c.replaceChildren(t(`div`,{class:`aaa-wrap`},...n))}l.ontoolresult=e=>{let t=e.structuredContent;t?.__mikrotikView===`aaa-settings`?b(t):y(t),j()},l.ontoolinput=()=>j(),n(l),j(),l.connect().catch(e=>console.error(`[aaa] connect failed`,e));
|
|
9
|
+
import{i as e}from"./app-BbgpcVv8.js";import{a as t,s as n,t as r}from"./kit-DyrRNi79.js";var i=[{slug:`radius`,label:`RADIUS Servers`,idKey:`.id`,toggle:!0,columns:[{key:`address`,label:`Address`},{key:`service`,label:`Service`},{key:`authentication-port`,label:`Auth`},{key:`accounting-port`,label:`Acct`},{key:`_status`,label:`Status`}],fields:[{key:`address`,label:`Address`,required:!0},{key:`secret`,label:`Secret`,type:`password`,required:!0},{key:`service`,label:`Service`,required:!0,placeholder:`login,ppp,hotspot,…`},{key:`authentication-port`,label:`Auth port`,type:`number`,placeholder:`1812`},{key:`accounting-port`,label:`Acct port`,type:`number`,placeholder:`1813`},{key:`timeout`,label:`Timeout`,placeholder:`300ms`},{key:`src-address`,label:`Src address`},{key:`realm`,label:`Realm`},{key:`called-id`,label:`Called ID`},{key:`domain`,label:`Domain`},{key:`protocol`,label:`Protocol`,type:`select`,options:[`udp`,`radsec`]},{key:`certificate`,label:`Certificate`},{key:`accounting-backup`,label:`Acct backup`,type:`bool`},{key:`comment`,label:`Comment`},{key:`disabled`,label:`Disabled`,type:`bool`}]},{slug:`um-users`,label:`Users`,idKey:`name`,toggle:!0,columns:[{key:`name`,label:`Name`},{key:`group`,label:`Group`},{key:`shared-users`,label:`Shared`},{key:`comment`,label:`Comment`},{key:`_status`,label:`Status`}],fields:[{key:`name`,label:`Name`,required:!0},{key:`password`,label:`Password`,type:`password`,required:!0},{key:`group`,label:`Group`},{key:`shared-users`,label:`Shared users`,type:`number`},{key:`attributes`,label:`Attributes`},{key:`caller-id`,label:`Caller ID`},{key:`otp-secret`,label:`OTP secret`,type:`password`},{key:`comment`,label:`Comment`},{key:`disabled`,label:`Disabled`,type:`bool`}]},{slug:`um-profiles`,label:`Profiles`,idKey:`name`,columns:[{key:`name`,label:`Name`},{key:`validity`,label:`Validity`},{key:`price`,label:`Price`},{key:`starts-when`,label:`Starts`}],fields:[{key:`name`,label:`Name`,required:!0},{key:`name-for-users`,label:`Display name`},{key:`validity`,label:`Validity`,placeholder:`30d`},{key:`price`,label:`Price`,type:`number`},{key:`starts-when`,label:`Starts when`,type:`select`,options:[`assigned`,`first-auth`]},{key:`override-shared-users`,label:`Override shared`},{key:`comment`,label:`Comment`}]},{slug:`um-limitations`,label:`Limitations`,idKey:`name`,columns:[{key:`name`,label:`Name`},{key:`rate-limit-rx`,label:`Rate ↓`},{key:`rate-limit-tx`,label:`Rate ↑`},{key:`transfer-limit`,label:`Transfer`},{key:`uptime-limit`,label:`Uptime`}],fields:[{key:`name`,label:`Name`,required:!0},{key:`rate-limit-rx`,label:`Download rate`,placeholder:`10M`},{key:`rate-limit-tx`,label:`Upload rate`,placeholder:`10M`},{key:`rate-limit-min-rx`,label:`Min ↓ (CIR)`},{key:`rate-limit-min-tx`,label:`Min ↑ (CIR)`},{key:`rate-limit-burst-rx`,label:`Burst ↓`},{key:`rate-limit-burst-tx`,label:`Burst ↑`},{key:`rate-limit-priority`,label:`Priority`,type:`number`},{key:`transfer-limit`,label:`Transfer cap`,placeholder:`10G`},{key:`uptime-limit`,label:`Uptime cap`,placeholder:`1d`},{key:`comment`,label:`Comment`}]},{slug:`um-routers`,label:`NAS Clients`,idKey:`name`,toggle:!0,columns:[{key:`name`,label:`Name`},{key:`address`,label:`Address`},{key:`coa-port`,label:`CoA`},{key:`_status`,label:`Status`}],fields:[{key:`name`,label:`Name`,required:!0},{key:`address`,label:`Address`,required:!0},{key:`shared-secret`,label:`Shared secret`,type:`password`,required:!0},{key:`coa-port`,label:`CoA port`,type:`number`},{key:`protocol`,label:`Protocol`},{key:`comment`,label:`Comment`},{key:`disabled`,label:`Disabled`,type:`bool`}]},{slug:`um-user-profiles`,label:`Assignments`,idKey:`.id`,addOnly:!0,columns:[{key:`user`,label:`User`},{key:`profile`,label:`Profile`},{key:`state`,label:`State`}],fields:[{key:`user`,label:`User`,required:!0},{key:`profile`,label:`Profile`,required:!0}]},{slug:`um-sessions`,label:`Sessions`,idKey:`.id`,readonly:!0,columns:[{key:`user`,label:`User`},{key:`calling-station-id`,label:`Caller`},{key:`started`,label:`Started`},{key:`uptime`,label:`Uptime`},{key:`status`,label:`Status`}],fields:[]}],a={slug:`settings`,label:`Settings`},o=e=>i.find(t=>t.slug===e),s=e=>(e.flags??``).includes(`X`)||e.disabled===`yes`,c=document.getElementById(`app`),l=new e({name:`mikrotik-aaa`,version:`1.0.0`}),u=`radius`,d=!0,f=[],p=null,m={},h=null,g=!1,_=null;function v(e){return e?.structuredContent}function y(e){if(!e||e.__mikrotikView!==`aaa-section`)return;typeof e.slug==`string`&&(u=e.slug),d=e.available!==!1,f=e.rows??[];let t=e.lastOp;_=t&&!t.ok?t.message:null}function b(e){if(!e||e.__mikrotikView!==`aaa-settings`)return;h={radiusIncoming:e.radiusIncoming??{},umAvailable:e.umAvailable!==!1,umSettings:e.umSettings??{}};let t=e.lastOp;_=t&&!t.ok?t.message:null}async function x(e){u=e,p=null,_=null,g=!0,j();try{y(v(await l.callServerTool({name:`get_aaa_section`,arguments:{slug:e}})))}catch(e){_=String(e)}finally{g=!1,j()}}async function S(){u=`settings`,_=null,g=!0,j();try{b(v(await l.callServerTool({name:`get_aaa_settings`,arguments:{}})))}catch(e){_=String(e)}finally{g=!1,j()}}async function C(e,t){if(!g){g=!0,_=null,j();try{y(v(await l.callServerTool({name:`aaa_mutate`,arguments:{op:e,slug:u,...t}}))),p=null}catch(e){_=String(e)}finally{g=!1,j()}}}async function w(e,t){if(!g){g=!0,_=null,j();try{b(v(await l.callServerTool({name:`set_aaa_settings`,arguments:{target:e,fields:t}})))}catch(e){_=String(e)}finally{g=!1,j()}}}function T(e){if(e.type===`bool`||e.type===`select`){let n=t(`select`,{class:`aaa-input`}),r=e.type===`bool`?[``,`no`,`yes`]:[``,...e.options??[]];for(let e of r){let r=t(`option`,{value:e},e||`—`);n.append(r)}return n.value=m[e.key]??``,n.addEventListener(`change`,()=>{m[e.key]=n.value}),n}let n=t(`input`,{class:`aaa-input`,type:e.type===`password`?`password`:e.type===`number`?`number`:`text`,placeholder:e.placeholder??(e.type===`password`?`(unchanged)`:``)});return n.value=m[e.key]??``,n.addEventListener(`input`,()=>{m[e.key]=n.value}),n}function E(e){let n=t(`div`,{class:`aaa-form-grid`},...e.fields.map(e=>t(`label`,{class:`aaa-field`},t(`span`,{class:`aaa-field-label`},`${e.label}${e.required&&p===`new`?` *`:``}`),T(e)))),i=r(p===`new`?`Create`:`Save`,()=>{p===`new`?C(`add`,{fields:{...m}}):C(`update`,{id:p,fields:{...m}})},{class:`primary`}),a=r(`Cancel`,()=>{p=null,j()});return t(`div`,{class:`aaa-form`},t(`div`,{class:`aaa-form-title`},p===`new`?`New ${e.label}`:`Edit ${p}`),n,t(`div`,{class:`aaa-form-actions`},i,a))}function D(e,t){p=t[e.idKey],m={};for(let n of e.fields)n.type!==`password`&&(n.key===`disabled`?m.disabled=s(t)?`yes`:`no`:t[n.key]!=null&&(m[n.key]=t[n.key]));j()}function O(e){let n=t(`div`,{class:`aaa-row aaa-head`},...e.columns.map(e=>t(`span`,{},e.label)),t(`span`,{class:`aaa-actions`},e.readonly?``:`Actions`)),i=f.map(n=>{let i=n[e.idKey],a=s(n),o=e.columns.map(e=>e.key===`_status`?t(`span`,{},t(`span`,{class:`aaa-badge ${a?`off`:`on`}`},a?`disabled`:`enabled`)):t(`span`,{class:`aaa-cell`,title:n[e.key]??``},n[e.key]??``)),c=[];return e.readonly||(e.toggle&&c.push(r(a?`Enable`:`Disable`,()=>void C(`toggle`,{id:i,enable:a}))),e.addOnly||c.push(r(`Edit`,()=>D(e,n))),c.push(r(`Remove`,()=>void C(`remove`,{id:i}),{class:`danger`}))),t(`div`,{class:`aaa-row${a?` is-off`:``}`},...o,t(`span`,{class:`aaa-actions`},...c))});return t(`div`,{class:`aaa-table`,style:`--cols:${e.columns.length}`},n,...i)}function k(e,n,i,a,o){let s={},c=t(`div`,{class:`aaa-form-grid`},...i.map(e=>{let r=t(`label`,{class:`aaa-field`},t(`span`,{class:`aaa-field-label`},e.label)),i;if(e.type===`bool`){let n=t(`select`,{class:`aaa-input`});for(let e of[``,`no`,`yes`])n.append(t(`option`,{value:e},e||`(unchanged)`));n.addEventListener(`change`,()=>{s[e.key]=n.value}),i=n}else{let r=t(`input`,{class:`aaa-input`,type:e.type===`number`?`number`:`text`,placeholder:n[e.key]??e.placeholder??``});r.addEventListener(`input`,()=>{s[e.key]=r.value}),i=r}return r.append(i),r})),l=t(`div`,{class:`aaa-current`},i.map(e=>`${e.label}: ${n[e.key]??`—`}`).join(` · `)),u=t(`div`,{class:`aaa-form-actions`},r(`Save`,()=>void w(a,s),{class:`primary`}),o??null);return t(`div`,{class:`aaa-card`},t(`div`,{class:`aaa-form-title`},e),l,c,u)}function A(){return h?t(`div`,{class:`aaa-settings`},k(`RADIUS Incoming (CoA listener)`,h.radiusIncoming,[{key:`accept`,label:`Accept CoA`,type:`bool`},{key:`port`,label:`CoA port`,type:`number`,placeholder:`3799`}],`radius-incoming`,r(`Reset counters`,()=>void w(`radius-reset-counters`,{}))),h.umAvailable?k(`User Manager (built-in RADIUS server)`,h.umSettings,[{key:`enabled`,label:`Enabled`,type:`bool`},{key:`use-profiles`,label:`Use profiles`,type:`bool`},{key:`certificate`,label:`Certificate`},{key:`authentication-port`,label:`Auth port`,type:`number`},{key:`accounting-port`,label:`Acct port`,type:`number`}],`um-settings`):t(`div`,{class:`aaa-card`},t(`div`,{class:`aaa-form-title`},`User Manager`),t(`div`,{class:`muted`},`The user-manager package is not installed on this device.`))):t(`div`,{class:`muted`},`Loading settings…`)}function j(){let e=t(`div`,{class:`aaa-tabs`},...[...i,a].map(e=>t(`button`,{class:`aaa-tab${u===e.slug?` is-active`:``}`},e.label)));[...e.children].forEach((e,t)=>{let n=[...i,a][t];e.addEventListener(`click`,()=>{n.slug===`settings`?S():x(n.slug)})});let n=[t(`div`,{class:`aaa-titlebar`},t(`div`,{class:`aaa-title`},`RADIUS & User Manager`)),e];if(_&&n.push(t(`div`,{class:`aaa-error`},_)),u===`settings`)n.push(A());else{let e=o(u);if(!e)n.push(t(`div`,{class:`muted`},`Unknown section.`));else if(!d)n.push(t(`div`,{class:`aaa-notice`},`User Manager is not installed on this device. Install the user-manager package (System → Packages) and reboot.`));else{let i=t(`div`,{class:`aaa-bar`});i.append(t(`span`,{class:`muted`},`${f.length} row(s)`)),i.append(t(`span`,{class:`spacer`})),i.append(r(`↻ Refresh`,()=>void x(u))),e.readonly||i.append(r(`+ Add`,()=>{p=`new`,m={},j()},{class:`primary`})),n.push(i),p&&n.push(E(e)),n.push(f.length||!e?O(e):t(`div`,{class:`muted`},`No rows.`))}}c.replaceChildren(t(`div`,{class:`aaa-wrap`},...n))}l.ontoolresult=e=>{let t=e.structuredContent;t?.__mikrotikView===`aaa-settings`?b(t):y(t),j()},l.ontoolinput=()=>j(),n(l),j(),l.connect().catch(e=>console.error(`[aaa] connect failed`,e));
|
|
10
10
|
</script>
|
|
11
|
-
<link rel="modulepreload" crossorigin href="../assets/app-
|
|
12
|
-
<link rel="modulepreload" crossorigin href="../assets/kit-
|
|
11
|
+
<link rel="modulepreload" crossorigin href="../assets/app-BbgpcVv8.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="../assets/kit-DyrRNi79.js">
|
|
13
13
|
<style>
|
|
14
14
|
:root{--bg:#0b0d10;--surface:#14181d;--surface-2:#1b2027;--border:#2a313a;--text:#e8eaed;--text-dim:#9aa0a6;--accent:#7c9cff;--danger:#f2685a;--ok:#4ade80}@media (prefers-color-scheme:light){:root{--bg:#fff;--surface:#f6f8fa;--surface-2:#eef1f4;--border:#d8dee4;--text:#1c2024;--text-dim:#5b6470;--accent:#3b5bdb}}.aaa-wrap{padding:4px 2px 16px}.aaa-titlebar{padding:6px 8px 10px}.aaa-title{font-size:15px;font-weight:600}.aaa-tabs{border-bottom:1px solid var(--border);flex-wrap:wrap;gap:2px;margin-bottom:12px;display:flex}.aaa-tab{appearance:none;color:var(--text-dim);font:inherit;cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-1px;padding:7px 11px;font-size:12.5px}.aaa-tab:hover{color:var(--text)}.aaa-tab.is-active{color:var(--text);border-bottom-color:var(--accent)}.aaa-bar{align-items:center;gap:8px;padding:0 8px 10px;display:flex}.aaa-bar .spacer{flex:1}.btn{font:inherit;color:var(--text);background:var(--surface-2);border:1px solid var(--border);cursor:pointer;border-radius:8px;padding:4px 10px;font-size:12px}.btn.primary{border-color:color-mix(in srgb, var(--accent) 55%, var(--border));color:var(--accent)}.aaa-error{color:var(--danger);background:color-mix(in srgb, var(--danger) 12%, transparent);border:1px solid color-mix(in srgb, var(--danger) 35%, var(--border));border-radius:8px;margin:0 8px 10px;padding:8px 10px;font-size:12px}.aaa-notice{color:var(--text-dim);background:var(--surface);border:1px solid var(--border);border-radius:10px;margin:0 8px;padding:14px;font-size:12.5px}.aaa-table{padding:0 8px}.aaa-row{grid-template-columns:repeat(var(--cols), minmax(70px, 1fr)) minmax(150px, auto);border-radius:8px;align-items:center;gap:8px;padding:7px 8px;font-size:12.5px;display:grid}.aaa-row.aaa-head{color:var(--text-dim);text-transform:uppercase;letter-spacing:.04em;font-size:10.5px}.aaa-row:not(.aaa-head):hover{background:var(--surface)}.aaa-row.is-off .aaa-cell{color:var(--text-dim)}.aaa-cell{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.aaa-actions{justify-content:flex-end;gap:5px;display:flex}.aaa-actions .btn{padding:3px 8px}.aaa-badge{border-radius:100px;padding:1px 7px;font-size:10.5px;display:inline-block}.aaa-badge.on{background:color-mix(in srgb, var(--ok) 20%, transparent);color:var(--ok)}.aaa-badge.off{background:color-mix(in srgb, var(--text-dim) 22%, transparent);color:var(--text-dim)}.aaa-form,.aaa-card{border:1px solid var(--border);background:var(--surface-2);border-radius:10px;margin:0 8px 12px;padding:12px}.aaa-form-title{margin-bottom:10px;font-size:12.5px;font-weight:600}.aaa-current{color:var(--text-dim);margin-bottom:10px;font-size:11.5px}.aaa-form-grid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:9px 12px;display:grid}.aaa-field{flex-direction:column;gap:3px;display:flex}.aaa-field-label{color:var(--text-dim);font-size:11px}.aaa-input{font:inherit;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:7px;padding:5px 8px;font-size:12.5px}.aaa-input:focus{border-color:var(--accent);outline:none}.aaa-form-actions{align-items:center;gap:8px;margin-top:12px;display:flex}.aaa-settings{flex-direction:column;gap:4px;display:flex}:root{--bg:#0b0d10;--surface:#14181d;--surface-2:#1b2027;--border:#2a313a;--text:#e8eaed;--text-dim:#9aa0a6;--accent:#7c9cff;--rx:#4ade80;--tx:#f59e0b;--danger:#f2685a;--ok:#4ade80}@media (prefers-color-scheme:light){:root{--bg:#fff;--surface:#f6f8fa;--surface-2:#eef1f4;--border:#d8dee4;--text:#1c2024;--text-dim:#5b6470;--accent:#3b5bdb}}body{background:var(--bg);color:var(--text);margin:0;font:13px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}.mono{font-family:ui-monospace,SF Mono,Menlo,monospace}.muted{color:var(--text-dim)}.loading{padding:24px}.toolbar{border-bottom:1px solid var(--border);align-items:center;gap:12px;padding:12px 14px;display:flex}.toolbar .title{font-size:15px;font-weight:600}.toolbar .counts{font-size:12px}.btn{font:inherit;color:var(--text);background:var(--surface-2);border:1px solid var(--border);cursor:pointer;border-radius:8px;margin-left:auto;padding:5px 10px}.toolbar .btn{margin-left:auto}.btn:hover{border-color:var(--accent)}.btn.is-busy{opacity:.5;pointer-events:none}.table{padding:6px 8px 14px}.row{cursor:pointer;border-radius:8px;grid-template-columns:110px 1.4fr 150px 90px 110px 150px;align-items:center;gap:8px;padding:7px 8px;display:grid}.row.header{cursor:default;color:var(--text-dim);text-transform:uppercase;letter-spacing:.04em;font-size:11px}.row:not(.header):hover{background:var(--surface)}.row.is-selected{background:color-mix(in srgb, var(--accent) 16%, var(--surface))}.row.is-blocked .name,.row.is-blocked .ip{color:var(--text-dim);text-decoration:line-through}.cell{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.cell.mac{color:var(--text-dim);font-family:ui-monospace,monospace;font-size:11.5px}.badge{border-radius:100px;margin-right:4px;padding:1px 7px;font-size:10.5px;display:inline-block}.badge.static{background:color-mix(in srgb, var(--accent) 22%, transparent);color:var(--accent)}.badge.blocked{background:color-mix(in srgb, var(--danger) 22%, transparent);color:var(--danger)}.cell.actions{justify-content:flex-end;gap:6px;display:flex}.cell.actions .btn{margin:0;padding:3px 9px;font-size:12px}.btn.danger{border-color:color-mix(in srgb, var(--danger) 45%, var(--border));color:var(--danger)}.btn.ok{border-color:color-mix(in srgb, var(--ok) 45%, var(--border));color:var(--ok)}.detail{border-top:1px solid var(--border);padding:14px}.detail-title{font-size:14px;font-weight:600}.detail-sub{color:var(--text-dim);margin-bottom:10px;font-size:12px}.note{max-width:460px;font-size:12px}.rates{font-variant-numeric:tabular-nums;gap:16px;margin-bottom:6px;display:flex}.rate{font-weight:600}.rate.rx{color:var(--rx)}.rate.tx{color:var(--tx)}.traffic-chart{background:var(--surface);border:1px solid var(--border);border-radius:10px;width:100%;max-width:460px;height:140px;display:block}.traffic-chart .line{fill:none;stroke-width:2px}.traffic-chart .rx.line{stroke:var(--rx)}.traffic-chart .tx.line{stroke:var(--tx)}.traffic-chart .rx.area{fill:color-mix(in srgb, var(--rx) 18%, transparent);stroke:none}.traffic-chart .tx.area{fill:color-mix(in srgb, var(--tx) 14%, transparent);stroke:none}.totals{font-variant-numeric:tabular-nums;margin-top:6px;font-size:12px}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}.card__value small{color:var(--mt-text-dim);font-size:12px;font-weight:400}.gauge{align-items:center;gap:14px;display:flex}.gauge svg{flex:none}.gauge__track{stroke:var(--mt-surface-2)}.gauge__bar{stroke:var(--mt-accent);stroke-linecap:round;transform-origin:50%;transition:stroke-dashoffset .6s cubic-bezier(.2,.8,.2,1);transform:rotate(-90deg)}.gauge__pct{font-family:var(--mt-mono);fill:var(--mt-text);font-size:16px;font-weight:650}.gauge__meta{min-width:0}.gauge__meta .card__label{margin-bottom:4px}.gauge__meta small{color:var(--mt-text-faint);font-family:var(--mt-mono);font-size:11px}.is-good{stroke:var(--mt-good)}.is-warn{stroke:var(--mt-warn)}.is-bad{stroke:var(--mt-bad)}details.kv{background:var(--mt-surface);border:1px solid var(--mt-border);border-radius:var(--mt-radius);overflow:hidden}details.kv>summary{cursor:pointer;color:var(--mt-text-dim);-webkit-user-select:none;user-select:none;padding:12px 14px;font-weight:600;list-style:none}details.kv>summary::-webkit-details-marker{display:none}details.kv>summary:after{content:"▸";float:right;color:var(--mt-text-faint);transition:transform .2s}details.kv[open]>summary:after{transform:rotate(90deg)}.kv__body{border-top:1px solid var(--mt-border);grid-template-columns:minmax(120px,.4fr) 1fr;display:grid}.kv__body>div{border-bottom:1px solid color-mix(in srgb, var(--mt-border) 55%, transparent);overflow-wrap:anywhere;min-width:0;padding:7px 14px;font-size:12px}.kv__k{color:var(--mt-text-dim);font-family:var(--mt-mono)}.kv__v{font-family:var(--mt-mono);color:var(--mt-text)}.foot{color:var(--mt-text-faint);font-size:11px;font-family:var(--mt-mono);align-items:center;gap:10px;display:flex}.btn{appearance:none;border:1px solid var(--mt-border);background:var(--mt-surface);color:var(--mt-text);font:inherit;border-radius:var(--mt-radius-sm);cursor:pointer;padding:5px 11px;font-size:12px;transition:border-color .15s,background .15s}.skeleton{color:var(--mt-text-faint);text-align:center;padding:40px 0}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}table.tbl tbody tr.is-disabled td,table.tbl td.col-num{color:var(--mt-text-faint)}.act{font-family:var(--mt-mono);border:1px solid var(--mt-border);color:var(--mt-text-dim);border-radius:999px;padding:1px 8px;font-size:11px;display:inline-block}.act.is-accept{color:var(--mt-good);border-color:color-mix(in srgb, var(--mt-good) 45%, var(--mt-border))}.act.is-drop{color:var(--mt-bad);border-color:color-mix(in srgb, var(--mt-bad) 45%, var(--mt-border))}.act.is-jump{color:var(--mt-accent);border-color:color-mix(in srgb, var(--mt-accent) 45%, var(--mt-border))}.matchers{color:var(--mt-text-dim);max-width:340px;white-space:normal!important}.matchers b{color:var(--mt-text);font-weight:500}td.num{text-align:right;color:var(--mt-text-dim)}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}table.tbl tbody tr.is-disabled td,table.tbl td.col-num{color:var(--mt-text-faint)}.hd{align-items:center;gap:14px}.hd__meta{flex-direction:column;gap:6px;display:flex}.counts{flex-wrap:wrap;gap:6px;display:flex}.gauge{flex:none;width:64px;height:64px}.gauge__bg{fill:none;stroke:var(--mt-border);stroke-width:9px}.gauge__fg{fill:none;stroke-width:9px;stroke-linecap:round;stroke:var(--mt-good);transition:stroke-dasharray .4s}.gauge.is-warn .gauge__fg{stroke:var(--mt-warn)}.gauge.is-bad .gauge__fg{stroke:var(--mt-bad)}.gauge__num{fill:var(--mt-text);font:700 22px var(--mt-mono)}.is-good{color:var(--mt-good)}.is-warn{color:var(--mt-warn)}.is-bad{color:var(--mt-bad)}.chip.is-low{color:var(--mt-text-dim);border-color:var(--mt-border)}.f-list{flex-direction:column;gap:10px;margin-top:14px;display:flex}.f-card{border:1px solid var(--mt-border);border-radius:var(--mt-radius-sm);background:var(--mt-surface);border-left-width:3px;padding:11px 13px}.f-card.is-bad{border-left-color:var(--mt-bad)}.f-card.is-warn{border-left-color:var(--mt-warn)}.f-card.is-low{border-left-color:var(--mt-text-faint)}.f-head{flex-wrap:wrap;align-items:center;gap:9px;display:flex}.f-title{font-size:13px;font-weight:600}.f-where{font-family:var(--mt-mono);color:var(--mt-text-faint);font-size:11px}.f-body{margin-top:6px}.f-detail{color:var(--mt-text);margin:0 0 5px;font-size:13px;line-height:1.45}.f-fix{color:var(--mt-text-dim);margin:0;font-size:12px;line-height:1.45}.f-actions{margin-top:9px}.btn-danger{color:var(--mt-bad);border-color:color-mix(in srgb, var(--mt-bad) 45%, var(--mt-border))}.btn-danger:hover{border-color:var(--mt-bad);background:color-mix(in srgb, var(--mt-bad) 12%, transparent)}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}table.tbl tbody tr.is-disabled td,table.tbl td.col-num{color:var(--mt-text-faint)}.if-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;display:grid}.if-card{gap:8px;display:grid}.if-card__top{align-items:center;gap:8px;display:flex}.if-card__name{font-family:var(--mt-mono);overflow-wrap:anywhere;font-size:14px;font-weight:650}.if-card.is-disabled{opacity:.6}.dot{background:var(--mt-text-faint);border-radius:50%;flex:none;width:9px;height:9px}.dot.is-up{background:var(--mt-good);box-shadow:0 0 0 4px color-mix(in srgb, var(--mt-good) 22%, transparent)}.dot.is-down{background:var(--mt-bad);box-shadow:0 0 0 4px color-mix(in srgb, var(--mt-bad) 18%, transparent)}.if-card__meta{font-family:var(--mt-mono);color:var(--mt-text-dim);grid-template-columns:auto 1fr;gap:2px 10px;font-size:11px;display:grid}.if-card__meta b{color:var(--mt-text);overflow-wrap:anywhere;font-weight:500}.if-card__meta .contents{display:contents}.badge{border:1px solid var(--mt-border);background:var(--mt-surface-2);color:var(--mt-text-dim);font-family:var(--mt-mono);text-transform:uppercase;letter-spacing:.04em;border-radius:999px;margin-left:auto;padding:1px 7px;font-size:10px}:root{--mt-bg:var(--color-background-primary,#0b0d10);--mt-surface:var(--color-background-secondary,#14171c);--mt-surface-2:var(--color-background-tertiary,#1b1f26);--mt-border:var(--color-border-primary,#262b33);--mt-text:var(--color-text-primary,#e8eaed);--mt-text-dim:var(--color-text-secondary,#9aa3af);--mt-text-faint:var(--color-text-tertiary,#6b7280);--mt-accent:var(--color-accent-primary,#6ea8fe);--mt-good:#34d399;--mt-warn:#fbbf24;--mt-bad:#f87171;--mt-radius:var(--border-radius-md,14px);--mt-radius-sm:var(--border-radius-sm,9px);--mt-mono:var(--font-mono,ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace);--mt-sans:var(--font-sans,system-ui, -apple-system, "Segoe UI", sans-serif);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}*{box-sizing:border-box}body{background:var(--mt-bg);color:var(--mt-text);font-family:var(--mt-sans);-webkit-font-smoothing:antialiased;margin:0;padding:16px;font-size:13px;line-height:1.5}.app{gap:14px;max-width:960px;margin:0 auto;display:grid}.hd{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.hd__dot{background:var(--mt-good);width:9px;height:9px;box-shadow:0 0 0 4px color-mix(in srgb, var(--mt-good) 22%, transparent);border-radius:50%}.hd__title{letter-spacing:-.01em;margin:0;font-size:17px;font-weight:650}.hd__sub{color:var(--mt-text-dim);font-family:var(--mt-mono);margin:0;font-size:12px}.hd__spacer{flex:1}.pill{border:1px solid var(--mt-border);background:var(--mt-surface);color:var(--mt-text-dim);font-family:var(--mt-mono);border-radius:999px;align-items:center;gap:6px;padding:3px 9px;font-size:11px;display:inline-flex}.pill b{color:var(--mt-text);font-weight:600}.grid{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;display:grid}.card{background:linear-gradient(180deg, var(--mt-surface), var(--mt-surface-2));border:1px solid var(--mt-border);border-radius:var(--mt-radius);padding:14px}.card__label{color:var(--mt-text-dim);text-transform:uppercase;letter-spacing:.06em;margin:0 0 8px;font-size:11px}.card__value{font-family:var(--mt-mono);letter-spacing:-.01em;font-size:20px;font-weight:600}.toolbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.toolbar .grow{flex:1;min-width:140px}.search{appearance:none;border:1px solid var(--mt-border);background:var(--mt-surface);width:100%;color:var(--mt-text);font:inherit;border-radius:var(--mt-radius-sm);padding:7px 11px;font-size:13px}.search:focus{border-color:var(--mt-accent);outline:none}.search::placeholder{color:var(--mt-text-faint)}.btn{appearance:none;border:1px solid var(--mt-border);background:var(--mt-surface);color:var(--mt-text);font:inherit;border-radius:var(--mt-radius-sm);cursor:pointer;white-space:nowrap;padding:6px 11px;font-size:12px;transition:border-color .15s,background .15s}.btn:hover{border-color:var(--mt-accent);background:var(--mt-surface-2)}.btn:disabled{opacity:.5;cursor:default}.btn.is-active{border-color:var(--mt-accent);color:var(--mt-accent)}.chip{border:1px solid var(--mt-border);background:var(--mt-surface);color:var(--mt-text-dim);font-family:var(--mt-mono);border-radius:999px;align-items:center;gap:4px;padding:1px 7px;font-size:11px;display:inline-flex}.chip.is-good{color:var(--mt-good);border-color:color-mix(in srgb, var(--mt-good) 45%, var(--mt-border))}.chip.is-warn{color:var(--mt-warn);border-color:color-mix(in srgb, var(--mt-warn) 45%, var(--mt-border))}.chip.is-bad{color:var(--mt-bad);border-color:color-mix(in srgb, var(--mt-bad) 45%, var(--mt-border))}.tablewrap{border:1px solid var(--mt-border);border-radius:var(--mt-radius);max-height:70vh;overflow:auto}table.tbl{border-collapse:collapse;width:100%;font-size:12px}table.tbl th,table.tbl td{text-align:left;border-bottom:1px solid color-mix(in srgb, var(--mt-border) 55%, transparent);white-space:nowrap;font-family:var(--mt-mono);padding:7px 12px}table.tbl thead th{z-index:1;background:var(--mt-surface-2);color:var(--mt-text-dim);cursor:pointer;-webkit-user-select:none;user-select:none;font-weight:600;position:sticky;top:0}table.tbl thead th:hover{color:var(--mt-text)}table.tbl thead th .arrow{color:var(--mt-accent);margin-left:4px}table.tbl tbody tr{cursor:pointer}table.tbl tbody tr:hover{background:color-mix(in srgb, var(--mt-accent) 8%, transparent)}table.tbl tbody tr.is-disabled td,table.tbl td.col-num{color:var(--mt-text-faint)}.kv__body{border:1px solid var(--mt-border);border-radius:var(--mt-radius);grid-template-columns:minmax(120px,.4fr) 1fr;display:grid;overflow:hidden}.kv__body>div{border-bottom:1px solid color-mix(in srgb, var(--mt-border) 55%, transparent);overflow-wrap:anywhere;min-width:0;font-size:12px;font-family:var(--mt-mono);padding:7px 14px}.kv__k{color:var(--mt-text-dim);background:color-mix(in srgb, var(--mt-surface) 60%, transparent)}.kv__v{color:var(--mt-text)}.drawer{border:1px solid var(--mt-accent);border-radius:var(--mt-radius);background:var(--mt-surface);gap:10px;padding:12px;display:grid}.drawer__hd{align-items:center;gap:8px;display:flex}.drawer__hd b{font-family:var(--mt-mono)}pre.raw{border:1px solid var(--mt-border);border-radius:var(--mt-radius);background:var(--mt-surface);color:var(--mt-text);font-family:var(--mt-mono);white-space:pre;max-height:70vh;margin:0;padding:14px;font-size:12px;overflow:auto}.foot{color:var(--mt-text-faint);font-size:11px;font-family:var(--mt-mono);flex-wrap:wrap;align-items:center;gap:10px;display:flex}.foot .grow{flex:1}.skeleton,.empty{color:var(--mt-text-faint);text-align:center;padding:40px 0}.count-pill b{color:var(--mt-accent)}table.tbl td.col-num,table.tbl th.col-num{width:1%}
|
|
15
15
|
/*$vite$:1*/
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
<meta name="color-scheme" content="light dark" />
|
|
7
7
|
<title>MikroTik Connected Devices</title>
|
|
8
8
|
<script type="module">
|
|
9
|
-
import{i as e}from"./app-
|
|
9
|
+
import{i as e}from"./app-BbgpcVv8.js";import{a as t,n,s as r,t as i}from"./kit-DyrRNi79.js";var a=document.getElementById(`app`),o=`http://www.w3.org/2000/svg`,s=40,c=2e3,l=null,u=null,d=[],f=null,p=!1,m=null,h=null,g=new e({name:`mikrotik-connected-devices`,version:`1.0.0`});function _(e){return`${(e/1e6).toFixed(2)} Mbps`}function v(){return l?.devices.find(e=>e.mac===u)}function y(){m&&clearInterval(m),m=null}function b(e){y(),d=[],f=null;let t=async()=>{try{let t=(await g.callServerTool({name:`get_device_traffic`,arguments:{ip:e}})).structuredContent;if(!t||t.ip!==e)return;f=t,d.push({rx:t.rxBitsPerSec,tx:t.txBitsPerSec}),d.length>s&&d.shift(),S()}catch(e){console.error(`[connected-devices] traffic poll failed`,e)}};t(),m=setInterval(()=>void t(),c)}function x(){let e=document.createElementNS(o,`svg`);e.setAttribute(`viewBox`,`0 0 460 140`),e.setAttribute(`class`,`traffic-chart`);let t=Math.max(1,...d.flatMap(e=>[e.rx,e.tx])),n=e=>6+e*448/Math.max(1,s-1),r=e=>134-e/t*128,i=(t,i)=>{if(d.length<2)return;let a=d.map((e,i)=>`${n(i).toFixed(1)},${r(t(e)).toFixed(1)}`).join(` `),s=document.createElementNS(o,`polygon`),c=n(0).toFixed(1),l=n(d.length-1).toFixed(1);s.setAttribute(`points`,`${c},134 ${a} ${l},134`),s.setAttribute(`class`,`${i} area`),e.appendChild(s);let u=document.createElementNS(o,`polyline`);u.setAttribute(`points`,a),u.setAttribute(`class`,`${i} line`),e.appendChild(u)};return i(e=>e.rx,`rx`),i(e=>e.tx,`tx`),e}function S(){let e=document.getElementById(`detail`);if(!e)return;let r=v();if(e.replaceChildren(),!r){e.appendChild(t(`div`,{class:`muted`},`Select a device to see its traffic.`));return}e.appendChild(t(`div`,{class:`detail-title`},r.host||r.comment||r.ip)),e.appendChild(t(`div`,{class:`detail-sub`},`${r.ip} · ${r.mac} · ${r.iface||`?`} · ${r.status}`)),f&&f.source===`none`?e.appendChild(t(`div`,{class:`muted note`},`No per-device counter. Ask: “create a simple queue for this device” to enable Download/Upload tracking.`)):(e.appendChild(t(`div`,{class:`rates`},t(`span`,{class:`rate rx`},`↓ ${f?_(f.rxBitsPerSec):`…`}`),t(`span`,{class:`rate tx`},`↑ ${f?_(f.txBitsPerSec):`…`}`))),e.appendChild(x()),f&&e.appendChild(t(`div`,{class:`totals muted`},`total ↓ ${n(f.rxBytes)} · ↑ ${n(f.txBytes)}`)))}function C(e){e&&typeof e==`object`&&e.__mikrotikView===`connected-devices`&&(l=e,E())}async function w(e,t){if(!p){p=!0,E();try{C((await g.callServerTool({name:e,arguments:{mac:t}})).structuredContent)}catch(t){console.error(`[connected-devices] ${e} failed`,t)}finally{p=!1,E()}}}async function T(){if(!p){p=!0,E();try{C((await g.callServerTool({name:`list_connected_devices`,arguments:{}})).structuredContent)}catch(e){console.error(`[connected-devices] refresh failed`,e)}finally{p=!1,E()}}}function E(){if(!l){a.replaceChildren(t(`div`,{class:`muted loading`},`Loading connected devices…`));return}let e=l.counts,n=t(`div`,{class:`toolbar`},t(`div`,{class:`title`},`Connected Devices`),t(`div`,{class:`counts muted`},`${e.total} total · ${e.static} static · ${e.blocked} blocked`),i(`↻ Refresh`,()=>void T(),{class:p?`is-busy`:``})),r=l.devices.map(e=>{let n=t(`div`,{class:`row${e.mac===u?` is-selected`:``}${e.blocked?` is-blocked`:``}`},t(`span`,{class:`cell ip`},e.ip||`—`),t(`span`,{class:`cell name`},e.host||e.comment||`(unknown)`),t(`span`,{class:`cell mac mono`},e.mac),t(`span`,{class:`cell iface`},e.iface||``),t(`span`,{class:`cell badges`},e.static?t(`span`,{class:`badge static`},`static`):null,e.blocked?t(`span`,{class:`badge blocked`},`blocked`):null),t(`span`,{class:`cell actions`},e.blocked?i(`Allow`,()=>void w(`allow_device`,e.mac),{class:`ok`}):i(`Block`,()=>void w(`block_device`,e.mac),{class:`danger`}),e.static?null:i(`Pin IP`,()=>void w(`make_device_static`,e.mac),{class:``})));return n.addEventListener(`click`,t=>{t.target.closest(`.actions`)||(u=e.mac,e.ip?b(e.ip):y(),E())}),n});a.replaceChildren(n,t(`div`,{class:`table`},t(`div`,{class:`row header`},t(`span`,{class:`cell ip`},`IP`),t(`span`,{class:`cell name`},`Name`),t(`span`,{class:`cell mac`},`MAC`),t(`span`,{class:`cell iface`},`Iface`),t(`span`,{class:`cell badges`},``),t(`span`,{class:`cell actions`},``)),...r),t(`div`,{id:`detail`,class:`detail`})),S()}g.ontoolresult=e=>C(e.structuredContent),g.ontoolinput=()=>{l||E()},r(g),g.onteardown=async()=>(y(),h&&clearInterval(h),{}),E(),g.connect().catch(e=>console.error(`[connected-devices] connect failed`,e)),h=setInterval(()=>void T(),15e3);
|
|
10
10
|
</script>
|
|
11
|
-
<link rel="modulepreload" crossorigin href="../assets/app-
|
|
12
|
-
<link rel="modulepreload" crossorigin href="../assets/kit-
|
|
11
|
+
<link rel="modulepreload" crossorigin href="../assets/app-BbgpcVv8.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="../assets/kit-DyrRNi79.js">
|
|
13
13
|
<style>
|
|
14
14
|
:root{--bg:#0b0d10;--surface:#14181d;--surface-2:#1b2027;--border:#2a313a;--text:#e8eaed;--text-dim:#9aa0a6;--accent:#7c9cff;--danger:#f2685a;--ok:#4ade80}@media (prefers-color-scheme:light){:root{--bg:#fff;--surface:#f6f8fa;--surface-2:#eef1f4;--border:#d8dee4;--text:#1c2024;--text-dim:#5b6470;--accent:#3b5bdb}}.aaa-wrap{padding:4px 2px 16px}.aaa-titlebar{padding:6px 8px 10px}.aaa-title{font-size:15px;font-weight:600}.aaa-tabs{border-bottom:1px solid var(--border);flex-wrap:wrap;gap:2px;margin-bottom:12px;display:flex}.aaa-tab{appearance:none;color:var(--text-dim);font:inherit;cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-1px;padding:7px 11px;font-size:12.5px}.aaa-tab:hover{color:var(--text)}.aaa-tab.is-active{color:var(--text);border-bottom-color:var(--accent)}.aaa-bar{align-items:center;gap:8px;padding:0 8px 10px;display:flex}.aaa-bar .spacer{flex:1}.btn{font:inherit;color:var(--text);background:var(--surface-2);border:1px solid var(--border);cursor:pointer;border-radius:8px;padding:4px 10px;font-size:12px}.btn.primary{border-color:color-mix(in srgb, var(--accent) 55%, var(--border));color:var(--accent)}.aaa-error{color:var(--danger);background:color-mix(in srgb, var(--danger) 12%, transparent);border:1px solid color-mix(in srgb, var(--danger) 35%, var(--border));border-radius:8px;margin:0 8px 10px;padding:8px 10px;font-size:12px}.aaa-notice{color:var(--text-dim);background:var(--surface);border:1px solid var(--border);border-radius:10px;margin:0 8px;padding:14px;font-size:12.5px}.aaa-table{padding:0 8px}.aaa-row{grid-template-columns:repeat(var(--cols), minmax(70px, 1fr)) minmax(150px, auto);border-radius:8px;align-items:center;gap:8px;padding:7px 8px;font-size:12.5px;display:grid}.aaa-row.aaa-head{color:var(--text-dim);text-transform:uppercase;letter-spacing:.04em;font-size:10.5px}.aaa-row:not(.aaa-head):hover{background:var(--surface)}.aaa-row.is-off .aaa-cell{color:var(--text-dim)}.aaa-cell{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.aaa-actions{justify-content:flex-end;gap:5px;display:flex}.aaa-actions .btn{padding:3px 8px}.aaa-badge{border-radius:100px;padding:1px 7px;font-size:10.5px;display:inline-block}.aaa-badge.on{background:color-mix(in srgb, var(--ok) 20%, transparent);color:var(--ok)}.aaa-badge.off{background:color-mix(in srgb, var(--text-dim) 22%, transparent);color:var(--text-dim)}.aaa-form,.aaa-card{border:1px solid var(--border);background:var(--surface-2);border-radius:10px;margin:0 8px 12px;padding:12px}.aaa-form-title{margin-bottom:10px;font-size:12.5px;font-weight:600}.aaa-current{color:var(--text-dim);margin-bottom:10px;font-size:11.5px}.aaa-form-grid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:9px 12px;display:grid}.aaa-field{flex-direction:column;gap:3px;display:flex}.aaa-field-label{color:var(--text-dim);font-size:11px}.aaa-input{font:inherit;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:7px;padding:5px 8px;font-size:12.5px}.aaa-input:focus{border-color:var(--accent);outline:none}.aaa-form-actions{align-items:center;gap:8px;margin-top:12px;display:flex}.aaa-settings{flex-direction:column;gap:4px;display:flex}:root{--bg:#0b0d10;--surface:#14181d;--surface-2:#1b2027;--border:#2a313a;--text:#e8eaed;--text-dim:#9aa0a6;--accent:#7c9cff;--rx:#4ade80;--tx:#f59e0b;--danger:#f2685a;--ok:#4ade80}@media (prefers-color-scheme:light){:root{--bg:#fff;--surface:#f6f8fa;--surface-2:#eef1f4;--border:#d8dee4;--text:#1c2024;--text-dim:#5b6470;--accent:#3b5bdb}}body{background:var(--bg);color:var(--text);margin:0;font:13px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}.mono{font-family:ui-monospace,SF Mono,Menlo,monospace}.muted{color:var(--text-dim)}.loading{padding:24px}.toolbar{border-bottom:1px solid var(--border);align-items:center;gap:12px;padding:12px 14px;display:flex}.toolbar .title{font-size:15px;font-weight:600}.toolbar .counts{font-size:12px}.btn{font:inherit;color:var(--text);background:var(--surface-2);border:1px solid var(--border);cursor:pointer;border-radius:8px;margin-left:auto;padding:5px 10px}.toolbar .btn{margin-left:auto}.btn:hover{border-color:var(--accent)}.btn.is-busy{opacity:.5;pointer-events:none}.table{padding:6px 8px 14px}.row{cursor:pointer;border-radius:8px;grid-template-columns:110px 1.4fr 150px 90px 110px 150px;align-items:center;gap:8px;padding:7px 8px;display:grid}.row.header{cursor:default;color:var(--text-dim);text-transform:uppercase;letter-spacing:.04em;font-size:11px}.row:not(.header):hover{background:var(--surface)}.row.is-selected{background:color-mix(in srgb, var(--accent) 16%, var(--surface))}.row.is-blocked .name,.row.is-blocked .ip{color:var(--text-dim);text-decoration:line-through}.cell{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.cell.mac{color:var(--text-dim);font-family:ui-monospace,monospace;font-size:11.5px}.badge{border-radius:100px;margin-right:4px;padding:1px 7px;font-size:10.5px;display:inline-block}.badge.static{background:color-mix(in srgb, var(--accent) 22%, transparent);color:var(--accent)}.badge.blocked{background:color-mix(in srgb, var(--danger) 22%, transparent);color:var(--danger)}.cell.actions{justify-content:flex-end;gap:6px;display:flex}.cell.actions .btn{margin:0;padding:3px 9px;font-size:12px}.btn.danger{border-color:color-mix(in srgb, var(--danger) 45%, var(--border));color:var(--danger)}.btn.ok{border-color:color-mix(in srgb, var(--ok) 45%, var(--border));color:var(--ok)}.detail{border-top:1px solid var(--border);padding:14px}.detail-title{font-size:14px;font-weight:600}.detail-sub{color:var(--text-dim);margin-bottom:10px;font-size:12px}.note{max-width:460px;font-size:12px}.rates{font-variant-numeric:tabular-nums;gap:16px;margin-bottom:6px;display:flex}.rate{font-weight:600}.rate.rx{color:var(--rx)}.rate.tx{color:var(--tx)}.traffic-chart{background:var(--surface);border:1px solid var(--border);border-radius:10px;width:100%;max-width:460px;height:140px;display:block}.traffic-chart .line{fill:none;stroke-width:2px}.traffic-chart .rx.line{stroke:var(--rx)}.traffic-chart .tx.line{stroke:var(--tx)}.traffic-chart .rx.area{fill:color-mix(in srgb, var(--rx) 18%, transparent);stroke:none}.traffic-chart .tx.area{fill:color-mix(in srgb, var(--tx) 14%, transparent);stroke:none}.totals{font-variant-numeric:tabular-nums;margin-top:6px;font-size:12px}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}.card__value small{color:var(--mt-text-dim);font-size:12px;font-weight:400}.gauge{align-items:center;gap:14px;display:flex}.gauge svg{flex:none}.gauge__track{stroke:var(--mt-surface-2)}.gauge__bar{stroke:var(--mt-accent);stroke-linecap:round;transform-origin:50%;transition:stroke-dashoffset .6s cubic-bezier(.2,.8,.2,1);transform:rotate(-90deg)}.gauge__pct{font-family:var(--mt-mono);fill:var(--mt-text);font-size:16px;font-weight:650}.gauge__meta{min-width:0}.gauge__meta .card__label{margin-bottom:4px}.gauge__meta small{color:var(--mt-text-faint);font-family:var(--mt-mono);font-size:11px}.is-good{stroke:var(--mt-good)}.is-warn{stroke:var(--mt-warn)}.is-bad{stroke:var(--mt-bad)}details.kv{background:var(--mt-surface);border:1px solid var(--mt-border);border-radius:var(--mt-radius);overflow:hidden}details.kv>summary{cursor:pointer;color:var(--mt-text-dim);-webkit-user-select:none;user-select:none;padding:12px 14px;font-weight:600;list-style:none}details.kv>summary::-webkit-details-marker{display:none}details.kv>summary:after{content:"▸";float:right;color:var(--mt-text-faint);transition:transform .2s}details.kv[open]>summary:after{transform:rotate(90deg)}.kv__body{border-top:1px solid var(--mt-border);grid-template-columns:minmax(120px,.4fr) 1fr;display:grid}.kv__body>div{border-bottom:1px solid color-mix(in srgb, var(--mt-border) 55%, transparent);overflow-wrap:anywhere;min-width:0;padding:7px 14px;font-size:12px}.kv__k{color:var(--mt-text-dim);font-family:var(--mt-mono)}.kv__v{font-family:var(--mt-mono);color:var(--mt-text)}.foot{color:var(--mt-text-faint);font-size:11px;font-family:var(--mt-mono);align-items:center;gap:10px;display:flex}.btn{appearance:none;border:1px solid var(--mt-border);background:var(--mt-surface);color:var(--mt-text);font:inherit;border-radius:var(--mt-radius-sm);cursor:pointer;padding:5px 11px;font-size:12px;transition:border-color .15s,background .15s}.skeleton{color:var(--mt-text-faint);text-align:center;padding:40px 0}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}table.tbl tbody tr.is-disabled td,table.tbl td.col-num{color:var(--mt-text-faint)}.act{font-family:var(--mt-mono);border:1px solid var(--mt-border);color:var(--mt-text-dim);border-radius:999px;padding:1px 8px;font-size:11px;display:inline-block}.act.is-accept{color:var(--mt-good);border-color:color-mix(in srgb, var(--mt-good) 45%, var(--mt-border))}.act.is-drop{color:var(--mt-bad);border-color:color-mix(in srgb, var(--mt-bad) 45%, var(--mt-border))}.act.is-jump{color:var(--mt-accent);border-color:color-mix(in srgb, var(--mt-accent) 45%, var(--mt-border))}.matchers{color:var(--mt-text-dim);max-width:340px;white-space:normal!important}.matchers b{color:var(--mt-text);font-weight:500}td.num{text-align:right;color:var(--mt-text-dim)}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}table.tbl tbody tr.is-disabled td,table.tbl td.col-num{color:var(--mt-text-faint)}.hd{align-items:center;gap:14px}.hd__meta{flex-direction:column;gap:6px;display:flex}.counts{flex-wrap:wrap;gap:6px;display:flex}.gauge{flex:none;width:64px;height:64px}.gauge__bg{fill:none;stroke:var(--mt-border);stroke-width:9px}.gauge__fg{fill:none;stroke-width:9px;stroke-linecap:round;stroke:var(--mt-good);transition:stroke-dasharray .4s}.gauge.is-warn .gauge__fg{stroke:var(--mt-warn)}.gauge.is-bad .gauge__fg{stroke:var(--mt-bad)}.gauge__num{fill:var(--mt-text);font:700 22px var(--mt-mono)}.is-good{color:var(--mt-good)}.is-warn{color:var(--mt-warn)}.is-bad{color:var(--mt-bad)}.chip.is-low{color:var(--mt-text-dim);border-color:var(--mt-border)}.f-list{flex-direction:column;gap:10px;margin-top:14px;display:flex}.f-card{border:1px solid var(--mt-border);border-radius:var(--mt-radius-sm);background:var(--mt-surface);border-left-width:3px;padding:11px 13px}.f-card.is-bad{border-left-color:var(--mt-bad)}.f-card.is-warn{border-left-color:var(--mt-warn)}.f-card.is-low{border-left-color:var(--mt-text-faint)}.f-head{flex-wrap:wrap;align-items:center;gap:9px;display:flex}.f-title{font-size:13px;font-weight:600}.f-where{font-family:var(--mt-mono);color:var(--mt-text-faint);font-size:11px}.f-body{margin-top:6px}.f-detail{color:var(--mt-text);margin:0 0 5px;font-size:13px;line-height:1.45}.f-fix{color:var(--mt-text-dim);margin:0;font-size:12px;line-height:1.45}.f-actions{margin-top:9px}.btn-danger{color:var(--mt-bad);border-color:color-mix(in srgb, var(--mt-bad) 45%, var(--mt-border))}.btn-danger:hover{border-color:var(--mt-bad);background:color-mix(in srgb, var(--mt-bad) 12%, transparent)}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}table.tbl tbody tr.is-disabled td,table.tbl td.col-num{color:var(--mt-text-faint)}.if-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;display:grid}.if-card{gap:8px;display:grid}.if-card__top{align-items:center;gap:8px;display:flex}.if-card__name{font-family:var(--mt-mono);overflow-wrap:anywhere;font-size:14px;font-weight:650}.if-card.is-disabled{opacity:.6}.dot{background:var(--mt-text-faint);border-radius:50%;flex:none;width:9px;height:9px}.dot.is-up{background:var(--mt-good);box-shadow:0 0 0 4px color-mix(in srgb, var(--mt-good) 22%, transparent)}.dot.is-down{background:var(--mt-bad);box-shadow:0 0 0 4px color-mix(in srgb, var(--mt-bad) 18%, transparent)}.if-card__meta{font-family:var(--mt-mono);color:var(--mt-text-dim);grid-template-columns:auto 1fr;gap:2px 10px;font-size:11px;display:grid}.if-card__meta b{color:var(--mt-text);overflow-wrap:anywhere;font-weight:500}.if-card__meta .contents{display:contents}.badge{border:1px solid var(--mt-border);background:var(--mt-surface-2);color:var(--mt-text-dim);font-family:var(--mt-mono);text-transform:uppercase;letter-spacing:.04em;border-radius:999px;margin-left:auto;padding:1px 7px;font-size:10px}:root{--mt-bg:var(--color-background-primary,#0b0d10);--mt-surface:var(--color-background-secondary,#14171c);--mt-surface-2:var(--color-background-tertiary,#1b1f26);--mt-border:var(--color-border-primary,#262b33);--mt-text:var(--color-text-primary,#e8eaed);--mt-text-dim:var(--color-text-secondary,#9aa3af);--mt-text-faint:var(--color-text-tertiary,#6b7280);--mt-accent:var(--color-accent-primary,#6ea8fe);--mt-good:#34d399;--mt-warn:#fbbf24;--mt-bad:#f87171;--mt-radius:var(--border-radius-md,14px);--mt-radius-sm:var(--border-radius-sm,9px);--mt-mono:var(--font-mono,ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace);--mt-sans:var(--font-sans,system-ui, -apple-system, "Segoe UI", sans-serif);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}*{box-sizing:border-box}body{background:var(--mt-bg);color:var(--mt-text);font-family:var(--mt-sans);-webkit-font-smoothing:antialiased;margin:0;padding:16px;font-size:13px;line-height:1.5}.app{gap:14px;max-width:960px;margin:0 auto;display:grid}.hd{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.hd__dot{background:var(--mt-good);width:9px;height:9px;box-shadow:0 0 0 4px color-mix(in srgb, var(--mt-good) 22%, transparent);border-radius:50%}.hd__title{letter-spacing:-.01em;margin:0;font-size:17px;font-weight:650}.hd__sub{color:var(--mt-text-dim);font-family:var(--mt-mono);margin:0;font-size:12px}.hd__spacer{flex:1}.pill{border:1px solid var(--mt-border);background:var(--mt-surface);color:var(--mt-text-dim);font-family:var(--mt-mono);border-radius:999px;align-items:center;gap:6px;padding:3px 9px;font-size:11px;display:inline-flex}.pill b{color:var(--mt-text);font-weight:600}.grid{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;display:grid}.card{background:linear-gradient(180deg, var(--mt-surface), var(--mt-surface-2));border:1px solid var(--mt-border);border-radius:var(--mt-radius);padding:14px}.card__label{color:var(--mt-text-dim);text-transform:uppercase;letter-spacing:.06em;margin:0 0 8px;font-size:11px}.card__value{font-family:var(--mt-mono);letter-spacing:-.01em;font-size:20px;font-weight:600}.toolbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.toolbar .grow{flex:1;min-width:140px}.search{appearance:none;border:1px solid var(--mt-border);background:var(--mt-surface);width:100%;color:var(--mt-text);font:inherit;border-radius:var(--mt-radius-sm);padding:7px 11px;font-size:13px}.search:focus{border-color:var(--mt-accent);outline:none}.search::placeholder{color:var(--mt-text-faint)}.btn{appearance:none;border:1px solid var(--mt-border);background:var(--mt-surface);color:var(--mt-text);font:inherit;border-radius:var(--mt-radius-sm);cursor:pointer;white-space:nowrap;padding:6px 11px;font-size:12px;transition:border-color .15s,background .15s}.btn:hover{border-color:var(--mt-accent);background:var(--mt-surface-2)}.btn:disabled{opacity:.5;cursor:default}.btn.is-active{border-color:var(--mt-accent);color:var(--mt-accent)}.chip{border:1px solid var(--mt-border);background:var(--mt-surface);color:var(--mt-text-dim);font-family:var(--mt-mono);border-radius:999px;align-items:center;gap:4px;padding:1px 7px;font-size:11px;display:inline-flex}.chip.is-good{color:var(--mt-good);border-color:color-mix(in srgb, var(--mt-good) 45%, var(--mt-border))}.chip.is-warn{color:var(--mt-warn);border-color:color-mix(in srgb, var(--mt-warn) 45%, var(--mt-border))}.chip.is-bad{color:var(--mt-bad);border-color:color-mix(in srgb, var(--mt-bad) 45%, var(--mt-border))}.tablewrap{border:1px solid var(--mt-border);border-radius:var(--mt-radius);max-height:70vh;overflow:auto}table.tbl{border-collapse:collapse;width:100%;font-size:12px}table.tbl th,table.tbl td{text-align:left;border-bottom:1px solid color-mix(in srgb, var(--mt-border) 55%, transparent);white-space:nowrap;font-family:var(--mt-mono);padding:7px 12px}table.tbl thead th{z-index:1;background:var(--mt-surface-2);color:var(--mt-text-dim);cursor:pointer;-webkit-user-select:none;user-select:none;font-weight:600;position:sticky;top:0}table.tbl thead th:hover{color:var(--mt-text)}table.tbl thead th .arrow{color:var(--mt-accent);margin-left:4px}table.tbl tbody tr{cursor:pointer}table.tbl tbody tr:hover{background:color-mix(in srgb, var(--mt-accent) 8%, transparent)}table.tbl tbody tr.is-disabled td,table.tbl td.col-num{color:var(--mt-text-faint)}.kv__body{border:1px solid var(--mt-border);border-radius:var(--mt-radius);grid-template-columns:minmax(120px,.4fr) 1fr;display:grid;overflow:hidden}.kv__body>div{border-bottom:1px solid color-mix(in srgb, var(--mt-border) 55%, transparent);overflow-wrap:anywhere;min-width:0;font-size:12px;font-family:var(--mt-mono);padding:7px 14px}.kv__k{color:var(--mt-text-dim);background:color-mix(in srgb, var(--mt-surface) 60%, transparent)}.kv__v{color:var(--mt-text)}.drawer{border:1px solid var(--mt-accent);border-radius:var(--mt-radius);background:var(--mt-surface);gap:10px;padding:12px;display:grid}.drawer__hd{align-items:center;gap:8px;display:flex}.drawer__hd b{font-family:var(--mt-mono)}pre.raw{border:1px solid var(--mt-border);border-radius:var(--mt-radius);background:var(--mt-surface);color:var(--mt-text);font-family:var(--mt-mono);white-space:pre;max-height:70vh;margin:0;padding:14px;font-size:12px;overflow:auto}.foot{color:var(--mt-text-faint);font-size:11px;font-family:var(--mt-mono);flex-wrap:wrap;align-items:center;gap:10px;display:flex}.foot .grow{flex:1}.skeleton,.empty{color:var(--mt-text-faint);text-align:center;padding:40px 0}.count-pill b{color:var(--mt-accent)}table.tbl td.col-num,table.tbl th.col-num{width:1%}
|
|
15
15
|
/*$vite$:1*/
|