aws-runtime-bridge 1.9.10 → 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 +5 -1
- package/dist/routes/dashboard.d.ts.map +1 -1
- package/dist/routes/dashboard.js +749 -15
- 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 +8 -0
- package/dist/services/panel-auth.d.ts.map +1 -1
- package/dist/services/panel-auth.js +8 -0
- 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, hasConfiguredCredentials, isDefaultCredentials, setPanelCredentials, } 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
|
|
@@ -141,32 +143,162 @@ dashboardRouter.post("/runtime/logout", (req, res) => {
|
|
|
141
143
|
dashboardRouter.get("/runtime/credentials-status", (req, res) => {
|
|
142
144
|
if (!requirePanelAuth(req, res))
|
|
143
145
|
return;
|
|
144
|
-
|
|
146
|
+
const creds = getPanelCredentials();
|
|
147
|
+
res.json({ ok: true, isDefault: isDefaultCredentials(), configured: hasConfiguredCredentials(), username: creds.username });
|
|
145
148
|
});
|
|
146
149
|
/**
|
|
147
150
|
* POST /runtime/change-credentials
|
|
148
151
|
* 修改面板登录凭据,写入 ~/.aws-bridge/config.json。
|
|
149
|
-
*
|
|
152
|
+
* 需要面板登录会话。修改密码需校验原密码;用户名可同时修改。
|
|
150
153
|
*/
|
|
151
154
|
dashboardRouter.post("/runtime/change-credentials", (req, res) => {
|
|
152
155
|
if (!requirePanelAuth(req, res))
|
|
153
156
|
return;
|
|
154
|
-
const { username, password, passwordConfirm } = req.body || {};
|
|
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
|
+
// 至少需要用户名或密码的变更
|
|
155
179
|
if (!username || !String(username).trim()) {
|
|
156
180
|
res.status(400).json({ ok: false, error: "username required" });
|
|
157
181
|
return;
|
|
158
182
|
}
|
|
159
|
-
|
|
160
|
-
|
|
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))
|
|
161
194
|
return;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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))
|
|
165
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 });
|
|
166
248
|
}
|
|
167
|
-
|
|
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);
|
|
168
275
|
res.json({ ok: true });
|
|
169
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
|
+
});
|
|
170
302
|
/**
|
|
171
303
|
* GET /runtime/dashboard
|
|
172
304
|
* 聚合 bridge 所有状态数据。使用 2 秒缓存避免频繁 I/O,需要面板登录会话。
|
|
@@ -219,6 +351,9 @@ dashboardRouter.get("/runtime/dashboard", async (req, res) => {
|
|
|
219
351
|
managementStatus: p.managementStatus,
|
|
220
352
|
sessionId: record?.sessionId,
|
|
221
353
|
state: record?.state,
|
|
354
|
+
command: p.command,
|
|
355
|
+
cwd: p.cwd,
|
|
356
|
+
startedAt: p.startedAt,
|
|
222
357
|
};
|
|
223
358
|
});
|
|
224
359
|
const sortOrder = { managed: 0, orphan: 1, unknown: 2 };
|
|
@@ -619,6 +754,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
619
754
|
<span id="updateIndicator" style="display:none"></span>
|
|
620
755
|
</span>
|
|
621
756
|
<span class="lang-btn" id="langBtn" onclick="toggleLang()">中文</span>
|
|
757
|
+
<button onclick="openSettings()" id="settingsBtn" title="Settings / 设置">⚙</button>
|
|
622
758
|
<button onclick="refresh()" id="refreshBtn">↻</button>
|
|
623
759
|
</div>
|
|
624
760
|
</div>
|
|
@@ -662,6 +798,200 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
662
798
|
<div class="section-title" id="sectionPersisted">Persisted Sessions <span class="count" id="persistedCount"></span></div>
|
|
663
799
|
<div class="table-wrap" id="persistedTable"><div class="empty" id="loadingPersisted">Loading...</div></div>
|
|
664
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
|
+
|
|
665
995
|
<script>
|
|
666
996
|
// ── 多语言翻译表 ──────────────────────────────────────────
|
|
667
997
|
const T = {
|
|
@@ -705,6 +1035,7 @@ const T = {
|
|
|
705
1035
|
secUnknown: "\u5176\u4ed6\u8fdb\u7a0b",
|
|
706
1036
|
orphanBanner: "\u4ee5\u4e0b\u8fdb\u7a0b\u4ecd\u5728\u8fd0\u884c\u4f46\u8131\u79bb\u4e86 bridge \u7ba1\u7406",
|
|
707
1037
|
btnTerminate: "\u7ec8\u6b62",
|
|
1038
|
+
btnKill: "\u6740\u6b7b",
|
|
708
1039
|
showUnknown: "\u663e\u793a",
|
|
709
1040
|
hideUnknown: "\u9690\u85cf",
|
|
710
1041
|
noOrphans: "\u65e0\u5b64\u513f\u8fdb\u7a0b",
|
|
@@ -728,8 +1059,62 @@ const T = {
|
|
|
728
1059
|
thWorkspace: "\u5de5\u4f5c\u533a",
|
|
729
1060
|
thProvider: "\u63d0\u4f9b\u65b9",
|
|
730
1061
|
thCommand: "\u547d\u4ee4",
|
|
1062
|
+
thDirectory: "\u76ee\u5f55",
|
|
731
1063
|
thStarted: "\u5f00\u59cb\u65f6\u95f4",
|
|
732
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",
|
|
733
1118
|
// 状态文本
|
|
734
1119
|
stHealthy: "\u5065\u5eb7",
|
|
735
1120
|
stError: "\u5f02\u5e38",
|
|
@@ -780,6 +1165,7 @@ const T = {
|
|
|
780
1165
|
secUnknown: "Other Processes",
|
|
781
1166
|
orphanBanner: "These processes are running but no longer managed by bridge",
|
|
782
1167
|
btnTerminate: "Terminate",
|
|
1168
|
+
btnKill: "Kill",
|
|
783
1169
|
showUnknown: "Show",
|
|
784
1170
|
hideUnknown: "Hide",
|
|
785
1171
|
noOrphans: "No orphan processes",
|
|
@@ -802,8 +1188,63 @@ const T = {
|
|
|
802
1188
|
thWorkspace: "Workspace",
|
|
803
1189
|
thProvider: "Provider",
|
|
804
1190
|
thCommand: "Command",
|
|
1191
|
+
thDirectory: "Directory",
|
|
805
1192
|
thStarted: "Started",
|
|
806
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
|
+
// 状态文本
|
|
807
1248
|
stHealthy: "Healthy",
|
|
808
1249
|
stError: "Error",
|
|
809
1250
|
stRunning: "Running",
|
|
@@ -837,6 +1278,10 @@ function toggleLang() {
|
|
|
837
1278
|
localStorage.setItem("dashboard-lang", lang);
|
|
838
1279
|
document.documentElement.lang = lang === "en" ? "en" : "zh-CN";
|
|
839
1280
|
document.getElementById("langBtn").textContent = t("langName");
|
|
1281
|
+
if (document.getElementById("settingsModal").style.display === "flex") {
|
|
1282
|
+
renderSettingsUI();
|
|
1283
|
+
settingsLang = lang;
|
|
1284
|
+
}
|
|
840
1285
|
if (lastData) render(lastData);
|
|
841
1286
|
}
|
|
842
1287
|
|
|
@@ -869,6 +1314,20 @@ function fmtDuration(seconds) {
|
|
|
869
1314
|
parts.push(s + "s");
|
|
870
1315
|
return parts.join(" ");
|
|
871
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
|
+
}
|
|
872
1331
|
|
|
873
1332
|
function statusClass(v) {
|
|
874
1333
|
if (v === true || v === "healthy" || v === "paired" || v === "running" || v === "managed" || v === "registered") return "status-green";
|
|
@@ -1030,7 +1489,7 @@ function render(data) {
|
|
|
1030
1489
|
return '<tr><td>' + s.agentId + '</td><td>' + (pid ? '<code>' + pid + '</code>' : '<span class="status-muted">-</span>') + '</td>'
|
|
1031
1490
|
+ '<td><span class="' + statusClass(st) + '">' + (t("st" + st.charAt(0).toUpperCase() + st.slice(1)) || st) + '</span></td>'
|
|
1032
1491
|
+ '<td>' + (s.runtimeActionLabel || '-') + '</td>'
|
|
1033
|
-
|
|
1492
|
+
+ '<td title="' + s.workspacePath + '">' + (s.workspacePath ? s.workspacePath.replace(/\\\\/g, "/").split("/").pop() : "-") + '</td>'
|
|
1034
1493
|
+ '<td>' + (s.providerId || "-") + '</td></tr>';
|
|
1035
1494
|
}).join("") +
|
|
1036
1495
|
'</tbody></table>';
|
|
@@ -1058,9 +1517,9 @@ function render(data) {
|
|
|
1058
1517
|
unknownTable.innerHTML = '<div class="empty">' + t("noProcesses") + '</div>';
|
|
1059
1518
|
} else {
|
|
1060
1519
|
unknownTable.innerHTML =
|
|
1061
|
-
'<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>' +
|
|
1062
1521
|
unknownProcs.map(p =>
|
|
1063
|
-
'<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>'
|
|
1064
1523
|
).join("") +
|
|
1065
1524
|
'</tbody></table>';
|
|
1066
1525
|
}
|
|
@@ -1075,7 +1534,7 @@ function render(data) {
|
|
|
1075
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>' +
|
|
1076
1535
|
persisted.map(s =>
|
|
1077
1536
|
'<tr><td>' + s.agentId + '</td><td>' + (s.status || "stopped") + '</td>'
|
|
1078
|
-
+ '<td title="' + s.workspacePath + '">' + (s.workspacePath ? s.workspacePath.split(/
|
|
1537
|
+
+ '<td title="' + s.workspacePath + '">' + (s.workspacePath ? s.workspacePath.replace(/\\\\/g, "/").split("/").pop() : "-") + '</td>'
|
|
1079
1538
|
+ '<td>' + (s.command || "-") + '</td>'
|
|
1080
1539
|
+ '<td>' + (s.startedAt ? new Date(s.startedAt).toLocaleString() : "-") + '</td></tr>'
|
|
1081
1540
|
).join("") +
|
|
@@ -1226,6 +1685,281 @@ render = function(data) {
|
|
|
1226
1685
|
checkUpdate();
|
|
1227
1686
|
// Also check periodically (every 5 minutes)
|
|
1228
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
|
+
};
|
|
1229
1963
|
</script>
|
|
1230
1964
|
</body>
|
|
1231
1965
|
</html>`;
|