dsh-winrm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +112 -0
  3. package/README.zh.md +119 -0
  4. package/RELEASING.md +75 -0
  5. package/cordis.patch.yml +8 -0
  6. package/lib/client.js +2076 -0
  7. package/lib/index.js +2458 -0
  8. package/lib/invariant.js +6 -0
  9. package/lib/types/client/api.d.ts +59 -0
  10. package/lib/types/client/index.d.ts +38 -0
  11. package/lib/types/client/locales.d.ts +231 -0
  12. package/lib/types/client/panel/ConsoleTab.d.ts +11 -0
  13. package/lib/types/client/panel/HostFormDialog.d.ts +12 -0
  14. package/lib/types/client/panel/HostsTab.d.ts +9 -0
  15. package/lib/types/client/panel/ProcessesTab.d.ts +7 -0
  16. package/lib/types/client/panel/ServicesTab.d.ts +7 -0
  17. package/lib/types/client/panel/TransferTab.d.ts +7 -0
  18. package/lib/types/client/panel/WinrmPanel.d.ts +12 -0
  19. package/lib/types/client/panel/WinrmPanelIcon.d.ts +8 -0
  20. package/lib/types/client/panel/helpers.d.ts +16 -0
  21. package/lib/types/engine/client.d.ts +49 -0
  22. package/lib/types/engine/console.d.ts +23 -0
  23. package/lib/types/engine.d.ts +58 -0
  24. package/lib/types/index.d.ts +44 -0
  25. package/lib/types/invariant.d.ts +3 -0
  26. package/lib/types/loopback.d.ts +24 -0
  27. package/lib/types/mount-once.d.ts +24 -0
  28. package/lib/types/powershell.d.ts +73 -0
  29. package/lib/types/protocol.d.ts +176 -0
  30. package/lib/types/routes.d.ts +26 -0
  31. package/lib/types/smb.d.ts +31 -0
  32. package/lib/types/store.d.ts +44 -0
  33. package/lib/types/tools.d.ts +20 -0
  34. package/package.json +111 -0
  35. package/scripts/enable-winrm.ps1 +0 -0
  36. package/scripts/postbuild.mjs +40 -0
  37. package/scripts/validate-manifest.mjs +18 -0
  38. package/src/client/api.ts +345 -0
  39. package/src/client/css-modules.d.ts +5 -0
  40. package/src/client/index.ts +82 -0
  41. package/src/client/locales.ts +243 -0
  42. package/src/client/panel/ConsoleTab.tsx +142 -0
  43. package/src/client/panel/HostFormDialog.tsx +142 -0
  44. package/src/client/panel/HostsTab.tsx +153 -0
  45. package/src/client/panel/ProcessesTab.tsx +119 -0
  46. package/src/client/panel/ServicesTab.tsx +123 -0
  47. package/src/client/panel/TransferTab.tsx +161 -0
  48. package/src/client/panel/WinrmPanel.tsx +85 -0
  49. package/src/client/panel/WinrmPanelIcon.tsx +27 -0
  50. package/src/client/panel/helpers.ts +33 -0
  51. package/src/client/panel/panel.module.css +1000 -0
  52. package/src/engine/client.ts +291 -0
  53. package/src/engine/console.ts +62 -0
  54. package/src/engine.ts +331 -0
  55. package/src/index.ts +142 -0
  56. package/src/invariant.ts +4 -0
  57. package/src/loopback.ts +63 -0
  58. package/src/mount-once.ts +48 -0
  59. package/src/powershell.ts +208 -0
  60. package/src/protocol.ts +178 -0
  61. package/src/routes.ts +490 -0
  62. package/src/smb.ts +129 -0
  63. package/src/store.ts +232 -0
  64. package/src/tools.ts +387 -0
