aws-runtime-bridge 1.9.9 → 1.9.20
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/adapter/AcodeSdkAdapter.d.ts +40 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +353 -7
- package/dist/adapter/AcodeSdkAdapter.test.js +155 -8
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +36 -3
- package/dist/adapter/instance-ai-config-resolver.d.ts +49 -0
- package/dist/adapter/instance-ai-config-resolver.d.ts.map +1 -0
- package/dist/adapter/instance-ai-config-resolver.js +46 -0
- package/dist/adapter/types.d.ts +27 -2
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/routes/dashboard.d.ts.map +1 -1
- package/dist/routes/dashboard.js +1092 -8
- package/dist/routes/events.d.ts +7 -0
- package/dist/routes/events.d.ts.map +1 -0
- package/dist/routes/events.js +22 -0
- package/dist/routes/file-browser.d.ts +1 -1
- package/dist/routes/file-browser.d.ts.map +1 -1
- package/dist/routes/file-browser.js +25 -9
- package/dist/routes/file-snapshot.d.ts +10 -0
- package/dist/routes/file-snapshot.d.ts.map +1 -0
- package/dist/routes/file-snapshot.js +156 -0
- package/dist/routes/git.d.ts +10 -0
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +305 -2
- package/dist/routes/git.test.js +39 -2
- package/dist/routes/instance.js +3 -1
- package/dist/routes/memory.d.ts +3 -0
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +3 -0
- package/dist/routes/terminal.d.ts +2 -1
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +378 -41
- package/dist/routes/terminal.test.js +33 -7
- package/dist/services/agent-event-store.d.ts +59 -0
- package/dist/services/agent-event-store.d.ts.map +1 -0
- package/dist/services/agent-event-store.js +68 -0
- package/dist/services/event-delta-pusher.d.ts +40 -0
- package/dist/services/event-delta-pusher.d.ts.map +1 -0
- package/dist/services/event-delta-pusher.js +95 -0
- package/dist/services/fileSnapshotStore.d.ts +96 -0
- package/dist/services/fileSnapshotStore.d.ts.map +1 -0
- package/dist/services/fileSnapshotStore.js +500 -0
- package/dist/services/instance-ai-config-service.d.ts +49 -0
- package/dist/services/instance-ai-config-service.d.ts.map +1 -0
- package/dist/services/instance-ai-config-service.js +174 -0
- package/dist/services/panel-auth.d.ts +23 -0
- package/dist/services/panel-auth.d.ts.map +1 -1
- package/dist/services/panel-auth.js +64 -1
- package/dist/services/ring-buffer.d.ts +67 -0
- package/dist/services/ring-buffer.d.ts.map +1 -0
- package/dist/services/ring-buffer.js +103 -0
- package/dist/services/session-output.d.ts +47 -3
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +112 -2
- package/dist/utils/instance-ai-config-debug-logger.d.ts +37 -0
- package/dist/utils/instance-ai-config-debug-logger.d.ts.map +1 -0
- package/dist/utils/instance-ai-config-debug-logger.js +63 -0
- package/dist/utils/logger.d.ts +18 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +48 -4
- package/package/acode/dist/built-in-file-tools.d.ts +1 -1
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
- package/package/acode/dist/built-in-file-tools.js +5 -3
- package/package/acode/dist/builtins/commandArtifacts.d.ts +8 -1
- package/package/acode/dist/builtins/commandArtifacts.d.ts.map +1 -1
- package/package/acode/dist/builtins/commandArtifacts.js +24 -4
- package/package/acode/dist/builtins/shellCommandResult.d.ts +1 -0
- package/package/acode/dist/builtins/shellCommandResult.d.ts.map +1 -1
- package/package/acode/dist/builtins/shellCommandResult.js +3 -3
- package/package/acode/dist/builtins/shellCommandRunner.d.ts +1 -0
- package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +1 -1
- package/package/acode/dist/builtins/shellCommandRunner.js +1 -0
- package/package/acode/dist/cli.js +2 -0
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +156 -5
- package/package/acode/dist/types.d.ts +1 -0
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +5 -0
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js +16 -0
- package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/context-manager.d.ts +136 -0
- package/package/aws-client-agent-mcp/dist/context-manager.d.ts.map +1 -0
- package/package/aws-client-agent-mcp/dist/context-manager.js +473 -0
- package/package/aws-client-agent-mcp/dist/context-manager.js.map +1 -0
- package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts +6 -0
- package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts.map +1 -0
- package/package/aws-client-agent-mcp/dist/context-manager.test.js +307 -0
- package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +170 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +947 -9
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +103 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +15 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +62 -2
- package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +46 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.js +164 -12
- package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +8 -0
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.js +88 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +7 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.js +18 -0
- package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +103 -2
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +27 -2
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +122 -5
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package.json +1 -1
package/dist/routes/dashboard.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Router } from "express";
|
|
8
8
|
import os from "node:os";
|
|
9
|
-
import {
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
10
|
import { getRuntimeBindingPublicState, getRuntimePairingCode } from "../services/runtime-binding.js";
|
|
11
11
|
import { getAgentProcessManager } from "../services/agent-process-manager.js";
|
|
12
12
|
import { collectSystemMetrics } from "../services/system-metrics.js";
|
|
@@ -14,7 +14,9 @@ import { loadPersistedSessions } from "../services/terminal-persistence.js";
|
|
|
14
14
|
import { createLogger } from "../utils/logger.js";
|
|
15
15
|
import { getBridgeVersion } from "../config.js";
|
|
16
16
|
import { sdkSessions } from "./terminal.js";
|
|
17
|
-
import { createSession, validateSession, destroySession, validateCredentials, } from "../services/panel-auth.js";
|
|
17
|
+
import { createSession, getPanelCredentials, validateSession, destroySession, validateCredentials, hasConfiguredCredentials, isDefaultCredentials, setPanelCredentials, getAllSessions, getActiveSessionCount, } from "../services/panel-auth.js";
|
|
18
|
+
import { getRecentLogs } from "../utils/logger.js";
|
|
19
|
+
import { autoRegister, getRegistrationState, getAutoRegisterConfigFilePath } from "../services/auto-register.js";
|
|
18
20
|
const log = createLogger("dashboard");
|
|
19
21
|
// ── 内存缓存(两级) ─────────────────────────────────────
|
|
20
22
|
// 一级:完整响应缓存,匹配前端轮询间隔(10s),避免大部分请求走 I/O
|
|
@@ -119,9 +121,10 @@ dashboardRouter.post("/runtime/login", (req, res) => {
|
|
|
119
121
|
return;
|
|
120
122
|
}
|
|
121
123
|
const token = createSession();
|
|
124
|
+
const needSetup = !hasConfiguredCredentials();
|
|
122
125
|
// 设置 HttpOnly cookie,24 小时过期
|
|
123
126
|
res.setHeader("Set-Cookie", `${COOKIE_NAME}=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400`);
|
|
124
|
-
res.json({ ok: true });
|
|
127
|
+
res.json({ ok: true, needSetup });
|
|
125
128
|
});
|
|
126
129
|
/** POST /runtime/logout 登出 */
|
|
127
130
|
dashboardRouter.post("/runtime/logout", (req, res) => {
|
|
@@ -132,6 +135,170 @@ dashboardRouter.post("/runtime/logout", (req, res) => {
|
|
|
132
135
|
res.setHeader("Set-Cookie", `${COOKIE_NAME}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`);
|
|
133
136
|
res.json({ ok: true });
|
|
134
137
|
});
|
|
138
|
+
/**
|
|
139
|
+
* GET /runtime/credentials-status
|
|
140
|
+
* 返回当前凭据是否仍是默认值 root/root。
|
|
141
|
+
* 需要面板登录会话。
|
|
142
|
+
*/
|
|
143
|
+
dashboardRouter.get("/runtime/credentials-status", (req, res) => {
|
|
144
|
+
if (!requirePanelAuth(req, res))
|
|
145
|
+
return;
|
|
146
|
+
const creds = getPanelCredentials();
|
|
147
|
+
res.json({ ok: true, isDefault: isDefaultCredentials(), configured: hasConfiguredCredentials(), username: creds.username });
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
150
|
+
* POST /runtime/change-credentials
|
|
151
|
+
* 修改面板登录凭据,写入 ~/.aws-bridge/config.json。
|
|
152
|
+
* 需要面板登录会话。修改密码需校验原密码;用户名可同时修改。
|
|
153
|
+
*/
|
|
154
|
+
dashboardRouter.post("/runtime/change-credentials", (req, res) => {
|
|
155
|
+
if (!requirePanelAuth(req, res))
|
|
156
|
+
return;
|
|
157
|
+
const { username, password, passwordConfirm, oldPassword } = req.body || {};
|
|
158
|
+
// 校验原密码(仅凭据已配置且新密码不为空时要求)
|
|
159
|
+
if (password && String(password).trim()) {
|
|
160
|
+
// 密码一致性校验
|
|
161
|
+
if (String(password) !== String(passwordConfirm)) {
|
|
162
|
+
res.status(400).json({ ok: false, error: "passwords do not match" });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (hasConfiguredCredentials()) {
|
|
166
|
+
if (!oldPassword) {
|
|
167
|
+
res.status(400).json({ ok: false, error: "old password required" });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const current = getPanelCredentials();
|
|
171
|
+
if (String(oldPassword) !== current.password) {
|
|
172
|
+
res.status(403).json({ ok: false, error: "old password is incorrect" });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// 首次设置时不校验原密码
|
|
177
|
+
}
|
|
178
|
+
// 至少需要用户名或密码的变更
|
|
179
|
+
if (!username || !String(username).trim()) {
|
|
180
|
+
res.status(400).json({ ok: false, error: "username required" });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const finalPassword = (password && String(password).trim()) ? String(password).trim() : getPanelCredentials().password;
|
|
184
|
+
setPanelCredentials(String(username).trim(), finalPassword);
|
|
185
|
+
res.json({ ok: true });
|
|
186
|
+
});
|
|
187
|
+
/**
|
|
188
|
+
* GET /runtime/node-info
|
|
189
|
+
* 返回节点详细信息,供设置面板展示。
|
|
190
|
+
* 需要面板登录会话。
|
|
191
|
+
*/
|
|
192
|
+
dashboardRouter.get("/runtime/node-info", (req, res) => {
|
|
193
|
+
if (!requirePanelAuth(req, res))
|
|
194
|
+
return;
|
|
195
|
+
const binding = getRuntimeBindingPublicState();
|
|
196
|
+
const reg = getRegistrationState();
|
|
197
|
+
res.json({
|
|
198
|
+
ok: true,
|
|
199
|
+
hostname: os.hostname(),
|
|
200
|
+
platform: os.platform(),
|
|
201
|
+
arch: os.arch(),
|
|
202
|
+
uptime: Math.floor(process.uptime()),
|
|
203
|
+
nodeVersion: process.version,
|
|
204
|
+
bridgeVersion,
|
|
205
|
+
configPath: getAutoRegisterConfigFilePath(),
|
|
206
|
+
homeDir: process.env.AWS_RUNTIME_HOME_DIR || os.homedir(),
|
|
207
|
+
bindingStatus: binding.status,
|
|
208
|
+
paired: binding.paired,
|
|
209
|
+
instanceId: binding.instanceId || reg.instanceId,
|
|
210
|
+
schedulerBaseUrl: binding.schedulerBaseUrl,
|
|
211
|
+
registered: reg.registered,
|
|
212
|
+
pairingCode: binding.pairingCode,
|
|
213
|
+
activeSessions: getActiveSessionCount(),
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
/**
|
|
217
|
+
* GET /runtime/logs
|
|
218
|
+
* 返回最近的 bridge 日志。
|
|
219
|
+
* 需要面板登录会话。
|
|
220
|
+
*/
|
|
221
|
+
dashboardRouter.get("/runtime/logs", (req, res) => {
|
|
222
|
+
if (!requirePanelAuth(req, res))
|
|
223
|
+
return;
|
|
224
|
+
const level = req.query.level || undefined;
|
|
225
|
+
const maxCount = Math.min(Number(req.query.count) || 200, 500);
|
|
226
|
+
const validLevels = ['debug', 'info', 'warn', 'error'];
|
|
227
|
+
const filterLevel = level && validLevels.includes(level)
|
|
228
|
+
? level
|
|
229
|
+
: undefined;
|
|
230
|
+
const logs = getRecentLogs(maxCount, filterLevel);
|
|
231
|
+
res.json({ ok: true, logs });
|
|
232
|
+
});
|
|
233
|
+
/**
|
|
234
|
+
* POST /runtime/re-register
|
|
235
|
+
* 向调度中心重新发起注册。
|
|
236
|
+
* 需要面板登录会话。
|
|
237
|
+
*/
|
|
238
|
+
dashboardRouter.post("/runtime/re-register", async (req, res) => {
|
|
239
|
+
if (!requirePanelAuth(req, res))
|
|
240
|
+
return;
|
|
241
|
+
try {
|
|
242
|
+
const success = await autoRegister();
|
|
243
|
+
res.json({ ok: success });
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
const e = err;
|
|
247
|
+
res.status(500).json({ ok: false, error: e.message });
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
/**
|
|
251
|
+
* GET /runtime/admin/sessions
|
|
252
|
+
* 列出当前所有活跃的面板登录会话。
|
|
253
|
+
* 需要面板登录会话。
|
|
254
|
+
*/
|
|
255
|
+
dashboardRouter.get("/runtime/admin/sessions", (req, res) => {
|
|
256
|
+
if (!requirePanelAuth(req, res))
|
|
257
|
+
return;
|
|
258
|
+
const list = getAllSessions().map(s => ({
|
|
259
|
+
token: s.token.slice(0, 8) + "...",
|
|
260
|
+
createdAt: new Date(s.createdAt).toISOString(),
|
|
261
|
+
lastUsedAt: new Date(s.lastUsedAt).toISOString(),
|
|
262
|
+
age: Math.floor((Date.now() - s.createdAt) / 1000),
|
|
263
|
+
}));
|
|
264
|
+
res.json({ ok: true, sessions: list });
|
|
265
|
+
});
|
|
266
|
+
/**
|
|
267
|
+
* POST /runtime/admin/sessions/:token/destroy
|
|
268
|
+
* 强制销毁指定的面板会话。
|
|
269
|
+
* 需要面板登录会话。
|
|
270
|
+
*/
|
|
271
|
+
dashboardRouter.post("/runtime/admin/sessions/:token/destroy", (req, res) => {
|
|
272
|
+
if (!requirePanelAuth(req, res))
|
|
273
|
+
return;
|
|
274
|
+
destroySession(req.params.token);
|
|
275
|
+
res.json({ ok: true });
|
|
276
|
+
});
|
|
277
|
+
/**
|
|
278
|
+
* POST /runtime/kill-process
|
|
279
|
+
* 强制终止指定 PID 的进程。
|
|
280
|
+
* 需要面板登录会话。
|
|
281
|
+
*/
|
|
282
|
+
dashboardRouter.post("/runtime/kill-process", (req, res) => {
|
|
283
|
+
if (!requirePanelAuth(req, res))
|
|
284
|
+
return;
|
|
285
|
+
const pid = Number(req.body.pid);
|
|
286
|
+
if (!pid || !Number.isInteger(pid) || pid <= 0) {
|
|
287
|
+
res.status(400).json({ ok: false, error: "Invalid PID" });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const cmd = os.platform() === "win32" ? `taskkill /PID ${pid} /F` : `kill -9 ${pid}`;
|
|
292
|
+
execSync(cmd, { timeout: 5000 });
|
|
293
|
+
log.info("Killed process", { pid });
|
|
294
|
+
res.json({ ok: true });
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
const e = err;
|
|
298
|
+
log.warn("Failed to kill process", { pid, error: e.message });
|
|
299
|
+
res.status(500).json({ ok: false, error: e.message });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
135
302
|
/**
|
|
136
303
|
* GET /runtime/dashboard
|
|
137
304
|
* 聚合 bridge 所有状态数据。使用 2 秒缓存避免频繁 I/O,需要面板登录会话。
|
|
@@ -184,6 +351,9 @@ dashboardRouter.get("/runtime/dashboard", async (req, res) => {
|
|
|
184
351
|
managementStatus: p.managementStatus,
|
|
185
352
|
sessionId: record?.sessionId,
|
|
186
353
|
state: record?.state,
|
|
354
|
+
command: p.command,
|
|
355
|
+
cwd: p.cwd,
|
|
356
|
+
startedAt: p.startedAt,
|
|
187
357
|
};
|
|
188
358
|
});
|
|
189
359
|
const sortOrder = { managed: 0, orphan: 1, unknown: 2 };
|
|
@@ -254,6 +424,7 @@ dashboardRouter.get("/login", (_req, res) => {
|
|
|
254
424
|
/**
|
|
255
425
|
* GET / 仪表盘主页
|
|
256
426
|
* 需要面板登录会话,未登录时跳转到 /login。
|
|
427
|
+
* 当凭据仍是默认值 root/root 时跳转到 /setup 引导首次设置。
|
|
257
428
|
* 支持 ?lang=en 切换英文,默认中文
|
|
258
429
|
*/
|
|
259
430
|
dashboardRouter.get("/", (req, res) => {
|
|
@@ -262,10 +433,28 @@ dashboardRouter.get("/", (req, res) => {
|
|
|
262
433
|
res.redirect("/login");
|
|
263
434
|
return;
|
|
264
435
|
}
|
|
436
|
+
// 如果 config.json 尚未配置过凭据,引导首次设置
|
|
437
|
+
if (!hasConfiguredCredentials()) {
|
|
438
|
+
res.redirect("/setup");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
265
441
|
const lang = req.query.lang === "en" ? "en" : "zh";
|
|
266
442
|
res.type("text/html; charset=utf-8");
|
|
267
443
|
res.send(DASHBOARD_HTML.replace("{{LANG}}", lang));
|
|
268
444
|
});
|
|
445
|
+
/**
|
|
446
|
+
* GET /setup 首次登录设置页面
|
|
447
|
+
* 需要面板登录会话。提供用户名/密码修改表单,也支持跳过(保持 root/root)。
|
|
448
|
+
*/
|
|
449
|
+
dashboardRouter.get("/setup", (req, res) => {
|
|
450
|
+
const token = getSessionToken(req);
|
|
451
|
+
if (!token || !validateSession(token)) {
|
|
452
|
+
res.redirect("/login");
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
res.type("text/html; charset=utf-8");
|
|
456
|
+
res.send(SETUP_HTML);
|
|
457
|
+
});
|
|
269
458
|
// ======================================================================
|
|
270
459
|
// DASHBOARD HTML — 单页自包含,支持中文/英文界面,默认中文
|
|
271
460
|
// ======================================================================
|
|
@@ -565,6 +754,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
565
754
|
<span id="updateIndicator" style="display:none"></span>
|
|
566
755
|
</span>
|
|
567
756
|
<span class="lang-btn" id="langBtn" onclick="toggleLang()">中文</span>
|
|
757
|
+
<button onclick="openSettings()" id="settingsBtn" title="Settings / 设置">⚙</button>
|
|
568
758
|
<button onclick="refresh()" id="refreshBtn">↻</button>
|
|
569
759
|
</div>
|
|
570
760
|
</div>
|
|
@@ -608,6 +798,200 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
608
798
|
<div class="section-title" id="sectionPersisted">Persisted Sessions <span class="count" id="persistedCount"></span></div>
|
|
609
799
|
<div class="table-wrap" id="persistedTable"><div class="empty" id="loadingPersisted">Loading...</div></div>
|
|
610
800
|
|
|
801
|
+
<!-- Settings Modal -->
|
|
802
|
+
<div class="modal-overlay" id="settingsModal" style="display:none" onclick="closeSettings(event)">
|
|
803
|
+
<div class="settings-panel" onclick="event.stopPropagation()">
|
|
804
|
+
<div class="settings-head">
|
|
805
|
+
<span id="settingsTitle"></span>
|
|
806
|
+
<button class="settings-close" onclick="closeSettings()">×</button>
|
|
807
|
+
</div>
|
|
808
|
+
<div class="settings-body">
|
|
809
|
+
<div class="settings-nav" id="settingsNav">
|
|
810
|
+
<div class="settings-nav-item active" data-page="account" onclick="switchSettingsPage('account')"><span id="navAccount"></span></div>
|
|
811
|
+
<div class="settings-nav-item" data-page="node" onclick="switchSettingsPage('node')"><span id="navNode"></span></div>
|
|
812
|
+
<div class="settings-nav-item" data-page="logs" onclick="switchSettingsPage('logs')"><span id="navLogs"></span></div>
|
|
813
|
+
<div class="settings-nav-item" data-page="scheduler" onclick="switchSettingsPage('scheduler')"><span id="navScheduler"></span></div>
|
|
814
|
+
<div class="settings-nav-item" data-page="maintenance" onclick="switchSettingsPage('maintenance')"><span id="navMaintenance"></span></div>
|
|
815
|
+
</div>
|
|
816
|
+
<div class="settings-content">
|
|
817
|
+
<!-- Account page -->
|
|
818
|
+
<div class="settings-page active" id="pageAccount">
|
|
819
|
+
<form id="settingsForm" onsubmit="return saveSettings(event)">
|
|
820
|
+
<div class="s-field">
|
|
821
|
+
<label id="lblUsername"></label>
|
|
822
|
+
<input type="text" id="setUsername" name="username" autocomplete="username">
|
|
823
|
+
</div>
|
|
824
|
+
<div class="s-field">
|
|
825
|
+
<label id="lblCurrentPwd"></label>
|
|
826
|
+
<input type="password" id="setOldPassword" name="oldPassword" autocomplete="current-password">
|
|
827
|
+
</div>
|
|
828
|
+
<div class="s-field">
|
|
829
|
+
<label id="lblNewPwd"></label>
|
|
830
|
+
<input type="password" id="setNewPassword" name="newPassword" autocomplete="new-password">
|
|
831
|
+
<span class="s-hint" id="hintKeepUnchanged"></span>
|
|
832
|
+
</div>
|
|
833
|
+
<div class="s-field">
|
|
834
|
+
<label id="lblConfirmPwd"></label>
|
|
835
|
+
<input type="password" id="setPasswordConfirm" name="passwordConfirm" autocomplete="new-password">
|
|
836
|
+
</div>
|
|
837
|
+
<div class="s-msg" id="settingsMsg"></div>
|
|
838
|
+
<div class="s-actions">
|
|
839
|
+
<button type="submit" class="s-btn s-btn-primary" id="settingsSaveBtn"></button>
|
|
840
|
+
</div>
|
|
841
|
+
</form>
|
|
842
|
+
</div>
|
|
843
|
+
<!-- Node Info page -->
|
|
844
|
+
<div class="settings-page" id="pageNode">
|
|
845
|
+
<div class="s-info-grid" id="nodeInfoGrid">
|
|
846
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeHostname"></span><span class="s-info-value" id="valNodeHostname">-</span></div>
|
|
847
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodePlatform"></span><span class="s-info-value" id="valNodePlatform">-</span></div>
|
|
848
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeUptime"></span><span class="s-info-value" id="valNodeUptime">-</span></div>
|
|
849
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeVersion"></span><span class="s-info-value" id="valNodeVersion">-</span></div>
|
|
850
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeNodeVer"></span><span class="s-info-value" id="valNodeNodeVer">-</span></div>
|
|
851
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeConfigPath"></span><span class="s-info-value" id="valNodeConfigPath">-</span></div>
|
|
852
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeHomeDir"></span><span class="s-info-value" id="valNodeHomeDir">-</span></div>
|
|
853
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeScheduler"></span><span class="s-info-value" id="valNodeScheduler">-</span></div>
|
|
854
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeBinding"></span><span class="s-info-value" id="valNodeBinding">-</span></div>
|
|
855
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeRegistered"></span><span class="s-info-value" id="valNodeRegistered">-</span></div>
|
|
856
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeInstanceId"></span><span class="s-info-value" id="valNodeInstanceId">-</span></div>
|
|
857
|
+
<div class="s-info-row"><span class="s-info-label" id="lblNodeActiveSessions"></span><span class="s-info-value" id="valNodeActiveSessions">-</span></div>
|
|
858
|
+
</div>
|
|
859
|
+
<div class="s-actions">
|
|
860
|
+
<button class="s-btn s-btn-secondary" onclick="refreshNodeInfo()">Refresh / 🔄</button>
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
<!-- Logs page -->
|
|
864
|
+
<div class="settings-page" id="pageLogs">
|
|
865
|
+
<div class="s-log-toolbar">
|
|
866
|
+
<select id="logLevelFilter" onchange="refreshLogs()">
|
|
867
|
+
<option value="">All / 全部</option>
|
|
868
|
+
<option value="info">INFO</option>
|
|
869
|
+
<option value="warn">WARN</option>
|
|
870
|
+
<option value="error">ERROR</option>
|
|
871
|
+
</select>
|
|
872
|
+
<label class="s-log-toggle"><input type="checkbox" id="logAutoRefresh" checked> <span id="lblLogAutoRefresh"></span></label>
|
|
873
|
+
<button class="s-btn s-btn-sm" onclick="refreshLogs()" id="btnRefreshLogs">🔄</button>
|
|
874
|
+
</div>
|
|
875
|
+
<div class="s-log-list" id="logList"><div class="s-log-empty" id="logEmpty"></div></div>
|
|
876
|
+
</div>
|
|
877
|
+
<!-- Scheduler page -->
|
|
878
|
+
<div class="settings-page" id="pageScheduler">
|
|
879
|
+
<div class="s-field">
|
|
880
|
+
<label id="lblSchedUrl"></label>
|
|
881
|
+
<div class="s-info-row"><span class="s-info-value" id="valSchedUrl">-</span></div>
|
|
882
|
+
</div>
|
|
883
|
+
<div class="s-field">
|
|
884
|
+
<p class="s-desc" id="descSched"></p>
|
|
885
|
+
</div>
|
|
886
|
+
<div class="s-msg" id="schedMsg"></div>
|
|
887
|
+
<div class="s-actions">
|
|
888
|
+
<button class="s-btn s-btn-primary" id="btnReRegister" onclick="doReRegister()"></button>
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
<!-- Maintenance page -->
|
|
892
|
+
<div class="settings-page" id="pageMaintenance">
|
|
893
|
+
<div class="s-field">
|
|
894
|
+
<label id="lblRestartBridge"></label>
|
|
895
|
+
<p class="s-desc" id="descRestart"></p>
|
|
896
|
+
</div>
|
|
897
|
+
<div class="s-restart-msg" id="restartMsg"></div>
|
|
898
|
+
<div class="s-actions">
|
|
899
|
+
<button class="s-btn s-btn-danger" id="btnRestartBridge" onclick="confirmRestartBridge()"></button>
|
|
900
|
+
</div>
|
|
901
|
+
</div>
|
|
902
|
+
</div>
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
</div>
|
|
906
|
+
|
|
907
|
+
<style>
|
|
908
|
+
.modal-overlay {
|
|
909
|
+
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
910
|
+
background: rgba(0,0,0,0.55); z-index: 1000;
|
|
911
|
+
display: flex; align-items: center; justify-content: center;
|
|
912
|
+
padding: 20px;
|
|
913
|
+
}
|
|
914
|
+
.settings-panel {
|
|
915
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
916
|
+
border-radius: var(--radius); width: 640px; max-width: 100%;
|
|
917
|
+
max-height: 90vh; display: flex; flex-direction: column;
|
|
918
|
+
}
|
|
919
|
+
.settings-head {
|
|
920
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
921
|
+
padding: 16px 20px; border-bottom: 1px solid var(--border);
|
|
922
|
+
}
|
|
923
|
+
.settings-head span { font-size: 16px; font-weight: 600; }
|
|
924
|
+
.settings-close {
|
|
925
|
+
background: none; border: none; color: var(--text2);
|
|
926
|
+
font-size: 22px; cursor: pointer; padding: 0 4px; line-height: 1;
|
|
927
|
+
}
|
|
928
|
+
.settings-close:hover { color: var(--text); }
|
|
929
|
+
.settings-body { display: flex; flex: 1; min-height: 320px; }
|
|
930
|
+
.settings-nav { width: 100px; flex-shrink: 0; border-right: 1px solid var(--border); padding: 8px 0; }
|
|
931
|
+
.settings-nav-item { padding: 7px 14px; font-size: 13px; cursor: pointer; color: var(--text2); transition: all .15s; }
|
|
932
|
+
.settings-nav-item:hover { color: var(--text); background: rgba(255,255,255,0.03); }
|
|
933
|
+
.settings-nav-item.active { color: var(--accent); background: rgba(91,141,239,0.1); }
|
|
934
|
+
.settings-content { flex: 1; padding: 20px; overflow-y: auto; }
|
|
935
|
+
.settings-page { display: none; }
|
|
936
|
+
.settings-page.active { display: block; }
|
|
937
|
+
.s-field { margin-bottom: 16px; }
|
|
938
|
+
.s-field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: var(--text2); }
|
|
939
|
+
.s-field input { width: 100%; padding: 9px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 14px; outline: none; }
|
|
940
|
+
.s-field input:focus { border-color: var(--accent); }
|
|
941
|
+
.s-hint { display: block; font-size: 11px; color: var(--text2); margin-top: 4px; }
|
|
942
|
+
.s-msg { padding: 8px 12px; border-radius: 6px; font-size: 13px; margin-bottom: 14px; display: none; }
|
|
943
|
+
.s-msg.error { display: block; background: rgba(240,84,84,0.12); color: var(--red); }
|
|
944
|
+
.s-msg.success { display: block; background: rgba(67,198,168,0.12); color: var(--green); }
|
|
945
|
+
.s-msg.info { display: block; background: rgba(91,141,239,0.12); color: var(--accent); }
|
|
946
|
+
.s-actions { margin-top: 12px; }
|
|
947
|
+
.s-btn { padding: 9px 24px; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all .15s; border: none; }
|
|
948
|
+
.s-btn-sm { padding: 5px 12px; font-size: 13px; }
|
|
949
|
+
.s-btn-primary { background: var(--accent); color: #fff; }
|
|
950
|
+
.s-btn-primary:hover { background: var(--accent-hover); }
|
|
951
|
+
.s-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
952
|
+
.s-btn-secondary { background: var(--surface2); color: var(--text); border: 1px solid var(--border); }
|
|
953
|
+
.s-btn-secondary:hover { background: var(--border); }
|
|
954
|
+
.s-btn-danger { background: var(--red); color: #fff; }
|
|
955
|
+
.s-btn-danger:hover { background: #d94a4a; }
|
|
956
|
+
.s-btn-danger:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
957
|
+
.s-desc { font-size: 13px; color: var(--text2); line-height: 1.6; margin: 0; }
|
|
958
|
+
.s-restart-msg { padding: 8px 12px; border-radius: 6px; font-size: 13px; margin-bottom: 14px; display: none; }
|
|
959
|
+
.s-restart-msg.info { display: block; background: rgba(91,141,239,0.12); color: var(--accent); }
|
|
960
|
+
.s-restart-msg.error { display: block; background: rgba(240,84,84,0.12); color: var(--red); }
|
|
961
|
+
/* Info grid */
|
|
962
|
+
.s-info-grid { display: grid; gap: 2px; margin-bottom: 16px; }
|
|
963
|
+
.s-info-row { display: flex; padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
|
|
964
|
+
.s-info-label { color: var(--text2); min-width: 110px; flex-shrink: 0; }
|
|
965
|
+
.s-info-value { color: var(--text); word-break: break-all; }
|
|
966
|
+
/* Log viewer */
|
|
967
|
+
.s-log-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
|
|
968
|
+
.s-log-toolbar select { background: var(--surface2); border: 1px solid var(--border); color: var(--text); padding: 5px 8px; border-radius: 4px; font-size: 12px; outline: none; }
|
|
969
|
+
.s-log-toolbar select:focus { border-color: var(--accent); }
|
|
970
|
+
.s-log-toggle { font-size: 12px; color: var(--text2); cursor: pointer; display: flex; align-items: center; gap: 4px; }
|
|
971
|
+
.s-log-toggle input { accent-color: var(--accent); }
|
|
972
|
+
.s-log-list { max-height: 360px; overflow-y: auto; font-family: "Cascadia Code", "Fira Code", "JetBrains Mono", Consolas, monospace; font-size: 11px; line-height: 1.7; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; padding: 8px; }
|
|
973
|
+
.s-log-entry { padding: 1px 4px; white-space: pre-wrap; word-break: break-all; }
|
|
974
|
+
.s-log-entry .ll { font-weight: 600; }
|
|
975
|
+
.s-log-entry .ll.INFO { color: var(--accent); }
|
|
976
|
+
.s-log-entry .ll.WARN { color: var(--orange); }
|
|
977
|
+
.s-log-entry .ll.ERROR { color: var(--red); }
|
|
978
|
+
.s-log-entry .ts { color: var(--text2); }
|
|
979
|
+
.s-log-empty { color: var(--text2); font-size: 13px; text-align: center; padding: 24px; }
|
|
980
|
+
/* Table */
|
|
981
|
+
.s-table-wrap { max-height: 320px; overflow-y: auto; }
|
|
982
|
+
.s-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
983
|
+
.s-table th { text-align: left; padding: 7px 10px; font-size: 11px; font-weight: 600; color: var(--text2); background: var(--surface2); border-bottom: 1px solid var(--border); white-space: nowrap; }
|
|
984
|
+
.s-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); }
|
|
985
|
+
.s-table tr:last-child td { border-bottom: none; }
|
|
986
|
+
.s-tag { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 11px; font-weight: 600; }
|
|
987
|
+
.s-tag.green { background: rgba(67,198,168,0.12); color: var(--green); }
|
|
988
|
+
.s-tag.red { background: rgba(240,84,84,0.12); color: var(--red); }
|
|
989
|
+
.s-tag.orange { background: rgba(240,160,48,0.12); color: var(--orange); }
|
|
990
|
+
.btn-kill { padding: 2px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; cursor: pointer; border: 1px solid var(--red); background: transparent; color: var(--red); transition: all .15s; }
|
|
991
|
+
.btn-kill:hover { background: var(--red); color: #fff; }
|
|
992
|
+
.btn-kill:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
993
|
+
</style>
|
|
994
|
+
|
|
611
995
|
<script>
|
|
612
996
|
// ── 多语言翻译表 ──────────────────────────────────────────
|
|
613
997
|
const T = {
|
|
@@ -651,6 +1035,7 @@ const T = {
|
|
|
651
1035
|
secUnknown: "\u5176\u4ed6\u8fdb\u7a0b",
|
|
652
1036
|
orphanBanner: "\u4ee5\u4e0b\u8fdb\u7a0b\u4ecd\u5728\u8fd0\u884c\u4f46\u8131\u79bb\u4e86 bridge \u7ba1\u7406",
|
|
653
1037
|
btnTerminate: "\u7ec8\u6b62",
|
|
1038
|
+
btnKill: "\u6740\u6b7b",
|
|
654
1039
|
showUnknown: "\u663e\u793a",
|
|
655
1040
|
hideUnknown: "\u9690\u85cf",
|
|
656
1041
|
noOrphans: "\u65e0\u5b64\u513f\u8fdb\u7a0b",
|
|
@@ -674,8 +1059,62 @@ const T = {
|
|
|
674
1059
|
thWorkspace: "\u5de5\u4f5c\u533a",
|
|
675
1060
|
thProvider: "\u63d0\u4f9b\u65b9",
|
|
676
1061
|
thCommand: "\u547d\u4ee4",
|
|
1062
|
+
thDirectory: "\u76ee\u5f55",
|
|
677
1063
|
thStarted: "\u5f00\u59cb\u65f6\u95f4",
|
|
678
1064
|
countManaged: "\u7ba1\u7406\u4e2d",
|
|
1065
|
+
// 设置
|
|
1066
|
+
setTitle: "\u8bbe\u7f6e",
|
|
1067
|
+
setAccount: "\u8d26\u53f7",
|
|
1068
|
+
setUsername: "\u7528\u6237\u540d",
|
|
1069
|
+
setCurrentPwd: "\u5f53\u524d\u5bc6\u7801",
|
|
1070
|
+
setNewPwd: "\u65b0\u5bc6\u7801",
|
|
1071
|
+
setConfirmPwd: "\u786e\u8ba4\u65b0\u5bc6\u7801",
|
|
1072
|
+
setSave: "\u4fdd\u5b58",
|
|
1073
|
+
setCancel: "\u53d6\u6d88",
|
|
1074
|
+
setKeepUnchanged: "\u4e0d\u586b\u5219\u4e0d\u4fee\u6539\u5bc6\u7801",
|
|
1075
|
+
setPwdRequired: "\u4fee\u6539\u5bc6\u7801\u9700\u8981\u8f93\u5165\u5f53\u524d\u5bc6\u7801",
|
|
1076
|
+
setPwdMismatch: "\u4e24\u6b21\u8f93\u5165\u7684\u65b0\u5bc6\u7801\u4e0d\u4e00\u81f4",
|
|
1077
|
+
setPwdTooShort: "\u5bc6\u7801\u81f3\u5c114\u4f4d",
|
|
1078
|
+
setSaved: "\u8bbe\u7f6e\u5df2\u4fdd\u5b58",
|
|
1079
|
+
setSaveFailed: "\u4fdd\u5b58\u5931\u8d25",
|
|
1080
|
+
setNetworkError: "\u7f51\u7edc\u9519\u8bef",
|
|
1081
|
+
setMaintenance: "\u7ef4\u62a4",
|
|
1082
|
+
setRestartBridge: "\u91cd\u542f Bridge",
|
|
1083
|
+
setRestartDesc: "\u91cd\u542f\u6865\u63a5\u670d\u52a1\uff0c\u6b63\u5728\u8fd0\u884c\u7684\u4f1a\u8bdd\u4e0d\u53d7\u5f71\u54cd\u3002",
|
|
1084
|
+
setRestartConfirm: "\u786e\u8ba4\u91cd\u542f Bridge\uff1f",
|
|
1085
|
+
setRestarting: "\u91cd\u542f\u4e2d...",
|
|
1086
|
+
// 节点信息
|
|
1087
|
+
nodeTitle: "\u8282\u70b9\u4fe1\u606f",
|
|
1088
|
+
nodeHostname: "\u4e3b\u673a\u540d",
|
|
1089
|
+
nodePlatform: "\u5e73\u53f0",
|
|
1090
|
+
nodeUptime: "\u8fd0\u884c\u65f6\u957f",
|
|
1091
|
+
nodeVersion: "Bridge \u7248\u672c",
|
|
1092
|
+
nodeNodeVer: "Node \u7248\u672c",
|
|
1093
|
+
nodeConfigPath: "\u914d\u7f6e\u6587\u4ef6\u8def\u5f84",
|
|
1094
|
+
nodeHomeDir: "\u8fd0\u884c\u65f6\u76ee\u5f55",
|
|
1095
|
+
nodeScheduler: "\u8c03\u5ea6\u4e2d\u5fc3",
|
|
1096
|
+
nodeBinding: "\u7ed1\u5b9a\u72b6\u6001",
|
|
1097
|
+
nodeRegistered: "\u6ce8\u518c\u72b6\u6001",
|
|
1098
|
+
nodeInstanceId: "\u5b9e\u4f8b ID",
|
|
1099
|
+
nodeActiveSessions: "\u6d3b\u8dc3\u4f1a\u8bdd",
|
|
1100
|
+
nodePaired: "\u5df2\u7ed1\u5b9a",
|
|
1101
|
+
nodeUnpaired: "\u672a\u7ed1\u5b9a",
|
|
1102
|
+
nodeRegYes: "\u5df2\u6ce8\u518c",
|
|
1103
|
+
nodeRegNo: "\u672a\u6ce8\u518c",
|
|
1104
|
+
// 日志
|
|
1105
|
+
logTitle: "\u65e5\u5fd7",
|
|
1106
|
+
logAutoRefresh: "\u81ea\u52a8\u5237\u65b0",
|
|
1107
|
+
logLevelAll: "\u5168\u90e8",
|
|
1108
|
+
logRefreshing: "\u5237\u65b0\u4e2d...",
|
|
1109
|
+
logEmpty: "\u6682\u65e0\u65e5\u5fd7",
|
|
1110
|
+
// 调度中心
|
|
1111
|
+
schedTitle: "\u8c03\u5ea6\u4e2d\u5fc3",
|
|
1112
|
+
schedUrl: "\u670d\u52a1\u5730\u5740",
|
|
1113
|
+
schedReRegister: "\u91cd\u65b0\u6ce8\u518c",
|
|
1114
|
+
schedReRegistering: "\u6ce8\u518c\u4e2d...",
|
|
1115
|
+
schedReRegistered: "\u91cd\u65b0\u6ce8\u518c\u6210\u529f",
|
|
1116
|
+
schedReRegisterFail: "\u91cd\u65b0\u6ce8\u518c\u5931\u8d25",
|
|
1117
|
+
schedDesc: "\u5411\u8c03\u5ea6\u4e2d\u5fc3\u53d1\u8d77\u91cd\u65b0\u6ce8\u518c\uff0c\u5efa\u8bae\u5728\u8fde\u63a5\u5f02\u5e38\u65f6\u4f7f\u7528\u3002",
|
|
679
1118
|
// 状态文本
|
|
680
1119
|
stHealthy: "\u5065\u5eb7",
|
|
681
1120
|
stError: "\u5f02\u5e38",
|
|
@@ -726,6 +1165,7 @@ const T = {
|
|
|
726
1165
|
secUnknown: "Other Processes",
|
|
727
1166
|
orphanBanner: "These processes are running but no longer managed by bridge",
|
|
728
1167
|
btnTerminate: "Terminate",
|
|
1168
|
+
btnKill: "Kill",
|
|
729
1169
|
showUnknown: "Show",
|
|
730
1170
|
hideUnknown: "Hide",
|
|
731
1171
|
noOrphans: "No orphan processes",
|
|
@@ -748,8 +1188,63 @@ const T = {
|
|
|
748
1188
|
thWorkspace: "Workspace",
|
|
749
1189
|
thProvider: "Provider",
|
|
750
1190
|
thCommand: "Command",
|
|
1191
|
+
thDirectory: "Directory",
|
|
751
1192
|
thStarted: "Started",
|
|
752
1193
|
countManaged: "managed",
|
|
1194
|
+
// Settings
|
|
1195
|
+
setTitle: "Settings",
|
|
1196
|
+
setAccount: "Account",
|
|
1197
|
+
setUsername: "Username",
|
|
1198
|
+
setCurrentPwd: "Current Password",
|
|
1199
|
+
setNewPwd: "New Password",
|
|
1200
|
+
setConfirmPwd: "Confirm New Password",
|
|
1201
|
+
setSave: "Save",
|
|
1202
|
+
setCancel: "Cancel",
|
|
1203
|
+
setKeepUnchanged: "Leave blank to keep current password",
|
|
1204
|
+
setPwdRequired: "Current password is required to change password",
|
|
1205
|
+
setPwdMismatch: "New passwords do not match",
|
|
1206
|
+
setPwdTooShort: "Password must be at least 4 characters",
|
|
1207
|
+
setSaved: "Settings saved",
|
|
1208
|
+
setSaveFailed: "Save failed",
|
|
1209
|
+
setNetworkError: "Network error",
|
|
1210
|
+
setMaintenance: "Maintenance",
|
|
1211
|
+
setRestartBridge: "Restart Bridge",
|
|
1212
|
+
setRestartDesc: "Restart the bridge service. Running sessions will be preserved.",
|
|
1213
|
+
setRestartConfirm: "Are you sure you want to restart Bridge?",
|
|
1214
|
+
setRestarting: "Restarting...",
|
|
1215
|
+
// Node Info
|
|
1216
|
+
nodeTitle: "Node Info",
|
|
1217
|
+
nodeHostname: "Hostname",
|
|
1218
|
+
nodePlatform: "Platform",
|
|
1219
|
+
nodeUptime: "Uptime",
|
|
1220
|
+
nodeVersion: "Bridge Version",
|
|
1221
|
+
nodeNodeVer: "Node Version",
|
|
1222
|
+
nodeConfigPath: "Config Path",
|
|
1223
|
+
nodeHomeDir: "Home Directory",
|
|
1224
|
+
nodeScheduler: "Scheduler",
|
|
1225
|
+
nodeBinding: "Binding",
|
|
1226
|
+
nodeRegistered: "Registration",
|
|
1227
|
+
nodeInstanceId: "Instance ID",
|
|
1228
|
+
nodeActiveSessions: "Active Sessions",
|
|
1229
|
+
nodePaired: "Paired",
|
|
1230
|
+
nodeUnpaired: "Unpaired",
|
|
1231
|
+
nodeRegYes: "Registered",
|
|
1232
|
+
nodeRegNo: "Not Registered",
|
|
1233
|
+
// Logs
|
|
1234
|
+
logTitle: "Logs",
|
|
1235
|
+
logAutoRefresh: "Auto Refresh",
|
|
1236
|
+
logLevelAll: "All",
|
|
1237
|
+
logRefreshing: "Refreshing...",
|
|
1238
|
+
logEmpty: "No logs yet",
|
|
1239
|
+
// Scheduler
|
|
1240
|
+
schedTitle: "Scheduler",
|
|
1241
|
+
schedUrl: "Server URL",
|
|
1242
|
+
schedReRegister: "Re-register",
|
|
1243
|
+
schedReRegistering: "Registering...",
|
|
1244
|
+
schedReRegistered: "Re-registered successfully",
|
|
1245
|
+
schedReRegisterFail: "Re-registration failed",
|
|
1246
|
+
schedDesc: "Re-register with the scheduler. Use when connection is abnormal.",
|
|
1247
|
+
// 状态文本
|
|
753
1248
|
stHealthy: "Healthy",
|
|
754
1249
|
stError: "Error",
|
|
755
1250
|
stRunning: "Running",
|
|
@@ -783,6 +1278,10 @@ function toggleLang() {
|
|
|
783
1278
|
localStorage.setItem("dashboard-lang", lang);
|
|
784
1279
|
document.documentElement.lang = lang === "en" ? "en" : "zh-CN";
|
|
785
1280
|
document.getElementById("langBtn").textContent = t("langName");
|
|
1281
|
+
if (document.getElementById("settingsModal").style.display === "flex") {
|
|
1282
|
+
renderSettingsUI();
|
|
1283
|
+
settingsLang = lang;
|
|
1284
|
+
}
|
|
786
1285
|
if (lastData) render(lastData);
|
|
787
1286
|
}
|
|
788
1287
|
|
|
@@ -815,6 +1314,20 @@ function fmtDuration(seconds) {
|
|
|
815
1314
|
parts.push(s + "s");
|
|
816
1315
|
return parts.join(" ");
|
|
817
1316
|
}
|
|
1317
|
+
function escHtml(s) {
|
|
1318
|
+
if (typeof s !== "string") return "";
|
|
1319
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1320
|
+
}
|
|
1321
|
+
function fmtShortDate(iso) {
|
|
1322
|
+
if (!iso) return "-";
|
|
1323
|
+
return iso.slice(0, 19).replace("T", " ");
|
|
1324
|
+
}
|
|
1325
|
+
function fmtShortPath(p) {
|
|
1326
|
+
if (!p) return "-";
|
|
1327
|
+
var parts = p.replace(/\\\\/g, "/").split("/").filter(Boolean);
|
|
1328
|
+
if (parts.length <= 2) return p;
|
|
1329
|
+
return parts.slice(-2).join("/");
|
|
1330
|
+
}
|
|
818
1331
|
|
|
819
1332
|
function statusClass(v) {
|
|
820
1333
|
if (v === true || v === "healthy" || v === "paired" || v === "running" || v === "managed" || v === "registered") return "status-green";
|
|
@@ -976,7 +1489,7 @@ function render(data) {
|
|
|
976
1489
|
return '<tr><td>' + s.agentId + '</td><td>' + (pid ? '<code>' + pid + '</code>' : '<span class="status-muted">-</span>') + '</td>'
|
|
977
1490
|
+ '<td><span class="' + statusClass(st) + '">' + (t("st" + st.charAt(0).toUpperCase() + st.slice(1)) || st) + '</span></td>'
|
|
978
1491
|
+ '<td>' + (s.runtimeActionLabel || '-') + '</td>'
|
|
979
|
-
|
|
1492
|
+
+ '<td title="' + s.workspacePath + '">' + (s.workspacePath ? s.workspacePath.replace(/\\\\/g, "/").split("/").pop() : "-") + '</td>'
|
|
980
1493
|
+ '<td>' + (s.providerId || "-") + '</td></tr>';
|
|
981
1494
|
}).join("") +
|
|
982
1495
|
'</tbody></table>';
|
|
@@ -1004,9 +1517,9 @@ function render(data) {
|
|
|
1004
1517
|
unknownTable.innerHTML = '<div class="empty">' + t("noProcesses") + '</div>';
|
|
1005
1518
|
} else {
|
|
1006
1519
|
unknownTable.innerHTML =
|
|
1007
|
-
'<table><thead><tr><th>' + t("thPid") + '</th></tr></thead><tbody>' +
|
|
1520
|
+
'<table><thead><tr><th>' + t("thPid") + '</th><th>' + t("thCommand") + '</th><th>' + t("thDirectory") + '</th><th>' + t("thStarted") + '</th><th>' + t("thAction") + '</th></tr></thead><tbody>' +
|
|
1008
1521
|
unknownProcs.map(p =>
|
|
1009
|
-
'<tr><td><code>' + p.pid + '</code></td></tr>'
|
|
1522
|
+
'<tr><td><code>' + p.pid + '</code></td><td>' + escHtml(p.command || "-") + '</td><td title="' + escHtml(p.cwd || "") + '">' + escHtml(fmtShortPath(p.cwd)) + '</td><td>' + (p.startedAt ? fmtShortDate(p.startedAt) : "-") + '</td><td><button class="btn-kill" data-pid="' + p.pid + '">' + t("btnKill") + '</button></td></tr>'
|
|
1010
1523
|
).join("") +
|
|
1011
1524
|
'</tbody></table>';
|
|
1012
1525
|
}
|
|
@@ -1021,7 +1534,7 @@ function render(data) {
|
|
|
1021
1534
|
'<table><thead><tr><th>' + t("thAgentId") + '</th><th>' + t("thStatus") + '</th><th>' + t("thWorkspace") + '</th><th>' + t("thCommand") + '</th><th>' + t("thStarted") + '</th></tr></thead><tbody>' +
|
|
1022
1535
|
persisted.map(s =>
|
|
1023
1536
|
'<tr><td>' + s.agentId + '</td><td>' + (s.status || "stopped") + '</td>'
|
|
1024
|
-
+ '<td title="' + s.workspacePath + '">' + (s.workspacePath ? s.workspacePath.split(/
|
|
1537
|
+
+ '<td title="' + s.workspacePath + '">' + (s.workspacePath ? s.workspacePath.replace(/\\\\/g, "/").split("/").pop() : "-") + '</td>'
|
|
1025
1538
|
+ '<td>' + (s.command || "-") + '</td>'
|
|
1026
1539
|
+ '<td>' + (s.startedAt ? new Date(s.startedAt).toLocaleString() : "-") + '</td></tr>'
|
|
1027
1540
|
).join("") +
|
|
@@ -1172,6 +1685,281 @@ render = function(data) {
|
|
|
1172
1685
|
checkUpdate();
|
|
1173
1686
|
// Also check periodically (every 5 minutes)
|
|
1174
1687
|
setInterval(checkUpdate, 300000);
|
|
1688
|
+
|
|
1689
|
+
// ── Settings Modal ────────────────────────────────────────
|
|
1690
|
+
let settingsLang = null;
|
|
1691
|
+
function renderSettingsUI() {
|
|
1692
|
+
document.getElementById("settingsTitle").textContent = t("setTitle");
|
|
1693
|
+
document.getElementById("navAccount").textContent = t("setAccount");
|
|
1694
|
+
document.getElementById("lblUsername").textContent = t("setUsername");
|
|
1695
|
+
document.getElementById("lblCurrentPwd").textContent = t("setCurrentPwd");
|
|
1696
|
+
document.getElementById("lblNewPwd").textContent = t("setNewPwd");
|
|
1697
|
+
document.getElementById("lblConfirmPwd").textContent = t("setConfirmPwd");
|
|
1698
|
+
document.getElementById("hintKeepUnchanged").textContent = t("setKeepUnchanged");
|
|
1699
|
+
document.getElementById("settingsSaveBtn").textContent = t("setSave");
|
|
1700
|
+
document.getElementById("navMaintenance").textContent = t("setMaintenance");
|
|
1701
|
+
document.getElementById("lblRestartBridge").textContent = t("setRestartBridge");
|
|
1702
|
+
document.getElementById("descRestart").textContent = t("setRestartDesc");
|
|
1703
|
+
document.getElementById("btnRestartBridge").textContent = t("setRestartBridge");
|
|
1704
|
+
document.getElementById("navNode").textContent = t("nodeTitle");
|
|
1705
|
+
document.getElementById("navLogs").textContent = t("logTitle");
|
|
1706
|
+
document.getElementById("navScheduler").textContent = t("schedTitle");
|
|
1707
|
+
document.getElementById("lblNodeHostname").textContent = t("nodeHostname");
|
|
1708
|
+
document.getElementById("lblNodePlatform").textContent = t("nodePlatform");
|
|
1709
|
+
document.getElementById("lblNodeUptime").textContent = t("nodeUptime");
|
|
1710
|
+
document.getElementById("lblNodeVersion").textContent = t("nodeVersion");
|
|
1711
|
+
document.getElementById("lblNodeNodeVer").textContent = t("nodeNodeVer");
|
|
1712
|
+
document.getElementById("lblNodeConfigPath").textContent = t("nodeConfigPath");
|
|
1713
|
+
document.getElementById("lblNodeHomeDir").textContent = t("nodeHomeDir");
|
|
1714
|
+
document.getElementById("lblNodeScheduler").textContent = t("nodeScheduler");
|
|
1715
|
+
document.getElementById("lblNodeBinding").textContent = t("nodeBinding");
|
|
1716
|
+
document.getElementById("lblNodeRegistered").textContent = t("nodeRegistered");
|
|
1717
|
+
document.getElementById("lblNodeInstanceId").textContent = t("nodeInstanceId");
|
|
1718
|
+
document.getElementById("lblNodeActiveSessions").textContent = t("nodeActiveSessions");
|
|
1719
|
+
document.getElementById("lblLogAutoRefresh").textContent = t("logAutoRefresh");
|
|
1720
|
+
document.getElementById("logEmpty").textContent = t("logEmpty");
|
|
1721
|
+
document.getElementById("lblSchedUrl").textContent = t("schedUrl");
|
|
1722
|
+
document.getElementById("descSched").textContent = t("schedDesc");
|
|
1723
|
+
document.getElementById("btnReRegister").textContent = t("schedReRegister");
|
|
1724
|
+
}
|
|
1725
|
+
function switchSettingsPage(page) {
|
|
1726
|
+
document.querySelectorAll(".settings-nav-item").forEach(function(el) { el.classList.remove("active"); });
|
|
1727
|
+
document.querySelectorAll(".settings-page").forEach(function(el) { el.classList.remove("active"); });
|
|
1728
|
+
document.querySelector('.settings-nav-item[data-page="' + page + '"]').classList.add("active");
|
|
1729
|
+
document.getElementById('page' + page.charAt(0).toUpperCase() + page.slice(1)).classList.add("active");
|
|
1730
|
+
}
|
|
1731
|
+
function openSettings() {
|
|
1732
|
+
if (settingsLang !== lang) { renderSettingsUI(); settingsLang = lang; }
|
|
1733
|
+
const modal = document.getElementById("settingsModal");
|
|
1734
|
+
const msg = document.getElementById("settingsMsg");
|
|
1735
|
+
msg.className = "s-msg";
|
|
1736
|
+
msg.style.display = "none";
|
|
1737
|
+
document.getElementById("settingsForm").reset();
|
|
1738
|
+
document.getElementById("settingsSaveBtn").disabled = false;
|
|
1739
|
+
document.getElementById("settingsSaveBtn").textContent = t("setSave");
|
|
1740
|
+
switchSettingsPage("account");
|
|
1741
|
+
// 预填当前用户名
|
|
1742
|
+
fetch("/runtime/credentials-status").then(r => r.json()).then(d => {
|
|
1743
|
+
if (d.ok) document.getElementById("setUsername").value = d.username || "root";
|
|
1744
|
+
}).catch(() => {});
|
|
1745
|
+
modal.style.display = "flex";
|
|
1746
|
+
}
|
|
1747
|
+
function closeSettings(e) {
|
|
1748
|
+
if (e && e.target !== e.currentTarget) return;
|
|
1749
|
+
document.getElementById("settingsModal").style.display = "none";
|
|
1750
|
+
}
|
|
1751
|
+
async function saveSettings(e) {
|
|
1752
|
+
e.preventDefault();
|
|
1753
|
+
const msg = document.getElementById("settingsMsg");
|
|
1754
|
+
const btn = document.getElementById("settingsSaveBtn");
|
|
1755
|
+
msg.className = "s-msg";
|
|
1756
|
+
msg.style.display = "none";
|
|
1757
|
+
|
|
1758
|
+
const username = document.getElementById("setUsername").value.trim();
|
|
1759
|
+
const oldPassword = document.getElementById("setOldPassword").value;
|
|
1760
|
+
const newPassword = document.getElementById("setNewPassword").value;
|
|
1761
|
+
const passwordConfirm = document.getElementById("setPasswordConfirm").value;
|
|
1762
|
+
|
|
1763
|
+
if (newPassword) {
|
|
1764
|
+
if (!oldPassword) {
|
|
1765
|
+
msg.textContent = t("setPwdRequired");
|
|
1766
|
+
msg.className = "s-msg error";
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
if (newPassword !== passwordConfirm) {
|
|
1770
|
+
msg.textContent = t("setPwdMismatch");
|
|
1771
|
+
msg.className = "s-msg error";
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (newPassword.length < 4) {
|
|
1775
|
+
msg.textContent = t("setPwdTooShort");
|
|
1776
|
+
msg.className = "s-msg error";
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
btn.disabled = true;
|
|
1782
|
+
btn.textContent = "Saving...";
|
|
1783
|
+
|
|
1784
|
+
try {
|
|
1785
|
+
const resp = await fetch("/runtime/change-credentials", {
|
|
1786
|
+
method: "POST",
|
|
1787
|
+
headers: { "Content-Type": "application/json" },
|
|
1788
|
+
body: JSON.stringify({
|
|
1789
|
+
username,
|
|
1790
|
+
oldPassword,
|
|
1791
|
+
password: newPassword || undefined,
|
|
1792
|
+
passwordConfirm: newPassword || undefined,
|
|
1793
|
+
}),
|
|
1794
|
+
});
|
|
1795
|
+
const data = await resp.json();
|
|
1796
|
+
if (data.ok) {
|
|
1797
|
+
msg.textContent = t("setSaved");
|
|
1798
|
+
msg.className = "s-msg success";
|
|
1799
|
+
setTimeout(() => closeSettings(), 1500);
|
|
1800
|
+
} else {
|
|
1801
|
+
msg.textContent = data.error || t("setSaveFailed");
|
|
1802
|
+
msg.className = "s-msg error";
|
|
1803
|
+
btn.disabled = false;
|
|
1804
|
+
btn.textContent = t("setSave");
|
|
1805
|
+
}
|
|
1806
|
+
} catch (err) {
|
|
1807
|
+
msg.textContent = t("setNetworkError");
|
|
1808
|
+
msg.className = "s-msg error";
|
|
1809
|
+
btn.disabled = false;
|
|
1810
|
+
btn.textContent = t("setSave");
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
function confirmRestartBridge() {
|
|
1814
|
+
const msg = document.getElementById("restartMsg");
|
|
1815
|
+
msg.className = "s-restart-msg";
|
|
1816
|
+
msg.style.display = "none";
|
|
1817
|
+
if (!confirm(t("setRestartConfirm"))) return;
|
|
1818
|
+
const btn = document.getElementById("btnRestartBridge");
|
|
1819
|
+
btn.disabled = true;
|
|
1820
|
+
btn.textContent = t("setRestarting");
|
|
1821
|
+
msg.textContent = t("setRestarting");
|
|
1822
|
+
msg.className = "s-restart-msg info";
|
|
1823
|
+
doRestartBridge();
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// ── Node Info ─────────────────────────────────────────────
|
|
1827
|
+
let nodeInfoTimer = null;
|
|
1828
|
+
function refreshNodeInfo() {
|
|
1829
|
+
fetch("/runtime/node-info").then(function(r) { return r.json(); }).then(function(d) {
|
|
1830
|
+
if (!d.ok) return;
|
|
1831
|
+
document.getElementById("valNodeHostname").textContent = d.hostname;
|
|
1832
|
+
document.getElementById("valNodePlatform").textContent = d.platform + " " + d.arch;
|
|
1833
|
+
document.getElementById("valNodeUptime").textContent = fmtDuration(d.uptime);
|
|
1834
|
+
document.getElementById("valNodeVersion").textContent = d.bridgeVersion;
|
|
1835
|
+
document.getElementById("valNodeNodeVer").textContent = d.nodeVersion;
|
|
1836
|
+
document.getElementById("valNodeConfigPath").textContent = d.configPath;
|
|
1837
|
+
document.getElementById("valNodeHomeDir").textContent = d.homeDir;
|
|
1838
|
+
document.getElementById("valNodeScheduler").textContent = d.schedulerBaseUrl || "-";
|
|
1839
|
+
document.getElementById("valNodeBinding").textContent = d.paired ? t("nodePaired") : t("nodeUnpaired");
|
|
1840
|
+
document.getElementById("valNodeRegistered").textContent = d.registered ? t("nodeRegYes") : t("nodeRegNo");
|
|
1841
|
+
document.getElementById("valNodeInstanceId").textContent = d.instanceId || "-";
|
|
1842
|
+
document.getElementById("valNodeActiveSessions").textContent = d.activeSessions;
|
|
1843
|
+
}).catch(function() {});
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// ── Logs ──────────────────────────────────────────────────
|
|
1847
|
+
let logTimer = null;
|
|
1848
|
+
function refreshLogs() {
|
|
1849
|
+
var level = document.getElementById("logLevelFilter").value;
|
|
1850
|
+
var url = "/runtime/logs?count=300";
|
|
1851
|
+
if (level) url += "&level=" + level;
|
|
1852
|
+
document.getElementById("btnRefreshLogs").textContent = t("logRefreshing");
|
|
1853
|
+
fetch(url).then(function(r) { return r.json(); }).then(function(d) {
|
|
1854
|
+
if (!d.ok) return;
|
|
1855
|
+
var list = document.getElementById("logList");
|
|
1856
|
+
if (!d.logs || d.logs.length === 0) {
|
|
1857
|
+
list.innerHTML = '<div class="s-log-empty">' + t("logEmpty") + '</div>';
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
var html = "";
|
|
1861
|
+
for (var i = 0; i < d.logs.length; i++) {
|
|
1862
|
+
var e = d.logs[i];
|
|
1863
|
+
html += '<div class="s-log-entry"><span class="ts">' + escHtml(e.timestamp.slice(0, 19).replace("T", " ")) + '</span> <span class="ll ' + e.level.toUpperCase() + '">[' + e.level.toUpperCase() + ']</span>';
|
|
1864
|
+
if (e.prefix) html += ' [' + escHtml(e.prefix) + ']';
|
|
1865
|
+
html += ' ' + escHtml(e.message) + '</div>';
|
|
1866
|
+
}
|
|
1867
|
+
list.innerHTML = html;
|
|
1868
|
+
list.scrollTop = 0;
|
|
1869
|
+
}).catch(function() {}).then(function() {
|
|
1870
|
+
document.getElementById("btnRefreshLogs").textContent = "\u21bb";
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
function startLogAutoRefresh() {
|
|
1874
|
+
stopLogAutoRefresh();
|
|
1875
|
+
if (document.getElementById("logAutoRefresh").checked) {
|
|
1876
|
+
logTimer = setInterval(refreshLogs, 5000);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
function stopLogAutoRefresh() {
|
|
1880
|
+
if (logTimer) { clearInterval(logTimer); logTimer = null; }
|
|
1881
|
+
}
|
|
1882
|
+
// wire log auto-refresh toggle
|
|
1883
|
+
document.addEventListener("change", function(e) {
|
|
1884
|
+
if (e.target && e.target.id === "logAutoRefresh") {
|
|
1885
|
+
if (e.target.checked) { refreshLogs(); startLogAutoRefresh(); }
|
|
1886
|
+
else { stopLogAutoRefresh(); }
|
|
1887
|
+
}
|
|
1888
|
+
});
|
|
1889
|
+
|
|
1890
|
+
// ── Scheduler ─────────────────────────────────────────────
|
|
1891
|
+
function doReRegister() {
|
|
1892
|
+
var btn = document.getElementById("btnReRegister");
|
|
1893
|
+
var msg = document.getElementById("schedMsg");
|
|
1894
|
+
msg.className = "s-msg";
|
|
1895
|
+
msg.style.display = "none";
|
|
1896
|
+
btn.disabled = true;
|
|
1897
|
+
btn.textContent = t("schedReRegistering");
|
|
1898
|
+
fetch("/runtime/re-register", { method: "POST", headers: { "Content-Type": "application/json" } })
|
|
1899
|
+
.then(function(r) { return r.json(); })
|
|
1900
|
+
.then(function(d) {
|
|
1901
|
+
if (d.ok) {
|
|
1902
|
+
msg.textContent = t("schedReRegistered");
|
|
1903
|
+
msg.className = "s-msg success";
|
|
1904
|
+
refreshNodeInfo();
|
|
1905
|
+
} else {
|
|
1906
|
+
msg.textContent = t("schedReRegisterFail") + (d.error ? ": " + d.error : "");
|
|
1907
|
+
msg.className = "s-msg error";
|
|
1908
|
+
}
|
|
1909
|
+
})
|
|
1910
|
+
.catch(function() {
|
|
1911
|
+
msg.textContent = t("schedReRegisterFail");
|
|
1912
|
+
msg.className = "s-msg error";
|
|
1913
|
+
})
|
|
1914
|
+
.then(function() {
|
|
1915
|
+
btn.disabled = false;
|
|
1916
|
+
btn.textContent = t("schedReRegister");
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// ── Kill process (unknown processes table) ────────────────
|
|
1921
|
+
function killProcess(pid) {
|
|
1922
|
+
if (!confirm(t("btnKill") + " PID " + pid + "?")) return;
|
|
1923
|
+
var btn = document.querySelector('[data-pid="' + pid + '"]');
|
|
1924
|
+
if (btn) btn.disabled = true;
|
|
1925
|
+
fetch("/runtime/kill-process", {
|
|
1926
|
+
method: "POST",
|
|
1927
|
+
headers: { "Content-Type": "application/json" },
|
|
1928
|
+
body: JSON.stringify({ pid: pid }),
|
|
1929
|
+
})
|
|
1930
|
+
.then(function(r) { return r.json(); })
|
|
1931
|
+
.then(function(d) {
|
|
1932
|
+
if (d.ok) {
|
|
1933
|
+
var row = btn ? btn.closest("tr") : null;
|
|
1934
|
+
if (row) row.style.display = "none";
|
|
1935
|
+
} else {
|
|
1936
|
+
if (btn) btn.disabled = false;
|
|
1937
|
+
alert(t("btnKill") + " " + t("setSaveFailed") + (d.error ? ": " + d.error : ""));
|
|
1938
|
+
}
|
|
1939
|
+
})
|
|
1940
|
+
.catch(function() {
|
|
1941
|
+
if (btn) btn.disabled = false;
|
|
1942
|
+
alert(t("btnKill") + " " + t("setNetworkError"));
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
// Event delegation for kill buttons
|
|
1946
|
+
document.addEventListener("click", function(e) {
|
|
1947
|
+
var btn = e.target.closest ? e.target.closest(".btn-kill") : null;
|
|
1948
|
+
if (btn && btn.getAttribute("data-pid")) {
|
|
1949
|
+
killProcess(parseInt(btn.getAttribute("data-pid"), 10));
|
|
1950
|
+
}
|
|
1951
|
+
});
|
|
1952
|
+
|
|
1953
|
+
// ── Switch settings page with data refresh ────────────────
|
|
1954
|
+
var __origSwitch = switchSettingsPage;
|
|
1955
|
+
switchSettingsPage = function(page) {
|
|
1956
|
+
__origSwitch(page);
|
|
1957
|
+
stopLogAutoRefresh();
|
|
1958
|
+
if (nodeInfoTimer) { clearTimeout(nodeInfoTimer); nodeInfoTimer = null; }
|
|
1959
|
+
if (page === "node") { refreshNodeInfo(); }
|
|
1960
|
+
else if (page === "logs") { refreshLogs(); startLogAutoRefresh(); }
|
|
1961
|
+
else if (page === "scheduler") { refreshNodeInfo(); }
|
|
1962
|
+
};
|
|
1175
1963
|
</script>
|
|
1176
1964
|
</body>
|
|
1177
1965
|
</html>`;
|
|
@@ -1322,7 +2110,12 @@ async function doLogin(e) {
|
|
|
1322
2110
|
});
|
|
1323
2111
|
|
|
1324
2112
|
if (resp.ok) {
|
|
1325
|
-
|
|
2113
|
+
const data = await resp.json();
|
|
2114
|
+
if (data.needSetup) {
|
|
2115
|
+
window.location.href = "/setup";
|
|
2116
|
+
} else {
|
|
2117
|
+
window.location.href = "/";
|
|
2118
|
+
}
|
|
1326
2119
|
return;
|
|
1327
2120
|
}
|
|
1328
2121
|
|
|
@@ -1345,3 +2138,294 @@ document.getElementById("loginForm").addEventListener("keydown", function(e) {
|
|
|
1345
2138
|
</script>
|
|
1346
2139
|
</body>
|
|
1347
2140
|
</html>`;
|
|
2141
|
+
// ======================================================================
|
|
2142
|
+
// SETUP HTML — 首次登录设置页面
|
|
2143
|
+
// ======================================================================
|
|
2144
|
+
const SETUP_HTML = `<!DOCTYPE html>
|
|
2145
|
+
<html lang="zh-CN">
|
|
2146
|
+
<head>
|
|
2147
|
+
<meta charset="UTF-8">
|
|
2148
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2149
|
+
<title>Bridge Dashboard - Setup</title>
|
|
2150
|
+
<style>
|
|
2151
|
+
:root {
|
|
2152
|
+
--bg: #0f1117;
|
|
2153
|
+
--surface: #1a1d2e;
|
|
2154
|
+
--surface2: #232738;
|
|
2155
|
+
--border: #2d3248;
|
|
2156
|
+
--text: #e1e4f0;
|
|
2157
|
+
--text2: #8b90a8;
|
|
2158
|
+
--accent: #5b8def;
|
|
2159
|
+
--accent-hover: #4a7de0;
|
|
2160
|
+
--green: #43c6a8;
|
|
2161
|
+
--red: #f05454;
|
|
2162
|
+
--radius: 10px;
|
|
2163
|
+
}
|
|
2164
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
2165
|
+
body {
|
|
2166
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", sans-serif;
|
|
2167
|
+
background: var(--bg);
|
|
2168
|
+
color: var(--text);
|
|
2169
|
+
min-height: 100vh;
|
|
2170
|
+
display: flex;
|
|
2171
|
+
align-items: center;
|
|
2172
|
+
justify-content: center;
|
|
2173
|
+
padding: 20px;
|
|
2174
|
+
}
|
|
2175
|
+
.setup-card {
|
|
2176
|
+
background: var(--surface);
|
|
2177
|
+
border: 1px solid var(--border);
|
|
2178
|
+
border-radius: var(--radius);
|
|
2179
|
+
padding: 40px 36px;
|
|
2180
|
+
width: 400px;
|
|
2181
|
+
max-width: 100%;
|
|
2182
|
+
}
|
|
2183
|
+
.setup-card h1 {
|
|
2184
|
+
font-size: 20px;
|
|
2185
|
+
font-weight: 600;
|
|
2186
|
+
margin-bottom: 4px;
|
|
2187
|
+
text-align: center;
|
|
2188
|
+
}
|
|
2189
|
+
.setup-card p.subtitle {
|
|
2190
|
+
font-size: 13px;
|
|
2191
|
+
color: var(--text2);
|
|
2192
|
+
text-align: center;
|
|
2193
|
+
margin-bottom: 24px;
|
|
2194
|
+
}
|
|
2195
|
+
.badge {
|
|
2196
|
+
display: inline-block;
|
|
2197
|
+
background: rgba(240,160,48,0.12);
|
|
2198
|
+
color: #f0a030;
|
|
2199
|
+
font-size: 12px;
|
|
2200
|
+
padding: 2px 10px;
|
|
2201
|
+
border-radius: 4px;
|
|
2202
|
+
margin-bottom: 20px;
|
|
2203
|
+
text-align: center;
|
|
2204
|
+
width: 100%;
|
|
2205
|
+
}
|
|
2206
|
+
.form-group {
|
|
2207
|
+
margin-bottom: 18px;
|
|
2208
|
+
}
|
|
2209
|
+
.form-group label {
|
|
2210
|
+
display: block;
|
|
2211
|
+
font-size: 13px;
|
|
2212
|
+
font-weight: 500;
|
|
2213
|
+
margin-bottom: 6px;
|
|
2214
|
+
color: var(--text2);
|
|
2215
|
+
}
|
|
2216
|
+
.form-group input {
|
|
2217
|
+
width: 100%;
|
|
2218
|
+
padding: 10px 12px;
|
|
2219
|
+
background: var(--surface2);
|
|
2220
|
+
border: 1px solid var(--border);
|
|
2221
|
+
border-radius: 6px;
|
|
2222
|
+
color: var(--text);
|
|
2223
|
+
font-size: 14px;
|
|
2224
|
+
outline: none;
|
|
2225
|
+
transition: border-color .15s;
|
|
2226
|
+
}
|
|
2227
|
+
.form-group input:focus {
|
|
2228
|
+
border-color: var(--accent);
|
|
2229
|
+
}
|
|
2230
|
+
.form-group input::placeholder {
|
|
2231
|
+
color: var(--text2);
|
|
2232
|
+
opacity: 0.6;
|
|
2233
|
+
}
|
|
2234
|
+
.btn-row {
|
|
2235
|
+
display: flex;
|
|
2236
|
+
gap: 12px;
|
|
2237
|
+
margin-top: 24px;
|
|
2238
|
+
}
|
|
2239
|
+
.btn-row button {
|
|
2240
|
+
flex: 1;
|
|
2241
|
+
padding: 10px;
|
|
2242
|
+
border-radius: 6px;
|
|
2243
|
+
font-size: 14px;
|
|
2244
|
+
font-weight: 600;
|
|
2245
|
+
cursor: pointer;
|
|
2246
|
+
transition: all .15s;
|
|
2247
|
+
border: none;
|
|
2248
|
+
}
|
|
2249
|
+
.btn-primary {
|
|
2250
|
+
background: var(--accent);
|
|
2251
|
+
color: #fff;
|
|
2252
|
+
}
|
|
2253
|
+
.btn-primary:hover { background: var(--accent-hover); }
|
|
2254
|
+
.btn-primary:disabled {
|
|
2255
|
+
opacity: 0.5;
|
|
2256
|
+
cursor: not-allowed;
|
|
2257
|
+
}
|
|
2258
|
+
.btn-skip {
|
|
2259
|
+
background: var(--surface2);
|
|
2260
|
+
color: var(--text);
|
|
2261
|
+
border: 1px solid var(--border) !important;
|
|
2262
|
+
}
|
|
2263
|
+
.btn-skip:hover { background: var(--border); }
|
|
2264
|
+
.error-msg {
|
|
2265
|
+
background: rgba(240,84,84,0.12);
|
|
2266
|
+
color: var(--red);
|
|
2267
|
+
padding: 8px 12px;
|
|
2268
|
+
border-radius: 6px;
|
|
2269
|
+
font-size: 13px;
|
|
2270
|
+
margin-bottom: 16px;
|
|
2271
|
+
display: none;
|
|
2272
|
+
}
|
|
2273
|
+
.error-msg.visible { display: block; }
|
|
2274
|
+
.success-msg {
|
|
2275
|
+
background: rgba(67,198,168,0.12);
|
|
2276
|
+
color: var(--green);
|
|
2277
|
+
padding: 8px 12px;
|
|
2278
|
+
border-radius: 6px;
|
|
2279
|
+
font-size: 13px;
|
|
2280
|
+
margin-bottom: 16px;
|
|
2281
|
+
display: none;
|
|
2282
|
+
text-align: center;
|
|
2283
|
+
}
|
|
2284
|
+
.success-msg.visible { display: block; }
|
|
2285
|
+
.hint {
|
|
2286
|
+
font-size: 12px;
|
|
2287
|
+
color: var(--text2);
|
|
2288
|
+
margin-top: 16px;
|
|
2289
|
+
text-align: center;
|
|
2290
|
+
line-height: 1.6;
|
|
2291
|
+
}
|
|
2292
|
+
.spinner {
|
|
2293
|
+
display: inline-block;
|
|
2294
|
+
width: 14px;
|
|
2295
|
+
height: 14px;
|
|
2296
|
+
border: 2px solid var(--text2);
|
|
2297
|
+
border-top-color: var(--accent);
|
|
2298
|
+
border-radius: 50%;
|
|
2299
|
+
animation: spin .6s linear infinite;
|
|
2300
|
+
vertical-align: middle;
|
|
2301
|
+
margin-right: 6px;
|
|
2302
|
+
}
|
|
2303
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
2304
|
+
</style>
|
|
2305
|
+
</head>
|
|
2306
|
+
<body>
|
|
2307
|
+
<div class="setup-card" id="setupCard">
|
|
2308
|
+
<h1>Bridge Dashboard</h1>
|
|
2309
|
+
<p class="subtitle">First-time setup — 首次登录设置</p>
|
|
2310
|
+
<div class="badge">Default credentials detected. Please set a new username and password.<br>检测到默认凭据,请设置新的用户名和密码</div>
|
|
2311
|
+
<div class="error-msg" id="errorMsg"></div>
|
|
2312
|
+
<div class="success-msg" id="successMsg">Credentials saved! Redirecting... / 凭据已保存,正在跳转...</div>
|
|
2313
|
+
<form id="setupForm">
|
|
2314
|
+
<div class="form-group">
|
|
2315
|
+
<label for="username">Username / 用户名</label>
|
|
2316
|
+
<input type="text" id="username" name="username" value="root" autocomplete="username" autofocus>
|
|
2317
|
+
</div>
|
|
2318
|
+
<div class="form-group">
|
|
2319
|
+
<label for="password">New Password / 新密码</label>
|
|
2320
|
+
<input type="password" id="password" name="password" placeholder="Enter new password" autocomplete="new-password">
|
|
2321
|
+
</div>
|
|
2322
|
+
<div class="form-group">
|
|
2323
|
+
<label for="passwordConfirm">Confirm Password / 确认密码</label>
|
|
2324
|
+
<input type="password" id="passwordConfirm" name="passwordConfirm" placeholder="Enter password again" autocomplete="new-password">
|
|
2325
|
+
</div>
|
|
2326
|
+
<div class="btn-row">
|
|
2327
|
+
<button type="button" class="btn-skip" id="skipBtn" onclick="doSkip()">Skip / 跳过</button>
|
|
2328
|
+
<button type="submit" class="btn-primary" id="saveBtn">Save / 保存</button>
|
|
2329
|
+
</div>
|
|
2330
|
+
</form>
|
|
2331
|
+
<div class="hint">Skip to keep default credentials <b>root/root</b>. You can change them later via <code>~/.aws-bridge/config.json</code>.<br>跳过以保留默认凭据 <b>root/root</b>。</div>
|
|
2332
|
+
</div>
|
|
2333
|
+
<script>
|
|
2334
|
+
async function doSave(e) {
|
|
2335
|
+
e.preventDefault();
|
|
2336
|
+
const errorEl = document.getElementById("errorMsg");
|
|
2337
|
+
const saveBtn = document.getElementById("saveBtn");
|
|
2338
|
+
errorEl.classList.remove("visible");
|
|
2339
|
+
|
|
2340
|
+
const username = document.getElementById("username").value.trim();
|
|
2341
|
+
const password = document.getElementById("password").value;
|
|
2342
|
+
const passwordConfirm = document.getElementById("passwordConfirm").value;
|
|
2343
|
+
|
|
2344
|
+
if (!username) {
|
|
2345
|
+
errorEl.textContent = "Username is required / 用户名不能为空";
|
|
2346
|
+
errorEl.classList.add("visible");
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
if (!password) {
|
|
2350
|
+
errorEl.textContent = "Password is required / 密码不能为空";
|
|
2351
|
+
errorEl.classList.add("visible");
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
if (password !== passwordConfirm) {
|
|
2355
|
+
errorEl.textContent = "Passwords do not match / 两次输入的密码不一致";
|
|
2356
|
+
errorEl.classList.add("visible");
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
if (password.length < 4) {
|
|
2360
|
+
errorEl.textContent = "Password must be at least 4 characters / 密码至少4位";
|
|
2361
|
+
errorEl.classList.add("visible");
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
saveBtn.disabled = true;
|
|
2366
|
+
saveBtn.innerHTML = '<span class="spinner"></span>Saving...';
|
|
2367
|
+
|
|
2368
|
+
try {
|
|
2369
|
+
const resp = await fetch("/runtime/change-credentials", {
|
|
2370
|
+
method: "POST",
|
|
2371
|
+
headers: { "Content-Type": "application/json" },
|
|
2372
|
+
body: JSON.stringify({ username, password, passwordConfirm }),
|
|
2373
|
+
});
|
|
2374
|
+
const data = await resp.json();
|
|
2375
|
+
if (data.ok) {
|
|
2376
|
+
document.getElementById("successMsg").classList.add("visible");
|
|
2377
|
+
document.getElementById("setupForm").style.display = "none";
|
|
2378
|
+
document.querySelector(".hint").style.display = "none";
|
|
2379
|
+
document.querySelector(".badge").style.display = "none";
|
|
2380
|
+
setTimeout(() => { window.location.href = "/"; }, 1500);
|
|
2381
|
+
} else {
|
|
2382
|
+
errorEl.textContent = data.error || "Save failed";
|
|
2383
|
+
errorEl.classList.add("visible");
|
|
2384
|
+
saveBtn.disabled = false;
|
|
2385
|
+
saveBtn.textContent = "Save / 保存";
|
|
2386
|
+
}
|
|
2387
|
+
} catch (err) {
|
|
2388
|
+
errorEl.textContent = "Network error, please try again / 网络错误,请重试";
|
|
2389
|
+
errorEl.classList.add("visible");
|
|
2390
|
+
saveBtn.disabled = false;
|
|
2391
|
+
saveBtn.textContent = "Save / 保存";
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
async function doSkip() {
|
|
2396
|
+
const skipBtn = document.getElementById("skipBtn");
|
|
2397
|
+
const errorEl = document.getElementById("errorMsg");
|
|
2398
|
+
skipBtn.disabled = true;
|
|
2399
|
+
skipBtn.textContent = "Skipping... / 跳过中...";
|
|
2400
|
+
|
|
2401
|
+
try {
|
|
2402
|
+
const resp = await fetch("/runtime/change-credentials", {
|
|
2403
|
+
method: "POST",
|
|
2404
|
+
headers: { "Content-Type": "application/json" },
|
|
2405
|
+
body: JSON.stringify({ username: "root", password: "root", passwordConfirm: "root" }),
|
|
2406
|
+
});
|
|
2407
|
+
const data = await resp.json();
|
|
2408
|
+
if (data.ok) {
|
|
2409
|
+
document.getElementById("successMsg").classList.add("visible");
|
|
2410
|
+
document.getElementById("setupForm").style.display = "none";
|
|
2411
|
+
document.querySelector(".hint").style.display = "none";
|
|
2412
|
+
document.querySelector(".badge").style.display = "none";
|
|
2413
|
+
setTimeout(() => { window.location.href = "/"; }, 1500);
|
|
2414
|
+
} else {
|
|
2415
|
+
errorEl.textContent = data.error || "Skip failed";
|
|
2416
|
+
errorEl.classList.add("visible");
|
|
2417
|
+
skipBtn.disabled = false;
|
|
2418
|
+
skipBtn.textContent = "Skip / 跳过";
|
|
2419
|
+
}
|
|
2420
|
+
} catch (err) {
|
|
2421
|
+
errorEl.textContent = "Network error, please try again / 网络错误,请重试";
|
|
2422
|
+
errorEl.classList.add("visible");
|
|
2423
|
+
skipBtn.disabled = false;
|
|
2424
|
+
skipBtn.textContent = "Skip / 跳过";
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
document.getElementById("setupForm").addEventListener("submit", doSave);
|
|
2429
|
+
</script>
|
|
2430
|
+
</body>
|
|
2431
|
+
</html>`;
|