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 +9 -2
- package/package.json +1 -1
- package/routes/access-control.ts +162 -0
- package/routes/index.ts +1 -0
- package/system.ts +26 -0
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 {
|
|
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
|
|
223
|
+
version: "4.2.0",
|
|
217
224
|
description: "Mioku WebUI 管理服务",
|
|
218
225
|
api: {
|
|
219
226
|
getSettings: () => getWebUISettings(),
|
package/package.json
CHANGED
|
@@ -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
|
}
|