package/lib/client.js ADDED
@@ -0,0 +1,2076 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-winrm",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/protocol.ts
10
+ const WINRM_API = {
11
+ hosts: "/api/dsh-winrm/hosts",
12
+ test: "/api/dsh-winrm/test",
13
+ exec: "/api/dsh-winrm/exec",
14
+ cluster: "/api/dsh-winrm/cluster",
15
+ services: "/api/dsh-winrm/services",
16
+ processes: "/api/dsh-winrm/processes",
17
+ ls: "/api/dsh-winrm/ls",
18
+ upload: "/api/dsh-winrm/upload",
19
+ download: "/api/dsh-winrm/download",
20
+ console: "/api/dsh-winrm/console"
21
+ };
22
+ //#endregion
23
+ //#region src/client/api.ts
24
+ /**
25
+ * Browser-side API client for the /api/dsh-winrm route family. The only data
26
+ * access path the panel components use — plain fetch/WebSocket, same origin.
27
+ */
28
+ /** Error carrying the route's JSON error message. */
29
+ var WinrmApiError = class extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = "WinrmApiError";
33
+ }
34
+ };
35
+ /** Parse a JSON response or throw a WinrmApiError. */
36
+ async function readJson(response) {
37
+ let body;
38
+ try {
39
+ body = await response.json();
40
+ } catch {
41
+ throw new WinrmApiError(`HTTP ${response.status}: invalid JSON response`);
42
+ }
43
+ if (!response.ok) throw new WinrmApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : `HTTP ${response.status}`);
44
+ return body;
45
+ }
46
+ /** Query-string helper. */
47
+ function query(params) {
48
+ const search = new URLSearchParams();
49
+ for (const [key, value] of Object.entries(params)) if (value !== void 0 && value !== "") search.set(key, String(value));
50
+ const text = search.toString();
51
+ return text === "" ? "" : "?" + text;
52
+ }
53
+ /** The browser half's only data entry point. */
54
+ var WinrmApi = class {
55
+ async listHosts(queryText) {
56
+ return (await readJson(await fetch(WINRM_API.hosts + query({ query: queryText })))).hosts;
57
+ }
58
+ async createHost(payload) {
59
+ return (await readJson(await fetch(WINRM_API.hosts, {
60
+ method: "POST",
61
+ headers: { "content-type": "application/json" },
62
+ body: JSON.stringify(payload)
63
+ }))).host;
64
+ }
65
+ async updateHost(alias, patch) {
66
+ return (await readJson(await fetch(WINRM_API.hosts + query({ alias }), {
67
+ method: "PATCH",
68
+ headers: { "content-type": "application/json" },
69
+ body: JSON.stringify(patch)
70
+ }))).host;
71
+ }
72
+ async deleteHost(alias) {
73
+ await readJson(await fetch(WINRM_API.hosts + query({ alias }), { method: "DELETE" }));
74
+ }
75
+ async testHost(alias) {
76
+ return (await readJson(await fetch(WINRM_API.test, {
77
+ method: "POST",
78
+ headers: { "content-type": "application/json" },
79
+ body: JSON.stringify({ alias })
80
+ }))).result;
81
+ }
82
+ async exec(alias, command, timeoutMs) {
83
+ return (await readJson(await fetch(WINRM_API.exec, {
84
+ method: "POST",
85
+ headers: { "content-type": "application/json" },
86
+ body: JSON.stringify({
87
+ alias,
88
+ command,
89
+ timeoutMs
90
+ })
91
+ }))).result;
92
+ }
93
+ async cluster(options) {
94
+ return (await readJson(await fetch(WINRM_API.cluster, {
95
+ method: "POST",
96
+ headers: { "content-type": "application/json" },
97
+ body: JSON.stringify(options)
98
+ }))).results;
99
+ }
100
+ async listServices(alias) {
101
+ return (await readJson(await fetch(WINRM_API.services, {
102
+ method: "POST",
103
+ headers: { "content-type": "application/json" },
104
+ body: JSON.stringify({
105
+ alias,
106
+ action: "list"
107
+ })
108
+ }))).services;
109
+ }
110
+ async serviceAction(alias, name, action) {
111
+ return (await readJson(await fetch(WINRM_API.services, {
112
+ method: "POST",
113
+ headers: { "content-type": "application/json" },
114
+ body: JSON.stringify({
115
+ alias,
116
+ name,
117
+ action
118
+ })
119
+ }))).service;
120
+ }
121
+ async listProcesses(alias) {
122
+ return (await readJson(await fetch(WINRM_API.processes, {
123
+ method: "POST",
124
+ headers: { "content-type": "application/json" },
125
+ body: JSON.stringify({
126
+ alias,
127
+ action: "list"
128
+ })
129
+ }))).processes;
130
+ }
131
+ async killProcess(alias, id) {
132
+ await readJson(await fetch(WINRM_API.processes, {
133
+ method: "POST",
134
+ headers: { "content-type": "application/json" },
135
+ body: JSON.stringify({
136
+ alias,
137
+ action: "kill",
138
+ id
139
+ })
140
+ }));
141
+ }
142
+ async ls(alias, dir) {
143
+ return (await readJson(await fetch(WINRM_API.ls + query({
144
+ alias,
145
+ path: dir
146
+ })))).entries;
147
+ }
148
+ /**
149
+ * Upload one file (raw bytes) to a remote path. Progress arrives through
150
+ * the NDJSON response stream; resolves when the result frame lands.
151
+ */
152
+ async uploadFile(file, alias, remotePath, onProgress) {
153
+ const response = await fetch(WINRM_API.upload + query({
154
+ alias,
155
+ remotePath
156
+ }), {
157
+ method: "POST",
158
+ body: file
159
+ });
160
+ if (!response.ok || response.body === null) throw new WinrmApiError(`upload failed: HTTP ${response.status}`);
161
+ const reader = response.body.getReader();
162
+ const decoder = new TextDecoder();
163
+ let buffer = "";
164
+ let finalError;
165
+ let sawResult = false;
166
+ let transferredBytes = 0;
167
+ for (;;) {
168
+ const { done, value } = await reader.read();
169
+ if (done) break;
170
+ buffer += decoder.decode(value, { stream: true });
171
+ const lines = buffer.split("\n");
172
+ buffer = lines.pop() ?? "";
173
+ for (const line of lines) {
174
+ if (line.trim() === "") continue;
175
+ let parsed;
176
+ try {
177
+ parsed = JSON.parse(line);
178
+ } catch {
179
+ continue;
180
+ }
181
+ if (parsed.type === "progress") onProgress?.(parsed.progress);
182
+ else if (parsed.type === "result") {
183
+ sawResult = true;
184
+ if (parsed.ok) transferredBytes = parsed.transferredBytes ?? 0;
185
+ finalError = parsed.ok ? void 0 : parsed.error ?? "upload failed";
186
+ }
187
+ }
188
+ }
189
+ if (finalError !== void 0) throw new WinrmApiError(finalError);
190
+ if (!sawResult) throw new WinrmApiError("upload ended without a result frame — the transfer did not complete");
191
+ return { transferredBytes };
192
+ }
193
+ /**
194
+ * Download a remote file with client-side progress. Streams straight to
195
+ * disk when the File System Access API is available; otherwise falls back
196
+ * to an in-memory Blob.
197
+ */
198
+ async downloadFile(alias, remotePath, onProgress) {
199
+ const response = await fetch(WINRM_API.download + query({
200
+ alias,
201
+ remotePath
202
+ }));
203
+ if (!response.ok || response.body === null) {
204
+ const text = await response.text().catch(() => "");
205
+ throw new WinrmApiError(text !== "" && text.startsWith("{") ? text : `download failed: HTTP ${response.status}`);
206
+ }
207
+ const total = Number(response.headers.get("content-length") ?? "0");
208
+ const disposition = response.headers.get("content-disposition") ?? "";
209
+ const filename = /filename="([^"]+)"/.exec(disposition)?.[1] ?? remotePath.split(/[\\/]/).pop() ?? "download";
210
+ const reader = response.body.getReader();
211
+ const picker = typeof window !== "undefined" ? window.showSaveFilePicker : void 0;
212
+ let streamed = false;
213
+ let writable;
214
+ const chunks = [];
215
+ let received = 0;
216
+ const progress = () => {
217
+ onProgress?.({
218
+ phase: "transferring",
219
+ file: remotePath,
220
+ transferred: received,
221
+ total,
222
+ percent: total > 0 ? Math.round(received / total * 1e3) / 10 : 0
223
+ });
224
+ };
225
+ try {
226
+ if (picker !== void 0) {
227
+ writable = await (await picker.call(window, { suggestedName: filename })).createWritable();
228
+ streamed = true;
229
+ }
230
+ } catch {}
231
+ for (;;) {
232
+ const { done, value } = await reader.read();
233
+ if (done) break;
234
+ if (writable !== void 0) await writable.write(value);
235
+ else chunks.push(value);
236
+ received += value.length;
237
+ progress();
238
+ }
239
+ if (writable !== void 0) await writable.close();
240
+ onProgress?.({
241
+ phase: "done",
242
+ file: remotePath,
243
+ transferred: received,
244
+ total: received > 0 ? received : total,
245
+ percent: 100
246
+ });
247
+ return {
248
+ blob: streamed ? void 0 : new Blob(chunks),
249
+ filename,
250
+ streamed,
251
+ bytes: received
252
+ };
253
+ }
254
+ /** Open a WebSocket PowerShell console session. */
255
+ openConsole(alias) {
256
+ const url = (window.location.protocol === "https:" ? "wss" : "ws") + "://" + window.location.host + WINRM_API.console + query({ alias });
257
+ const socket = new WebSocket(url);
258
+ const connection = {
259
+ onReady: void 0,
260
+ onOutput: void 0,
261
+ onExit: void 0,
262
+ send: (data) => {
263
+ if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({
264
+ type: "input",
265
+ data
266
+ }));
267
+ },
268
+ close: () => {
269
+ try {
270
+ socket.close();
271
+ } catch {}
272
+ }
273
+ };
274
+ socket.onmessage = (event) => {
275
+ let frame;
276
+ try {
277
+ frame = JSON.parse(event.data);
278
+ } catch {
279
+ return;
280
+ }
281
+ if (frame.type === "ready") connection.onReady?.();
282
+ else if (frame.type === "output") connection.onOutput?.(frame.data);
283
+ else if (frame.type === "exit") connection.onExit?.(frame.code, frame.error);
284
+ };
285
+ socket.onclose = () => {
286
+ connection.onExit?.(null, "connection closed");
287
+ };
288
+ socket.onerror = () => {
289
+ connection.onExit?.(null, "connection error");
290
+ };
291
+ return connection;
292
+ }
293
+ };
294
+ //#endregion
295
+ //#region src/client/locales.ts
296
+ /**
297
+ * Locale dictionaries for the WinRM panel (zh / en), bound to the shared
298
+ * interpolator in panel/helpers.ts.
299
+ */
300
+ const zh = {
301
+ "entry.label": "Windows",
302
+ "entry.tooltip": "Windows 远程管理(WinRM)",
303
+ "panel.title": "Windows 远程管理",
304
+ "panel.backToConversation": "返回对话",
305
+ "tab.hosts": "主机",
306
+ "tab.console": "控制台",
307
+ "tab.services": "服务",
308
+ "tab.processes": "进程",
309
+ "tab.transfer": "传输",
310
+ "common.loading": "加载中…",
311
+ "common.error": "错误:{error}",
312
+ "common.refresh": "刷新",
313
+ "hosts.search": "搜索主机…",
314
+ "hosts.add": "添加主机",
315
+ "hosts.test": "测试",
316
+ "hosts.testing": "测试中…",
317
+ "hosts.edit": "编辑",
318
+ "hosts.delete": "删除",
319
+ "hosts.deleteConfirm": "确认删除主机 {alias}?",
320
+ "hosts.connect": "连接控制台",
321
+ "hosts.testOk": "连接成功({latency} ms)",
322
+ "hosts.testFail": "连接失败:{error}",
323
+ "hosts.empty": "尚未配置 Windows 主机,点「添加主机」开始。",
324
+ "hosts.col.alias": "别名",
325
+ "hosts.col.host": "主机",
326
+ "hosts.col.user": "用户",
327
+ "hosts.col.auth": "认证",
328
+ "hosts.col.transport": "传输",
329
+ "hosts.col.environment": "环境",
330
+ "hosts.col.tags": "标签",
331
+ "hosts.col.description": "备注",
332
+ "hosts.col.actions": "操作",
333
+ "form.title.create": "添加 Windows 主机",
334
+ "form.title.edit": "编辑主机 {alias}",
335
+ "form.alias": "别名",
336
+ "form.aliasHint": "英文字母/数字/点/连字符/下划线",
337
+ "form.host": "主机名或 IP",
338
+ "form.port": "WinRM 端口",
339
+ "form.user": "用户名",
340
+ "form.userHint": "由原生 Node WinRM 客户端自动选择 NTLM/Negotiate 或 Basic;支持本地账户、DOMAIN\\user 或 user@domain",
341
+ "form.password": "密码",
342
+ "form.passwordHint": "仅保存在本机 ~/.dsh/dsh-winrm.json(0600),不显示在页面上",
343
+ "form.transport": "传输",
344
+ "form.transport.http": "HTTP (5985)",
345
+ "form.transport.https": "HTTPS (5986)",
346
+ "form.rejectUnauthorized": "接受自签名证书(HTTPS)",
347
+ "form.environment": "环境(development / production…)",
348
+ "form.tags": "标签(逗号分隔)",
349
+ "form.description": "备注",
350
+ "form.save": "保存",
351
+ "form.cancel": "取消",
352
+ "form.error": "错误:{error}",
353
+ "form.passwordPlaceholder": "(保持不变)",
354
+ "console.select": "选择主机开始控制台",
355
+ "console.connecting": "正在连接 {alias}…",
356
+ "console.connected": "已连接 {alias} — 输入 PowerShell 命令,回车执行;输入 exit 退出",
357
+ "console.disconnected": "连接已关闭",
358
+ "console.disconnect": "断开",
359
+ "console.inputPlaceholder": "PowerShell 命令…",
360
+ "console.send": "发送",
361
+ "console.utf8Note": "注意:控制台经 WinRM 传输,中文输出可能乱码;需要精确中文输出请用「exec」或文件传输",
362
+ "services.select": "选择主机查看服务",
363
+ "services.list": "服务列表",
364
+ "services.name": "服务名",
365
+ "services.displayName": "显示名",
366
+ "services.status": "状态",
367
+ "services.startMode": "启动类型",
368
+ "services.startName": "运行账户",
369
+ "services.start": "启动",
370
+ "services.stop": "停止",
371
+ "services.restart": "重启",
372
+ "services.auto": "自动",
373
+ "services.manual": "手动",
374
+ "services.disabled": "禁用",
375
+ "services.actionOk": "操作成功:{name} → {status}",
376
+ "services.actionFail": "操作失败:{error}",
377
+ "services.filter": "筛选…",
378
+ "processes.select": "选择主机查看进程",
379
+ "processes.id": "PID",
380
+ "processes.name": "名称",
381
+ "processes.cpu": "CPU",
382
+ "processes.memMB": "内存(MB)",
383
+ "processes.startTime": "启动时间",
384
+ "processes.path": "路径",
385
+ "processes.kill": "结束",
386
+ "processes.killConfirm": "确认结束进程 {name} (PID {id})?",
387
+ "processes.killed": "已结束进程 PID {id}",
388
+ "processes.killFail": "结束失败:{error}",
389
+ "processes.filter": "筛选…",
390
+ "transfer.select": "选择主机浏览文件",
391
+ "transfer.path": "路径",
392
+ "transfer.go": "打开",
393
+ "transfer.up": "上级",
394
+ "transfer.root": "根目录",
395
+ "transfer.name": "名称",
396
+ "transfer.type": "类型",
397
+ "transfer.size": "大小",
398
+ "transfer.modified": "修改时间",
399
+ "transfer.download": "下载",
400
+ "transfer.upload": "上传文件",
401
+ "transfer.uploading": "上传中…",
402
+ "transfer.downloading": "下载中…",
403
+ "transfer.uploaded": "已上传 {bytes} 字节",
404
+ "transfer.downloaded": "已下载 {bytes} 字节",
405
+ "transfer.fail": "传输失败:{error}",
406
+ "transfer.bytes": "B",
407
+ "transfer.kib": "KB",
408
+ "transfer.mib": "MB",
409
+ "transfer.dir": "目录",
410
+ "transfer.file": "文件"
411
+ };
412
+ const en = {
413
+ "entry.label": "Windows",
414
+ "entry.tooltip": "Windows remote administration (WinRM)",
415
+ "panel.title": "Windows Remote",
416
+ "panel.backToConversation": "Back to chat",
417
+ "tab.hosts": "Hosts",
418
+ "tab.console": "Console",
419
+ "tab.services": "Services",
420
+ "tab.processes": "Processes",
421
+ "tab.transfer": "Transfer",
422
+ "common.loading": "Loading…",
423
+ "common.error": "Error: {error}",
424
+ "common.refresh": "Refresh",
425
+ "hosts.search": "Search hosts…",
426
+ "hosts.add": "Add host",
427
+ "hosts.test": "Test",
428
+ "hosts.testing": "Testing…",
429
+ "hosts.edit": "Edit",
430
+ "hosts.delete": "Delete",
431
+ "hosts.deleteConfirm": "Delete host {alias}?",
432
+ "hosts.connect": "Console",
433
+ "hosts.testOk": "connected ({latency} ms)",
434
+ "hosts.testFail": "failed: {error}",
435
+ "hosts.empty": "No Windows hosts configured yet — click \"Add host\" to start.",
436
+ "hosts.col.alias": "Alias",
437
+ "hosts.col.host": "Host",
438
+ "hosts.col.user": "User",
439
+ "hosts.col.auth": "Auth",
440
+ "hosts.col.transport": "Transport",
441
+ "hosts.col.environment": "Environment",
442
+ "hosts.col.tags": "Tags",
443
+ "hosts.col.description": "Description",
444
+ "hosts.col.actions": "Actions",
445
+ "form.title.create": "Add Windows host",
446
+ "form.title.edit": "Edit host {alias}",
447
+ "form.alias": "Alias",
448
+ "form.aliasHint": "letters, digits, dots, hyphens or underscores",
449
+ "form.host": "Hostname or IP",
450
+ "form.port": "WinRM port",
451
+ "form.user": "Username",
452
+ "form.userHint": "Native Node WinRM client auto-selects NTLM/Negotiate or Basic; supports local, DOMAIN\\user, or user@domain accounts",
453
+ "form.password": "Password",
454
+ "form.passwordHint": "Stored locally only in ~/.dsh/dsh-winrm.json (0600), never shown here",
455
+ "form.transport": "Transport",
456
+ "form.transport.http": "HTTP (5985)",
457
+ "form.transport.https": "HTTPS (5986)",
458
+ "form.rejectUnauthorized": "Accept self-signed certificates (HTTPS)",
459
+ "form.environment": "Environment (development / production…)",
460
+ "form.tags": "Tags (comma separated)",
461
+ "form.description": "Description",
462
+ "form.save": "Save",
463
+ "form.cancel": "Cancel",
464
+ "form.error": "Error: {error}",
465
+ "form.passwordPlaceholder": "(unchanged)",
466
+ "console.select": "Pick a host to open the console",
467
+ "console.connecting": "Connecting to {alias}…",
468
+ "console.connected": "Connected to {alias} — type a PowerShell command and press Enter; type exit to quit",
469
+ "console.disconnected": "Connection closed",
470
+ "console.disconnect": "Disconnect",
471
+ "console.inputPlaceholder": "PowerShell command…",
472
+ "console.send": "Send",
473
+ "console.utf8Note": "Note: the console travels over WinRM and non-ASCII output may garble; use exec or file transfer for exact UTF-8 content",
474
+ "services.select": "Pick a host to view services",
475
+ "services.list": "Services",
476
+ "services.name": "Name",
477
+ "services.displayName": "Display name",
478
+ "services.status": "Status",
479
+ "services.startMode": "Start mode",
480
+ "services.startName": "Run as",
481
+ "services.start": "Start",
482
+ "services.stop": "Stop",
483
+ "services.restart": "Restart",
484
+ "services.auto": "Auto",
485
+ "services.manual": "Manual",
486
+ "services.disabled": "Disabled",
487
+ "services.actionOk": "OK: {name} → {status}",
488
+ "services.actionFail": "Failed: {error}",
489
+ "services.filter": "Filter…",
490
+ "processes.select": "Pick a host to view processes",
491
+ "processes.id": "PID",
492
+ "processes.name": "Name",
493
+ "processes.cpu": "CPU",
494
+ "processes.memMB": "Mem(MB)",
495
+ "processes.startTime": "Started",
496
+ "processes.path": "Path",
497
+ "processes.kill": "Kill",
498
+ "processes.killConfirm": "Kill process {name} (PID {id})?",
499
+ "processes.killed": "Killed PID {id}",
500
+ "processes.killFail": "Failed: {error}",
501
+ "processes.filter": "Filter…",
502
+ "transfer.select": "Pick a host to browse files",
503
+ "transfer.path": "Path",
504
+ "transfer.go": "Open",
505
+ "transfer.up": "Up",
506
+ "transfer.root": "Root",
507
+ "transfer.name": "Name",
508
+ "transfer.type": "Type",
509
+ "transfer.size": "Size",
510
+ "transfer.modified": "Modified",
511
+ "transfer.download": "Download",
512
+ "transfer.upload": "Upload file",
513
+ "transfer.uploading": "Uploading…",
514
+ "transfer.downloading": "Downloading…",
515
+ "transfer.uploaded": "Uploaded {bytes} bytes",
516
+ "transfer.downloaded": "Downloaded {bytes} bytes",
517
+ "transfer.fail": "Transfer failed: {error}",
518
+ "transfer.bytes": "B",
519
+ "transfer.kib": "KB",
520
+ "transfer.mib": "MB",
521
+ "transfer.dir": "dir",
522
+ "transfer.file": "file"
523
+ };
524
+ /** Translate a key in one dictionary with optional {name} params. */
525
+ function t(dict, key, values) {
526
+ let text = dict[key] ?? key;
527
+ if (values !== void 0) for (const [name, value] of Object.entries(values)) text = text.split("{" + name + "}").join(String(value));
528
+ return text;
529
+ }
530
+ //#endregion
531
+ //#region src/client/panel/helpers.ts
532
+ /**
533
+ * Shared panel helpers: the active-dictionary pick (document-language based)
534
+ * bound to the dsh-winrm interpolator in locales.ts, plus a small
535
+ * error-message extractor.
536
+ */
537
+ /** Active dictionary, picked by the document language at call time. */
538
+ function dictionary() {
539
+ return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
540
+ }
541
+ /** Translate a key with optional {name} template params (current language). */
542
+ function tt(key, values) {
543
+ return t(dictionary(), key, values);
544
+ }
545
+ /** Human-readable error text from an unknown thrown value. */
546
+ function errorMessage(error) {
547
+ if (error instanceof Error) return error.message;
548
+ return String(error);
549
+ }
550
+ /** Format a byte count into B/KB/MB. */
551
+ function formatBytes(bytes) {
552
+ if (bytes >= 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + " MB";
553
+ if (bytes >= 1024) return (bytes / 1024).toFixed(1) + " KB";
554
+ return bytes + " B";
555
+ }
556
+ //#endregion
557
+ //#region \0dsh-css:D:\GITHUB\dsh-winrm\src\client\panel\panel.module.css.mjs
558
+ const css = ".Z2pyhW_entry{width:100%;height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}.Z2pyhW_entry:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}.Z2pyhW_entry[data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}.Z2pyhW_entryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex}.Z2pyhW_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .Z2pyhW_entry{justify-content:center;width:100%;padding:0}[data-dsh-frame][data-sidebar-collapsed] .Z2pyhW_entryLabel{display:none}.Z2pyhW_panel{background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;padding:14px 16px 16px;display:flex}.Z2pyhW_panelHeader{flex:none;align-items:center;gap:10px;display:flex}.Z2pyhW_panelTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;flex:1;margin:0;font-size:16px;font-weight:700}.Z2pyhW_backButton{align-items:center;gap:4px;display:inline-flex}.Z2pyhW_tabBar{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}.Z2pyhW_tab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}.Z2pyhW_tab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.Z2pyhW_tab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}.Z2pyhW_panelContent{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.Z2pyhW_tabBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow-y:auto}.Z2pyhW_fillBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow:hidden}.Z2pyhW_toolbar,.Z2pyhW_controls{flex-wrap:wrap;flex:none;align-items:center;gap:8px;display:flex}.Z2pyhW_controls .Z2pyhW_input{flex:0 260px;min-width:150px}.Z2pyhW_toolbarSpacer{flex:1}.Z2pyhW_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}.Z2pyhW_search::placeholder{color:var(--dsw-alias-label-tertiary)}.Z2pyhW_groupBySelect{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;padding:6px 8px;font-size:13px}.Z2pyhW_groupSection+.Z2pyhW_groupSection{border-top:1px solid var(--dsw-alias-border-l1)}.Z2pyhW_groupHeader{background:var(--dsw-alias-bg-base,var(--dsw-specific-input-major));align-items:center;gap:8px;padding:4px 8px;display:flex;position:sticky;top:0}.Z2pyhW_groupToggle{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:6px;flex:1;align-items:center;gap:6px;padding:4px 6px;font-size:12.5px;font-weight:500;display:flex}.Z2pyhW_groupToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.Z2pyhW_groupChevron{border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);flex:none;width:7px;height:7px;margin-bottom:2px;transition:transform .12s;transform:rotate(45deg)}.Z2pyhW_groupChevron[data-collapsed]{margin-bottom:0;transform:rotate(-45deg)}.Z2pyhW_groupName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.Z2pyhW_groupCount{color:var(--dsw-alias-label-tertiary);flex:none;font-weight:400}.Z2pyhW_tableWrap{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex:1;min-height:0;overflow:auto}.Z2pyhW_table{border-collapse:collapse;width:100%;font-size:12.5px}.Z2pyhW_table th{z-index:1;text-align:left;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);white-space:nowrap;padding:8px 10px;font-weight:600;position:sticky;top:0}.Z2pyhW_table td{border-bottom:1px solid var(--dsw-alias-separator-primary);vertical-align:top;padding:7px 10px}.Z2pyhW_table tbody tr:last-child td{border-bottom:none}.Z2pyhW_table tbody tr:hover td{background:var(--dsw-alias-interactive-bg-hover)}.Z2pyhW_mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.Z2pyhW_cellMuted{color:var(--dsw-alias-label-tertiary)}.Z2pyhW_actions{white-space:nowrap;align-items:center;gap:8px;display:flex}.Z2pyhW_inlineTest{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:11.5px;display:inline-flex}.Z2pyhW_inlineTest[data-status=ok]{color:var(--dsw-alias-state-success-primary)}.Z2pyhW_inlineTest[data-status=fail]{color:var(--dsw-alias-state-error-primary)}.Z2pyhW_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}.Z2pyhW_badge[data-kind=key]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.Z2pyhW_badge[data-kind=password]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.Z2pyhW_badge[data-status=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.Z2pyhW_badge[data-status=fail]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.Z2pyhW_badge[data-status=timeout]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.Z2pyhW_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}.Z2pyhW_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}.Z2pyhW_primaryButton:disabled{opacity:.5;cursor:default}.Z2pyhW_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}.Z2pyhW_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.Z2pyhW_ghostButton:disabled{opacity:.45;cursor:default}.Z2pyhW_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}.Z2pyhW_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.Z2pyhW_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}.Z2pyhW_linkButton:hover:not(:disabled){text-decoration:underline}.Z2pyhW_linkButton:disabled{opacity:.45;cursor:default}.Z2pyhW_linkButton[data-danger]{color:var(--dsw-alias-state-error-primary)}.Z2pyhW_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite Z2pyhW_dshSshSpin;display:inline-block}@keyframes Z2pyhW_dshSshSpin{to{transform:rotate(360deg)}}.Z2pyhW_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}.Z2pyhW_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.Z2pyhW_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.Z2pyhW_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.Z2pyhW_empty,.Z2pyhW_loading{text-align:center;color:var(--dsw-alias-label-tertiary);padding:28px 12px;font-size:12.5px}.Z2pyhW_modalBackdrop{z-index:40;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.Z2pyhW_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(560px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}.Z2pyhW_modalTitle{margin:0;font-size:15px;font-weight:700}.Z2pyhW_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}.Z2pyhW_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}.Z2pyhW_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px}.Z2pyhW_field{flex-direction:column;gap:5px;display:flex}.Z2pyhW_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.Z2pyhW_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}.Z2pyhW_input:focus{border-color:var(--dsw-alias-state-business-primary)}.Z2pyhW_input::placeholder{color:var(--dsw-alias-label-tertiary)}.Z2pyhW_input:disabled{opacity:.55}.Z2pyhW_formRow{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;display:grid}.Z2pyhW_radioRow{align-items:center;gap:16px;display:flex}.Z2pyhW_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}.Z2pyhW_termBody{flex-direction:column;flex:1;gap:8px;min-height:0;display:flex;overflow:hidden}.Z2pyhW_termWrap{border:1px solid var(--dsw-alias-border-l1);background:#0b0e14;border-radius:10px;flex:1;min-height:0;position:relative;overflow:hidden}.Z2pyhW_termContainer{position:absolute;inset:8px 10px}.Z2pyhW_termPlaceholder{z-index:2;text-align:center;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-base);justify-content:center;align-items:center;padding:0 24px;font-size:12.5px;display:flex;position:absolute;inset:0}.Z2pyhW_hiddenFile{display:none}.Z2pyhW_browsePanel{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;display:flex;overflow:hidden}.Z2pyhW_browseHeader{background:var(--dsw-alias-bg-layer-2);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:8px 10px;display:flex}.Z2pyhW_browsePath{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow:hidden}.Z2pyhW_browseList{max-height:240px;overflow-y:auto}.Z2pyhW_dirRow{text-align:left;width:100%;font:inherit;color:var(--dsw-alias-label-primary);border:none;border-bottom:1px solid var(--dsw-alias-separator-primary);cursor:pointer;background:0 0;grid-template-columns:minmax(0,1fr) auto auto;gap:12px;padding:5px 10px;font-size:12.5px;display:grid}.Z2pyhW_dirRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.Z2pyhW_dirRow[data-type=dir] .Z2pyhW_dirName{color:var(--dsw-alias-state-business-primary)}.Z2pyhW_dirRow[data-up]{color:var(--dsw-alias-label-secondary)}.Z2pyhW_dirName{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:hidden}.Z2pyhW_dirType{color:var(--dsw-alias-label-tertiary);white-space:nowrap}.Z2pyhW_dirSize{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-align:right}.Z2pyhW_transferBlock{flex-direction:column;flex:none;gap:6px;display:flex}.Z2pyhW_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}.Z2pyhW_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}.Z2pyhW_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}.Z2pyhW_tunnelList{flex-direction:column;flex:none;gap:8px;display:flex}.Z2pyhW_tunnelRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;align-items:center;gap:10px;padding:8px 12px;display:flex}.Z2pyhW_tunnelRow[data-state=forwarding]{border-color:var(--dsw-alias-state-success-primary)}.Z2pyhW_tunnelRow[data-state=connecting]{border-color:var(--dsw-alias-state-warn-primary)}.Z2pyhW_tunnelRow[data-state=failed]{border-color:var(--dsw-alias-state-error-primary)}.Z2pyhW_tunnelLabel{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;overflow:hidden}.Z2pyhW_formCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}.Z2pyhW_clusterForm{flex-direction:column;flex:none;gap:8px;display:flex}.Z2pyhW_clusterFilters{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;display:grid}.Z2pyhW_commandInput{min-height:96px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.Z2pyhW_cellDetails summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px}.Z2pyhW_cellPre{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;border-radius:6px;max-height:180px;margin:4px 0 0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:auto}.Z2pyhW_consoleWrap{flex-direction:column;flex:1;gap:8px;min-height:0;display:flex}.Z2pyhW_consoleHeader{flex:none;align-items:center;gap:8px;display:flex}.Z2pyhW_consoleOutput{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;min-height:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;margin:0;padding:8px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;line-height:1.45;overflow:auto}.Z2pyhW_consoleInputRow{flex:none;align-items:center;gap:8px;display:flex}.Z2pyhW_consoleInputRow .Z2pyhW_input{flex:1}.Z2pyhW_utf8Note{color:var(--dsw-alias-label-tertiary);flex:none;font-size:11.5px}.Z2pyhW_checkRow{align-items:center;gap:6px;font-size:13px;display:flex}.Z2pyhW_dialogOverlay{z-index:120;background:#0006;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.Z2pyhW_dialog{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;gap:10px;width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);padding:16px;display:flex;overflow-y:auto}.Z2pyhW_dialogTitle{margin:0;font-size:15px;font-weight:700}.Z2pyhW_field{flex-direction:column;gap:4px;font-size:13px;display:flex}.Z2pyhW_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px}.Z2pyhW_fieldHint{color:var(--dsw-alias-label-tertiary);font-size:11.5px}.Z2pyhW_formRow{align-items:flex-start;gap:10px;display:flex}.Z2pyhW_formRow .Z2pyhW_field{flex:1}.Z2pyhW_formError{color:var(--dsw-alias-state-error-primary);font-size:12.5px}.Z2pyhW_badge[data-kind=ok]{color:var(--dsw-alias-state-success-primary)}.Z2pyhW_badge[data-kind=muted]{color:var(--dsw-alias-label-secondary)}.Z2pyhW_badge[data-kind=password]{color:var(--dsw-alias-state-info-primary)}";
559
+ const tagId = "dsh-winrm/src/client/panel/panel.module.css";
560
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
561
+ const tag = document.createElement("style");
562
+ tag.dataset.plugin = "dsh-winrm";
563
+ tag.dataset.pluginCss = tagId;
564
+ tag.textContent = css;
565
+ document.head.appendChild(tag);
566
+ }
567
+ var panel_module_css_default = {
568
+ "groupHeader": "Z2pyhW_groupHeader",
569
+ "field": "Z2pyhW_field",
570
+ "transferBlock": "Z2pyhW_transferBlock",
571
+ "progressMeta": "Z2pyhW_progressMeta",
572
+ "dialog": "Z2pyhW_dialog",
573
+ "controls": "Z2pyhW_controls",
574
+ "formRow": "Z2pyhW_formRow",
575
+ "radioLabel": "Z2pyhW_radioLabel",
576
+ "utf8Note": "Z2pyhW_utf8Note",
577
+ "cellMuted": "Z2pyhW_cellMuted",
578
+ "mono": "Z2pyhW_mono",
579
+ "termContainer": "Z2pyhW_termContainer",
580
+ "tabBar": "Z2pyhW_tabBar",
581
+ "hiddenFile": "Z2pyhW_hiddenFile",
582
+ "entryLabel": "Z2pyhW_entryLabel",
583
+ "inlineTest": "Z2pyhW_inlineTest",
584
+ "badge": "Z2pyhW_badge",
585
+ "panelHeader": "Z2pyhW_panelHeader",
586
+ "browseList": "Z2pyhW_browseList",
587
+ "commandInput": "Z2pyhW_commandInput",
588
+ "tab": "Z2pyhW_tab",
589
+ "groupToggle": "Z2pyhW_groupToggle",
590
+ "table": "Z2pyhW_table",
591
+ "panel": "Z2pyhW_panel",
592
+ "ghostButton": "Z2pyhW_ghostButton",
593
+ "dirSize": "Z2pyhW_dirSize",
594
+ "banner": "Z2pyhW_banner",
595
+ "formCard": "Z2pyhW_formCard",
596
+ "dialogTitle": "Z2pyhW_dialogTitle",
597
+ "termWrap": "Z2pyhW_termWrap",
598
+ "loading": "Z2pyhW_loading",
599
+ "modalFooter": "Z2pyhW_modalFooter",
600
+ "clusterFilters": "Z2pyhW_clusterFilters",
601
+ "groupBySelect": "Z2pyhW_groupBySelect",
602
+ "dirRow": "Z2pyhW_dirRow",
603
+ "entry": "Z2pyhW_entry",
604
+ "tunnelRow": "Z2pyhW_tunnelRow",
605
+ "search": "Z2pyhW_search",
606
+ "checkRow": "Z2pyhW_checkRow",
607
+ "hint": "Z2pyhW_hint",
608
+ "groupCount": "Z2pyhW_groupCount",
609
+ "groupName": "Z2pyhW_groupName",
610
+ "dshSshSpin": "Z2pyhW_dshSshSpin",
611
+ "groupSection": "Z2pyhW_groupSection",
612
+ "cellPre": "Z2pyhW_cellPre",
613
+ "iconButton": "Z2pyhW_iconButton",
614
+ "linkButton": "Z2pyhW_linkButton",
615
+ "fieldLabel": "Z2pyhW_fieldLabel",
616
+ "tableWrap": "Z2pyhW_tableWrap",
617
+ "panelTitle": "Z2pyhW_panelTitle",
618
+ "panelContent": "Z2pyhW_panelContent",
619
+ "browsePath": "Z2pyhW_browsePath",
620
+ "spinner": "Z2pyhW_spinner",
621
+ "dirName": "Z2pyhW_dirName",
622
+ "clusterForm": "Z2pyhW_clusterForm",
623
+ "tunnelList": "Z2pyhW_tunnelList",
624
+ "formError": "Z2pyhW_formError",
625
+ "modalBackdrop": "Z2pyhW_modalBackdrop",
626
+ "radioRow": "Z2pyhW_radioRow",
627
+ "cellDetails": "Z2pyhW_cellDetails",
628
+ "consoleInputRow": "Z2pyhW_consoleInputRow",
629
+ "termBody": "Z2pyhW_termBody",
630
+ "consoleOutput": "Z2pyhW_consoleOutput",
631
+ "toolbarSpacer": "Z2pyhW_toolbarSpacer",
632
+ "dirType": "Z2pyhW_dirType",
633
+ "fieldHint": "Z2pyhW_fieldHint",
634
+ "tabBody": "Z2pyhW_tabBody",
635
+ "groupChevron": "Z2pyhW_groupChevron",
636
+ "modal": "Z2pyhW_modal",
637
+ "dialogOverlay": "Z2pyhW_dialogOverlay",
638
+ "termPlaceholder": "Z2pyhW_termPlaceholder",
639
+ "input": "Z2pyhW_input",
640
+ "fillBody": "Z2pyhW_fillBody",
641
+ "consoleHeader": "Z2pyhW_consoleHeader",
642
+ "backButton": "Z2pyhW_backButton",
643
+ "entryIcon": "Z2pyhW_entryIcon",
644
+ "browsePanel": "Z2pyhW_browsePanel",
645
+ "progressBar": "Z2pyhW_progressBar",
646
+ "progressTrack": "Z2pyhW_progressTrack",
647
+ "browseHeader": "Z2pyhW_browseHeader",
648
+ "toolbar": "Z2pyhW_toolbar",
649
+ "consoleWrap": "Z2pyhW_consoleWrap",
650
+ "empty": "Z2pyhW_empty",
651
+ "modalTitle": "Z2pyhW_modalTitle",
652
+ "actions": "Z2pyhW_actions",
653
+ "primaryButton": "Z2pyhW_primaryButton",
654
+ "tunnelLabel": "Z2pyhW_tunnelLabel"
655
+ };
656
+ //#endregion
657
+ //#region src/client/panel/ConsoleTab.tsx
658
+ /**
659
+ * Console tab: a streaming PowerShell console over the /api/dsh-winrm
660
+ * WebSocket. Output renders into a monospace pre; commands are sent as stdin
661
+ * lines. Non-ASCII output may garble (transport decodes as ASCII) — the
662
+ * exec route/tool carries the UTF-8 envelope instead.
663
+ */
664
+ /** The streaming PowerShell console. */
665
+ function ConsoleTab({ api, presetAlias, requestId }) {
666
+ const [aliases, setAliases] = (0, react.useState)([]);
667
+ const [session, setSession] = (0, react.useState)(null);
668
+ const [input, setInput] = (0, react.useState)("");
669
+ const [busy, setBusy] = (0, react.useState)(false);
670
+ const connRef = (0, react.useRef)(null);
671
+ const linesRef = (0, react.useRef)([]);
672
+ const bottomRef = (0, react.useRef)(null);
673
+ (0, react.useEffect)(() => {
674
+ api.listHosts().then((list) => setAliases(list.map((h) => h.alias))).catch(() => {});
675
+ }, [api]);
676
+ const pushLines = (text) => {
677
+ linesRef.current = [...linesRef.current, ...text.split("\n")].slice(-5e3);
678
+ setSession((prev) => prev === null ? prev : {
679
+ ...prev,
680
+ lines: [...linesRef.current]
681
+ });
682
+ };
683
+ (0, react.useEffect)(() => {
684
+ if (presetAlias !== void 0 && presetAlias !== "" && requestId !== void 0) connect(presetAlias);
685
+ }, [presetAlias, requestId]);
686
+ (0, react.useEffect)(() => {
687
+ bottomRef.current?.scrollIntoView({ block: "end" });
688
+ }, [session?.lines]);
689
+ (0, react.useEffect)(() => () => {
690
+ connRef.current?.close();
691
+ connRef.current = null;
692
+ }, []);
693
+ const connect = (alias) => {
694
+ connRef.current?.close();
695
+ linesRef.current = [];
696
+ setSession({
697
+ alias,
698
+ lines: [],
699
+ connected: false
700
+ });
701
+ setBusy(true);
702
+ const connection = api.openConsole(alias);
703
+ connRef.current = connection;
704
+ connection.onReady = () => {
705
+ setBusy(false);
706
+ setSession((prev) => prev === null ? prev : {
707
+ ...prev,
708
+ connected: true
709
+ });
710
+ pushLines(tt("console.connected", { alias }));
711
+ };
712
+ connection.onOutput = (data) => {
713
+ pushLines(data);
714
+ };
715
+ connection.onExit = (code, error) => {
716
+ connRef.current = null;
717
+ setBusy(false);
718
+ setSession((prev) => prev === null ? prev : {
719
+ ...prev,
720
+ connected: false,
721
+ error: error !== void 0 ? errorMessage(error) : tt("console.disconnected")
722
+ });
723
+ };
724
+ };
725
+ const disconnect = () => {
726
+ connRef.current?.close();
727
+ connRef.current = null;
728
+ setSession((prev) => prev === null ? prev : {
729
+ ...prev,
730
+ connected: false,
731
+ error: tt("console.disconnected")
732
+ });
733
+ };
734
+ const send = () => {
735
+ const command = input;
736
+ if (command.trim() === "" || connRef.current === null) return;
737
+ connRef.current?.send(command + "\r\n");
738
+ setInput("");
739
+ };
740
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
741
+ className: panel_module_css_default.fillBody,
742
+ children: [session === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
743
+ className: panel_module_css_default.controls,
744
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
745
+ className: panel_module_css_default.input,
746
+ value: "",
747
+ onChange: (event) => {
748
+ if (event.target.value !== "") connect(event.target.value);
749
+ },
750
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
751
+ value: "",
752
+ children: tt("console.select")
753
+ }), aliases.map((alias) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
754
+ value: alias,
755
+ children: alias
756
+ }, alias))]
757
+ })
758
+ }), session !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
759
+ className: panel_module_css_default.consoleWrap,
760
+ children: [
761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
762
+ className: panel_module_css_default.consoleHeader,
763
+ children: [
764
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
765
+ className: panel_module_css_default.mono,
766
+ children: session.alias
767
+ }),
768
+ session.connected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
769
+ className: panel_module_css_default.badge,
770
+ "data-kind": "ok",
771
+ children: tt("console.connected", { alias: session.alias }).split("—")[0]
772
+ }),
773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.toolbarSpacer }),
774
+ busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
775
+ className: panel_module_css_default.loading,
776
+ children: tt("console.connecting", { alias: session.alias })
777
+ }),
778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
779
+ type: "button",
780
+ className: panel_module_css_default.ghostButton,
781
+ onClick: disconnect,
782
+ children: tt("console.disconnect")
783
+ })
784
+ ]
785
+ }),
786
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("pre", {
787
+ className: panel_module_css_default.consoleOutput,
788
+ children: [session.lines.join("\n"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ref: bottomRef })]
789
+ }),
790
+ session.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
791
+ className: panel_module_css_default.banner,
792
+ "data-kind": "error",
793
+ children: session.error
794
+ }),
795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
796
+ className: panel_module_css_default.consoleInputRow,
797
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
798
+ className: panel_module_css_default.input,
799
+ value: input,
800
+ placeholder: tt("console.inputPlaceholder"),
801
+ disabled: !session.connected,
802
+ onChange: (event) => {
803
+ setInput(event.target.value);
804
+ },
805
+ onKeyDown: (event) => {
806
+ if (event.key === "Enter") send();
807
+ }
808
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
809
+ type: "button",
810
+ className: panel_module_css_default.primaryButton,
811
+ disabled: !session.connected,
812
+ onClick: send,
813
+ children: tt("console.send")
814
+ })]
815
+ }),
816
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
817
+ className: panel_module_css_default.utf8Note,
818
+ children: tt("console.utf8Note")
819
+ })
820
+ ]
821
+ })]
822
+ });
823
+ }
824
+ //#endregion
825
+ //#region src/client/panel/HostFormDialog.tsx
826
+ /**
827
+ * Host form dialog: create/edit one Windows host entry. Passwords are never
828
+ * returned by the API — the edit form shows a placeholder and keeps the
829
+ * stored secret unless a new one is typed.
830
+ */
831
+ /** The create/edit host dialog. */
832
+ function HostFormDialog({ api, editing, onClose, onSaved }) {
833
+ const [alias, setAlias] = (0, react.useState)(editing?.alias ?? "");
834
+ const [host, setHost] = (0, react.useState)(editing?.host ?? "");
835
+ const [port, setPort] = (0, react.useState)(String(editing?.port ?? 5985));
836
+ const [user, setUser] = (0, react.useState)(editing?.user ?? "");
837
+ const [password, setPassword] = (0, react.useState)("");
838
+ const [transport, setTransport] = (0, react.useState)(editing?.transport ?? "http");
839
+ const [rejectUnauthorized, setRejectUnauthorized] = (0, react.useState)(editing?.rejectUnauthorized ?? false);
840
+ const [environment, setEnvironment] = (0, react.useState)(editing?.environment ?? "");
841
+ const [tags, setTags] = (0, react.useState)(editing?.tags.join(", ") ?? "");
842
+ const [description, setDescription] = (0, react.useState)(editing?.description ?? "");
843
+ const [error, setError] = (0, react.useState)(null);
844
+ const [saving, setSaving] = (0, react.useState)(false);
845
+ (0, react.useEffect)(() => {
846
+ const onKey = (event) => {
847
+ if (event.key === "Escape") onClose();
848
+ };
849
+ window.addEventListener("keydown", onKey);
850
+ return () => {
851
+ window.removeEventListener("keydown", onKey);
852
+ };
853
+ }, [onClose]);
854
+ const submit = async () => {
855
+ setSaving(true);
856
+ setError(null);
857
+ const payload = {
858
+ host: host.trim(),
859
+ port: Number.parseInt(port, 10) || 5985,
860
+ user: user.trim(),
861
+ transport,
862
+ rejectUnauthorized,
863
+ environment: environment.trim() || void 0,
864
+ tags: tags.split(",").map((tag) => tag.trim()).filter((tag) => tag !== ""),
865
+ description: description.trim() || void 0
866
+ };
867
+ if (password !== "") payload.auth = {
868
+ kind: "password",
869
+ password
870
+ };
871
+ try {
872
+ if (editing === null) {
873
+ payload.alias = alias.trim();
874
+ await api.createHost(payload);
875
+ } else await api.updateHost(editing.alias, payload);
876
+ onSaved();
877
+ } catch (cause) {
878
+ setError(errorMessage(cause));
879
+ setSaving(false);
880
+ }
881
+ };
882
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
883
+ className: panel_module_css_default.dialogOverlay,
884
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
885
+ className: panel_module_css_default.dialog,
886
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
887
+ className: panel_module_css_default.dialogTitle,
888
+ children: editing === null ? tt("form.title.create") : tt("form.title.edit", { alias: editing.alias })
889
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
890
+ className: panel_module_css_default.formCard,
891
+ children: [
892
+ editing === null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
893
+ className: panel_module_css_default.field,
894
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
895
+ className: panel_module_css_default.fieldLabel,
896
+ children: tt("form.alias")
897
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
898
+ className: panel_module_css_default.input,
899
+ value: alias,
900
+ onChange: (event) => {
901
+ setAlias(event.target.value);
902
+ },
903
+ placeholder: tt("form.aliasHint")
904
+ })]
905
+ }),
906
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
907
+ className: panel_module_css_default.field,
908
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
909
+ className: panel_module_css_default.fieldLabel,
910
+ children: tt("form.host")
911
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
912
+ className: panel_module_css_default.input,
913
+ value: host,
914
+ onChange: (event) => {
915
+ setHost(event.target.value);
916
+ }
917
+ })]
918
+ }),
919
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
920
+ className: panel_module_css_default.formRow,
921
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
922
+ className: panel_module_css_default.field,
923
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
924
+ className: panel_module_css_default.fieldLabel,
925
+ children: tt("form.port")
926
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
927
+ className: panel_module_css_default.input,
928
+ type: "number",
929
+ value: port,
930
+ onChange: (event) => {
931
+ setPort(event.target.value);
932
+ }
933
+ })]
934
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
935
+ className: panel_module_css_default.field,
936
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
937
+ className: panel_module_css_default.fieldLabel,
938
+ children: tt("form.transport")
939
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
940
+ className: panel_module_css_default.input,
941
+ value: transport,
942
+ onChange: (event) => {
943
+ const next = event.target.value;
944
+ setTransport(next);
945
+ if (port === "5985" || port === "5986") setPort(next === "https" ? "5986" : "5985");
946
+ },
947
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
948
+ value: "http",
949
+ children: tt("form.transport.http")
950
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
951
+ value: "https",
952
+ children: tt("form.transport.https")
953
+ })]
954
+ })]
955
+ })]
956
+ }),
957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
958
+ className: panel_module_css_default.field,
959
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
960
+ className: panel_module_css_default.fieldLabel,
961
+ children: tt("form.user")
962
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
963
+ className: panel_module_css_default.input,
964
+ value: user,
965
+ onChange: (event) => {
966
+ setUser(event.target.value);
967
+ },
968
+ placeholder: tt("form.userHint")
969
+ })]
970
+ }),
971
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
972
+ className: panel_module_css_default.field,
973
+ children: [
974
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
975
+ className: panel_module_css_default.fieldLabel,
976
+ children: tt("form.password")
977
+ }),
978
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
979
+ className: panel_module_css_default.input,
980
+ type: "password",
981
+ value: password,
982
+ onChange: (event) => {
983
+ setPassword(event.target.value);
984
+ },
985
+ placeholder: editing !== null ? tt("form.passwordPlaceholder") : void 0
986
+ }),
987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
988
+ className: panel_module_css_default.fieldHint,
989
+ children: tt("form.passwordHint")
990
+ })
991
+ ]
992
+ }),
993
+ transport === "https" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
994
+ className: panel_module_css_default.checkRow,
995
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
996
+ type: "checkbox",
997
+ checked: !rejectUnauthorized,
998
+ onChange: (event) => {
999
+ setRejectUnauthorized(!event.target.checked);
1000
+ }
1001
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("form.rejectUnauthorized") })]
1002
+ }),
1003
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1004
+ className: panel_module_css_default.field,
1005
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1006
+ className: panel_module_css_default.fieldLabel,
1007
+ children: tt("form.environment")
1008
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1009
+ className: panel_module_css_default.input,
1010
+ value: environment,
1011
+ onChange: (event) => {
1012
+ setEnvironment(event.target.value);
1013
+ }
1014
+ })]
1015
+ }),
1016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1017
+ className: panel_module_css_default.field,
1018
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1019
+ className: panel_module_css_default.fieldLabel,
1020
+ children: tt("form.tags")
1021
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1022
+ className: panel_module_css_default.input,
1023
+ value: tags,
1024
+ onChange: (event) => {
1025
+ setTags(event.target.value);
1026
+ }
1027
+ })]
1028
+ }),
1029
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1030
+ className: panel_module_css_default.field,
1031
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1032
+ className: panel_module_css_default.fieldLabel,
1033
+ children: tt("form.description")
1034
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1035
+ className: panel_module_css_default.input,
1036
+ value: description,
1037
+ onChange: (event) => {
1038
+ setDescription(event.target.value);
1039
+ }
1040
+ })]
1041
+ }),
1042
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1043
+ className: panel_module_css_default.formError,
1044
+ children: tt("form.error", { error })
1045
+ }),
1046
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1047
+ className: panel_module_css_default.actions,
1048
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1049
+ type: "button",
1050
+ className: panel_module_css_default.ghostButton,
1051
+ onClick: onClose,
1052
+ children: tt("form.cancel")
1053
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1054
+ type: "button",
1055
+ className: panel_module_css_default.primaryButton,
1056
+ disabled: saving,
1057
+ onClick: () => {
1058
+ submit();
1059
+ },
1060
+ children: saving ? tt("common.loading") : tt("form.save")
1061
+ })]
1062
+ })
1063
+ ]
1064
+ })]
1065
+ })
1066
+ });
1067
+ }
1068
+ //#endregion
1069
+ //#region src/client/panel/HostsTab.tsx
1070
+ /**
1071
+ * Hosts tab: the host table with search (debounced through listHosts),
1072
+ * add/edit/delete/test actions, and a connect action that hands the alias to
1073
+ * the console tab via onConnect.
1074
+ */
1075
+ /** The hosts table plus its toolbar and dialogs. */
1076
+ function HostsTab({ api, onConnect }) {
1077
+ const [hosts, setHosts] = (0, react.useState)(null);
1078
+ const [error, setError] = (0, react.useState)(null);
1079
+ const [search, setSearch] = (0, react.useState)("");
1080
+ const [testingAlias, setTestingAlias] = (0, react.useState)(null);
1081
+ const [testResults, setTestResults] = (0, react.useState)({});
1082
+ const [dialog, setDialog] = (0, react.useState)(null);
1083
+ const seqRef = (0, react.useRef)(0);
1084
+ const mountedRef = (0, react.useRef)(true);
1085
+ (0, react.useEffect)(() => () => {
1086
+ mountedRef.current = false;
1087
+ }, []);
1088
+ const load = (0, react.useCallback)(async (query) => {
1089
+ const seq = ++seqRef.current;
1090
+ try {
1091
+ const list = await api.listHosts(query);
1092
+ if (!mountedRef.current || seq !== seqRef.current) return;
1093
+ setHosts(list);
1094
+ setError(null);
1095
+ } catch (cause) {
1096
+ if (!mountedRef.current || seq !== seqRef.current) return;
1097
+ setError(errorMessage(cause));
1098
+ }
1099
+ }, [api]);
1100
+ (0, react.useEffect)(() => {
1101
+ load();
1102
+ }, [load]);
1103
+ (0, react.useEffect)(() => {
1104
+ const timer = setTimeout(() => {
1105
+ const query = search.trim();
1106
+ load(query === "" ? void 0 : query);
1107
+ }, 300);
1108
+ return () => {
1109
+ clearTimeout(timer);
1110
+ };
1111
+ }, [search, load]);
1112
+ const runTest = async (alias) => {
1113
+ setTestingAlias(alias);
1114
+ try {
1115
+ const result = await api.testHost(alias);
1116
+ setTestResults((prev) => ({
1117
+ ...prev,
1118
+ [alias]: result
1119
+ }));
1120
+ } catch (cause) {
1121
+ setTestResults((prev) => ({
1122
+ ...prev,
1123
+ [alias]: {
1124
+ ok: false,
1125
+ error: errorMessage(cause)
1126
+ }
1127
+ }));
1128
+ } finally {
1129
+ setTestingAlias(null);
1130
+ }
1131
+ };
1132
+ const deleteHost = async (alias) => {
1133
+ if (!window.confirm(tt("hosts.deleteConfirm", { alias }))) return;
1134
+ try {
1135
+ await api.deleteHost(alias);
1136
+ load();
1137
+ } catch (cause) {
1138
+ setError(errorMessage(cause));
1139
+ }
1140
+ };
1141
+ const renderHostRow = (host) => {
1142
+ const test = testResults[host.alias];
1143
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1145
+ className: panel_module_css_default.mono,
1146
+ children: host.alias
1147
+ }),
1148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1149
+ className: panel_module_css_default.mono,
1150
+ children: [
1151
+ host.host,
1152
+ ":",
1153
+ host.port
1154
+ ]
1155
+ }),
1156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: host.user }),
1157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1158
+ className: panel_module_css_default.badge,
1159
+ "data-kind": "password",
1160
+ children: host.transport === "https" ? "HTTPS" : "HTTP"
1161
+ }) }),
1162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1163
+ className: panel_module_css_default.cellMuted,
1164
+ children: host.environment ?? ""
1165
+ }),
1166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1167
+ className: panel_module_css_default.cellMuted,
1168
+ children: host.tags.join(", ")
1169
+ }),
1170
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1171
+ className: panel_module_css_default.cellMuted,
1172
+ children: host.description ?? ""
1173
+ }),
1174
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1175
+ className: panel_module_css_default.actions,
1176
+ children: [
1177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1178
+ type: "button",
1179
+ className: panel_module_css_default.linkButton,
1180
+ disabled: testingAlias === host.alias,
1181
+ onClick: () => {
1182
+ runTest(host.alias);
1183
+ },
1184
+ children: testingAlias === host.alias ? tt("hosts.testing") : tt("hosts.test")
1185
+ }),
1186
+ testingAlias === host.alias && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1187
+ className: panel_module_css_default.spinner,
1188
+ "aria-hidden": "true"
1189
+ }),
1190
+ test !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1191
+ className: panel_module_css_default.inlineTest,
1192
+ "data-status": test.ok ? "ok" : "fail",
1193
+ children: test.ok ? tt("hosts.testOk", { latency: test.latencyMs ?? 0 }) : tt("hosts.testFail", { error: test.error ?? "" })
1194
+ }),
1195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1196
+ type: "button",
1197
+ className: panel_module_css_default.linkButton,
1198
+ onClick: () => {
1199
+ setDialog({
1200
+ mode: "edit",
1201
+ host
1202
+ });
1203
+ },
1204
+ children: tt("hosts.edit")
1205
+ }),
1206
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1207
+ type: "button",
1208
+ className: panel_module_css_default.linkButton,
1209
+ "data-danger": true,
1210
+ onClick: () => {
1211
+ deleteHost(host.alias);
1212
+ },
1213
+ children: tt("hosts.delete")
1214
+ }),
1215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1216
+ type: "button",
1217
+ className: panel_module_css_default.ghostButton,
1218
+ onClick: () => {
1219
+ onConnect(host.alias);
1220
+ },
1221
+ children: tt("hosts.connect")
1222
+ })
1223
+ ]
1224
+ }) })
1225
+ ] }, host.alias);
1226
+ };
1227
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1228
+ className: panel_module_css_default.fillBody,
1229
+ children: [
1230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1231
+ className: panel_module_css_default.toolbar,
1232
+ children: [
1233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1234
+ className: panel_module_css_default.search,
1235
+ type: "search",
1236
+ placeholder: tt("hosts.search"),
1237
+ value: search,
1238
+ onChange: (event) => {
1239
+ setSearch(event.target.value);
1240
+ }
1241
+ }),
1242
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.toolbarSpacer }),
1243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1244
+ type: "button",
1245
+ className: panel_module_css_default.ghostButton,
1246
+ onClick: () => {
1247
+ load(search.trim() || void 0);
1248
+ },
1249
+ children: tt("common.refresh")
1250
+ }),
1251
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1252
+ type: "button",
1253
+ className: panel_module_css_default.primaryButton,
1254
+ onClick: () => {
1255
+ setDialog({ mode: "create" });
1256
+ },
1257
+ children: tt("hosts.add")
1258
+ })
1259
+ ]
1260
+ }),
1261
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1262
+ className: panel_module_css_default.banner,
1263
+ "data-kind": "error",
1264
+ children: tt("common.error", { error })
1265
+ }),
1266
+ hosts === null && error === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1267
+ className: panel_module_css_default.loading,
1268
+ children: tt("common.loading")
1269
+ }),
1270
+ hosts !== null && hosts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1271
+ className: panel_module_css_default.empty,
1272
+ children: tt("hosts.empty")
1273
+ }),
1274
+ hosts !== null && hosts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1275
+ className: panel_module_css_default.tableWrap,
1276
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1277
+ className: panel_module_css_default.table,
1278
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1279
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.alias") }),
1280
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.host") }),
1281
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.user") }),
1282
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.transport") }),
1283
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.environment") }),
1284
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.tags") }),
1285
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.description") }),
1286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.actions") })
1287
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: hosts.map(renderHostRow) })]
1288
+ })
1289
+ }),
1290
+ dialog !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HostFormDialog, {
1291
+ api,
1292
+ editing: dialog.mode === "edit" ? dialog.host : null,
1293
+ onClose: () => {
1294
+ setDialog(null);
1295
+ },
1296
+ onSaved: () => {
1297
+ setDialog(null);
1298
+ load();
1299
+ }
1300
+ })
1301
+ ]
1302
+ });
1303
+ }
1304
+ //#endregion
1305
+ //#region src/client/panel/ProcessesTab.tsx
1306
+ /**
1307
+ * Processes tab: list processes for a host, filter, and kill by id.
1308
+ */
1309
+ /** The processes table with host picker and kill actions. */
1310
+ function ProcessesTab({ api }) {
1311
+ const [aliases, setAliases] = (0, react.useState)([]);
1312
+ const [alias, setAlias] = (0, react.useState)("");
1313
+ const [processes, setProcesses] = (0, react.useState)(null);
1314
+ const [filter, setFilter] = (0, react.useState)("");
1315
+ const [error, setError] = (0, react.useState)(null);
1316
+ const [notice, setNotice] = (0, react.useState)(null);
1317
+ const [killing, setKilling] = (0, react.useState)(null);
1318
+ const mountedRef = (0, react.useRef)(true);
1319
+ (0, react.useEffect)(() => () => {
1320
+ mountedRef.current = false;
1321
+ }, []);
1322
+ (0, react.useEffect)(() => {
1323
+ api.listHosts().then((list) => setAliases(list.map((h) => h.alias))).catch(() => {});
1324
+ }, [api]);
1325
+ const load = (0, react.useCallback)(async (target) => {
1326
+ try {
1327
+ const list = await api.listProcesses(target);
1328
+ if (!mountedRef.current) return;
1329
+ setProcesses(list);
1330
+ setError(null);
1331
+ } catch (cause) {
1332
+ if (!mountedRef.current) return;
1333
+ setError(errorMessage(cause));
1334
+ setProcesses(null);
1335
+ }
1336
+ }, [api]);
1337
+ (0, react.useEffect)(() => {
1338
+ if (alias === "") return;
1339
+ setProcesses(null);
1340
+ load(alias);
1341
+ }, [alias, load]);
1342
+ const kill = async (process) => {
1343
+ if (!window.confirm(tt("processes.killConfirm", {
1344
+ name: process.name,
1345
+ id: process.id
1346
+ }))) return;
1347
+ setKilling(process.id);
1348
+ setNotice(null);
1349
+ setError(null);
1350
+ try {
1351
+ await api.killProcess(alias, process.id);
1352
+ setNotice(tt("processes.killed", { id: process.id }));
1353
+ load(alias);
1354
+ } catch (cause) {
1355
+ setError(tt("processes.killFail", { error: errorMessage(cause) }));
1356
+ } finally {
1357
+ setKilling(null);
1358
+ }
1359
+ };
1360
+ const needle = filter.trim().toLowerCase();
1361
+ const visible = processes === null ? [] : processes.filter((p) => p.name.toLowerCase().includes(needle));
1362
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1363
+ className: panel_module_css_default.fillBody,
1364
+ children: [
1365
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1366
+ className: panel_module_css_default.toolbar,
1367
+ children: [
1368
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1369
+ className: panel_module_css_default.input,
1370
+ value: alias,
1371
+ onChange: (event) => {
1372
+ setAlias(event.target.value);
1373
+ },
1374
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1375
+ value: "",
1376
+ children: tt("processes.select")
1377
+ }), aliases.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1378
+ value: a,
1379
+ children: a
1380
+ }, a))]
1381
+ }),
1382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1383
+ className: panel_module_css_default.search,
1384
+ type: "search",
1385
+ placeholder: tt("processes.filter"),
1386
+ value: filter,
1387
+ onChange: (event) => {
1388
+ setFilter(event.target.value);
1389
+ }
1390
+ }),
1391
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.toolbarSpacer }),
1392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1393
+ type: "button",
1394
+ className: panel_module_css_default.ghostButton,
1395
+ disabled: alias === "",
1396
+ onClick: () => {
1397
+ load(alias);
1398
+ },
1399
+ children: tt("common.refresh")
1400
+ })
1401
+ ]
1402
+ }),
1403
+ notice !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1404
+ className: panel_module_css_default.banner,
1405
+ "data-kind": "ok",
1406
+ children: notice
1407
+ }),
1408
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1409
+ className: panel_module_css_default.banner,
1410
+ "data-kind": "error",
1411
+ children: error
1412
+ }),
1413
+ processes === null && alias !== "" && error === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1414
+ className: panel_module_css_default.loading,
1415
+ children: tt("common.loading")
1416
+ }),
1417
+ processes !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1418
+ className: panel_module_css_default.tableWrap,
1419
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1420
+ className: panel_module_css_default.table,
1421
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("processes.id") }),
1423
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("processes.name") }),
1424
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("processes.cpu") }),
1425
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("processes.memMB") }),
1426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("processes.startTime") }),
1427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("processes.path") }),
1428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.actions") })
1429
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: visible.map((process) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1431
+ className: panel_module_css_default.mono,
1432
+ children: process.id
1433
+ }),
1434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: process.name }),
1435
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: process.cpu ?? "" }),
1436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: process.memMB ?? "" }),
1437
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1438
+ className: panel_module_css_default.cellMuted,
1439
+ children: process.startTime ?? ""
1440
+ }),
1441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1442
+ className: panel_module_css_default.cellMuted,
1443
+ title: process.path ?? "",
1444
+ children: process.path ?? ""
1445
+ }),
1446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1447
+ type: "button",
1448
+ className: panel_module_css_default.linkButton,
1449
+ "data-danger": true,
1450
+ disabled: killing === process.id,
1451
+ onClick: () => {
1452
+ kill(process);
1453
+ },
1454
+ children: killing === process.id ? tt("common.loading") : tt("processes.kill")
1455
+ }) })
1456
+ ] }, process.id)) })]
1457
+ })
1458
+ })
1459
+ ]
1460
+ });
1461
+ }
1462
+ //#endregion
1463
+ //#region src/client/panel/ServicesTab.tsx
1464
+ /**
1465
+ * Services tab: list Windows services for a host, filter, and run
1466
+ * start/stop/restart/startup-type actions.
1467
+ */
1468
+ /** The services table with host picker and actions. */
1469
+ function ServicesTab({ api }) {
1470
+ const [aliases, setAliases] = (0, react.useState)([]);
1471
+ const [alias, setAlias] = (0, react.useState)("");
1472
+ const [services, setServices] = (0, react.useState)(null);
1473
+ const [filter, setFilter] = (0, react.useState)("");
1474
+ const [error, setError] = (0, react.useState)(null);
1475
+ const [notice, setNotice] = (0, react.useState)(null);
1476
+ const [acting, setActing] = (0, react.useState)(null);
1477
+ const mountedRef = (0, react.useRef)(true);
1478
+ (0, react.useEffect)(() => () => {
1479
+ mountedRef.current = false;
1480
+ }, []);
1481
+ (0, react.useEffect)(() => {
1482
+ api.listHosts().then((list) => setAliases(list.map((h) => h.alias))).catch(() => {});
1483
+ }, [api]);
1484
+ const load = (0, react.useCallback)(async (target) => {
1485
+ try {
1486
+ const list = await api.listServices(target);
1487
+ if (!mountedRef.current) return;
1488
+ setServices(list);
1489
+ setError(null);
1490
+ } catch (cause) {
1491
+ if (!mountedRef.current) return;
1492
+ setError(errorMessage(cause));
1493
+ setServices(null);
1494
+ }
1495
+ }, [api]);
1496
+ (0, react.useEffect)(() => {
1497
+ if (alias === "") return;
1498
+ setServices(null);
1499
+ load(alias);
1500
+ }, [alias, load]);
1501
+ const act = async (name, action) => {
1502
+ setActing(name);
1503
+ setNotice(null);
1504
+ setError(null);
1505
+ try {
1506
+ const updated = await api.serviceAction(alias, name, action);
1507
+ setNotice(tt("services.actionOk", {
1508
+ name: updated.name,
1509
+ status: updated.status
1510
+ }));
1511
+ load(alias);
1512
+ } catch (cause) {
1513
+ setError(tt("services.actionFail", { error: errorMessage(cause) }));
1514
+ } finally {
1515
+ setActing(null);
1516
+ }
1517
+ };
1518
+ const needle = filter.trim().toLowerCase();
1519
+ const visible = services === null ? [] : services.filter((s) => s.name.toLowerCase().includes(needle) || s.displayName.toLowerCase().includes(needle));
1520
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1521
+ className: panel_module_css_default.fillBody,
1522
+ children: [
1523
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1524
+ className: panel_module_css_default.toolbar,
1525
+ children: [
1526
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1527
+ className: panel_module_css_default.input,
1528
+ value: alias,
1529
+ onChange: (event) => {
1530
+ setAlias(event.target.value);
1531
+ },
1532
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1533
+ value: "",
1534
+ children: tt("services.select")
1535
+ }), aliases.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1536
+ value: a,
1537
+ children: a
1538
+ }, a))]
1539
+ }),
1540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1541
+ className: panel_module_css_default.search,
1542
+ type: "search",
1543
+ placeholder: tt("services.filter"),
1544
+ value: filter,
1545
+ onChange: (event) => {
1546
+ setFilter(event.target.value);
1547
+ }
1548
+ }),
1549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.toolbarSpacer }),
1550
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1551
+ type: "button",
1552
+ className: panel_module_css_default.ghostButton,
1553
+ disabled: alias === "",
1554
+ onClick: () => {
1555
+ load(alias);
1556
+ },
1557
+ children: tt("common.refresh")
1558
+ })
1559
+ ]
1560
+ }),
1561
+ notice !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1562
+ className: panel_module_css_default.banner,
1563
+ "data-kind": "ok",
1564
+ children: notice
1565
+ }),
1566
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1567
+ className: panel_module_css_default.banner,
1568
+ "data-kind": "error",
1569
+ children: error
1570
+ }),
1571
+ services === null && alias !== "" && error === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1572
+ className: panel_module_css_default.loading,
1573
+ children: tt("common.loading")
1574
+ }),
1575
+ services !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1576
+ className: panel_module_css_default.tableWrap,
1577
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1578
+ className: panel_module_css_default.table,
1579
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("services.name") }),
1581
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("services.displayName") }),
1582
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("services.status") }),
1583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("services.startMode") }),
1584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("services.startName") }),
1585
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.actions") })
1586
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: visible.map((service) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1587
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1588
+ className: panel_module_css_default.mono,
1589
+ children: service.name
1590
+ }),
1591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: service.displayName }),
1592
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1593
+ className: panel_module_css_default.badge,
1594
+ "data-kind": service.status === "Running" ? "ok" : "muted",
1595
+ children: service.status
1596
+ }) }),
1597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: service.startMode }),
1598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1599
+ className: panel_module_css_default.cellMuted,
1600
+ children: service.startName
1601
+ }),
1602
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1603
+ className: panel_module_css_default.actions,
1604
+ children: [
1605
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1606
+ type: "button",
1607
+ className: panel_module_css_default.linkButton,
1608
+ disabled: acting === service.name,
1609
+ onClick: () => {
1610
+ act(service.name, "start");
1611
+ },
1612
+ children: tt("services.start")
1613
+ }),
1614
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1615
+ type: "button",
1616
+ className: panel_module_css_default.linkButton,
1617
+ disabled: acting === service.name,
1618
+ onClick: () => {
1619
+ act(service.name, "stop");
1620
+ },
1621
+ children: tt("services.stop")
1622
+ }),
1623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1624
+ type: "button",
1625
+ className: panel_module_css_default.linkButton,
1626
+ disabled: acting === service.name,
1627
+ onClick: () => {
1628
+ act(service.name, "restart");
1629
+ },
1630
+ children: tt("services.restart")
1631
+ }),
1632
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1633
+ type: "button",
1634
+ className: panel_module_css_default.linkButton,
1635
+ disabled: acting === service.name,
1636
+ onClick: () => {
1637
+ act(service.name, "set-auto");
1638
+ },
1639
+ children: tt("services.auto")
1640
+ }),
1641
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1642
+ type: "button",
1643
+ className: panel_module_css_default.linkButton,
1644
+ disabled: acting === service.name,
1645
+ onClick: () => {
1646
+ act(service.name, "set-manual");
1647
+ },
1648
+ children: tt("services.manual")
1649
+ }),
1650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1651
+ type: "button",
1652
+ className: panel_module_css_default.linkButton,
1653
+ disabled: acting === service.name,
1654
+ onClick: () => {
1655
+ act(service.name, "set-disabled");
1656
+ },
1657
+ children: tt("services.disabled")
1658
+ })
1659
+ ]
1660
+ }) })
1661
+ ] }, service.name)) })]
1662
+ })
1663
+ })
1664
+ ]
1665
+ });
1666
+ }
1667
+ //#endregion
1668
+ //#region src/client/panel/TransferTab.tsx
1669
+ /**
1670
+ * Transfer tab: browse remote directories (ls), download files to the local
1671
+ * machine, and upload local files to the remote host. Transfers ride the
1672
+ * NDJSON progress stream (upload) / content-length progress (download).
1673
+ */
1674
+ /** One remote directory entry with a clickable path. */
1675
+ function joinPath(dir, name) {
1676
+ return dir.replace(/[\\/]+$/, "") + "\\" + name;
1677
+ }
1678
+ /** The file browser with upload/download. */
1679
+ function TransferTab({ api }) {
1680
+ const [aliases, setAliases] = (0, react.useState)([]);
1681
+ const [alias, setAlias] = (0, react.useState)("");
1682
+ const [dir, setDir] = (0, react.useState)("C:\\");
1683
+ const [entries, setEntries] = (0, react.useState)(null);
1684
+ const [error, setError] = (0, react.useState)(null);
1685
+ const [notice, setNotice] = (0, react.useState)(null);
1686
+ const [progress, setProgress] = (0, react.useState)(null);
1687
+ const fileInputRef = (0, react.useRef)(null);
1688
+ const mountedRef = (0, react.useRef)(true);
1689
+ (0, react.useEffect)(() => () => {
1690
+ mountedRef.current = false;
1691
+ }, []);
1692
+ (0, react.useEffect)(() => {
1693
+ api.listHosts().then((list) => setAliases(list.map((h) => h.alias))).catch(() => {});
1694
+ }, [api]);
1695
+ const load = (0, react.useCallback)(async (target, path) => {
1696
+ try {
1697
+ const list = await api.ls(target, path);
1698
+ if (!mountedRef.current) return;
1699
+ setEntries(list);
1700
+ setDir(path);
1701
+ setError(null);
1702
+ } catch (cause) {
1703
+ if (!mountedRef.current) return;
1704
+ setError(errorMessage(cause));
1705
+ setEntries(null);
1706
+ }
1707
+ }, [api]);
1708
+ const open = () => {
1709
+ if (alias === "" || dir === "") return;
1710
+ setEntries(null);
1711
+ load(alias, dir);
1712
+ };
1713
+ const pickAlias = (value) => {
1714
+ setAlias(value);
1715
+ setEntries(null);
1716
+ if (value !== "") load(value, "C:\\");
1717
+ };
1718
+ const download = async (entry) => {
1719
+ if (entry.type !== "file") return;
1720
+ setProgress({
1721
+ phase: "transferring",
1722
+ file: entry.name,
1723
+ transferred: 0,
1724
+ total: 0,
1725
+ percent: 0
1726
+ });
1727
+ setError(null);
1728
+ try {
1729
+ const outcome = await api.downloadFile(alias, joinPath(dir, entry.name), (p) => setProgress(p));
1730
+ setNotice(tt("transfer.downloaded", { bytes: outcome.bytes }));
1731
+ } catch (cause) {
1732
+ setError(tt("transfer.fail", { error: errorMessage(cause) }));
1733
+ } finally {
1734
+ setProgress(null);
1735
+ }
1736
+ };
1737
+ const upload = async (file) => {
1738
+ if (file === void 0) return;
1739
+ const remotePath = joinPath(dir, file.name);
1740
+ setProgress({
1741
+ phase: "transferring",
1742
+ file: file.name,
1743
+ transferred: 0,
1744
+ total: file.size,
1745
+ percent: 0
1746
+ });
1747
+ setError(null);
1748
+ try {
1749
+ const outcome = await api.uploadFile(file, alias, remotePath, (p) => setProgress(p));
1750
+ setNotice(tt("transfer.uploaded", { bytes: outcome.transferredBytes }));
1751
+ load(alias, dir);
1752
+ } catch (cause) {
1753
+ setError(tt("transfer.fail", { error: errorMessage(cause) }));
1754
+ } finally {
1755
+ setProgress(null);
1756
+ }
1757
+ };
1758
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1759
+ className: panel_module_css_default.fillBody,
1760
+ children: [
1761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1762
+ className: panel_module_css_default.toolbar,
1763
+ children: [
1764
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1765
+ className: panel_module_css_default.input,
1766
+ value: alias,
1767
+ onChange: (event) => {
1768
+ pickAlias(event.target.value);
1769
+ },
1770
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1771
+ value: "",
1772
+ children: tt("transfer.select")
1773
+ }), aliases.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1774
+ value: a,
1775
+ children: a
1776
+ }, a))]
1777
+ }),
1778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1779
+ className: panel_module_css_default.search,
1780
+ value: dir,
1781
+ onChange: (event) => {
1782
+ setDir(event.target.value);
1783
+ },
1784
+ onKeyDown: (event) => {
1785
+ if (event.key === "Enter") open();
1786
+ }
1787
+ }),
1788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1789
+ type: "button",
1790
+ className: panel_module_css_default.primaryButton,
1791
+ disabled: alias === "" || dir === "",
1792
+ onClick: open,
1793
+ children: tt("transfer.go")
1794
+ }),
1795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1796
+ type: "button",
1797
+ className: panel_module_css_default.ghostButton,
1798
+ disabled: alias === "",
1799
+ onClick: () => {
1800
+ load(alias, dir.replace(/[\\/][^\\/]*$/, "") || "C:\\");
1801
+ },
1802
+ children: tt("transfer.up")
1803
+ }),
1804
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1805
+ type: "button",
1806
+ className: panel_module_css_default.ghostButton,
1807
+ disabled: alias === "",
1808
+ onClick: () => {
1809
+ load(alias, "C:\\");
1810
+ },
1811
+ children: tt("transfer.root")
1812
+ }),
1813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.toolbarSpacer }),
1814
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1815
+ type: "button",
1816
+ className: panel_module_css_default.ghostButton,
1817
+ disabled: alias === "",
1818
+ onClick: () => {
1819
+ fileInputRef.current?.click();
1820
+ },
1821
+ children: tt("transfer.upload")
1822
+ }),
1823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1824
+ ref: fileInputRef,
1825
+ type: "file",
1826
+ style: { display: "none" },
1827
+ onChange: (event) => {
1828
+ const file = event.target.files?.[0];
1829
+ if (file !== void 0) upload(file);
1830
+ event.target.value = "";
1831
+ }
1832
+ })
1833
+ ]
1834
+ }),
1835
+ notice !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1836
+ className: panel_module_css_default.banner,
1837
+ "data-kind": "ok",
1838
+ children: notice
1839
+ }),
1840
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1841
+ className: panel_module_css_default.banner,
1842
+ "data-kind": "error",
1843
+ children: error
1844
+ }),
1845
+ progress !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1846
+ className: panel_module_css_default.banner,
1847
+ "data-kind": "ok",
1848
+ children: [
1849
+ progress.phase === "transferring" ? progress.total > 0 ? tt("transfer.downloading") : tt("transfer.uploading") : tt("transfer.uploading"),
1850
+ " ",
1851
+ formatBytes(progress.transferred),
1852
+ progress.total > 0 ? " / " + formatBytes(progress.total) + " (" + progress.percent + "%)" : ""
1853
+ ]
1854
+ }),
1855
+ entries === null && alias !== "" && error === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1856
+ className: panel_module_css_default.loading,
1857
+ children: tt("common.loading")
1858
+ }),
1859
+ entries !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1860
+ className: panel_module_css_default.tableWrap,
1861
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1862
+ className: panel_module_css_default.table,
1863
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("transfer.name") }),
1865
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("transfer.type") }),
1866
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("transfer.size") }),
1867
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("transfer.modified") }),
1868
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("hosts.col.actions") })
1869
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: entry.type === "dir" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1871
+ type: "button",
1872
+ className: panel_module_css_default.linkButton,
1873
+ onClick: () => {
1874
+ load(alias, joinPath(dir, entry.name));
1875
+ },
1876
+ children: ["📁 ", entry.name]
1877
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1878
+ className: panel_module_css_default.mono,
1879
+ children: entry.name
1880
+ }) }),
1881
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: entry.type === "dir" ? tt("transfer.dir") : tt("transfer.file") }),
1882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: entry.type === "file" ? formatBytes(entry.size) : "" }),
1883
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1884
+ className: panel_module_css_default.cellMuted,
1885
+ children: entry.mtimeMs > 0 ? new Date(entry.mtimeMs).toLocaleString() : ""
1886
+ }),
1887
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: entry.type === "file" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1888
+ type: "button",
1889
+ className: panel_module_css_default.linkButton,
1890
+ onClick: () => {
1891
+ download(entry);
1892
+ },
1893
+ children: tt("transfer.download")
1894
+ }) })
1895
+ ] }, entry.name)) })]
1896
+ })
1897
+ })
1898
+ ]
1899
+ });
1900
+ }
1901
+ //#endregion
1902
+ //#region src/client/panel/WinrmPanel.tsx
1903
+ /**
1904
+ * The WinRM operations panel shell: a header with a back control, a
1905
+ * five-tab bar, and the active tab's content. Tab state lives here (reset to
1906
+ * Hosts when the panel is reopened, since the keyed main slot unmounts the
1907
+ * occupant on close); inactive tabs unmount, so each tab fetches its own data
1908
+ * on activation. The hosts tab's connect action switches here to the console
1909
+ * tab with the chosen alias preselected.
1910
+ */
1911
+ /** The tab bar definition (labels resolved at render time). */
1912
+ const TABS = [
1913
+ {
1914
+ id: "hosts",
1915
+ label: () => tt("tab.hosts")
1916
+ },
1917
+ {
1918
+ id: "console",
1919
+ label: () => tt("tab.console")
1920
+ },
1921
+ {
1922
+ id: "services",
1923
+ label: () => tt("tab.services")
1924
+ },
1925
+ {
1926
+ id: "processes",
1927
+ label: () => tt("tab.processes")
1928
+ },
1929
+ {
1930
+ id: "transfer",
1931
+ label: () => tt("tab.transfer")
1932
+ }
1933
+ ];
1934
+ /** The tabbed WinRM panel. */
1935
+ function WinrmPanel({ api, onBack }) {
1936
+ const [activeTab, setActiveTab] = (0, react.useState)("hosts");
1937
+ const [connectRequest, setConnectRequest] = (0, react.useState)(null);
1938
+ const handleConnect = (alias) => {
1939
+ setActiveTab("console");
1940
+ setConnectRequest((prev) => ({
1941
+ alias,
1942
+ nonce: (prev?.nonce ?? 0) + 1
1943
+ }));
1944
+ };
1945
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1946
+ className: panel_module_css_default.panel,
1947
+ children: [
1948
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1949
+ className: panel_module_css_default.panelHeader,
1950
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1951
+ type: "button",
1952
+ className: `${panel_module_css_default.ghostButton} ${panel_module_css_default.backButton}`,
1953
+ "aria-label": tt("panel.backToConversation"),
1954
+ onClick: onBack,
1955
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1956
+ "aria-hidden": "true",
1957
+ children: "‹"
1958
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("panel.backToConversation") })]
1959
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1960
+ className: panel_module_css_default.panelTitle,
1961
+ children: tt("panel.title")
1962
+ })]
1963
+ }),
1964
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1965
+ className: panel_module_css_default.tabBar,
1966
+ role: "tablist",
1967
+ "data-dsh-part": "tab-bar",
1968
+ children: TABS.map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1969
+ type: "button",
1970
+ role: "tab",
1971
+ "aria-selected": activeTab === tab.id,
1972
+ "data-active": activeTab === tab.id ? "" : void 0,
1973
+ className: panel_module_css_default.tab,
1974
+ onClick: () => {
1975
+ setActiveTab(tab.id);
1976
+ },
1977
+ children: tab.label()
1978
+ }, tab.id))
1979
+ }),
1980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1981
+ className: panel_module_css_default.panelContent,
1982
+ children: [
1983
+ activeTab === "hosts" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HostsTab, {
1984
+ api,
1985
+ onConnect: handleConnect
1986
+ }),
1987
+ activeTab === "console" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsoleTab, {
1988
+ api,
1989
+ presetAlias: connectRequest?.alias,
1990
+ requestId: connectRequest?.nonce
1991
+ }),
1992
+ activeTab === "services" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ServicesTab, { api }),
1993
+ activeTab === "processes" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProcessesTab, { api }),
1994
+ activeTab === "transfer" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransferTab, { api })
1995
+ ]
1996
+ })
1997
+ ]
1998
+ });
1999
+ }
2000
+ //#endregion
2001
+ //#region src/client/panel/WinrmPanelIcon.tsx
2002
+ /** One glyph contribution for the `sidebar.panellist` list slot. */
2003
+ function WinrmPanelIcon({ size }) {
2004
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
2005
+ viewBox: "0 0 16 16",
2006
+ width: size,
2007
+ height: size,
2008
+ fill: "none",
2009
+ stroke: "currentColor",
2010
+ strokeWidth: 1.3,
2011
+ strokeLinecap: "round",
2012
+ strokeLinejoin: "round",
2013
+ "aria-hidden": "true",
2014
+ children: [
2015
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
2016
+ x: "2",
2017
+ y: "2.5",
2018
+ width: "12",
2019
+ height: "11",
2020
+ rx: "1.5"
2021
+ }),
2022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 6.5h11" }),
2023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6 6.5v7" })
2024
+ ]
2025
+ });
2026
+ }
2027
+ //#endregion
2028
+ //#region src/client/index.ts
2029
+ /** Locale namespace this plugin owns. */
2030
+ const NS = "dsh-winrm";
2031
+ /** The id shared by the sidebar entry and the main panel it opens. */
2032
+ const PANEL_ID = "winrm";
2033
+ /** Required services (fiber inject waiting — slots, locale, and layout must be up first). */
2034
+ const inject = [
2035
+ "slots",
2036
+ "locale",
2037
+ "layout"
2038
+ ];
2039
+ /**
2040
+ * Contribute the Windows sidebar entry and the tabbed WinRM panel it opens.
2041
+ * @param ctx - client root context (locale, slots, layout services).
2042
+ */
2043
+ function apply(ctx) {
2044
+ ctx.effect(() => ctx.locale.register(NS, {
2045
+ zh,
2046
+ en
2047
+ }), "dsh-winrm: dictionaries");
2048
+ const t = ctx.locale.bind(NS);
2049
+ const api = new WinrmApi();
2050
+ ctx.slots.inject("main", () => ctx.slots.register({
2051
+ name: "main",
2052
+ key: PANEL_ID,
2053
+ locale: NS,
2054
+ inject: () => ({
2055
+ api,
2056
+ onBack: () => {
2057
+ ctx.layout.selectPanel(null);
2058
+ }
2059
+ })
2060
+ }, WinrmPanel));
2061
+ ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
2062
+ name: "sidebar.panellist",
2063
+ id: PANEL_ID,
2064
+ order: 10,
2065
+ label: () => t("entry.label"),
2066
+ locale: NS
2067
+ }, WinrmPanelIcon));
2068
+ }
2069
+ //#endregion
2070
+ exports.apply = apply;
2071
+ exports.inject = inject;
2072
+
2073
+
2074
+ return module.exports;
2075
+ }
2076
+ });