mioku-service-webui 4.1.0 → 4.3.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/index.ts CHANGED
@@ -8,7 +8,12 @@ import { createNodeWebSocket } from "@hono/node-ws";
8
8
  import type { MiokuService } from "mioku";
9
9
  import type { WebUISettings } from "./types";
10
10
  import { ensureAuthConfig, loginWithToken, requireAuth } from "./auth";
11
- import { ensureWebUIDist, getWebUISettings, getSystemOverview, getSaying } from "./system";
11
+ import {
12
+ ensureWebUIDist,
13
+ getWebUISettings,
14
+ getSystemOverview,
15
+ getSaying,
16
+ } from "./system";
12
17
  import { CHAT_CONFIG_DIR, CHAT_DATA_DIR, LOGS_DIR, WEBUI_DIST } from "./utils";
13
18
  import { initBuiltinDatasources } from "./datasources";
14
19
  import {
@@ -22,6 +27,7 @@ import {
22
27
  createStoreRoutes,
23
28
  createDataManagementRoutes,
24
29
  createStrangerRoutes,
30
+ createAccessControlRoutes,
25
31
  } from "./routes";
26
32
 
27
33
  export interface WebUIServiceAPI {
@@ -102,6 +108,7 @@ class WebUIRuntime {
102
108
  this.app.route("/api/meme", createMemeRoutes());
103
109
  this.app.route("/api/data-management", createDataManagementRoutes());
104
110
  this.app.route("/api/stranger", createStrangerRoutes());
111
+ this.app.route("/api/access-control", createAccessControlRoutes());
105
112
 
106
113
  this.app.get(
107
114
  "/api/ws/logs",
@@ -213,7 +220,7 @@ const runtime = new WebUIRuntime();
213
220
 
214
221
  const webUIService: MiokuService = {
215
222
  name: "webui",
216
- version: "2.0.0",
223
+ version: "4.2.0",
217
224
  description: "Mioku WebUI 管理服务",
218
225
  api: {
219
226
  getSettings: () => getWebUISettings(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-service-webui",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "Mioku WebUI 服务",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -0,0 +1,162 @@
1
+ import { Hono } from "hono";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { logger } from "mioki";
5
+ import { readJsonFile, writeJsonFile, NODE_MODULES_DIR } from "../utils";
6
+
7
+ const ACCESS_CONFIG_PATH = path.resolve(
8
+ process.cwd(),
9
+ "config/boot/access-control.json",
10
+ );
11
+
12
+ const DEFAULT_CONFIG = {
13
+ version: 1,
14
+ global: { plugins: {}, commands: {} },
15
+ groups: {},
16
+ users: {},
17
+ };
18
+
19
+ export function normalizeAccessConfig(input: any) {
20
+ return {
21
+ version: 1,
22
+ global: {
23
+ plugins: input?.global?.plugins ?? {},
24
+ commands: input?.global?.commands ?? {},
25
+ },
26
+ groups: input?.groups ?? {},
27
+ users: input?.users ?? {},
28
+ };
29
+ }
30
+
31
+ type AccessItemKind = "plugin" | "command";
32
+
33
+ interface AccessItem {
34
+ kind: AccessItemKind;
35
+ plugin: string;
36
+ id: string;
37
+ label: string;
38
+ desc?: string;
39
+ match?: string;
40
+ event?: string;
41
+ fromHook: boolean;
42
+ }
43
+
44
+ function readAccessCatalog(): AccessItem[] {
45
+ if (!fs.existsSync(NODE_MODULES_DIR)) return [];
46
+ const entries = fs.readdirSync(NODE_MODULES_DIR, { withFileTypes: true });
47
+ const items: AccessItem[] = [];
48
+
49
+ for (const entry of entries) {
50
+ if (!entry.name.startsWith("mioku-plugin-")) continue;
51
+ const fullPath = path.join(NODE_MODULES_DIR, entry.name);
52
+ let stat: fs.Stats;
53
+ try {
54
+ stat = fs.lstatSync(fullPath);
55
+ } catch {
56
+ continue;
57
+ }
58
+ if (!stat.isDirectory() && !stat.isSymbolicLink()) continue;
59
+
60
+ const pkgPath = path.join(fullPath, "package.json");
61
+ if (!fs.existsSync(pkgPath)) continue;
62
+ let pkg: any;
63
+ try {
64
+ pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
65
+ } catch {
66
+ continue;
67
+ }
68
+
69
+ const name = entry.name.replace(/^mioku-plugin-/, "");
70
+ const mioku = pkg.mioku || {};
71
+ const help = mioku.help || { title: name, description: "", commands: [] };
72
+ const hooks: Array<{ id: string; match?: string; event?: string; description?: string }> =
73
+ mioku.accessHooks || [];
74
+ const helpCommands: Array<any> = help.commands || [];
75
+
76
+ items.push({
77
+ kind: "plugin",
78
+ plugin: name,
79
+ id: name,
80
+ label: help.title || name,
81
+ desc: help.description,
82
+ fromHook: false,
83
+ });
84
+
85
+ const seen = new Set<string>();
86
+ for (const hook of hooks) {
87
+ seen.add(hook.id);
88
+ items.push({
89
+ kind: "command",
90
+ plugin: name,
91
+ id: hook.id,
92
+ label: hook.id,
93
+ desc: hook.description,
94
+ match: hook.match,
95
+ event: hook.event,
96
+ fromHook: true,
97
+ });
98
+ }
99
+ for (const c of helpCommands) {
100
+ const id = String(c.cmd || "").trim();
101
+ if (!id || seen.has(id)) continue;
102
+ items.push({
103
+ kind: "command",
104
+ plugin: name,
105
+ id,
106
+ label: id,
107
+ desc: c.desc,
108
+ fromHook: false,
109
+ });
110
+ }
111
+ }
112
+
113
+ return items.sort((a, b) => {
114
+ if (a.kind !== b.kind) return a.kind === "plugin" ? -1 : 1;
115
+ if (a.plugin !== b.plugin) return a.plugin.localeCompare(b.plugin);
116
+ return a.id.localeCompare(b.id);
117
+ });
118
+ }
119
+
120
+ function filterCatalog(items: AccessItem[], query?: string): AccessItem[] {
121
+ if (!query) return items;
122
+ const q = query.trim().toLowerCase();
123
+ if (!q) return items;
124
+ return items.filter((it) => {
125
+ return (
126
+ it.id.toLowerCase().includes(q) ||
127
+ it.label.toLowerCase().includes(q) ||
128
+ it.plugin.toLowerCase().includes(q) ||
129
+ (it.desc || "").toLowerCase().includes(q)
130
+ );
131
+ });
132
+ }
133
+
134
+ export function createAccessControlRoutes() {
135
+ const app = new Hono();
136
+
137
+ app.get("/", (c) => {
138
+ const data = readJsonFile(ACCESS_CONFIG_PATH, DEFAULT_CONFIG);
139
+ return c.json({ ok: true, data: normalizeAccessConfig(data) });
140
+ });
141
+
142
+ app.put("/", async (c) => {
143
+ const body = await c.req.json().catch(() => null);
144
+ if (!body) {
145
+ return c.json({ ok: false, error: "INVALID_BODY" }, 400);
146
+ }
147
+ const data = normalizeAccessConfig(body);
148
+ writeJsonFile(ACCESS_CONFIG_PATH, data);
149
+ logger.info(
150
+ `[webui-action] access-control.update | groups=${Object.keys(data.groups).length} users=${Object.keys(data.users).length}`,
151
+ );
152
+ return c.json({ ok: true, data });
153
+ });
154
+
155
+ app.get("/catalog", (c) => {
156
+ const q = c.req.query("q");
157
+ const data = filterCatalog(readAccessCatalog(), q);
158
+ return c.json({ ok: true, data });
159
+ });
160
+
161
+ return app;
162
+ }
package/routes/index.ts CHANGED
@@ -6,3 +6,4 @@ export { createManageRoutes } from "./manage";
6
6
  export { createStoreRoutes } from "./store";
7
7
  export { createDataManagementRoutes } from "./data-management";
8
8
  export { createStrangerRoutes } from "./stranger";
9
+ export { createAccessControlRoutes } from "./access-control";
package/system.ts CHANGED
@@ -322,6 +322,26 @@ function readPackageJson(dir: string): any {
322
322
  }
323
323
  }
324
324
 
325
+ const WEBUI_PLUGIN_PREFIX = "mioku-plugin-";
326
+
327
+ function appendToMiokiPlugins(cwd: string, pkgName: string): boolean {
328
+ if (!pkgName.startsWith(WEBUI_PLUGIN_PREFIX)) return false;
329
+ const shortName = pkgName.slice(WEBUI_PLUGIN_PREFIX.length);
330
+
331
+ const packageJsonPath = path.join(cwd, "package.json");
332
+ if (!fs.existsSync(packageJsonPath)) return false;
333
+
334
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
335
+ const mioki = pkg.mioki ?? {};
336
+ const plugins = Array.isArray(mioki.plugins) ? [...mioki.plugins] : [];
337
+ if (plugins.includes(shortName)) return false;
338
+
339
+ plugins.push(shortName);
340
+ pkg.mioki = { ...mioki, plugins };
341
+ fs.writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
342
+ return true;
343
+ }
344
+
325
345
  function readRootPackageJson(): any {
326
346
  return JSON.parse(fs.readFileSync(ROOT_PACKAGE_PATH, "utf-8"));
327
347
  }
@@ -1020,12 +1040,18 @@ export async function installManagedPackage(
1020
1040
  makeManagedUpdateCacheKey(input.target, installedName),
1021
1041
  );
1022
1042
 
1043
+ let enabled = false;
1044
+ if (input.target === "plugin") {
1045
+ enabled = appendToMiokiPlugins(installDir, installedName);
1046
+ }
1047
+
1023
1048
  return {
1024
1049
  ok: true,
1025
1050
  name: installedName,
1026
1051
  missingServices,
1027
1052
  packageManager: "bun",
1028
1053
  restartRequired: true,
1054
+ enabled,
1029
1055
  installOutput: install.stdout || install.stderr,
1030
1056
  };
1031
1057
  }