@zhin.js/console 1.0.39 → 1.0.41
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/CHANGELOG.md +17 -0
- package/client/src/components/PluginConfigForm/index.tsx +20 -5
- package/client/src/components/PluginConfigForm/types.ts +1 -0
- package/client/src/main.tsx +22 -6
- package/client/src/pages/dashboard-config.tsx +421 -0
- package/client/src/pages/dashboard-env.tsx +219 -0
- package/dist/client.js +1 -1
- package/dist/index.js +137 -6
- package/dist/style.css +2 -2
- package/lib/index.js +204 -81
- package/lib/websocket.d.ts +0 -9
- package/lib/websocket.js +174 -56
- package/package.json +7 -6
package/lib/websocket.js
CHANGED
|
@@ -1,36 +1,41 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
1
3
|
import WebSocket from 'ws';
|
|
2
4
|
import { usePlugin } from '@zhin.js/core';
|
|
3
5
|
|
|
4
6
|
// src/websocket.ts
|
|
5
7
|
var { root, logger } = usePlugin();
|
|
8
|
+
var ENV_WHITELIST = [".env", ".env.development", ".env.production"];
|
|
9
|
+
function resolveConfigKey(pluginName) {
|
|
10
|
+
const schemaService = root.inject("schema");
|
|
11
|
+
return schemaService?.resolveConfigKey(pluginName) ?? pluginName;
|
|
12
|
+
}
|
|
13
|
+
function getPluginKeys() {
|
|
14
|
+
const schemaService = root.inject("schema");
|
|
15
|
+
if (!schemaService) return [];
|
|
16
|
+
const keys = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const [, configKey] of schemaService.getPluginKeyMap()) {
|
|
18
|
+
keys.add(configKey);
|
|
19
|
+
}
|
|
20
|
+
return Array.from(keys);
|
|
21
|
+
}
|
|
22
|
+
function getConfigFilePath() {
|
|
23
|
+
return path.resolve(process.cwd(), "zhin.config.yml");
|
|
24
|
+
}
|
|
6
25
|
function setupWebSocket(webServer) {
|
|
7
26
|
webServer.ws.on("connection", (ws) => {
|
|
8
|
-
ws.send(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
value: Object.values(webServer.entries)
|
|
14
|
-
}
|
|
15
|
-
})
|
|
16
|
-
);
|
|
17
|
-
ws.send(
|
|
18
|
-
JSON.stringify({
|
|
19
|
-
type: "init-data",
|
|
20
|
-
timestamp: Date.now()
|
|
21
|
-
})
|
|
22
|
-
);
|
|
27
|
+
ws.send(JSON.stringify({
|
|
28
|
+
type: "sync",
|
|
29
|
+
data: { key: "entries", value: Object.values(webServer.entries) }
|
|
30
|
+
}));
|
|
31
|
+
ws.send(JSON.stringify({ type: "init-data", timestamp: Date.now() }));
|
|
23
32
|
ws.on("message", async (data) => {
|
|
24
33
|
try {
|
|
25
34
|
const message = JSON.parse(data.toString());
|
|
26
35
|
await handleWebSocketMessage(ws, message, webServer);
|
|
27
36
|
} catch (error) {
|
|
28
37
|
console.error("WebSocket \u6D88\u606F\u5904\u7406\u9519\u8BEF:", error);
|
|
29
|
-
ws.send(
|
|
30
|
-
JSON.stringify({
|
|
31
|
-
error: "Invalid message format"
|
|
32
|
-
})
|
|
33
|
-
);
|
|
38
|
+
ws.send(JSON.stringify({ error: "Invalid message format" }));
|
|
34
39
|
}
|
|
35
40
|
});
|
|
36
41
|
ws.on("close", () => {
|
|
@@ -47,19 +52,50 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
47
52
|
ws.send(JSON.stringify({ type: "pong", requestId }));
|
|
48
53
|
break;
|
|
49
54
|
case "entries:get":
|
|
50
|
-
ws.send(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
ws.send(JSON.stringify({ requestId, data: Object.values(webServer.entries) }));
|
|
56
|
+
break;
|
|
57
|
+
// ================================================================
|
|
58
|
+
// 配置文件原始 YAML 读写(用于配置管理页面)
|
|
59
|
+
// ================================================================
|
|
60
|
+
case "config:get-yaml":
|
|
61
|
+
try {
|
|
62
|
+
const filePath = getConfigFilePath();
|
|
63
|
+
const yaml = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : "";
|
|
64
|
+
ws.send(JSON.stringify({ requestId, data: { yaml, pluginKeys: getPluginKeys() } }));
|
|
65
|
+
} catch (error) {
|
|
66
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to read config: ${error.message}` }));
|
|
67
|
+
}
|
|
56
68
|
break;
|
|
69
|
+
case "config:save-yaml":
|
|
70
|
+
try {
|
|
71
|
+
const { yaml } = message;
|
|
72
|
+
if (typeof yaml !== "string") {
|
|
73
|
+
ws.send(JSON.stringify({ requestId, error: "yaml field is required" }));
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
const filePath = getConfigFilePath();
|
|
77
|
+
fs.writeFileSync(filePath, yaml, "utf-8");
|
|
78
|
+
const configService = root.inject("config");
|
|
79
|
+
const loader = configService.configs.get("zhin.config.yml");
|
|
80
|
+
if (loader) loader.load();
|
|
81
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u751F\u6548" } }));
|
|
82
|
+
} catch (error) {
|
|
83
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to save config: ${error.message}` }));
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
// ================================================================
|
|
87
|
+
// 插件配置(供 PluginConfigForm 使用)
|
|
88
|
+
// ================================================================
|
|
57
89
|
case "config:get":
|
|
58
90
|
try {
|
|
59
91
|
const configService = root.inject("config");
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
const rawConfig = configService.getRaw("zhin.config.yml");
|
|
93
|
+
if (!pluginName) {
|
|
94
|
+
ws.send(JSON.stringify({ requestId, data: rawConfig }));
|
|
95
|
+
} else {
|
|
96
|
+
const configKey = resolveConfigKey(pluginName);
|
|
97
|
+
ws.send(JSON.stringify({ requestId, data: rawConfig[configKey] || {} }));
|
|
98
|
+
}
|
|
63
99
|
} catch (error) {
|
|
64
100
|
ws.send(JSON.stringify({ requestId, error: `Failed to get config: ${error.message}` }));
|
|
65
101
|
}
|
|
@@ -67,8 +103,17 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
67
103
|
case "config:get-all":
|
|
68
104
|
try {
|
|
69
105
|
const configService = root.inject("config");
|
|
70
|
-
const
|
|
71
|
-
|
|
106
|
+
const rawConfig = configService.getRaw("zhin.config.yml");
|
|
107
|
+
const allConfigs = { ...rawConfig };
|
|
108
|
+
const schemaService = root.inject("schema");
|
|
109
|
+
if (schemaService) {
|
|
110
|
+
for (const [pName, configKey] of schemaService.getPluginKeyMap()) {
|
|
111
|
+
if (pName !== configKey) {
|
|
112
|
+
allConfigs[pName] = rawConfig[configKey] || {};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
ws.send(JSON.stringify({ requestId, data: allConfigs }));
|
|
72
117
|
} catch (error) {
|
|
73
118
|
ws.send(JSON.stringify({ requestId, error: `Failed to get all configs: ${error.message}` }));
|
|
74
119
|
}
|
|
@@ -80,28 +125,45 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
80
125
|
ws.send(JSON.stringify({ requestId, error: "Plugin name is required" }));
|
|
81
126
|
break;
|
|
82
127
|
}
|
|
128
|
+
const configKey = resolveConfigKey(pluginName);
|
|
83
129
|
const configService = root.inject("config");
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
130
|
+
const loader = configService.configs.get("zhin.config.yml");
|
|
131
|
+
if (!loader) {
|
|
132
|
+
ws.send(JSON.stringify({ requestId, error: "Config file not loaded" }));
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
loader.patchKey(configKey, data);
|
|
136
|
+
broadcastToAll(webServer, { type: "config:updated", data: { pluginName, config: data } });
|
|
137
|
+
const schemaService = root.inject("schema");
|
|
138
|
+
const reloadable = schemaService?.isReloadable?.(pluginName) ?? false;
|
|
139
|
+
if (reloadable) {
|
|
140
|
+
const target = findPluginByConfigKey(root, pluginName);
|
|
141
|
+
if (target) {
|
|
142
|
+
try {
|
|
143
|
+
await target.reload();
|
|
144
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, reloaded: true } }));
|
|
145
|
+
} catch (reloadErr) {
|
|
146
|
+
logger.warn(`\u91CD\u8F7D\u63D2\u4EF6 ${pluginName} \u5931\u8D25: ${reloadErr.message}`);
|
|
147
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, reloaded: false, message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u91CD\u8F7D\u5931\u8D25" } }));
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, reloaded: false, message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58" } }));
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, reloaded: false, message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u8FDB\u7A0B\u624D\u80FD\u751F\u6548" } }));
|
|
154
|
+
}
|
|
92
155
|
} catch (error) {
|
|
93
156
|
ws.send(JSON.stringify({ requestId, error: `Failed to set config: ${error.message}` }));
|
|
94
157
|
}
|
|
95
158
|
break;
|
|
159
|
+
// ================================================================
|
|
160
|
+
// Schema(供 PluginConfigForm 使用)
|
|
161
|
+
// ================================================================
|
|
96
162
|
case "schema:get":
|
|
97
163
|
try {
|
|
98
164
|
const schemaService = root.inject("schema");
|
|
99
165
|
const schema = pluginName && schemaService ? schemaService.get(pluginName) : null;
|
|
100
|
-
|
|
101
|
-
ws.send(JSON.stringify({ requestId, data: schema.toJSON() }));
|
|
102
|
-
} else {
|
|
103
|
-
ws.send(JSON.stringify({ requestId, data: null }));
|
|
104
|
-
}
|
|
166
|
+
ws.send(JSON.stringify({ requestId, data: schema ? schema.toJSON() : null }));
|
|
105
167
|
} catch (error) {
|
|
106
168
|
ws.send(JSON.stringify({ requestId, error: `Failed to get schema: ${error.message}` }));
|
|
107
169
|
}
|
|
@@ -111,8 +173,15 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
111
173
|
const schemaService = root.inject("schema");
|
|
112
174
|
const schemas = {};
|
|
113
175
|
if (schemaService) {
|
|
114
|
-
for (const
|
|
115
|
-
|
|
176
|
+
for (const record of schemaService.items) {
|
|
177
|
+
if (record.key && record.schema) {
|
|
178
|
+
schemas[record.key] = record.schema.toJSON();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const [pName, configKey] of schemaService.getPluginKeyMap()) {
|
|
182
|
+
if (pName !== configKey && schemas[configKey]) {
|
|
183
|
+
schemas[pName] = schemas[configKey];
|
|
184
|
+
}
|
|
116
185
|
}
|
|
117
186
|
}
|
|
118
187
|
ws.send(JSON.stringify({ requestId, data: schemas }));
|
|
@@ -120,14 +189,66 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
120
189
|
ws.send(JSON.stringify({ requestId, error: `Failed to get all schemas: ${error.message}` }));
|
|
121
190
|
}
|
|
122
191
|
break;
|
|
192
|
+
// ================================================================
|
|
193
|
+
// 环境变量文件管理
|
|
194
|
+
// ================================================================
|
|
195
|
+
case "env:list":
|
|
196
|
+
try {
|
|
197
|
+
const cwd = process.cwd();
|
|
198
|
+
const files = ENV_WHITELIST.map((name) => ({
|
|
199
|
+
name,
|
|
200
|
+
exists: fs.existsSync(path.resolve(cwd, name))
|
|
201
|
+
}));
|
|
202
|
+
ws.send(JSON.stringify({ requestId, data: { files } }));
|
|
203
|
+
} catch (error) {
|
|
204
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to list env files: ${error.message}` }));
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
case "env:get":
|
|
208
|
+
try {
|
|
209
|
+
const { filename } = message;
|
|
210
|
+
if (!filename || !ENV_WHITELIST.includes(filename)) {
|
|
211
|
+
ws.send(JSON.stringify({ requestId, error: `Invalid env file: ${filename}` }));
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const envPath = path.resolve(process.cwd(), filename);
|
|
215
|
+
const content = fs.existsSync(envPath) ? fs.readFileSync(envPath, "utf-8") : "";
|
|
216
|
+
ws.send(JSON.stringify({ requestId, data: { content } }));
|
|
217
|
+
} catch (error) {
|
|
218
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to read env file: ${error.message}` }));
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
case "env:save":
|
|
222
|
+
try {
|
|
223
|
+
const { filename, content } = message;
|
|
224
|
+
if (!filename || !ENV_WHITELIST.includes(filename)) {
|
|
225
|
+
ws.send(JSON.stringify({ requestId, error: `Invalid env file: ${filename}` }));
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
if (typeof content !== "string") {
|
|
229
|
+
ws.send(JSON.stringify({ requestId, error: "content field is required" }));
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
const envPath = path.resolve(process.cwd(), filename);
|
|
233
|
+
fs.writeFileSync(envPath, content, "utf-8");
|
|
234
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u73AF\u5883\u53D8\u91CF\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u751F\u6548" } }));
|
|
235
|
+
} catch (error) {
|
|
236
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to save env file: ${error.message}` }));
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
123
239
|
default:
|
|
124
|
-
ws.send(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
240
|
+
ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${type}` }));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function findPluginByConfigKey(rootPlugin, configKey) {
|
|
244
|
+
for (const child of rootPlugin.children) {
|
|
245
|
+
if (child.name === configKey || child.name.endsWith(`-${configKey}`) || child.name.includes(configKey)) {
|
|
246
|
+
return child;
|
|
247
|
+
}
|
|
248
|
+
const found = findPluginByConfigKey(child, configKey);
|
|
249
|
+
if (found) return found;
|
|
130
250
|
}
|
|
251
|
+
return null;
|
|
131
252
|
}
|
|
132
253
|
function broadcastToAll(webServer, message) {
|
|
133
254
|
for (const ws of webServer.ws.clients || []) {
|
|
@@ -137,10 +258,7 @@ function broadcastToAll(webServer, message) {
|
|
|
137
258
|
}
|
|
138
259
|
}
|
|
139
260
|
function notifyDataUpdate(webServer) {
|
|
140
|
-
broadcastToAll(webServer, {
|
|
141
|
-
type: "data-update",
|
|
142
|
-
timestamp: Date.now()
|
|
143
|
-
});
|
|
261
|
+
broadcastToAll(webServer, { type: "data-update", timestamp: Date.now() });
|
|
144
262
|
}
|
|
145
263
|
|
|
146
264
|
export { broadcastToAll, notifyDataUpdate, setupWebSocket };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/console",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.41",
|
|
4
4
|
"description": "Web console service for Zhin.js with real-time monitoring",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -67,14 +67,15 @@
|
|
|
67
67
|
"tailwindcss": "latest",
|
|
68
68
|
"tsup": "^8.5.1",
|
|
69
69
|
"vite": "^7.0.6",
|
|
70
|
-
"
|
|
70
|
+
"yaml": "^2.8.2",
|
|
71
|
+
"zhin.js": "1.0.43"
|
|
71
72
|
},
|
|
72
73
|
"peerDependencies": {
|
|
73
74
|
"@types/ws": "^8.18.1",
|
|
74
|
-
"@zhin.js/client": "^1.0.
|
|
75
|
-
"@zhin.js/
|
|
76
|
-
"zhin.js": "1.0.
|
|
77
|
-
"@zhin.js/
|
|
75
|
+
"@zhin.js/client": "^1.0.11",
|
|
76
|
+
"@zhin.js/core": "^1.0.43",
|
|
77
|
+
"zhin.js": "1.0.43",
|
|
78
|
+
"@zhin.js/http": "^1.0.37"
|
|
78
79
|
},
|
|
79
80
|
"files": [
|
|
80
81
|
"lib",
|