mioku-service-webui 4.3.0 → 4.4.1
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/config-page-loader.ts +24 -11
- package/index.ts +11 -2
- package/package.json +1 -1
- package/port-utils.ts +159 -0
- package/routes/data-management.ts +1 -1
- package/routes/db.ts +100 -1
- package/routes/index.ts +1 -1
- package/system.ts +61 -2
package/config-page-loader.ts
CHANGED
|
@@ -27,25 +27,40 @@ export interface ConfigPageManifest {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function loadPluginConfigPage(pluginName: string): ConfigPageManifest | null {
|
|
30
|
-
// 支持两种路径:本地 plugins 目录和 npm 包
|
|
31
30
|
const possiblePaths = [
|
|
32
31
|
path.join(process.cwd(), "plugins", pluginName),
|
|
33
32
|
path.join(process.cwd(), "node_modules", `mioku-plugin-${pluginName}`),
|
|
34
33
|
];
|
|
35
34
|
|
|
35
|
+
return loadConfigPage(pluginName, possiblePaths);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loadServiceConfigPage(serviceName: string): ConfigPageManifest | null {
|
|
39
|
+
const possiblePaths = [
|
|
40
|
+
path.join(process.cwd(), "services", serviceName),
|
|
41
|
+
path.join(process.cwd(), "node_modules", `mioku-service-${serviceName}`),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
return loadConfigPage(serviceName, possiblePaths);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function loadConfigPage(
|
|
48
|
+
packageName: string,
|
|
49
|
+
possiblePaths: string[],
|
|
50
|
+
): ConfigPageManifest | null {
|
|
36
51
|
let configMdPath: string | null = null;
|
|
37
|
-
let
|
|
52
|
+
let packageDir: string | null = null;
|
|
38
53
|
|
|
39
54
|
for (const dir of possiblePaths) {
|
|
40
55
|
const candidate = path.join(dir, "config.md");
|
|
41
56
|
if (fs.existsSync(candidate)) {
|
|
42
57
|
configMdPath = candidate;
|
|
43
|
-
|
|
58
|
+
packageDir = dir;
|
|
44
59
|
break;
|
|
45
60
|
}
|
|
46
61
|
}
|
|
47
62
|
|
|
48
|
-
if (!configMdPath || !
|
|
63
|
+
if (!configMdPath || !packageDir) {
|
|
49
64
|
return null;
|
|
50
65
|
}
|
|
51
66
|
|
|
@@ -56,29 +71,27 @@ export function loadPluginConfigPage(pluginName: string): ConfigPageManifest | n
|
|
|
56
71
|
const frontmatter = parsed.data as any;
|
|
57
72
|
const fields = (frontmatter.fields || []) as ConfigField[];
|
|
58
73
|
|
|
59
|
-
// Validate fields
|
|
60
74
|
for (const field of fields) {
|
|
61
75
|
if (!field.key || !field.label || !field.type) {
|
|
62
|
-
logger.warn(`Invalid field in ${
|
|
76
|
+
logger.warn(`Invalid field in ${packageName}/config.md: missing key, label, or type`);
|
|
63
77
|
continue;
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
// Validate key format: <configName>.<jsonPath>
|
|
67
80
|
if (!field.key.includes(".")) {
|
|
68
|
-
logger.warn(`Invalid field key in ${
|
|
81
|
+
logger.warn(`Invalid field key in ${packageName}/config.md: ${field.key} (must be <configName>.<path>)`);
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
return {
|
|
73
|
-
plugin:
|
|
74
|
-
title: frontmatter.title || `${
|
|
86
|
+
plugin: packageName,
|
|
87
|
+
title: frontmatter.title || `${packageName} Configuration`,
|
|
75
88
|
description: frontmatter.description,
|
|
76
89
|
markdown: parsed.content,
|
|
77
90
|
fields,
|
|
78
91
|
hasCustomPage: true,
|
|
79
92
|
};
|
|
80
93
|
} catch (error: any) {
|
|
81
|
-
logger.error(`Failed to load config page for ${
|
|
94
|
+
logger.error(`Failed to load config page for ${packageName}: ${error.message}`);
|
|
82
95
|
return null;
|
|
83
96
|
}
|
|
84
97
|
}
|
package/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
createAIRoutes,
|
|
23
23
|
createDBRoutes,
|
|
24
24
|
createPluginConfigRoutes,
|
|
25
|
+
createServiceConfigRoutes,
|
|
25
26
|
createMemeRoutes,
|
|
26
27
|
createManageRoutes,
|
|
27
28
|
createStoreRoutes,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
createStrangerRoutes,
|
|
30
31
|
createAccessControlRoutes,
|
|
31
32
|
} from "./routes";
|
|
33
|
+
import { freePort } from "./port-utils";
|
|
32
34
|
|
|
33
35
|
export interface WebUIServiceAPI {
|
|
34
36
|
getSettings(): WebUISettings;
|
|
@@ -43,7 +45,7 @@ class WebUIRuntime {
|
|
|
43
45
|
logger.info(`[webui-action] ${action}${text}`);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
public initRoutes(): void {
|
|
48
|
+
public async initRoutes(): Promise<void> {
|
|
47
49
|
const { upgradeWebSocket, injectWebSocket } = createNodeWebSocket({
|
|
48
50
|
app: this.app,
|
|
49
51
|
});
|
|
@@ -105,6 +107,7 @@ class WebUIRuntime {
|
|
|
105
107
|
this.app.route("/api/store", createStoreRoutes());
|
|
106
108
|
this.app.route("/api/db", createDBRoutes());
|
|
107
109
|
this.app.route("/api/plugin-config", createPluginConfigRoutes());
|
|
110
|
+
this.app.route("/api/service-config", createServiceConfigRoutes());
|
|
108
111
|
this.app.route("/api/meme", createMemeRoutes());
|
|
109
112
|
this.app.route("/api/data-management", createDataManagementRoutes());
|
|
110
113
|
this.app.route("/api/stranger", createStrangerRoutes());
|
|
@@ -170,6 +173,12 @@ class WebUIRuntime {
|
|
|
170
173
|
});
|
|
171
174
|
|
|
172
175
|
const settings = getWebUISettings();
|
|
176
|
+
const freed = await freePort(settings.port);
|
|
177
|
+
if (freed.remainingPids.length > 0) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`webui 端口 ${settings.port} 被占用且无法结束 (PID: ${freed.remainingPids.join(", ")})`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
173
182
|
const server = serve({
|
|
174
183
|
fetch: this.app.fetch,
|
|
175
184
|
port: settings.port,
|
|
@@ -230,7 +239,7 @@ const webUIService: MiokuService = {
|
|
|
230
239
|
initBuiltinDatasources();
|
|
231
240
|
ensureAuthConfig();
|
|
232
241
|
await ensureWebUIDist();
|
|
233
|
-
runtime.initRoutes();
|
|
242
|
+
await runtime.initRoutes();
|
|
234
243
|
},
|
|
235
244
|
|
|
236
245
|
async dispose() {
|
package/package.json
CHANGED
package/port-utils.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { logger } from "mioki";
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
|
|
7
|
+
export interface FreePortLogger {
|
|
8
|
+
info: (msg: string) => void;
|
|
9
|
+
warn: (msg: string) => void;
|
|
10
|
+
error: (msg: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FreePortOptions {
|
|
14
|
+
logger?: FreePortLogger;
|
|
15
|
+
graceMs?: number;
|
|
16
|
+
sigkillDelayMs?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FreePortResult {
|
|
20
|
+
port: number;
|
|
21
|
+
wasOccupied: boolean;
|
|
22
|
+
killedPids: number[];
|
|
23
|
+
remainingPids: number[];
|
|
24
|
+
lsofAvailable: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sleep(ms: number): Promise<void> {
|
|
28
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parsePids(raw: string): number[] {
|
|
32
|
+
const pids = new Set<number>();
|
|
33
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
34
|
+
const trimmed = line.trim();
|
|
35
|
+
if (!trimmed) continue;
|
|
36
|
+
const pid = Number(trimmed);
|
|
37
|
+
if (Number.isFinite(pid) && pid > 0 && Math.floor(pid) === pid) {
|
|
38
|
+
pids.add(pid);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return Array.from(pids);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function listListeningPids(
|
|
45
|
+
port: number,
|
|
46
|
+
): Promise<{ pids: number[]; lsofAvailable: boolean }> {
|
|
47
|
+
try {
|
|
48
|
+
const { stdout } = await execFileAsync("lsof", [
|
|
49
|
+
"-nP",
|
|
50
|
+
`-iTCP:${port}`,
|
|
51
|
+
"-sTCP:LISTEN",
|
|
52
|
+
"-t",
|
|
53
|
+
]);
|
|
54
|
+
return { pids: parsePids(stdout), lsofAvailable: true };
|
|
55
|
+
} catch (err: any) {
|
|
56
|
+
if (err?.code === "ENOENT") {
|
|
57
|
+
return { pids: [], lsofAvailable: false };
|
|
58
|
+
}
|
|
59
|
+
return { pids: [], lsofAvailable: true };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function sendSignal(
|
|
64
|
+
pid: number,
|
|
65
|
+
signal: NodeJS.Signals,
|
|
66
|
+
): Promise<boolean> {
|
|
67
|
+
if (pid === process.pid) return false;
|
|
68
|
+
try {
|
|
69
|
+
process.kill(pid, signal);
|
|
70
|
+
return true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function freePort(
|
|
77
|
+
port: number,
|
|
78
|
+
options: FreePortOptions = {},
|
|
79
|
+
): Promise<FreePortResult> {
|
|
80
|
+
const log = options.logger ?? (logger as unknown as FreePortLogger);
|
|
81
|
+
const graceMs = options.graceMs ?? 1500;
|
|
82
|
+
const sigkillDelayMs = options.sigkillDelayMs ?? 500;
|
|
83
|
+
|
|
84
|
+
const initial = await listListeningPids(port);
|
|
85
|
+
|
|
86
|
+
if (!initial.lsofAvailable) {
|
|
87
|
+
log.warn(
|
|
88
|
+
`未检测到 lsof 命令,无法定位端口 ${port} 的占用进程,建议安装 lsof`,
|
|
89
|
+
);
|
|
90
|
+
return {
|
|
91
|
+
port,
|
|
92
|
+
wasOccupied: false,
|
|
93
|
+
killedPids: [],
|
|
94
|
+
remainingPids: [],
|
|
95
|
+
lsofAvailable: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const occupied = initial.pids.filter((pid) => pid !== process.pid);
|
|
100
|
+
if (occupied.length === 0) {
|
|
101
|
+
return {
|
|
102
|
+
port,
|
|
103
|
+
wasOccupied: false,
|
|
104
|
+
killedPids: [],
|
|
105
|
+
remainingPids: [],
|
|
106
|
+
lsofAvailable: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
log.warn(
|
|
111
|
+
`端口 ${port} 被占用,进程 PID: ${occupied.join(", ")},正在尝试结束...`,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const killed: number[] = [];
|
|
115
|
+
for (const pid of occupied) {
|
|
116
|
+
if (await sendSignal(pid, "SIGTERM")) {
|
|
117
|
+
killed.push(pid);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (killed.length > 0) {
|
|
122
|
+
await sleep(graceMs);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let remaining = (await listListeningPids(port)).pids.filter(
|
|
126
|
+
(pid) => pid !== process.pid,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (remaining.length > 0) {
|
|
130
|
+
log.warn(
|
|
131
|
+
`端口 ${port} 仍被占用 (PID: ${remaining.join(", ")}),发送 SIGKILL 强制结束...`,
|
|
132
|
+
);
|
|
133
|
+
for (const pid of remaining) {
|
|
134
|
+
if (await sendSignal(pid, "SIGKILL")) {
|
|
135
|
+
killed.push(pid);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await sleep(sigkillDelayMs);
|
|
139
|
+
remaining = (await listListeningPids(port)).pids.filter(
|
|
140
|
+
(pid) => pid !== process.pid,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (remaining.length > 0) {
|
|
145
|
+
log.error(
|
|
146
|
+
`端口 ${port} 仍被占用,无法结束进程 PID: ${remaining.join(", ")}`,
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
log.info(`端口 ${port} 已释放`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
port,
|
|
154
|
+
wasOccupied: true,
|
|
155
|
+
killedPids: killed,
|
|
156
|
+
remainingPids: remaining,
|
|
157
|
+
lsofAvailable: true,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -514,7 +514,7 @@ export function createDataManagementRoutes() {
|
|
|
514
514
|
|
|
515
515
|
app.post("/node-modules/refresh", async (c) => {
|
|
516
516
|
const settings = getWebUISettings();
|
|
517
|
-
const command = getInstallCommand(
|
|
517
|
+
const command = getInstallCommand();
|
|
518
518
|
const startedAt = Date.now();
|
|
519
519
|
const result = await runCommand(command.cmd, command.args, process.cwd());
|
|
520
520
|
const finishedAt = Date.now();
|
package/routes/db.ts
CHANGED
|
@@ -14,8 +14,12 @@ import {
|
|
|
14
14
|
updateMessage,
|
|
15
15
|
} from "../database";
|
|
16
16
|
import { CHAT_DATA_DIR } from "../utils";
|
|
17
|
-
import { loadPluginConfigPage } from "../config-page-loader";
|
|
17
|
+
import { loadPluginConfigPage, loadServiceConfigPage } from "../config-page-loader";
|
|
18
18
|
import { getDatasource } from "../datasources";
|
|
19
|
+
import {
|
|
20
|
+
getServiceConfigs,
|
|
21
|
+
updateServiceConfig,
|
|
22
|
+
} from "mioku";
|
|
19
23
|
|
|
20
24
|
export function createDBRoutes() {
|
|
21
25
|
const app = new Hono();
|
|
@@ -219,3 +223,98 @@ export function createMemeRoutes() {
|
|
|
219
223
|
|
|
220
224
|
return app;
|
|
221
225
|
}
|
|
226
|
+
|
|
227
|
+
export function createServiceConfigRoutes() {
|
|
228
|
+
const app = new Hono();
|
|
229
|
+
const SERVICE_CONFIG_ROOT = path.join(process.cwd(), "config", "service");
|
|
230
|
+
|
|
231
|
+
app.get("/overview", (c) => {
|
|
232
|
+
const modulesPath = path.join(process.cwd(), "node_modules");
|
|
233
|
+
if (!fs.existsSync(modulesPath)) {
|
|
234
|
+
return c.json({ ok: true, data: [] });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const services: string[] = [];
|
|
238
|
+
const entries = fs.readdirSync(modulesPath, { withFileTypes: true });
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
if (entry.name.startsWith("mioku-service-")) {
|
|
241
|
+
services.push(entry.name.replace(/^mioku-service-/, ""));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const overview = services
|
|
246
|
+
.map((name) => {
|
|
247
|
+
const configDir = path.join(SERVICE_CONFIG_ROOT, name);
|
|
248
|
+
const manifest = loadServiceConfigPage(name);
|
|
249
|
+
const existingConfigFiles =
|
|
250
|
+
fs.existsSync(configDir)
|
|
251
|
+
? fs
|
|
252
|
+
.readdirSync(configDir)
|
|
253
|
+
.filter((f) => f.endsWith(".json"))
|
|
254
|
+
.map((f) => f.replace(".json", ""))
|
|
255
|
+
: [];
|
|
256
|
+
const declaredConfigFiles = manifest
|
|
257
|
+
? Array.from(
|
|
258
|
+
new Set(
|
|
259
|
+
(manifest.fields || [])
|
|
260
|
+
.map((field) => String(field.key || ""))
|
|
261
|
+
.filter((key) => key.includes("."))
|
|
262
|
+
.map((key) => key.split(".")[0]),
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
: [];
|
|
266
|
+
const configFiles = Array.from(
|
|
267
|
+
new Set([...existingConfigFiles, ...declaredConfigFiles]),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
name,
|
|
272
|
+
title: manifest?.title || name,
|
|
273
|
+
description: manifest?.description,
|
|
274
|
+
hasPage: manifest?.hasCustomPage || false,
|
|
275
|
+
configFiles,
|
|
276
|
+
};
|
|
277
|
+
})
|
|
278
|
+
.filter((p) => p.hasPage);
|
|
279
|
+
|
|
280
|
+
return c.json({ ok: true, data: overview });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
app.get("/:name/page", (c) => {
|
|
284
|
+
const name = c.req.param("name");
|
|
285
|
+
const manifest = loadServiceConfigPage(name);
|
|
286
|
+
const configs = getServiceConfigs(name);
|
|
287
|
+
|
|
288
|
+
if (!manifest) {
|
|
289
|
+
return c.json({ ok: true, data: null });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return c.json({
|
|
293
|
+
ok: true,
|
|
294
|
+
data: {
|
|
295
|
+
plugin: manifest.plugin,
|
|
296
|
+
title: manifest.title,
|
|
297
|
+
description: manifest.description,
|
|
298
|
+
markdown: manifest.markdown,
|
|
299
|
+
fields: manifest.fields,
|
|
300
|
+
hasCustomPage: manifest.hasCustomPage,
|
|
301
|
+
configs,
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
app.get("/:name", (c) => {
|
|
307
|
+
const name = c.req.param("name");
|
|
308
|
+
return c.json({ ok: true, data: getServiceConfigs(name) });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
app.put("/:name/:config", async (c) => {
|
|
312
|
+
const serviceName = c.req.param("name");
|
|
313
|
+
const configName = c.req.param("config");
|
|
314
|
+
const body = await c.req.json();
|
|
315
|
+
updateServiceConfig(serviceName, configName, body);
|
|
316
|
+
return c.json({ ok: true });
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return app;
|
|
320
|
+
}
|
package/routes/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createConfigRoutes } from "./config";
|
|
2
2
|
export { createWebUISettingsRoutes } from "./webui";
|
|
3
3
|
export { createAIRoutes } from "./ai";
|
|
4
|
-
export { createDBRoutes, createPluginConfigRoutes, createMemeRoutes } from "./db";
|
|
4
|
+
export { createDBRoutes, createPluginConfigRoutes, createMemeRoutes, createServiceConfigRoutes } from "./db";
|
|
5
5
|
export { createManageRoutes } from "./manage";
|
|
6
6
|
export { createStoreRoutes } from "./store";
|
|
7
7
|
export { createDataManagementRoutes } from "./data-management";
|
package/system.ts
CHANGED
|
@@ -96,6 +96,66 @@ const BOOT_CONFIG_PATH = path.join(
|
|
|
96
96
|
"base.json",
|
|
97
97
|
);
|
|
98
98
|
|
|
99
|
+
export interface BootSystemCommandsConfig {
|
|
100
|
+
update: {
|
|
101
|
+
enabled: boolean;
|
|
102
|
+
selectTimeoutMs: number;
|
|
103
|
+
};
|
|
104
|
+
install: {
|
|
105
|
+
enabled: boolean;
|
|
106
|
+
};
|
|
107
|
+
market: {
|
|
108
|
+
enabled: boolean;
|
|
109
|
+
};
|
|
110
|
+
restart: {
|
|
111
|
+
enabled: boolean;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const DEFAULT_BOOT_SYSTEM_COMMANDS_CONFIG: BootSystemCommandsConfig = {
|
|
116
|
+
update: {
|
|
117
|
+
enabled: true,
|
|
118
|
+
selectTimeoutMs: 60000,
|
|
119
|
+
},
|
|
120
|
+
install: {
|
|
121
|
+
enabled: true,
|
|
122
|
+
},
|
|
123
|
+
market: {
|
|
124
|
+
enabled: true,
|
|
125
|
+
},
|
|
126
|
+
restart: {
|
|
127
|
+
enabled: true,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
function normalizeBootSystemCommandsConfig(
|
|
132
|
+
input: any,
|
|
133
|
+
): BootSystemCommandsConfig {
|
|
134
|
+
const merged = deepMerge(
|
|
135
|
+
cloneJson(DEFAULT_BOOT_SYSTEM_COMMANDS_CONFIG),
|
|
136
|
+
input || {},
|
|
137
|
+
);
|
|
138
|
+
const rawTimeout = Number(merged?.update?.selectTimeoutMs);
|
|
139
|
+
return {
|
|
140
|
+
update: {
|
|
141
|
+
enabled: Boolean(merged?.update?.enabled),
|
|
142
|
+
selectTimeoutMs:
|
|
143
|
+
Number.isFinite(rawTimeout) && rawTimeout >= 1000
|
|
144
|
+
? Math.floor(rawTimeout)
|
|
145
|
+
: DEFAULT_BOOT_SYSTEM_COMMANDS_CONFIG.update.selectTimeoutMs,
|
|
146
|
+
},
|
|
147
|
+
install: {
|
|
148
|
+
enabled: Boolean(merged?.install?.enabled),
|
|
149
|
+
},
|
|
150
|
+
market: {
|
|
151
|
+
enabled: Boolean(merged?.market?.enabled),
|
|
152
|
+
},
|
|
153
|
+
restart: {
|
|
154
|
+
enabled: Boolean(merged?.restart?.enabled),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
99
159
|
interface AccessRuleConfig {
|
|
100
160
|
whitelist: Array<string | number>;
|
|
101
161
|
blacklist: Array<string | number>;
|
|
@@ -1345,8 +1405,7 @@ export async function getManagedPackageDetail(
|
|
|
1345
1405
|
const missingServices = checkDependentServices(pkg);
|
|
1346
1406
|
|
|
1347
1407
|
const isSystemPlugin = target === "plugin" ? isSystemPluginName(name) : false;
|
|
1348
|
-
const isSystemService =
|
|
1349
|
-
target === "service" ? isSystemServiceName(name) : false;
|
|
1408
|
+
const isSystemService = false;
|
|
1350
1409
|
return {
|
|
1351
1410
|
ok: true,
|
|
1352
1411
|
data: {
|