rovloom 0.5.0 → 0.6.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.
- package/README.md +8 -0
- package/dist/src/admin/index.js +1 -1
- package/dist/src/{chunk-smpevv7n.js → chunk-0dqx2mmz.js} +9 -16
- package/dist/src/{chunk-rwpynx2j.js → chunk-0qgk7mhz.js} +12 -11
- package/dist/src/{chunk-erdq0rn8.js → chunk-3dvn33c4.js} +57 -58
- package/dist/src/chunk-5b8g62kw.js +438 -0
- package/dist/src/{chunk-64ftwe9b.js → chunk-8ykrqhxh.js} +8 -2
- package/dist/src/{chunk-9y36gntb.js → chunk-9zkq0kc8.js} +10 -5
- package/dist/src/{chunk-5n8csngz.js → chunk-d2kz25f2.js} +156 -11
- package/dist/src/{chunk-ympzv86c.js → chunk-ez7d5dac.js} +13 -4
- package/dist/src/{chunk-emcefrc1.js → chunk-g27b2gmx.js} +3 -4
- package/dist/src/{chunk-abd4envz.js → chunk-gyvqqnjb.js} +30 -57
- package/dist/src/{chunk-e7x51y0t.js → chunk-jf4r0r44.js} +2 -2
- package/dist/src/{chunk-4etdq0cp.js → chunk-jka7v3wb.js} +4 -7
- package/dist/src/{chunk-xhq21g24.js → chunk-m9c1pnv9.js} +3 -3
- package/dist/src/{chunk-7k9g0x5e.js → chunk-mg0fd6ph.js} +1 -1
- package/dist/src/{chunk-0ake7e4r.js → chunk-n6wtjyfb.js} +6 -14
- package/dist/src/{chunk-nyn75b8v.js → chunk-qjh9x872.js} +30 -1
- package/dist/src/{chunk-r0nfthsf.js → chunk-s3ydr65k.js} +2 -2
- package/dist/src/{chunk-ybte82d3.js → chunk-srfbjwtk.js} +4 -3
- package/dist/src/{chunk-sj24fpz3.js → chunk-ssp57p43.js} +24 -28
- package/dist/src/{chunk-ebm3kvnq.js → chunk-wxqhscsw.js} +26 -3
- package/dist/src/{chunk-7q96e161.js → chunk-x0td5wn4.js} +6 -12
- package/dist/src/{chunk-9yg6x3gt.js → chunk-ytpfmcp1.js} +4 -3
- package/dist/src/{chunk-fnakrk0a.js → chunk-yv9px0f5.js} +22 -25
- package/dist/src/{chunk-n073xnr1.js → chunk-zjkjjm81.js} +43 -46
- package/dist/src/cli/index.js +23 -15
- package/dist/src/cli/main.js +14 -9
- package/dist/src/mcp/index.js +6 -5
- package/dist/src/runtime/index.js +2 -2
- package/dist/src/service/index.js +3 -4
- package/dist/src/store.js +1 -1
- package/dist/src/webui/index.js +1095 -156
- package/package.json +1 -1
package/dist/src/webui/index.js
CHANGED
|
@@ -13,6 +13,111 @@ import {
|
|
|
13
13
|
|
|
14
14
|
// src/webui/i18n.ts
|
|
15
15
|
var chinese = Object.freeze({
|
|
16
|
+
"resources.previous": "\u4E0A\u4E00\u9875",
|
|
17
|
+
"resources.next": "\u4E0B\u4E00\u9875",
|
|
18
|
+
"resources.tokens": "MCP Token",
|
|
19
|
+
"resources.pairings": "\u914D\u5BF9\u8BB0\u5F55",
|
|
20
|
+
"resources.name": "\u540D\u79F0",
|
|
21
|
+
"resources.expiry": "\u6709\u6548\u671F",
|
|
22
|
+
"resources.days": "{count} \u5929",
|
|
23
|
+
"resources.create": "\u521B\u5EFA Token",
|
|
24
|
+
"resources.tokenNote": "\u4E3A AI \u5BA2\u6237\u7AEF\u521B\u5EFA Token\uFF0C\u53EF\u968F\u65F6\u67E5\u770B\u3001\u590D\u5236\u548C\u5220\u9664\u3002\u652F\u6301\u5F53\u524D\u548C\u4E4B\u540E\u914D\u5BF9\u7684\u8BBE\u5907\u3002",
|
|
25
|
+
"resources.endpoint": "MCP \u5730\u5740",
|
|
26
|
+
"resources.endpointNote": "\u8FDC\u7A0B\u8FDE\u63A5\u65F6\uFF0C\u5C06 127.0.0.1 \u6362\u6210 Gateway \u7684\u5730\u5740\u3002\u5728 AI \u5BA2\u6237\u7AEF\u7684 Authorization \u4E2D\u586B\u5199 Bearer \u52A0 Token\u3002",
|
|
27
|
+
"resources.expiresAt": "\u6709\u6548\u81F3 {time}",
|
|
28
|
+
"resources.active": "\u6709\u6548",
|
|
29
|
+
"resources.expired": "\u5DF2\u8FC7\u671F",
|
|
30
|
+
"resources.show": "\u663E\u793A",
|
|
31
|
+
"resources.hide": "\u9690\u85CF",
|
|
32
|
+
"resources.legacy": "\u65E7 Token \u4EC5\u4FDD\u5B58\u6458\u8981\uFF0C\u65E0\u6CD5\u8FD8\u539F\u3002",
|
|
33
|
+
"resources.reissue": "\u91CD\u65B0\u7B7E\u53D1",
|
|
34
|
+
"resources.confirmReissue": "\u91CD\u65B0\u7B7E\u53D1\u4F1A\u7ACB\u5373\u4F7F\u65E7 Token \u5931\u6548\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F",
|
|
35
|
+
"resources.delete": "\u5220\u9664",
|
|
36
|
+
"resources.deleteDevice": "\u5220\u9664\u8BBE\u5907",
|
|
37
|
+
"resources.deleteDeviceNote": "\u5220\u9664\u8BBE\u5907\u53CA\u5176\u8FD0\u884C\u914D\u7F6E\u3001\u914D\u5BF9\u8BB0\u5F55\u548C\u8C03\u7528\u5185\u5BB9\uFF0C\u5F53\u524D\u8FDE\u63A5\u4F1A\u65AD\u5F00\u3002",
|
|
38
|
+
"resources.confirmDevice": "\u5220\u9664\u8BE5\u8BBE\u5907\u53CA\u5176\u914D\u5BF9\u3001\u8C03\u7528\u8BB0\u5F55\uFF1F\u5982\u9700\u518D\u6B21\u8FDE\u63A5\uFF0C\u8BF7\u91CD\u65B0\u914D\u5BF9\u3002",
|
|
39
|
+
"resources.confirmToken": "\u5220\u9664 Token\u300C{name}\u300D\uFF1F\u4F7F\u7528\u5B83\u7684\u5BA2\u6237\u7AEF\u5C06\u65E0\u6CD5\u7EE7\u7EED\u8BBF\u95EE\u3002",
|
|
40
|
+
"resources.confirmPairing": "\u5220\u9664\u8FD9\u6761\u914D\u5BF9\u8BB0\u5F55\uFF1F\u672A\u5B8C\u6210\u7684\u914D\u5BF9\u4F1A\u7EC8\u6B62\uFF0C\u5DF2\u914D\u5BF9\u8BBE\u5907\u4ECD\u4FDD\u7559\u3002",
|
|
41
|
+
"resources.emptyTokens": "\u8FD8\u6CA1\u6709 MCP Token\u3002\u5728\u4E0A\u65B9\u521B\u5EFA\u4E00\u4E2A\uFF0C\u5373\u53EF\u8FDE\u63A5\u4F60\u7684 AI \u5BA2\u6237\u7AEF\u3002",
|
|
42
|
+
"resources.emptyPairings": "\u6682\u65E0\u914D\u5BF9\u8BB0\u5F55\u3002",
|
|
43
|
+
"resources.loading": "\u6B63\u5728\u52A0\u8F7D\u2026",
|
|
44
|
+
"resources.saving": "\u6B63\u5728\u4FDD\u5B58\u2026",
|
|
45
|
+
"resources.saved": "\u5DF2\u4FDD\u5B58",
|
|
46
|
+
"resources.failed": "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5\u3002",
|
|
47
|
+
"resources.invalid": "\u8BF7\u4F7F\u7528\u4E0D\u91CD\u590D\u7684\u540D\u79F0\uFF08\u5B57\u6BCD\u3001\u6570\u5B57\u3001- \u6216 _\uFF09\uFF0C\u5E76\u9009\u62E9\u6709\u6548\u671F\u3002",
|
|
48
|
+
"resources.pending": "\u7B49\u5F85\u914D\u5BF9",
|
|
49
|
+
"resources.paired": "\u5DF2\u914D\u5BF9",
|
|
50
|
+
"resources.cancelled": "\u5DF2\u53D6\u6D88",
|
|
51
|
+
"nav.workspace": "\u5DE5\u4F5C\u7A7A\u95F4",
|
|
52
|
+
"nav.overview": "\u6982\u89C8",
|
|
53
|
+
"nav.devices": "\u8BBE\u5907\u7BA1\u7406",
|
|
54
|
+
"nav.pairing": "\u914D\u5BF9\u8BBE\u5907",
|
|
55
|
+
"nav.connect": "\u8FDE\u63A5 MCP",
|
|
56
|
+
"nav.resources": "\u5E2E\u52A9\u4E0E\u8D44\u6E90",
|
|
57
|
+
"nav.docs": "\u5FEB\u901F\u5F00\u59CB",
|
|
58
|
+
"nav.console": "\u7BA1\u7406\u63A7\u5236\u53F0",
|
|
59
|
+
"overview.eyebrow": "\u4F60\u7684\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u5C3D\u5728\u638C\u63E1",
|
|
60
|
+
"overview.title": "\u5DE5\u4F5C\u53F0",
|
|
61
|
+
"overview.description": "\u67E5\u770B\u7F51\u5173\u4E0E\u8BBE\u5907\u72B6\u6001\uFF0C\u8BA9\u8FDC\u7A0B\u5DE5\u4F5C\u6D41\u987A\u7545\u8FDE\u63A5\u3002",
|
|
62
|
+
"overview.visible": "\u672C\u9875\u8BBE\u5907",
|
|
63
|
+
"overview.visibleNote": "\u5F53\u524D\u6388\u6743\u8303\u56F4\u4E2D\u7684\u5217\u8868\u9875",
|
|
64
|
+
"overview.online": "\u8FDE\u63A5\u5DF2\u9A8C\u8BC1",
|
|
65
|
+
"overview.onlineNote": "\u8FDE\u63A5\u4E0E\u8EAB\u4EFD\u8BA4\u8BC1\u5747\u6709\u6548",
|
|
66
|
+
"overview.attention": "\u9700\u8981\u5173\u6CE8",
|
|
67
|
+
"overview.attentionNote": "\u672C\u9875\u5C1A\u672A\u9A8C\u8BC1\u8FDE\u63A5\u7684\u8BBE\u5907",
|
|
68
|
+
"overview.scope": "\u8BBE\u5907\u7EDF\u8BA1\u4EC5\u8986\u76D6\u5F53\u524D\u9875\uFF1B\u8FC7\u671F\u89C2\u6D4B\u4E0D\u4EE3\u8868\u5F53\u524D\u72B6\u6001\u3002",
|
|
69
|
+
"overview.gateway": "\u7F51\u5173\u8FD0\u884C\u72B6\u6001",
|
|
70
|
+
"overview.available": "\u53EF\u7528",
|
|
71
|
+
"overview.degraded": "\u53D7\u9650",
|
|
72
|
+
"overview.unavailable": "\u4E0D\u53EF\u7528",
|
|
73
|
+
"overview.unverified": "\u672A\u6838\u5B9E",
|
|
74
|
+
"overview.store": "\u6570\u636E\u5B58\u50A8",
|
|
75
|
+
"overview.transport": "\u8BBE\u5907\u901A\u9053",
|
|
76
|
+
"overview.native": "\u539F\u751F\u8FD0\u884C\u73AF\u5883",
|
|
77
|
+
"overview.manage": "\u7BA1\u7406\u8BBE\u5907",
|
|
78
|
+
"overview.deviceSection": "\u5DF2\u8FDE\u63A5\u7684\u5DE5\u4F5C\u7A7A\u95F4",
|
|
79
|
+
"overview.deviceSubtitle": "\u4ECE\u4E00\u53F0\u8BBE\u5907\u5F00\u59CB\uFF0C\u5C06\u4F60\u7684\u5DE5\u5177\u548C\u5DE5\u4F5C\u533A\u8FDE\u63A5\u8D77\u6765\u3002",
|
|
80
|
+
"devices.search": "\u641C\u7D22\u5F53\u524D\u9875\u8BBE\u5907",
|
|
81
|
+
"devices.searchPlaceholder": "\u641C\u7D22\u8BBE\u5907\u540D\u79F0\u3001ID \u6216\u6807\u7B7E\u2026",
|
|
82
|
+
"devices.filterAll": "\u5168\u90E8",
|
|
83
|
+
"devices.filterOnline": "\u5DF2\u9A8C\u8BC1\u8FDE\u63A5",
|
|
84
|
+
"devices.filterAttention": "\u9700\u8981\u5173\u6CE8",
|
|
85
|
+
"devices.results": "\u5F53\u524D\u9875\u663E\u793A {shown} / {count} \u53F0\u8BBE\u5907",
|
|
86
|
+
"devices.noMatches": "\u6CA1\u6709\u5339\u914D\u7684\u8BBE\u5907",
|
|
87
|
+
"devices.noMatchesHint": "\u8BD5\u8BD5\u5176\u4ED6\u540D\u79F0\u3001ID \u6216\u6807\u7B7E\uFF0C\u6216\u5207\u6362\u72B6\u6001\u7B5B\u9009\u3002",
|
|
88
|
+
"devices.inspect": "\u67E5\u770B\u8BE6\u60C5",
|
|
89
|
+
"devices.record": "\u89C2\u6D4B\u8BE6\u60C5",
|
|
90
|
+
"devices.open": "\u67E5\u770B {name} \u7684\u8BE6\u60C5",
|
|
91
|
+
"pairing.pageTitle": "\u8FDE\u63A5\u4E00\u53F0\u65B0\u8BBE\u5907",
|
|
92
|
+
"pairing.pageDescription": "\u521B\u5EFA\u7533\u8BF7\u3001\u8FD0\u884C Connector\u3001\u6838\u5BF9\u4EE3\u7801\u3002\u4E09\u6B65\u628A\u5DE5\u4F5C\u7AD9\u52A0\u5165\u4F60\u7684\u5DE5\u4F5C\u7A7A\u95F4\u3002",
|
|
93
|
+
"pairing.stepCreate": "\u521B\u5EFA\u914D\u5BF9\u7533\u8BF7",
|
|
94
|
+
"pairing.stepRun": "\u8FD0\u884C Connector",
|
|
95
|
+
"pairing.stepApprove": "\u6838\u5BF9\u5E76\u6279\u51C6",
|
|
96
|
+
"pairing.formTitle": "\u6DFB\u52A0\u4F60\u7684\u5DE5\u4F5C\u7AD9",
|
|
97
|
+
"pairing.namePlaceholder": "\u4F8B\u5982 office-workstation",
|
|
98
|
+
"pairing.nameHint": "\u4F7F\u7528 1\u201363 \u4F4D\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u70B9\u3001\u4E0B\u5212\u7EBF\u6216\u8FDE\u5B57\u7B26\u3002",
|
|
99
|
+
"pairing.advanced": "\u9AD8\u7EA7\uFF1A\u914D\u5BF9\u7B56\u7565",
|
|
100
|
+
"pairing.guideTitle": "\u7136\u540E\uFF0C\u5728\u5DE5\u4F5C\u7AD9\u4E0A\u8FD0\u884C",
|
|
101
|
+
"pairing.guideDescription": "\u5728\u5B89\u88C5\u4E86 Luvus \u6216 Herdr \u7684\u8BBE\u5907\u7EC8\u7AEF\u4E2D\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\uFF0C\u6309\u63D0\u793A\u8F93\u5165 Gateway \u5730\u5740\u548C\u7533\u8BF7\u7F16\u53F7\u3002",
|
|
102
|
+
"pairing.guideNote": "\u8BA4\u9886\u540E\uFF0C\u6838\u5BF9\u7F51\u9875\u548C\u7EC8\u7AEF\u4E0A\u7684\u4EE3\u7801\uFF0C\u518D\u6279\u51C6\u672C\u673A\u539F\u751F\u5B89\u88C5\u8BBF\u95EE\u3002",
|
|
103
|
+
"pairing.loginRequired": "\u914D\u5BF9\u9700\u8981\u7BA1\u7406\u4F1A\u8BDD",
|
|
104
|
+
"pairing.loginHint": "\u4ECE Gateway \u7684\u7BA1\u7406\u5165\u53E3\u767B\u5F55\u540E\uFF0C\u5373\u53EF\u521B\u5EFA\u7533\u8BF7\u5E76\u6279\u51C6\u65B0\u8BBE\u5907\u3002",
|
|
105
|
+
"connect.title": "\u628A\u5DE5\u4F5C\u7A7A\u95F4\u8FDE\u63A5\u5230 AI",
|
|
106
|
+
"connect.description": "\u8BBE\u5907\u5B8C\u6210\u914D\u5BF9\u540E\uFF0C\u4E3A AI \u5BA2\u6237\u7AEF\u7B7E\u53D1\u4E13\u7528 MCP Bearer\u3002",
|
|
107
|
+
"connect.stepOne": "\u627E\u5230\u5DF2\u6709\u8C03\u7528\u65B9",
|
|
108
|
+
"connect.stepOneNote": "\u5728 Gateway \u673A\u5668\u4E0A\u8FD0\u884C\uFF0C\u8BB0\u4E0B\u5217\u8868\u4E2D\u7684\u5B9E\u9645 caller ID\u3002",
|
|
109
|
+
"connect.stepTwo": "\u4E3A\u5BA2\u6237\u7AEF\u521B\u5EFA\u4EE4\u724C",
|
|
110
|
+
"connect.stepTwoNote": "\u628A EXISTING_CALLER_ID \u66FF\u6362\u4E3A\u4E0A\u4E00\u6B65\u7684 ID\u3002\u65B0\u4EE4\u724C\u4E5F\u53EF\u5728\u7BA1\u7406\u9762\u677F\u4E2D\u518D\u6B21\u67E5\u770B\u3002",
|
|
111
|
+
"connect.stepThree": "\u914D\u7F6E\u4F60\u7684 AI \u5BA2\u6237\u7AEF",
|
|
112
|
+
"connect.stepThreeNote": "\u9009\u62E9 Streamable HTTP\uFF0C\u586B\u5165 Gateway \u5B9E\u9645 MCP URL\uFF0C\u5E76\u7528\u7B7E\u53D1\u7684\u4EE4\u724C\u4F5C\u4E3A Bearer\u3002\u7BA1\u7406\u5458\u5BC6\u7801\u4EC5\u7528\u4E8E\u7BA1\u7406\u9875\u3002",
|
|
113
|
+
"connect.docker": "Docker \u90E8\u7F72\u547D\u4EE4",
|
|
114
|
+
"connect.docs": "\u67E5\u770B\u5B8C\u6574\u8FDE\u63A5\u6307\u5357",
|
|
115
|
+
"copy.action": "\u590D\u5236\u547D\u4EE4",
|
|
116
|
+
"copy.id": "\u590D\u5236\u7533\u8BF7\u7F16\u53F7",
|
|
117
|
+
"copy.done": "\u5DF2\u590D\u5236",
|
|
118
|
+
"copy.failed": "\u672A\u80FD\u590D\u5236\uFF0C\u8BF7\u624B\u52A8\u9009\u62E9\u6587\u672C",
|
|
119
|
+
"empty.title": "\u4F60\u7684\u7B2C\u4E00\u53F0\u8BBE\u5907\uFF0C\u4ECE\u8FD9\u91CC\u5F00\u59CB",
|
|
120
|
+
"empty.description": "\u914D\u5BF9\u4E00\u53F0\u5B89\u88C5\u4E86 Luvus \u6216 Herdr \u7684\u5DE5\u4F5C\u7AD9\uFF0C\u5373\u53EF\u5728\u8FD9\u91CC\u67E5\u770B\u8FDE\u63A5\u3001\u8FD0\u884C\u5B9E\u4F8B\u548C\u8BBF\u95EE\u72B6\u6001\u3002",
|
|
16
121
|
"provider.disable": "\u7981\u7528\u6B64 provider",
|
|
17
122
|
"provider.target": "Provider \u76EE\u6807",
|
|
18
123
|
"provider.effect": "\u4EC5\u7981\u7528\u6B64\u5B9E\u4F8B\u5E76\u9012\u589E binding revision\uFF1B\u8BBE\u5907\u6388\u6743\u53CA\u5176\u5B83\u5B9E\u4F8B\u4E0D\u53D8\u3002\u65E7\u6D41\u3001\u53E5\u67C4\u548C attempt \u4E0D\u590D\u6D3B\uFF0C\u5728\u9014\u539F\u751F\u5DE5\u4F5C\u53EF\u80FD\u7EE7\u7EED\u3002",
|
|
@@ -21,8 +126,9 @@ var chinese = Object.freeze({
|
|
|
21
126
|
"app.subtitle": "\u8BBE\u5907\u4E0E\u8FDE\u63A5\u7BA1\u7406",
|
|
22
127
|
"app.console": "Gateway \u63A7\u5236\u53F0",
|
|
23
128
|
"app.skip": "\u8DF3\u5230\u7BA1\u7406\u5185\u5BB9",
|
|
24
|
-
"app.managedFooter": "\
|
|
25
|
-
"app.footer": "
|
|
129
|
+
"app.managedFooter": "Rovloom \xB7 \u4F60\u7684\u8BBE\u5907\u3001Agent \u4E0E\u5DE5\u4F5C\u6D41\u3002",
|
|
130
|
+
"app.footer": "Rovloom \xB7 Gateway \u63A7\u5236\u53F0",
|
|
131
|
+
"preference.switch": "\u5207\u6362\u4E3A {value}",
|
|
26
132
|
"language.label": "\u8BED\u8A00",
|
|
27
133
|
"language.system": "\u8DDF\u968F\u6D4F\u89C8\u5668",
|
|
28
134
|
"language.chinese": "\u7B80\u4F53\u4E2D\u6587",
|
|
@@ -60,7 +166,7 @@ var chinese = Object.freeze({
|
|
|
60
166
|
"manage.nativeEffect": "\u6B64\u64CD\u4F5C\u4E0D\u4FDD\u8BC1\u505C\u6B62\u6216\u56DE\u6EDA\u539F\u751F\u5DE5\u4F5C\u3002",
|
|
61
167
|
"manage.action": "\u64CD\u4F5C",
|
|
62
168
|
"pairing.title": "\u5728\u7EBF\u6DFB\u52A0\u8BBE\u5907",
|
|
63
|
-
"pairing.intro": "\
|
|
169
|
+
"pairing.intro": "\u4E3A\u5DE5\u4F5C\u7AD9\u8D77\u4E00\u4E2A\u540D\u79F0\u3002\u521B\u5EFA\u914D\u5BF9\u7533\u8BF7\u540E\uFF0C\u6309\u53F3\u4FA7\u6307\u5F15\u5B8C\u6210\u8FDE\u63A5\u3002",
|
|
64
170
|
"pairing.name": "\u65B0\u8BBE\u5907\u540D\u79F0",
|
|
65
171
|
"pairing.policy": "\u6388\u6743\u653F\u7B56\u5F15\u7528",
|
|
66
172
|
"pairing.create": "\u65B0\u5EFA\u914D\u5BF9\u7533\u8BF7",
|
|
@@ -70,7 +176,7 @@ var chinese = Object.freeze({
|
|
|
70
176
|
"pairing.cancel": "\u53D6\u6D88",
|
|
71
177
|
"pairing.created": "\u7533\u8BF7\u5DF2\u521B\u5EFA\u3002\u628A\u7533\u8BF7\u7F16\u53F7\u4EA4\u7ED9\u65B0\u8BBE\u5907\uFF1A",
|
|
72
178
|
"pairing.waitingClaim": "\u7B49\u5F85\u8BBE\u5907\u63D0\u4EA4\u8BA4\u9886\u2026",
|
|
73
|
-
"pairing.claimArrived": "\u8BBE\u5907\u5DF2\
|
|
179
|
+
"pairing.claimArrived": "\u8BBE\u5907\u5DF2\u53D1\u8D77\u8FDE\u63A5\u3002\u786E\u8BA4\u4E0B\u65B9\u4EE3\u7801\u4E0E\u5DE5\u4F5C\u7AD9\u7EC8\u7AEF\u4E00\u81F4\u540E\uFF0C\u518D\u6279\u51C6\u914D\u5BF9\uFF1A",
|
|
74
180
|
"pairing.userCode": "\u6838\u5BF9\u7801",
|
|
75
181
|
"pairing.approve": "\u6279\u51C6\u7B7E\u53D1\u51ED\u636E",
|
|
76
182
|
"pairing.approved": "\u5DF2\u6279\u51C6\uFF0C\u51ED\u636E\u5DF2\u6258\u7BA1\u4EA4\u4ED8\uFF0C\u7B49\u5F85\u8BBE\u5907\u53D6\u56DE\u2026",
|
|
@@ -102,7 +208,7 @@ var chinese = Object.freeze({
|
|
|
102
208
|
"action.cancel": "\u53D6\u6D88\u8BFB\u53D6",
|
|
103
209
|
"reader.pending": "\u6B63\u5728\u8BFB\u53D6\u2026",
|
|
104
210
|
"reader.stale": "\u5FEB\u7167\u5DF2\u8FC7\u671F",
|
|
105
|
-
"reader.snapshot": "\
|
|
211
|
+
"reader.snapshot": "\u5DF2\u66F4\u65B0",
|
|
106
212
|
"reader.empty": "\u65E0\u53EF\u7528\u5FEB\u7167",
|
|
107
213
|
"error.unauthenticated": "\u7BA1\u7406\u4F1A\u8BDD\u672A\u767B\u5F55\u6216\u5DF2\u8FC7\u671F\u3002\u8BF7\u901A\u8FC7\u53D7\u4FDD\u62A4\u7684\u7BA1\u7406\u5458\u5165\u53E3\u91CD\u65B0\u767B\u5F55\u3002",
|
|
108
214
|
"error.forbidden": "\u5F53\u524D\u7BA1\u7406\u5458\u6CA1\u6709\u6B64\u8303\u56F4\u7684\u67E5\u770B\u6743\u9650\u3002",
|
|
@@ -245,6 +351,111 @@ var chinese = Object.freeze({
|
|
|
245
351
|
"gateway.degradedNote": "\u5F53\u524D\u7981\u6B62\u7BA1\u7406\u5199\u5165\uFF1B\u6301\u4E45\u64A4\u9500\u7B49\u64CD\u4F5C\u53EF\u80FD\u5C1A\u672A\u751F\u6548\uFF0C\u8BF7\u4F7F\u7528\u53D7\u4FDD\u62A4\u672C\u673A\u8BCA\u65AD\u3002"
|
|
246
352
|
});
|
|
247
353
|
var english = Object.freeze({
|
|
354
|
+
"resources.previous": "Previous",
|
|
355
|
+
"resources.next": "Next",
|
|
356
|
+
"resources.tokens": "MCP tokens",
|
|
357
|
+
"resources.pairings": "Pairing records",
|
|
358
|
+
"resources.name": "Name",
|
|
359
|
+
"resources.expiry": "Valid for",
|
|
360
|
+
"resources.days": "{count} days",
|
|
361
|
+
"resources.create": "Create token",
|
|
362
|
+
"resources.tokenNote": "Create tokens for your AI clients. View, copy or delete them anytime. They work with current and future paired devices.",
|
|
363
|
+
"resources.endpoint": "MCP endpoint",
|
|
364
|
+
"resources.endpointNote": "For remote access, replace 127.0.0.1 with your Gateway address. Set Authorization to Bearer followed by the token in your AI client.",
|
|
365
|
+
"resources.expiresAt": "Expires {time}",
|
|
366
|
+
"resources.active": "Active",
|
|
367
|
+
"resources.expired": "Expired",
|
|
368
|
+
"resources.show": "Show",
|
|
369
|
+
"resources.hide": "Hide",
|
|
370
|
+
"resources.legacy": "This older token only has a hash and cannot be recovered.",
|
|
371
|
+
"resources.reissue": "Reissue",
|
|
372
|
+
"resources.confirmReissue": "Reissue this token? The previous token will stop working immediately.",
|
|
373
|
+
"resources.delete": "Delete",
|
|
374
|
+
"resources.deleteDevice": "Delete device",
|
|
375
|
+
"resources.deleteDeviceNote": "Remove this device, runtime configuration, pairing records and call content, and disconnect it.",
|
|
376
|
+
"resources.confirmDevice": "Delete this device and its pairing and call records? Pair it again to reconnect.",
|
|
377
|
+
"resources.confirmToken": "Delete token \u201C{name}\u201D? Clients using it will lose access.",
|
|
378
|
+
"resources.confirmPairing": "Delete this pairing record? Unfinished pairing will stop. Paired devices are kept.",
|
|
379
|
+
"resources.emptyTokens": "No MCP tokens yet. Create one above to connect your AI client.",
|
|
380
|
+
"resources.emptyPairings": "No pairing records.",
|
|
381
|
+
"resources.loading": "Loading\u2026",
|
|
382
|
+
"resources.saving": "Saving\u2026",
|
|
383
|
+
"resources.saved": "Saved",
|
|
384
|
+
"resources.failed": "The operation did not complete. Refresh and try again.",
|
|
385
|
+
"resources.invalid": "Use a unique name (letters, numbers, - or _) and a valid expiry.",
|
|
386
|
+
"resources.pending": "Pending",
|
|
387
|
+
"resources.paired": "Paired",
|
|
388
|
+
"resources.cancelled": "Cancelled",
|
|
389
|
+
"nav.workspace": "WORKSPACE",
|
|
390
|
+
"nav.overview": "Overview",
|
|
391
|
+
"nav.devices": "Devices",
|
|
392
|
+
"nav.pairing": "Pair a device",
|
|
393
|
+
"nav.connect": "Connect MCP",
|
|
394
|
+
"nav.resources": "RESOURCES",
|
|
395
|
+
"nav.docs": "Quick start",
|
|
396
|
+
"nav.console": "Management console",
|
|
397
|
+
"overview.eyebrow": "YOUR WORKSPACE, CONNECTED",
|
|
398
|
+
"overview.title": "Workspace overview",
|
|
399
|
+
"overview.description": "Keep your gateway and devices in view. Bring your remote workflow together.",
|
|
400
|
+
"overview.visible": "Devices on this page",
|
|
401
|
+
"overview.visibleNote": "In the current authorized list page",
|
|
402
|
+
"overview.online": "Verified connections",
|
|
403
|
+
"overview.onlineNote": "Connection and identity are current",
|
|
404
|
+
"overview.attention": "Needs attention",
|
|
405
|
+
"overview.attentionNote": "Devices without a verified connection",
|
|
406
|
+
"overview.scope": "Device counts cover this page only. Expired observations do not establish current state.",
|
|
407
|
+
"overview.gateway": "Gateway health",
|
|
408
|
+
"overview.available": "Available",
|
|
409
|
+
"overview.degraded": "Degraded",
|
|
410
|
+
"overview.unavailable": "Unavailable",
|
|
411
|
+
"overview.unverified": "Unverified",
|
|
412
|
+
"overview.store": "Storage",
|
|
413
|
+
"overview.transport": "Device transport",
|
|
414
|
+
"overview.native": "Native runtime",
|
|
415
|
+
"overview.manage": "Manage devices",
|
|
416
|
+
"overview.deviceSection": "Your connected workspaces",
|
|
417
|
+
"overview.deviceSubtitle": "Connect your tools and workspaces, one device at a time.",
|
|
418
|
+
"devices.search": "Search devices on this page",
|
|
419
|
+
"devices.searchPlaceholder": "Search by device name, ID or label\u2026",
|
|
420
|
+
"devices.filterAll": "All devices",
|
|
421
|
+
"devices.filterOnline": "Verified",
|
|
422
|
+
"devices.filterAttention": "Needs attention",
|
|
423
|
+
"devices.results": "Showing {shown} of {count} devices on this page",
|
|
424
|
+
"devices.noMatches": "No matching devices",
|
|
425
|
+
"devices.noMatchesHint": "Try another name, ID or label, or change the status filter.",
|
|
426
|
+
"devices.inspect": "View details",
|
|
427
|
+
"devices.record": "Observation details",
|
|
428
|
+
"devices.open": "View details for {name}",
|
|
429
|
+
"pairing.pageTitle": "Bring a new device on board",
|
|
430
|
+
"pairing.pageDescription": "Create an application, run Connector, and verify the code. Your workspace is three steps away.",
|
|
431
|
+
"pairing.stepCreate": "Create an application",
|
|
432
|
+
"pairing.stepRun": "Run Connector",
|
|
433
|
+
"pairing.stepApprove": "Verify and approve",
|
|
434
|
+
"pairing.formTitle": "Add your workstation",
|
|
435
|
+
"pairing.namePlaceholder": "e.g. office-workstation",
|
|
436
|
+
"pairing.nameHint": "Use 1\u201363 lowercase letters, digits, dots, underscores or hyphens.",
|
|
437
|
+
"pairing.advanced": "Advanced: pairing policy",
|
|
438
|
+
"pairing.guideTitle": "Then, on your workstation",
|
|
439
|
+
"pairing.guideDescription": "Run this command on the device with Luvus or Herdr installed. Enter the Gateway address and application ID when prompted.",
|
|
440
|
+
"pairing.guideNote": "Match the code on this page with the terminal, then approve local native installation access.",
|
|
441
|
+
"pairing.loginRequired": "A management session is required",
|
|
442
|
+
"pairing.loginHint": "Sign in through the Gateway management entry to create applications and approve new devices.",
|
|
443
|
+
"connect.title": "Connect your workspace to AI",
|
|
444
|
+
"connect.description": "Once a device is paired, issue a dedicated MCP Bearer for your AI client.",
|
|
445
|
+
"connect.stepOne": "Find an existing caller",
|
|
446
|
+
"connect.stepOneNote": "Run this on the Gateway machine and note the actual caller ID in the list.",
|
|
447
|
+
"connect.stepTwo": "Issue a client token",
|
|
448
|
+
"connect.stepTwoNote": "Replace EXISTING_CALLER_ID with that ID. New tokens can also be viewed later in the Dashboard.",
|
|
449
|
+
"connect.stepThree": "Configure your AI client",
|
|
450
|
+
"connect.stepThreeNote": "Choose Streamable HTTP, enter the actual Gateway MCP URL, and use the issued token as Bearer. The administrator password is for this console only.",
|
|
451
|
+
"connect.docker": "Docker deployment commands",
|
|
452
|
+
"connect.docs": "Read the connection guide",
|
|
453
|
+
"copy.action": "Copy command",
|
|
454
|
+
"copy.id": "Copy application ID",
|
|
455
|
+
"copy.done": "Copied",
|
|
456
|
+
"copy.failed": "Copy unavailable; select the text manually",
|
|
457
|
+
"empty.title": "Start with your first device",
|
|
458
|
+
"empty.description": "Pair a workstation running Luvus or Herdr. Its connection, runtime instances and access status will appear here.",
|
|
248
459
|
"provider.disable": "Disable this provider",
|
|
249
460
|
"provider.target": "Provider target",
|
|
250
461
|
"provider.effect": "Disables only this instance and advances its binding revision. Device authorization and other instances stay independent. Old streams, handles and attempts do not revive; in-flight native work may continue.",
|
|
@@ -253,8 +464,9 @@ var english = Object.freeze({
|
|
|
253
464
|
"app.subtitle": "Device and connection management",
|
|
254
465
|
"app.console": "Gateway console",
|
|
255
466
|
"app.skip": "Skip to management content",
|
|
256
|
-
"app.managedFooter": "
|
|
257
|
-
"app.footer": "
|
|
467
|
+
"app.managedFooter": "Rovloom \xB7 Your devices, agents and workflows.",
|
|
468
|
+
"app.footer": "Rovloom \xB7 Gateway console",
|
|
469
|
+
"preference.switch": "Switch to {value}",
|
|
258
470
|
"language.label": "Language",
|
|
259
471
|
"language.system": "Browser language",
|
|
260
472
|
"language.chinese": "\u7B80\u4F53\u4E2D\u6587",
|
|
@@ -292,7 +504,7 @@ var english = Object.freeze({
|
|
|
292
504
|
"manage.nativeEffect": "This operation does not guarantee stopping or rolling back native work.",
|
|
293
505
|
"manage.action": "Action",
|
|
294
506
|
"pairing.title": "Add a device online",
|
|
295
|
-
"pairing.intro": "
|
|
507
|
+
"pairing.intro": "Give your workstation a name. Create an application, then follow the steps on the right.",
|
|
296
508
|
"pairing.name": "New device name",
|
|
297
509
|
"pairing.policy": "Authorization policy reference",
|
|
298
510
|
"pairing.create": "Create pairing application",
|
|
@@ -334,7 +546,7 @@ var english = Object.freeze({
|
|
|
334
546
|
"action.cancel": "Cancel read",
|
|
335
547
|
"reader.pending": "Reading\u2026",
|
|
336
548
|
"reader.stale": "Snapshot expired",
|
|
337
|
-
"reader.snapshot": "
|
|
549
|
+
"reader.snapshot": "Up to date",
|
|
338
550
|
"reader.empty": "No snapshot",
|
|
339
551
|
"error.unauthenticated": "The management session is missing or expired. Sign in through the protected administrator entry.",
|
|
340
552
|
"error.forbidden": "The current administrator cannot view this scope.",
|
|
@@ -508,6 +720,84 @@ function createLocalizer(language) {
|
|
|
508
720
|
};
|
|
509
721
|
}
|
|
510
722
|
|
|
723
|
+
// src/webui/icons.ts
|
|
724
|
+
var paths = {
|
|
725
|
+
overview: "M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z",
|
|
726
|
+
devices: "M3 4h18v12H3zM8 21h8M12 16v5",
|
|
727
|
+
pairing: "M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M3 16v3a2 2 0 0 0 2 2h3M21 16v3a2 2 0 0 1-2 2h-3M12 8v8M8 12h8",
|
|
728
|
+
connect: "M9 8 6 12l3 4M15 8l3 4-3 4M13 5l-2 14",
|
|
729
|
+
arrow: "M5 12h14M14 7l5 5-5 5",
|
|
730
|
+
plus: "M12 5v14M5 12h14",
|
|
731
|
+
search: "M21 21l-5-5M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0",
|
|
732
|
+
activity: "M3 12h4l3-8 4 16 3-8h4",
|
|
733
|
+
shield: "M12 3 4 6v6c0 5 8 9 8 9s8-4 8-9V6zM8 12l3 3 5-6",
|
|
734
|
+
attention: "M12 3 2 21h20zM12 9v4M12 17h.01",
|
|
735
|
+
book: "M12 5c-3-2-6-2-10-1v15c4-1 7-1 10 1 3-2 6-2 10-1V4c-4-1-7-1-10 1zM12 5v15",
|
|
736
|
+
refresh: "M20 7v5h-5M4 17v-5h5M6 7a7 7 0 0 1 12-1l2 3M4 15l2 3a7 7 0 0 0 12-1",
|
|
737
|
+
copy: "M9 9h12v12H9zM15 9V3H3v12h6",
|
|
738
|
+
check: "M5 12l4 4L19 6",
|
|
739
|
+
chevron: "M9 5l7 7-7 7",
|
|
740
|
+
gateway: "M4 3h16v7H4zM4 14h16v7H4zM8 6.5h.01M8 17.5h.01M16 6.5h1M16 17.5h1"
|
|
741
|
+
};
|
|
742
|
+
function uiIcon(name) {
|
|
743
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
744
|
+
svg.classList.add("ui-icon");
|
|
745
|
+
for (const [key, value] of Object.entries({
|
|
746
|
+
viewBox: "0 0 24 24",
|
|
747
|
+
fill: "none",
|
|
748
|
+
stroke: "currentColor",
|
|
749
|
+
"stroke-width": "1.65",
|
|
750
|
+
"stroke-linecap": "round",
|
|
751
|
+
"stroke-linejoin": "round",
|
|
752
|
+
"aria-hidden": "true",
|
|
753
|
+
focusable: "false"
|
|
754
|
+
})) {
|
|
755
|
+
svg.setAttribute(key, value);
|
|
756
|
+
}
|
|
757
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
758
|
+
path.setAttribute("d", paths[name]);
|
|
759
|
+
svg.append(path);
|
|
760
|
+
return svg;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/webui/clipboard.ts
|
|
764
|
+
function copyControl(value, label) {
|
|
765
|
+
const button = document.createElement("button");
|
|
766
|
+
button.type = "button";
|
|
767
|
+
button.className = "copy-button";
|
|
768
|
+
button.dataset.copy = value;
|
|
769
|
+
button.setAttribute("aria-label", label);
|
|
770
|
+
button.title = label;
|
|
771
|
+
button.append(uiIcon("copy"));
|
|
772
|
+
return button;
|
|
773
|
+
}
|
|
774
|
+
async function copyText(value) {
|
|
775
|
+
try {
|
|
776
|
+
if (navigator.clipboard?.writeText) {
|
|
777
|
+
await navigator.clipboard.writeText(value);
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
} catch {}
|
|
781
|
+
const active = document.activeElement;
|
|
782
|
+
const input = document.createElement("textarea");
|
|
783
|
+
input.className = "copy-buffer";
|
|
784
|
+
input.value = value;
|
|
785
|
+
input.readOnly = true;
|
|
786
|
+
input.setAttribute("aria-label", "Copy");
|
|
787
|
+
document.body.append(input);
|
|
788
|
+
try {
|
|
789
|
+
input.select();
|
|
790
|
+
return document.execCommand("copy");
|
|
791
|
+
} catch {
|
|
792
|
+
return false;
|
|
793
|
+
} finally {
|
|
794
|
+
input.remove();
|
|
795
|
+
if (active instanceof HTMLElement && active.isConnected) {
|
|
796
|
+
active.focus({ preventScroll: true });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
511
801
|
// src/webui/view.ts
|
|
512
802
|
var connectionNames = {
|
|
513
803
|
connecting: "connection.connecting",
|
|
@@ -579,6 +869,9 @@ function routeHref(route) {
|
|
|
579
869
|
if (route.providerCursor !== null) {
|
|
580
870
|
parameters.set("providers", route.providerCursor);
|
|
581
871
|
}
|
|
872
|
+
if (route.machineId === null && route.section) {
|
|
873
|
+
parameters.set("view", route.section);
|
|
874
|
+
}
|
|
582
875
|
return `#${parameters.toString()}`;
|
|
583
876
|
}
|
|
584
877
|
function parseRoute(hash) {
|
|
@@ -587,7 +880,7 @@ function parseRoute(hash) {
|
|
|
587
880
|
return empty;
|
|
588
881
|
}
|
|
589
882
|
const parameters = new URLSearchParams(hash.replace(/^#/, ""));
|
|
590
|
-
for (const key of ["machine", "cursor", "providers"]) {
|
|
883
|
+
for (const key of ["machine", "cursor", "providers", "view"]) {
|
|
591
884
|
if (parameters.getAll(key).length > 1 || (parameters.get(key)?.length ?? 0) > 1024) {
|
|
592
885
|
return empty;
|
|
593
886
|
}
|
|
@@ -596,7 +889,8 @@ function parseRoute(hash) {
|
|
|
596
889
|
return {
|
|
597
890
|
machineId,
|
|
598
891
|
cursor: machineId ? null : parameters.get("cursor") || null,
|
|
599
|
-
providerCursor: machineId ? parameters.get("providers") || null : null
|
|
892
|
+
providerCursor: machineId ? parameters.get("providers") || null : null,
|
|
893
|
+
...!machineId && ["devices", "pairing", "connect"].includes(parameters.get("view") ?? "") ? { section: parameters.get("view") } : {}
|
|
600
894
|
};
|
|
601
895
|
}
|
|
602
896
|
function current(observedAt, freshness) {
|
|
@@ -624,7 +918,7 @@ function listObservation(key, observedAt, freshness) {
|
|
|
624
918
|
function currentDeviceLink(device, freshness) {
|
|
625
919
|
return device.persisted_state === "online" && current(device.observed_at, freshness) && device.connection.verification === "verified" && current(device.connection.observed_at, freshness) && device.connection.value.state === "online" && device.authentication.verification === "verified" && current(device.authentication.observed_at, freshness) && device.authentication.value.state === "authenticated";
|
|
626
920
|
}
|
|
627
|
-
function deviceBadges(device, freshness) {
|
|
921
|
+
function deviceBadges(device, freshness, compact = false) {
|
|
628
922
|
const { i18n } = freshness;
|
|
629
923
|
const { t } = i18n;
|
|
630
924
|
const result = node("div", "", "badges");
|
|
@@ -661,7 +955,9 @@ function deviceBadges(device, freshness) {
|
|
|
661
955
|
} else {
|
|
662
956
|
result.append(badge(t("device.authUnknown", { reason: i18n.reason(authentication.reason_code) }), "warning"));
|
|
663
957
|
}
|
|
664
|
-
|
|
958
|
+
if (!compact) {
|
|
959
|
+
result.append(badge(t("device.readyUnknown")));
|
|
960
|
+
}
|
|
665
961
|
return result;
|
|
666
962
|
}
|
|
667
963
|
function unavailableActions(kind, i18n) {
|
|
@@ -703,20 +999,154 @@ function deviceActions(page, freshness, connected) {
|
|
|
703
999
|
}
|
|
704
1000
|
return section;
|
|
705
1001
|
}
|
|
706
|
-
function
|
|
1002
|
+
function overviewMetrics(page, freshness) {
|
|
1003
|
+
const t = freshness.i18n.t;
|
|
1004
|
+
const online = page.devices.items.filter((device) => currentDeviceLink(device, freshness)).length;
|
|
1005
|
+
const section = node("section", "", "overview-metrics");
|
|
1006
|
+
section.setAttribute("aria-label", t("nav.overview"));
|
|
1007
|
+
const rows = [
|
|
1008
|
+
["overview.visible", "overview.visibleNote", String(page.devices.items.length), "devices"],
|
|
1009
|
+
["overview.online", "overview.onlineNote", freshness.pageStale ? "\u2014" : String(online), "shield"],
|
|
1010
|
+
[
|
|
1011
|
+
"overview.attention",
|
|
1012
|
+
"overview.attentionNote",
|
|
1013
|
+
freshness.pageStale ? "\u2014" : String(page.devices.items.length - online),
|
|
1014
|
+
"attention"
|
|
1015
|
+
]
|
|
1016
|
+
];
|
|
1017
|
+
for (const [label, description, value, icon] of rows) {
|
|
1018
|
+
const card = node("article", "", "metric-card");
|
|
1019
|
+
const top = node("div", "", "metric-heading");
|
|
1020
|
+
top.append(node("span", t(label)), uiIcon(icon));
|
|
1021
|
+
card.append(top, node("strong", value, "metric-value"), node("p", t(description), "muted"));
|
|
1022
|
+
section.append(card);
|
|
1023
|
+
}
|
|
1024
|
+
section.append(node("p", t("overview.scope"), "metric-scope"));
|
|
1025
|
+
return section;
|
|
1026
|
+
}
|
|
1027
|
+
function guidePage(section, i18n, management) {
|
|
1028
|
+
const { t } = i18n;
|
|
1029
|
+
const heading = node("header", "", "page-heading");
|
|
1030
|
+
const copy = node("div", "", "heading-copy");
|
|
1031
|
+
copy.append(node("p", t("nav.workspace"), "eyebrow"));
|
|
1032
|
+
copy.append(node("h1", t(section === "pairing" ? "pairing.pageTitle" : "connect.title")));
|
|
1033
|
+
copy.append(node("p", t(section === "pairing" ? "pairing.pageDescription" : "connect.description"), "muted"));
|
|
1034
|
+
heading.append(copy);
|
|
1035
|
+
const body = node("section", "", "connection-guide");
|
|
1036
|
+
if (section === "pairing") {
|
|
1037
|
+
if (!management) {
|
|
1038
|
+
const empty = node("div", "", "empty");
|
|
1039
|
+
empty.append(uiIcon("shield"), node("h2", t("pairing.loginRequired")), node("p", t("pairing.loginHint"), "muted"));
|
|
1040
|
+
body.append(empty);
|
|
1041
|
+
}
|
|
1042
|
+
return { heading, body };
|
|
1043
|
+
}
|
|
1044
|
+
const commands = ["rovloom token list", "rovloom token create --principal my_client --like EXISTING_CALLER_ID"];
|
|
1045
|
+
for (const [index, title, description] of [
|
|
1046
|
+
[0, "connect.stepOne", "connect.stepOneNote"],
|
|
1047
|
+
[1, "connect.stepTwo", "connect.stepTwoNote"],
|
|
1048
|
+
[2, "connect.stepThree", "connect.stepThreeNote"]
|
|
1049
|
+
]) {
|
|
1050
|
+
const card = node("article", "", "connection-step");
|
|
1051
|
+
const number = node("span", String(index + 1).padStart(2, "0"), "step-number");
|
|
1052
|
+
const details = node("div", "", "connection-step-body");
|
|
1053
|
+
details.append(node("h2", t(title)), node("p", t(description), "muted"));
|
|
1054
|
+
if (index < commands.length) {
|
|
1055
|
+
const command = commands[index];
|
|
1056
|
+
const block = node("div", "", "command-snippet");
|
|
1057
|
+
block.append(node("code", command), copyControl(command, t("copy.action")));
|
|
1058
|
+
const docker = node("details", "", "docker-command");
|
|
1059
|
+
docker.dataset.disclosure = "docker-command-" + index;
|
|
1060
|
+
docker.append(node("summary", t("connect.docker")));
|
|
1061
|
+
const dockerCommand = command.replace(/^rovloom /u, "docker compose run --rm gateway ");
|
|
1062
|
+
const snippet = node("div", "", "command-snippet");
|
|
1063
|
+
snippet.append(node("code", dockerCommand), copyControl(dockerCommand, t("copy.action")));
|
|
1064
|
+
docker.append(snippet);
|
|
1065
|
+
details.append(block, docker);
|
|
1066
|
+
} else {
|
|
1067
|
+
const link = node("a", t("connect.docs"), "text-link");
|
|
1068
|
+
link.href = `https://github.com/ArnoChenFx/rovloom/blob/main/docs/QUICK-START${i18n.language === "zh-CN" ? "-zh" : ""}.md`;
|
|
1069
|
+
link.target = "_blank";
|
|
1070
|
+
link.rel = "noreferrer noopener";
|
|
1071
|
+
details.append(link);
|
|
1072
|
+
}
|
|
1073
|
+
card.append(number, details);
|
|
1074
|
+
body.append(card);
|
|
1075
|
+
}
|
|
1076
|
+
return { heading, body };
|
|
1077
|
+
}
|
|
1078
|
+
function deviceList(page, freshness, route, filter) {
|
|
707
1079
|
const { i18n } = freshness;
|
|
708
1080
|
const { t } = i18n;
|
|
709
1081
|
const heading = node("header", "", "page-heading");
|
|
710
|
-
|
|
1082
|
+
const overview = route.section !== "devices";
|
|
1083
|
+
const copy = node("div", "", "heading-copy");
|
|
1084
|
+
copy.append(node("p", t("overview.eyebrow"), "eyebrow"));
|
|
1085
|
+
copy.append(node("h1", t(overview ? "overview.title" : "devices.heading")), node("p", t(overview ? "overview.description" : "devices.description"), "muted"));
|
|
1086
|
+
heading.append(copy);
|
|
1087
|
+
const add = node("a", "", "primary-button");
|
|
1088
|
+
add.href = "#view=pairing";
|
|
1089
|
+
add.append(uiIcon("plus"), node("span", t("nav.pairing")));
|
|
1090
|
+
heading.append(add);
|
|
711
1091
|
const section = node("section", "", "devices-section");
|
|
1092
|
+
const sectionHeader = node("div", "", "section-heading");
|
|
1093
|
+
const sectionCopy = node("div");
|
|
1094
|
+
sectionCopy.append(node("h2", t(overview ? "overview.deviceSection" : "devices.heading")), node("p", t("overview.deviceSubtitle"), "muted"));
|
|
1095
|
+
sectionHeader.append(sectionCopy);
|
|
1096
|
+
if (overview) {
|
|
1097
|
+
const all = node("a", "", "text-link");
|
|
1098
|
+
all.href = "#view=devices";
|
|
1099
|
+
all.append(node("span", t("overview.manage")), uiIcon("arrow"));
|
|
1100
|
+
sectionHeader.append(all);
|
|
1101
|
+
}
|
|
1102
|
+
section.append(sectionHeader);
|
|
1103
|
+
if (page.devices.items.length) {
|
|
1104
|
+
const tools = node("div", "", "device-tools");
|
|
1105
|
+
const search = node("label", "", "search-field");
|
|
1106
|
+
const input = node("input");
|
|
1107
|
+
input.type = "search";
|
|
1108
|
+
input.name = "device-search";
|
|
1109
|
+
input.value = filter.query;
|
|
1110
|
+
input.placeholder = t("devices.searchPlaceholder");
|
|
1111
|
+
input.setAttribute("aria-label", t("devices.search"));
|
|
1112
|
+
search.append(uiIcon("search"), input);
|
|
1113
|
+
const filters = node("div", "", "device-filters");
|
|
1114
|
+
filters.setAttribute("role", "group");
|
|
1115
|
+
filters.setAttribute("aria-label", t("devices.health"));
|
|
1116
|
+
for (const [value, key] of [
|
|
1117
|
+
["all", "devices.filterAll"],
|
|
1118
|
+
["online", "devices.filterOnline"],
|
|
1119
|
+
["attention", "devices.filterAttention"]
|
|
1120
|
+
]) {
|
|
1121
|
+
const button = node("button", t(key));
|
|
1122
|
+
button.type = "button";
|
|
1123
|
+
button.dataset.deviceFilter = value;
|
|
1124
|
+
button.setAttribute("aria-pressed", String(value === filter.status));
|
|
1125
|
+
filters.append(button);
|
|
1126
|
+
}
|
|
1127
|
+
tools.append(search, filters);
|
|
1128
|
+
section.append(tools);
|
|
1129
|
+
}
|
|
1130
|
+
const items = page.devices.items.filter((device) => {
|
|
1131
|
+
const query = filter.query.trim().toLocaleLowerCase(i18n.language);
|
|
1132
|
+
const matches = !query || [device.name, device.machine_id, ...device.labels ?? []].some((value) => value.toLocaleLowerCase(i18n.language).includes(query));
|
|
1133
|
+
const online = currentDeviceLink(device, freshness);
|
|
1134
|
+
return matches && (filter.status === "all" || (filter.status === "online" ? online : !online));
|
|
1135
|
+
});
|
|
712
1136
|
if (page.devices.items.length === 0) {
|
|
713
1137
|
const empty = node("div", "", "empty");
|
|
714
|
-
|
|
1138
|
+
const symbol = node("span", "", "empty-illustration");
|
|
1139
|
+
symbol.append(uiIcon("devices"));
|
|
1140
|
+
empty.append(symbol, node("h2", t("empty.title")), node("p", t("empty.description"), "muted"));
|
|
1141
|
+
const pair = node("a", "", "primary-button");
|
|
1142
|
+
pair.href = "#view=pairing";
|
|
1143
|
+
pair.append(uiIcon("plus"), node("span", t("nav.pairing")));
|
|
1144
|
+
empty.append(pair);
|
|
715
1145
|
section.append(empty);
|
|
716
1146
|
}
|
|
717
1147
|
const columns = node("div", "", "device-columns");
|
|
718
1148
|
columns.setAttribute("aria-hidden", "true");
|
|
719
|
-
for (const key of ["devices.identity", "devices.health", "devices.activity", "devices.
|
|
1149
|
+
for (const key of ["devices.identity", "devices.health", "devices.activity", "devices.record"]) {
|
|
720
1150
|
columns.append(node("span", t(key)));
|
|
721
1151
|
}
|
|
722
1152
|
if (page.devices.items.length) {
|
|
@@ -724,32 +1154,60 @@ function deviceList(page, freshness) {
|
|
|
724
1154
|
}
|
|
725
1155
|
const list = node("ul", "", "device-list");
|
|
726
1156
|
list.setAttribute("aria-label", t("devices.heading"));
|
|
727
|
-
for (const device of
|
|
1157
|
+
for (const device of items) {
|
|
728
1158
|
const row = node("li", "", "device-card");
|
|
729
1159
|
const identity = node("div", "", "device-identity");
|
|
730
1160
|
const link = node("a", device.name, "device-name");
|
|
731
1161
|
link.href = routeHref({ machineId: device.machine_id, cursor: null, providerCursor: null });
|
|
732
|
-
|
|
733
|
-
|
|
1162
|
+
const deviceIcon = node("span", "", "device-icon");
|
|
1163
|
+
deviceIcon.append(uiIcon("devices"));
|
|
1164
|
+
const deviceCopy = node("div", "", "device-copy");
|
|
1165
|
+
const identifier = node("code", device.machine_id);
|
|
1166
|
+
identifier.title = device.machine_id;
|
|
1167
|
+
deviceCopy.append(link, identifier);
|
|
1168
|
+
identity.append(deviceIcon, deviceCopy);
|
|
1169
|
+
deviceCopy.append(node("p", device.labels === null ? t("value.noLabelRecord") : device.labels.length ? device.labels.join(" \xB7 ") : t("value.noLabels"), "muted"));
|
|
734
1170
|
const health = node("div", "", "device-health");
|
|
735
|
-
health.append(node("span", t("devices.health"), "cell-label"), deviceBadges(device, freshness));
|
|
1171
|
+
health.append(node("span", t("devices.health"), "cell-label"), deviceBadges(device, freshness, true));
|
|
736
1172
|
const heartbeat = device.connection.verification === "verified" ? device.connection.value.last_heartbeat_at : null;
|
|
737
1173
|
const activity = node("div", "", "device-activity");
|
|
738
1174
|
activity.append(node("span", t("devices.activity"), "cell-label"));
|
|
739
1175
|
activity.append(node("strong", heartbeat === null ? t("time.noHeartbeat") : i18n.age(heartbeat, freshness.now), "activity-age"));
|
|
740
1176
|
activity.append(node("p", t("devices.received", { time: i18n.timestamp(heartbeat) }), "muted"));
|
|
741
1177
|
const observations = node("div", "", "device-observations");
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1178
|
+
const record = node("details", "", "device-record");
|
|
1179
|
+
record.dataset.disclosure = "device-record-" + device.machine_id;
|
|
1180
|
+
const summary = node("summary", t("devices.record"));
|
|
1181
|
+
record.append(summary);
|
|
1182
|
+
record.append(listObservation("devices.observed", device.observed_at, freshness));
|
|
1183
|
+
record.append(listObservation("devices.connectionObserved", device.connection.verification === "verified" ? device.connection.observed_at : null, freshness));
|
|
1184
|
+
record.append(listObservation("devices.authObserved", device.authentication.verification === "verified" ? device.authentication.observed_at : null, freshness));
|
|
1185
|
+
record.append(node("p", t("device.readyUnknown"), "muted"));
|
|
1186
|
+
const inspect = node("a", "", "inspect-device");
|
|
1187
|
+
inspect.href = link.href;
|
|
1188
|
+
inspect.setAttribute("aria-label", t("devices.open", { name: device.name }));
|
|
1189
|
+
inspect.append(uiIcon("arrow"));
|
|
1190
|
+
observations.append(record, inspect);
|
|
746
1191
|
row.append(identity, health, activity, observations);
|
|
747
1192
|
list.append(row);
|
|
748
1193
|
}
|
|
749
1194
|
section.append(list);
|
|
1195
|
+
if (page.devices.items.length && !items.length) {
|
|
1196
|
+
const empty = node("div", "", "empty search-empty");
|
|
1197
|
+
empty.append(uiIcon("search"), node("h3", t("devices.noMatches")), node("p", t("devices.noMatchesHint"), "muted"));
|
|
1198
|
+
section.append(empty);
|
|
1199
|
+
}
|
|
1200
|
+
if (page.devices.items.length) {
|
|
1201
|
+
section.append(node("p", t("devices.results", { shown: items.length, count: page.devices.items.length }), "list-count"));
|
|
1202
|
+
}
|
|
750
1203
|
if (page.devices.next_cursor !== undefined) {
|
|
751
1204
|
const next = node("a", t("devices.next"), "button-link");
|
|
752
|
-
next.href = routeHref({
|
|
1205
|
+
next.href = routeHref({
|
|
1206
|
+
machineId: null,
|
|
1207
|
+
cursor: page.devices.next_cursor,
|
|
1208
|
+
providerCursor: null,
|
|
1209
|
+
section: route.section
|
|
1210
|
+
});
|
|
753
1211
|
section.append(next);
|
|
754
1212
|
}
|
|
755
1213
|
return { heading, body: section };
|
|
@@ -894,11 +1352,15 @@ function syncChildren(parent, desired) {
|
|
|
894
1352
|
parent.removeChild(parent.lastChild);
|
|
895
1353
|
}
|
|
896
1354
|
}
|
|
897
|
-
function renderPage(content, state, route, i18n = createLocalizer("zh-CN"), management = false) {
|
|
1355
|
+
function renderPage(content, state, route, i18n = createLocalizer("zh-CN"), management = false, filter = { query: "", status: "all" }) {
|
|
898
1356
|
const { t } = i18n;
|
|
899
1357
|
const focused = content.contains(document.activeElement) && document.activeElement instanceof HTMLAnchorElement ? document.activeElement.getAttribute("href") : null;
|
|
900
1358
|
const metadataWasOpen = content.querySelector(".gateway-metadata")?.open ?? false;
|
|
901
1359
|
const metadataHadFocus = content.contains(document.activeElement) && document.activeElement?.matches(".gateway-metadata > summary");
|
|
1360
|
+
const disclosures = new Map([...content.querySelectorAll("details[data-disclosure]")].map((details) => [
|
|
1361
|
+
details.dataset.disclosure,
|
|
1362
|
+
details.open
|
|
1363
|
+
]));
|
|
902
1364
|
const view = document.createDocumentFragment();
|
|
903
1365
|
if (state.problem) {
|
|
904
1366
|
const message = node("p", t(problemNames[state.problem]), "notice warning");
|
|
@@ -927,8 +1389,28 @@ function renderPage(content, state, route, i18n = createLocalizer("zh-CN"), mana
|
|
|
927
1389
|
gatewayHeading.append(node("h2", t("gateway.heading")));
|
|
928
1390
|
gatewayHeading.append(badge(status.mode === "active" ? state.stale ? t("gateway.activeHistory") : t("gateway.active") : t("gateway.degraded"), status.mode === "active" && !state.stale ? "good" : "warning"));
|
|
929
1391
|
gatewayHeading.append(badge(state.stale ? t("reader.stale") : t("gateway.fresh"), state.stale ? "warning" : "neutral"));
|
|
930
|
-
gateway
|
|
931
|
-
|
|
1392
|
+
const mark = node("span", "", "gateway-symbol");
|
|
1393
|
+
mark.append(uiIcon("gateway"));
|
|
1394
|
+
gatewayHeading.prepend(mark);
|
|
1395
|
+
const components = node("div", "", "gateway-components");
|
|
1396
|
+
for (const [name, key] of [
|
|
1397
|
+
["store", "overview.store"],
|
|
1398
|
+
["transport", "overview.transport"],
|
|
1399
|
+
["native", "overview.native"]
|
|
1400
|
+
]) {
|
|
1401
|
+
const item = node("div", "", "component-item");
|
|
1402
|
+
item.append(node("span", t(key), "muted"), badge(t(`overview.${status.components[name]}`), status.components[name] === "available" && !state.stale ? "good" : "neutral"));
|
|
1403
|
+
components.append(item);
|
|
1404
|
+
}
|
|
1405
|
+
gateway.append(gatewayHeading, components);
|
|
1406
|
+
const metadata = node("details", "", "gateway-metadata");
|
|
1407
|
+
metadata.open = metadataWasOpen;
|
|
1408
|
+
metadata.append(node("summary", t("gateway.metadata")));
|
|
1409
|
+
metadata.append(facts([
|
|
1410
|
+
[t("gateway.generation"), status.recovery_generation],
|
|
1411
|
+
[t("gateway.policy"), status.policy_revision]
|
|
1412
|
+
]));
|
|
1413
|
+
metadata.append(facts([
|
|
932
1414
|
[t("gateway.listener"), t("gateway.listenerUnknown")],
|
|
933
1415
|
[
|
|
934
1416
|
t("gateway.authentication"),
|
|
@@ -947,22 +1429,28 @@ function renderPage(content, state, route, i18n = createLocalizer("zh-CN"), mana
|
|
|
947
1429
|
})
|
|
948
1430
|
]
|
|
949
1431
|
], "gateway-facts"));
|
|
950
|
-
const metadata = node("details", "", "gateway-metadata");
|
|
951
|
-
metadata.open = metadataWasOpen;
|
|
952
|
-
metadata.append(node("summary", t("gateway.metadata")));
|
|
953
|
-
metadata.append(facts([
|
|
954
|
-
[t("gateway.generation"), status.recovery_generation],
|
|
955
|
-
[t("gateway.policy"), status.policy_revision]
|
|
956
|
-
]));
|
|
957
1432
|
gateway.append(metadata);
|
|
958
1433
|
if (status.mode !== "active") {
|
|
959
1434
|
gateway.append(node("p", t("gateway.degradedNote"), "notice warning"));
|
|
960
1435
|
}
|
|
961
|
-
const section = route.machineId
|
|
962
|
-
view.append(section.heading
|
|
1436
|
+
const section = route.machineId !== null ? deviceDetail(page, freshness, management && status.mode === "active" && !state.stale) : route.section === "pairing" || route.section === "connect" ? guidePage(route.section, i18n, management) : deviceList(page, freshness, route, filter);
|
|
1437
|
+
view.append(section.heading);
|
|
1438
|
+
if (route.section === "pairing" && (state.stale || status.mode !== "active")) {
|
|
1439
|
+
view.append(node("p", t(state.stale ? "reader.stale" : "gateway.degradedNote"), "notice warning"));
|
|
1440
|
+
}
|
|
1441
|
+
if (route.machineId !== null || !["pairing", "connect"].includes(route.section ?? "")) {
|
|
1442
|
+
if (route.machineId === null) {
|
|
1443
|
+
view.append(overviewMetrics(page, freshness));
|
|
1444
|
+
}
|
|
1445
|
+
view.append(gateway);
|
|
1446
|
+
}
|
|
1447
|
+
view.append(section.body);
|
|
963
1448
|
}
|
|
964
1449
|
const identity = routeHref(route);
|
|
965
1450
|
if (content.dataset.route === identity) {
|
|
1451
|
+
for (const details of view.querySelectorAll("details[data-disclosure]")) {
|
|
1452
|
+
details.open = disclosures.get(details.dataset.disclosure) ?? false;
|
|
1453
|
+
}
|
|
966
1454
|
syncChildren(content, view);
|
|
967
1455
|
} else {
|
|
968
1456
|
content.replaceChildren(view);
|
|
@@ -1080,9 +1568,8 @@ function createUiPreferences(changed, host = browserHost) {
|
|
|
1080
1568
|
}
|
|
1081
1569
|
|
|
1082
1570
|
// src/webui/preference-control.ts
|
|
1083
|
-
var
|
|
1571
|
+
var paths2 = {
|
|
1084
1572
|
language: "M2 5h12M7 2h1M12 5c-1 6-4 9-8 11M5 8c2 4 4 6 7 8M13 22l5-11 5 11M15 18h6",
|
|
1085
|
-
system: "M3 4h18v12H3zM8 21h8M12 16v5",
|
|
1086
1573
|
light: "M16 12a4 4 0 1 0-8 0 4 4 0 0 0 8 0M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5 19 19M5 19l1.5-1.5M17.5 6.5 19 5",
|
|
1087
1574
|
dark: "M20.9 13A9 9 0 0 1 11 3.1 9 9 0 1 0 20.9 13Z"
|
|
1088
1575
|
};
|
|
@@ -1097,102 +1584,50 @@ function icon(name) {
|
|
|
1097
1584
|
svg.setAttribute("stroke-linecap", "round");
|
|
1098
1585
|
svg.setAttribute("stroke-linejoin", "round");
|
|
1099
1586
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1100
|
-
path.setAttribute("d",
|
|
1587
|
+
path.setAttribute("d", paths2[name]);
|
|
1101
1588
|
svg.append(path);
|
|
1102
1589
|
return svg;
|
|
1103
1590
|
}
|
|
1104
|
-
function createPreferenceControl(kind,
|
|
1591
|
+
function createPreferenceControl(kind, onChoose) {
|
|
1105
1592
|
const root = document.createElement("div");
|
|
1106
1593
|
root.className = "preference";
|
|
1107
1594
|
root.dataset.preference = kind;
|
|
1108
1595
|
const trigger = document.createElement("button");
|
|
1109
1596
|
trigger.type = "button";
|
|
1110
1597
|
trigger.className = "preference-trigger";
|
|
1111
|
-
trigger.setAttribute("aria-expanded", "false");
|
|
1112
1598
|
const current = document.createElement("span");
|
|
1113
1599
|
current.className = "visually-hidden";
|
|
1114
1600
|
current.id = `rovloom-${kind}-current`;
|
|
1115
1601
|
trigger.setAttribute("aria-describedby", current.id);
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
trigger.setAttribute("aria-controls", panel.id);
|
|
1122
|
-
const buttons = choices.map(([value]) => {
|
|
1123
|
-
const button = document.createElement("button");
|
|
1124
|
-
button.type = "button";
|
|
1125
|
-
button.dataset.choice = value;
|
|
1126
|
-
panel.append(button);
|
|
1127
|
-
return button;
|
|
1128
|
-
});
|
|
1129
|
-
root.append(trigger, current, panel);
|
|
1130
|
-
function close(restoreFocus = false) {
|
|
1131
|
-
panel.hidden = true;
|
|
1132
|
-
trigger.setAttribute("aria-expanded", "false");
|
|
1133
|
-
if (restoreFocus) {
|
|
1134
|
-
trigger.focus();
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
function click(event) {
|
|
1138
|
-
const target = event.target instanceof Element ? event.target.closest("button") : null;
|
|
1139
|
-
if (target === trigger) {
|
|
1140
|
-
panel.hidden = !panel.hidden;
|
|
1141
|
-
trigger.setAttribute("aria-expanded", String(!panel.hidden));
|
|
1142
|
-
} else if (target instanceof HTMLButtonElement && buttons.includes(target)) {
|
|
1143
|
-
onChoose(target.dataset.choice);
|
|
1144
|
-
close(true);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
function keydown(event) {
|
|
1148
|
-
if (event.key === "Escape" && !panel.hidden) {
|
|
1149
|
-
event.preventDefault();
|
|
1150
|
-
close(true);
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
1154
|
-
return;
|
|
1155
|
-
}
|
|
1156
|
-
event.preventDefault();
|
|
1157
|
-
const index = buttons.indexOf(document.activeElement);
|
|
1158
|
-
const next = event.key === "Home" ? 0 : event.key === "End" || index < 0 && event.key === "ArrowUp" ? buttons.length - 1 : (index + (event.key === "ArrowUp" ? -1 : 1) + buttons.length) % buttons.length;
|
|
1159
|
-
panel.hidden = false;
|
|
1160
|
-
trigger.setAttribute("aria-expanded", "true");
|
|
1161
|
-
buttons[next].focus();
|
|
1162
|
-
}
|
|
1163
|
-
function outside(event) {
|
|
1164
|
-
if (event.target instanceof Node && !root.contains(event.target)) {
|
|
1165
|
-
close();
|
|
1602
|
+
root.append(trigger, current);
|
|
1603
|
+
let next = "";
|
|
1604
|
+
function click() {
|
|
1605
|
+
if (next) {
|
|
1606
|
+
onChoose(next);
|
|
1166
1607
|
}
|
|
1167
1608
|
}
|
|
1168
1609
|
function update(i18n, value) {
|
|
1169
|
-
const
|
|
1610
|
+
const language = kind === "language";
|
|
1611
|
+
const label = i18n.t(language ? "language.label" : "theme.label");
|
|
1612
|
+
next = language ? value === "zh-CN" ? "en" : "zh-CN" : value === "dark" ? "light" : "dark";
|
|
1613
|
+
const name = i18n.t(language ? value === "zh-CN" ? "language.chinese" : "language.english" : value === "dark" ? "theme.dark" : "theme.light");
|
|
1614
|
+
const target = i18n.t(language ? next === "zh-CN" ? "language.chinese" : "language.english" : next === "dark" ? "theme.dark" : "theme.light");
|
|
1615
|
+
current.textContent = `${name}. ${i18n.t("preference.switch", { value: target })}`;
|
|
1170
1616
|
trigger.setAttribute("aria-label", label);
|
|
1171
|
-
panel.setAttribute("aria-label", label);
|
|
1172
|
-
for (let index = 0;index < buttons.length; index++) {
|
|
1173
|
-
const [choice, key] = choices[index];
|
|
1174
|
-
const button = buttons[index];
|
|
1175
|
-
button.textContent = i18n.t(key);
|
|
1176
|
-
button.setAttribute("aria-pressed", String(choice === value));
|
|
1177
|
-
if (choice === value) {
|
|
1178
|
-
current.textContent = i18n.t(key);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
1617
|
trigger.title = `${label}: ${current.textContent}`;
|
|
1182
|
-
trigger.
|
|
1618
|
+
trigger.dataset.value = value;
|
|
1619
|
+
trigger.dataset.nextValue = next;
|
|
1620
|
+
trigger.replaceChildren(icon(language ? "language" : value === "dark" ? "dark" : "light"));
|
|
1183
1621
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
root
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
document.addEventListener("pointerdown", outside);
|
|
1194
|
-
document.addEventListener("focusin", outside);
|
|
1195
|
-
return { root, update, close, dispose };
|
|
1622
|
+
trigger.addEventListener("click", click);
|
|
1623
|
+
return {
|
|
1624
|
+
root,
|
|
1625
|
+
update,
|
|
1626
|
+
dispose() {
|
|
1627
|
+
next = "";
|
|
1628
|
+
trigger.removeEventListener("click", click);
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1196
1631
|
}
|
|
1197
1632
|
|
|
1198
1633
|
// src/webui/request-id.ts
|
|
@@ -2552,6 +2987,18 @@ var errors2 = {
|
|
|
2552
2987
|
outcome_unconfirmed: "manage.unknown",
|
|
2553
2988
|
aborted: "manage.cancelled"
|
|
2554
2989
|
};
|
|
2990
|
+
function entryRenderSignature(language, entry) {
|
|
2991
|
+
return [
|
|
2992
|
+
language,
|
|
2993
|
+
entry.name,
|
|
2994
|
+
entry.machine_id,
|
|
2995
|
+
entry.pairing_id,
|
|
2996
|
+
entry.status,
|
|
2997
|
+
entry.claim?.status ?? "",
|
|
2998
|
+
entry.claim?.user_code ?? "",
|
|
2999
|
+
entry.claim?.activation ?? ""
|
|
3000
|
+
].join("|");
|
|
3001
|
+
}
|
|
2555
3002
|
function node3(tag, className = "") {
|
|
2556
3003
|
const element = document.createElement(tag);
|
|
2557
3004
|
element.className = className;
|
|
@@ -2559,6 +3006,18 @@ function node3(tag, className = "") {
|
|
|
2559
3006
|
}
|
|
2560
3007
|
function createPairingActions(options) {
|
|
2561
3008
|
const region = node3("section", "pairing-section");
|
|
3009
|
+
const steps = node3("ol", "pairing-steps");
|
|
3010
|
+
const stepItems = ["pairing.stepCreate", "pairing.stepRun", "pairing.stepApprove"].map((key, index) => {
|
|
3011
|
+
const item = node3("li");
|
|
3012
|
+
const number = node3("span", "pairing-step-number");
|
|
3013
|
+
number.textContent = String(index + 1).padStart(2, "0");
|
|
3014
|
+
const label = node3("span");
|
|
3015
|
+
item.append(number, label);
|
|
3016
|
+
steps.append(item);
|
|
3017
|
+
return { item, label, key };
|
|
3018
|
+
});
|
|
3019
|
+
const layout = node3("div", "pairing-layout");
|
|
3020
|
+
const setup = node3("div", "pairing-setup");
|
|
2562
3021
|
const heading = node3("h2");
|
|
2563
3022
|
const intro = node3("p", "muted");
|
|
2564
3023
|
const form = node3("form", "pairing-form");
|
|
@@ -2570,7 +3029,10 @@ function createPairingActions(options) {
|
|
|
2570
3029
|
nameInput.maxLength = 63;
|
|
2571
3030
|
nameInput.pattern = "[a-z0-9][a-z0-9._-]{0,62}";
|
|
2572
3031
|
nameInput.autocomplete = "off";
|
|
2573
|
-
|
|
3032
|
+
const nameHint = node3("small", "field-hint");
|
|
3033
|
+
nameHint.id = "pairing-name-hint";
|
|
3034
|
+
nameInput.setAttribute("aria-describedby", nameHint.id);
|
|
3035
|
+
nameLabel.append(nameCaption, nameInput, nameHint);
|
|
2574
3036
|
const policyLabel = node3("label");
|
|
2575
3037
|
const policyCaption = node3("span");
|
|
2576
3038
|
const policyInput = node3("input");
|
|
@@ -2582,7 +3044,10 @@ function createPairingActions(options) {
|
|
|
2582
3044
|
policyLabel.append(policyCaption, policyInput);
|
|
2583
3045
|
const create = node3("button", "pairing-confirm");
|
|
2584
3046
|
create.type = "submit";
|
|
2585
|
-
|
|
3047
|
+
const advanced = node3("details", "pairing-advanced");
|
|
3048
|
+
const advancedSummary = node3("summary");
|
|
3049
|
+
advanced.append(advancedSummary, policyLabel);
|
|
3050
|
+
form.append(nameLabel, advanced, create);
|
|
2586
3051
|
const reviewBox = node3("div", "pairing-review");
|
|
2587
3052
|
reviewBox.hidden = true;
|
|
2588
3053
|
const reviewText = node3("p", "pairing-status");
|
|
@@ -2600,7 +3065,21 @@ function createPairingActions(options) {
|
|
|
2600
3065
|
message.setAttribute("role", "status");
|
|
2601
3066
|
message.setAttribute("aria-live", "polite");
|
|
2602
3067
|
const list = node3("ul", "pairing-entries");
|
|
2603
|
-
|
|
3068
|
+
setup.append(heading, intro, form, reviewBox, message);
|
|
3069
|
+
const guide = node3("aside", "pairing-guide");
|
|
3070
|
+
const guideIcon = node3("span", "pairing-guide-icon");
|
|
3071
|
+
guideIcon.append(uiIcon("connect"));
|
|
3072
|
+
const guideHeading = node3("h2");
|
|
3073
|
+
const guideDescription = node3("p", "muted");
|
|
3074
|
+
const command = node3("div", "command-snippet");
|
|
3075
|
+
const commandCode = node3("code");
|
|
3076
|
+
commandCode.textContent = "rovloom connector --claim";
|
|
3077
|
+
const commandCopy = copyControl("rovloom connector --claim", "");
|
|
3078
|
+
command.append(commandCode, commandCopy);
|
|
3079
|
+
const guideNote = node3("p", "pairing-guide-note");
|
|
3080
|
+
guide.append(guideIcon, guideHeading, guideDescription, command, guideNote);
|
|
3081
|
+
layout.append(setup, guide);
|
|
3082
|
+
region.append(steps, layout, list);
|
|
2604
3083
|
let i18n;
|
|
2605
3084
|
let available = false;
|
|
2606
3085
|
let disposed = false;
|
|
@@ -2705,6 +3184,11 @@ function createPairingActions(options) {
|
|
|
2705
3184
|
if (!i18n) {
|
|
2706
3185
|
return;
|
|
2707
3186
|
}
|
|
3187
|
+
const signature = entryRenderSignature(i18n.language, entry);
|
|
3188
|
+
if (entry.rendered === signature) {
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
entry.rendered = signature;
|
|
2708
3192
|
const t = i18n.t.bind(i18n);
|
|
2709
3193
|
entry.item.replaceChildren();
|
|
2710
3194
|
const head = node3("div", "pairing-entry-head");
|
|
@@ -2714,19 +3198,21 @@ function createPairingActions(options) {
|
|
|
2714
3198
|
status.textContent = entry.claim?.activation === "configured" ? t("pairing.activated") : entry.status === "paired" ? t("pairing.paired") : entry.status === "failed" ? t("pairing.failed") : entry.status === "expired" || entry.status === "cancelled" ? t("pairing.expired") : entry.claim?.status === "approved" ? t("pairing.approved") : entry.claim ? t("pairing.claimArrived") : t("pairing.waitingClaim");
|
|
2715
3199
|
head.append(title, status);
|
|
2716
3200
|
entry.item.append(head);
|
|
3201
|
+
status.classList.add("badge");
|
|
2717
3202
|
const facts = node3("dl", "facts");
|
|
2718
3203
|
const idTerm = node3("dt");
|
|
2719
3204
|
idTerm.textContent = t("pairing.application");
|
|
2720
3205
|
const idValue = node3("dd");
|
|
2721
3206
|
const idCode = node3("code");
|
|
2722
3207
|
idCode.textContent = entry.pairing_id;
|
|
2723
|
-
idValue.append(idCode);
|
|
3208
|
+
idValue.append(idCode, copyControl(entry.pairing_id, t("copy.id")));
|
|
2724
3209
|
const machineTerm = node3("dt");
|
|
2725
3210
|
machineTerm.textContent = t("pairing.machine");
|
|
2726
3211
|
const machineValue = node3("dd");
|
|
2727
3212
|
machineValue.textContent = entry.machine_id;
|
|
2728
3213
|
facts.append(idTerm, idValue, machineTerm, machineValue);
|
|
2729
3214
|
entry.item.append(facts);
|
|
3215
|
+
updateSteps();
|
|
2730
3216
|
if (entry.status === "pending" && entry.claim?.status === "pending") {
|
|
2731
3217
|
const codeLabel = node3("p", "muted");
|
|
2732
3218
|
codeLabel.textContent = t("pairing.userCode");
|
|
@@ -2748,6 +3234,19 @@ function createPairingActions(options) {
|
|
|
2748
3234
|
entry.item.append(codeLabel, code, approve);
|
|
2749
3235
|
}
|
|
2750
3236
|
}
|
|
3237
|
+
function updateSteps() {
|
|
3238
|
+
const entry = [...entries.values()].at(-1);
|
|
3239
|
+
const finished = entry?.claim?.activation === "configured" || entry?.status === "paired" && !entry.claim;
|
|
3240
|
+
const stage = !entry || ["expired", "failed", "cancelled"].includes(entry.status) ? 0 : finished ? 3 : entry.claim ? 2 : 1;
|
|
3241
|
+
for (const [index, step] of stepItems.entries()) {
|
|
3242
|
+
step.item.dataset.state = index < stage ? "complete" : index === stage ? "current" : "upcoming";
|
|
3243
|
+
if (index === stage) {
|
|
3244
|
+
step.item.setAttribute("aria-current", "step");
|
|
3245
|
+
} else {
|
|
3246
|
+
step.item.removeAttribute("aria-current");
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
2751
3250
|
function addEntry(entry) {
|
|
2752
3251
|
const existing = entries.get(entry.pairing_id);
|
|
2753
3252
|
if (existing) {
|
|
@@ -2756,6 +3255,7 @@ function createPairingActions(options) {
|
|
|
2756
3255
|
return;
|
|
2757
3256
|
}
|
|
2758
3257
|
entries.set(entry.pairing_id, entry);
|
|
3258
|
+
options.committed?.();
|
|
2759
3259
|
list.append(entry.item);
|
|
2760
3260
|
renderEntry(entry);
|
|
2761
3261
|
}
|
|
@@ -2837,7 +3337,19 @@ function createPairingActions(options) {
|
|
|
2837
3337
|
available = isAvailable;
|
|
2838
3338
|
region.hidden = !isAvailable;
|
|
2839
3339
|
const t = localizer.t.bind(localizer);
|
|
2840
|
-
heading.textContent = t("pairing.
|
|
3340
|
+
heading.textContent = t("pairing.formTitle");
|
|
3341
|
+
for (const step of stepItems) {
|
|
3342
|
+
step.label.textContent = t(step.key);
|
|
3343
|
+
}
|
|
3344
|
+
updateSteps();
|
|
3345
|
+
nameInput.placeholder = t("pairing.namePlaceholder");
|
|
3346
|
+
nameHint.textContent = t("pairing.nameHint");
|
|
3347
|
+
advancedSummary.textContent = t("pairing.advanced");
|
|
3348
|
+
guideHeading.textContent = t("pairing.guideTitle");
|
|
3349
|
+
guideDescription.textContent = t("pairing.guideDescription");
|
|
3350
|
+
guideNote.textContent = t("pairing.guideNote");
|
|
3351
|
+
commandCopy.setAttribute("aria-label", t("copy.action"));
|
|
3352
|
+
commandCopy.title = t("copy.action");
|
|
2841
3353
|
intro.textContent = t("pairing.intro");
|
|
2842
3354
|
nameCaption.textContent = t("pairing.name");
|
|
2843
3355
|
policyCaption.textContent = t("pairing.policy");
|
|
@@ -2856,6 +3368,17 @@ function createPairingActions(options) {
|
|
|
2856
3368
|
suspend() {
|
|
2857
3369
|
stopPolling();
|
|
2858
3370
|
},
|
|
3371
|
+
remove(pairingId) {
|
|
3372
|
+
entries.get(pairingId)?.item.remove();
|
|
3373
|
+
entries.delete(pairingId);
|
|
3374
|
+
if (flowIntent && "pairing_id" in flowIntent && flowIntent.pairing_id === pairingId) {
|
|
3375
|
+
flow?.dispose();
|
|
3376
|
+
flow = undefined;
|
|
3377
|
+
flowIntent = undefined;
|
|
3378
|
+
reviewBox.hidden = true;
|
|
3379
|
+
}
|
|
3380
|
+
updateSteps();
|
|
3381
|
+
},
|
|
2859
3382
|
clear() {
|
|
2860
3383
|
stopPolling();
|
|
2861
3384
|
flow?.dispose();
|
|
@@ -2874,8 +3397,305 @@ function createPairingActions(options) {
|
|
|
2874
3397
|
};
|
|
2875
3398
|
}
|
|
2876
3399
|
|
|
3400
|
+
// src/webui/resource-client.ts
|
|
3401
|
+
class ResourceError extends Error {
|
|
3402
|
+
code;
|
|
3403
|
+
constructor(code) {
|
|
3404
|
+
super(code);
|
|
3405
|
+
this.code = code;
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
// src/webui/resource-panel.ts
|
|
3410
|
+
function node4(tag, text = "", className = "") {
|
|
3411
|
+
const element = document.createElement(tag);
|
|
3412
|
+
element.textContent = text;
|
|
3413
|
+
element.className = className;
|
|
3414
|
+
return element;
|
|
3415
|
+
}
|
|
3416
|
+
function createResourcePanel(options) {
|
|
3417
|
+
const region = node4("section", "", "resource-panel");
|
|
3418
|
+
const feedback = node4("p", "", "resource-feedback");
|
|
3419
|
+
feedback.setAttribute("role", "status");
|
|
3420
|
+
let i18n;
|
|
3421
|
+
let key = "";
|
|
3422
|
+
let active = false;
|
|
3423
|
+
let pending = false;
|
|
3424
|
+
let controller = new AbortController;
|
|
3425
|
+
let data;
|
|
3426
|
+
let cursor = 0;
|
|
3427
|
+
let tokenName = "";
|
|
3428
|
+
let tokenDays = "365";
|
|
3429
|
+
let version = 0;
|
|
3430
|
+
let route;
|
|
3431
|
+
let language = "";
|
|
3432
|
+
function button(label, action, danger = false) {
|
|
3433
|
+
const result = node4("button", label, danger ? "danger-button" : "");
|
|
3434
|
+
result.type = "button";
|
|
3435
|
+
result.disabled = pending;
|
|
3436
|
+
result.addEventListener("click", action);
|
|
3437
|
+
return result;
|
|
3438
|
+
}
|
|
3439
|
+
async function request(operation, args) {
|
|
3440
|
+
return options.transport(operation, args, controller.signal);
|
|
3441
|
+
}
|
|
3442
|
+
function failure(error) {
|
|
3443
|
+
if (error instanceof ResourceError && (error.code === "forbidden" || error.code === "unauthenticated")) {
|
|
3444
|
+
clear();
|
|
3445
|
+
options.authorizationLost(error.code);
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3448
|
+
feedback.textContent = i18n.t(error instanceof ResourceError && error.code === "invalid_arguments" ? "resources.invalid" : "resources.failed");
|
|
3449
|
+
}
|
|
3450
|
+
async function refresh() {
|
|
3451
|
+
const generation = ++version;
|
|
3452
|
+
controller.abort();
|
|
3453
|
+
controller = new AbortController;
|
|
3454
|
+
pending = true;
|
|
3455
|
+
feedback.textContent = i18n.t("resources.loading");
|
|
3456
|
+
try {
|
|
3457
|
+
const value = await request(key === "connect" ? "tokens_list" : "pairings_list", key === "connect" ? {} : { cursor });
|
|
3458
|
+
if (generation !== version || !active) {
|
|
3459
|
+
return;
|
|
3460
|
+
}
|
|
3461
|
+
if (!value || !Array.isArray(value.items)) {
|
|
3462
|
+
throw new Error("invalid_reply");
|
|
3463
|
+
}
|
|
3464
|
+
data = value;
|
|
3465
|
+
feedback.textContent = "";
|
|
3466
|
+
} catch (error) {
|
|
3467
|
+
if (generation === version && active) {
|
|
3468
|
+
failure(error);
|
|
3469
|
+
}
|
|
3470
|
+
} finally {
|
|
3471
|
+
if (generation === version && active) {
|
|
3472
|
+
pending = false;
|
|
3473
|
+
render();
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
async function mutate(operation, args) {
|
|
3478
|
+
if (pending || !active) {
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
const generation = version;
|
|
3482
|
+
pending = true;
|
|
3483
|
+
feedback.textContent = i18n.t("resources.saving");
|
|
3484
|
+
render();
|
|
3485
|
+
try {
|
|
3486
|
+
await request(operation, args);
|
|
3487
|
+
if (generation !== version || !active) {
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
feedback.textContent = i18n.t("resources.saved");
|
|
3491
|
+
if (operation === "tokens_create") {
|
|
3492
|
+
tokenName = "";
|
|
3493
|
+
tokenDays = "365";
|
|
3494
|
+
}
|
|
3495
|
+
if (operation === "devices_delete") {
|
|
3496
|
+
options.committed(route.machineId ?? undefined);
|
|
3497
|
+
} else {
|
|
3498
|
+
await refresh();
|
|
3499
|
+
if (operation === "pairings_delete") {
|
|
3500
|
+
options.committed(undefined, args.pairing_id);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
} catch (error) {
|
|
3504
|
+
if (generation === version && active) {
|
|
3505
|
+
failure(error);
|
|
3506
|
+
}
|
|
3507
|
+
} finally {
|
|
3508
|
+
if (generation === version && active) {
|
|
3509
|
+
pending = false;
|
|
3510
|
+
render();
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
function remove(operation, args, message) {
|
|
3515
|
+
if (window.confirm(message)) {
|
|
3516
|
+
mutate(operation, args);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
function render() {
|
|
3520
|
+
if (!active) {
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
const { t } = i18n;
|
|
3524
|
+
const body = node4("div", "", "resource-body");
|
|
3525
|
+
if (key === "device") {
|
|
3526
|
+
const card = node4("article", "", "resource-delete-card");
|
|
3527
|
+
card.append(node4("div", t("resources.deleteDeviceNote"), "muted"), button(t("resources.deleteDevice"), () => remove("devices_delete", { machine_id: route.machineId }, t("resources.confirmDevice")), true));
|
|
3528
|
+
body.append(card);
|
|
3529
|
+
} else {
|
|
3530
|
+
const heading = node4("div", "", "section-heading");
|
|
3531
|
+
heading.append(node4("h2", t(key === "connect" ? "resources.tokens" : "resources.pairings")), button(t("action.refresh"), () => {
|
|
3532
|
+
refresh();
|
|
3533
|
+
}));
|
|
3534
|
+
body.append(heading);
|
|
3535
|
+
if (key === "connect") {
|
|
3536
|
+
const form = node4("form", "", "token-create-form");
|
|
3537
|
+
const nameLabel = node4("label", t("resources.name"));
|
|
3538
|
+
const name = node4("input");
|
|
3539
|
+
name.name = "token-name";
|
|
3540
|
+
name.required = true;
|
|
3541
|
+
name.pattern = "[A-Za-z0-9_-]{1,128}";
|
|
3542
|
+
name.maxLength = 128;
|
|
3543
|
+
name.placeholder = "my-client";
|
|
3544
|
+
name.disabled = pending;
|
|
3545
|
+
name.value = tokenName;
|
|
3546
|
+
name.addEventListener("input", () => {
|
|
3547
|
+
tokenName = name.value;
|
|
3548
|
+
});
|
|
3549
|
+
nameLabel.append(name);
|
|
3550
|
+
const daysLabel = node4("label", t("resources.expiry"));
|
|
3551
|
+
const days = node4("select");
|
|
3552
|
+
days.name = "token-days";
|
|
3553
|
+
days.disabled = pending;
|
|
3554
|
+
for (const value of [365, 90, 30, 7]) {
|
|
3555
|
+
const option = node4("option", t("resources.days", { count: value }));
|
|
3556
|
+
option.value = String(value);
|
|
3557
|
+
days.append(option);
|
|
3558
|
+
}
|
|
3559
|
+
days.value = tokenDays;
|
|
3560
|
+
days.addEventListener("change", () => {
|
|
3561
|
+
tokenDays = days.value;
|
|
3562
|
+
});
|
|
3563
|
+
daysLabel.append(days);
|
|
3564
|
+
const submit = node4("button", t("resources.create"), "primary-button");
|
|
3565
|
+
submit.type = "submit";
|
|
3566
|
+
submit.disabled = pending;
|
|
3567
|
+
form.append(nameLabel, daysLabel, submit);
|
|
3568
|
+
form.addEventListener("submit", (event) => {
|
|
3569
|
+
event.preventDefault();
|
|
3570
|
+
mutate("tokens_create", { principal: name.value.trim(), days: Number(days.value) });
|
|
3571
|
+
});
|
|
3572
|
+
body.append(node4("p", t("resources.tokenNote"), "muted"), form);
|
|
3573
|
+
if (data?.resource) {
|
|
3574
|
+
const endpoint = node4("div", "", "mcp-endpoint");
|
|
3575
|
+
endpoint.append(node4("span", t("resources.endpoint")), node4("code", data.resource), copyControl(data.resource, t("copy.action")));
|
|
3576
|
+
body.append(endpoint, node4("p", t("resources.endpointNote"), "muted"));
|
|
3577
|
+
}
|
|
3578
|
+
for (const item of data?.items ?? []) {
|
|
3579
|
+
const token = item;
|
|
3580
|
+
const card = node4("article", "", "token-card");
|
|
3581
|
+
card.dataset.tokenPrincipal = token.principal;
|
|
3582
|
+
const summary = node4("div", "", "token-summary");
|
|
3583
|
+
summary.append(node4("h3", token.principal), node4("span", t(token.expires_at > Date.now() ? "resources.active" : "resources.expired"), "badge"), node4("span", t("resources.expiresAt", { time: i18n.timestamp(token.expires_at) }), "muted"));
|
|
3584
|
+
const controls = node4("div", "", "token-controls");
|
|
3585
|
+
if (token.token) {
|
|
3586
|
+
const value = node4("input");
|
|
3587
|
+
value.type = "password";
|
|
3588
|
+
value.value = token.token;
|
|
3589
|
+
value.readOnly = true;
|
|
3590
|
+
value.setAttribute("aria-label", token.principal + " Token");
|
|
3591
|
+
value.autocomplete = "off";
|
|
3592
|
+
value.spellcheck = false;
|
|
3593
|
+
const toggle = button(t("resources.show"), () => {
|
|
3594
|
+
const hidden = value.type === "password";
|
|
3595
|
+
value.type = hidden ? "text" : "password";
|
|
3596
|
+
toggle.textContent = t(hidden ? "resources.hide" : "resources.show");
|
|
3597
|
+
});
|
|
3598
|
+
controls.append(value, toggle, copyControl(token.token, t("copy.action")));
|
|
3599
|
+
} else {
|
|
3600
|
+
controls.append(node4("span", t("resources.legacy"), "muted"), button(t("resources.reissue"), () => {
|
|
3601
|
+
if (window.confirm(t("resources.confirmReissue"))) {
|
|
3602
|
+
mutate("tokens_rotate", { principal: token.principal });
|
|
3603
|
+
}
|
|
3604
|
+
}));
|
|
3605
|
+
}
|
|
3606
|
+
controls.append(button(t("resources.delete"), () => remove("tokens_delete", { principal: token.principal }, t("resources.confirmToken", { name: token.principal })), true));
|
|
3607
|
+
card.append(summary, controls);
|
|
3608
|
+
body.append(card);
|
|
3609
|
+
}
|
|
3610
|
+
} else {
|
|
3611
|
+
for (const item of data?.items ?? []) {
|
|
3612
|
+
const pairing = item;
|
|
3613
|
+
const card = node4("article", "", "pairing-record");
|
|
3614
|
+
const details = node4("div");
|
|
3615
|
+
const label = ["pending", "paired", "cancelled", "expired"].includes(pairing.status) ? t("resources." + pairing.status) : pairing.status;
|
|
3616
|
+
details.append(node4("h3", pairing.name), node4("code", pairing.pairing_id), node4("p", label + " \xB7 " + i18n.timestamp(pairing.expires), "muted"));
|
|
3617
|
+
card.append(details, button(t("resources.delete"), () => remove("pairings_delete", { pairing_id: pairing.pairing_id }, t("resources.confirmPairing")), true));
|
|
3618
|
+
body.append(card);
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
if (key === "pairing" && (cursor > 0 || typeof data?.next_cursor === "number")) {
|
|
3622
|
+
const pages = node4("div", "", "token-controls");
|
|
3623
|
+
if (cursor > 0) {
|
|
3624
|
+
pages.append(button(t("resources.previous"), () => {
|
|
3625
|
+
cursor = Math.max(0, cursor - 50);
|
|
3626
|
+
refresh();
|
|
3627
|
+
}));
|
|
3628
|
+
}
|
|
3629
|
+
if (typeof data?.next_cursor === "number") {
|
|
3630
|
+
pages.append(button(t("resources.next"), () => {
|
|
3631
|
+
cursor = data.next_cursor;
|
|
3632
|
+
refresh();
|
|
3633
|
+
}));
|
|
3634
|
+
}
|
|
3635
|
+
body.append(pages);
|
|
3636
|
+
}
|
|
3637
|
+
if (data?.items.length === 0) {
|
|
3638
|
+
body.append(node4("p", t(key === "connect" ? "resources.emptyTokens" : "resources.emptyPairings"), "empty"));
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
region.replaceChildren(body, feedback);
|
|
3642
|
+
}
|
|
3643
|
+
function clear() {
|
|
3644
|
+
active = false;
|
|
3645
|
+
key = "";
|
|
3646
|
+
data = undefined;
|
|
3647
|
+
cursor = 0;
|
|
3648
|
+
tokenName = "";
|
|
3649
|
+
tokenDays = "365";
|
|
3650
|
+
version++;
|
|
3651
|
+
controller.abort();
|
|
3652
|
+
pending = false;
|
|
3653
|
+
region.replaceChildren();
|
|
3654
|
+
region.hidden = true;
|
|
3655
|
+
}
|
|
3656
|
+
return {
|
|
3657
|
+
region,
|
|
3658
|
+
clear,
|
|
3659
|
+
dispose: clear,
|
|
3660
|
+
refresh() {
|
|
3661
|
+
if (active && key !== "device") {
|
|
3662
|
+
refresh();
|
|
3663
|
+
}
|
|
3664
|
+
},
|
|
3665
|
+
update(next, localizer, enabled) {
|
|
3666
|
+
const nextKey = next.machineId ? "device" : next.section === "connect" || next.section === "pairing" ? next.section : "";
|
|
3667
|
+
if (!enabled || !nextKey) {
|
|
3668
|
+
clear();
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
const changed = key !== nextKey || route?.machineId !== next.machineId;
|
|
3672
|
+
const translated = language !== localizer.language;
|
|
3673
|
+
route = next;
|
|
3674
|
+
i18n = localizer;
|
|
3675
|
+
language = localizer.language;
|
|
3676
|
+
active = true;
|
|
3677
|
+
region.hidden = false;
|
|
3678
|
+
if (changed) {
|
|
3679
|
+
key = nextKey;
|
|
3680
|
+
cursor = 0;
|
|
3681
|
+
data = undefined;
|
|
3682
|
+
version++;
|
|
3683
|
+
controller.abort();
|
|
3684
|
+
controller = new AbortController;
|
|
3685
|
+
pending = false;
|
|
3686
|
+
render();
|
|
3687
|
+
if (key !== "device") {
|
|
3688
|
+
refresh();
|
|
3689
|
+
}
|
|
3690
|
+
} else if (translated) {
|
|
3691
|
+
render();
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
|
|
2877
3697
|
// src/webui/page.ts
|
|
2878
|
-
function element(tag, text, className = "") {
|
|
3698
|
+
function element(tag, text = "", className = "") {
|
|
2879
3699
|
const result = document.createElement(tag);
|
|
2880
3700
|
result.textContent = text;
|
|
2881
3701
|
result.className = className;
|
|
@@ -2884,21 +3704,51 @@ function element(tag, text, className = "") {
|
|
|
2884
3704
|
async function unavailableRead() {
|
|
2885
3705
|
throw new ReadError2("unavailable");
|
|
2886
3706
|
}
|
|
2887
|
-
function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
2888
|
-
const
|
|
3707
|
+
function mountWebUi(root, read = unavailableRead, clock, management, resources) {
|
|
3708
|
+
const sidebar = element("aside", "", "sidebar");
|
|
2889
3709
|
const brand = element("a", "", "brand");
|
|
2890
3710
|
brand.href = "#";
|
|
2891
3711
|
const logo = element("img", "", "brand-logo");
|
|
2892
|
-
logo.src = "./rovloom-
|
|
3712
|
+
logo.src = "./rovloom-logo.png";
|
|
2893
3713
|
logo.alt = "Rovloom";
|
|
2894
|
-
logo.width =
|
|
2895
|
-
logo.height =
|
|
2896
|
-
brand.append(logo
|
|
3714
|
+
logo.width = 180;
|
|
3715
|
+
logo.height = 60;
|
|
3716
|
+
brand.append(logo);
|
|
3717
|
+
const sidebarCaption = element("p", "", "sidebar-caption");
|
|
3718
|
+
const navHeading = element("p", "", "nav-heading");
|
|
3719
|
+
const navigation = element("nav", "", "sidebar-nav");
|
|
3720
|
+
const navLinks = ["overview", "devices", "pairing", "connect"].map((section) => {
|
|
3721
|
+
const link = element("a", "", "nav-link");
|
|
3722
|
+
link.href = section === "overview" ? "#" : "#view=" + section;
|
|
3723
|
+
const label = element("span");
|
|
3724
|
+
link.append(uiIcon(section), label);
|
|
3725
|
+
navigation.append(link);
|
|
3726
|
+
return { section, link, label };
|
|
3727
|
+
});
|
|
3728
|
+
const resourceHeading = element("p", "", "nav-heading resource-heading");
|
|
3729
|
+
const docs = element("a", "", "nav-link docs-link");
|
|
3730
|
+
docs.target = "_blank";
|
|
3731
|
+
docs.rel = "noopener noreferrer";
|
|
3732
|
+
const docsLabel = element("span");
|
|
3733
|
+
docs.append(uiIcon("book"), docsLabel);
|
|
3734
|
+
const sidebarStatus = element("div", "", "sidebar-status");
|
|
3735
|
+
const statusIcon = element("span", "", "sidebar-gateway-icon");
|
|
3736
|
+
statusIcon.append(uiIcon("gateway"));
|
|
3737
|
+
const statusCopy = element("div");
|
|
3738
|
+
const statusLabel = element("strong", "Gateway");
|
|
3739
|
+
const statusValue = element("span", "", "sidebar-status-value");
|
|
3740
|
+
statusCopy.append(statusLabel, statusValue);
|
|
3741
|
+
sidebarStatus.append(statusIcon, statusCopy);
|
|
3742
|
+
sidebar.append(brand, sidebarCaption, navHeading, navigation, resourceHeading, docs, sidebarStatus);
|
|
3743
|
+
const workspace = element("div", "", "workspace");
|
|
3744
|
+
const header = element("header", "", "topbar");
|
|
3745
|
+
const headerLabel = element("div", "", "header-label");
|
|
3746
|
+
headerLabel.append(uiIcon("overview"));
|
|
2897
3747
|
const title = element("span", "", "brand-context");
|
|
3748
|
+
headerLabel.append(title);
|
|
2898
3749
|
const preferencesBar = element("div", "", "preferences");
|
|
2899
|
-
const commandbar = element("
|
|
2900
|
-
|
|
2901
|
-
sectionLink.href = "#";
|
|
3750
|
+
const commandbar = element("div", "", "commandbar");
|
|
3751
|
+
commandbar.setAttribute("role", "toolbar");
|
|
2902
3752
|
const toolbar = element("div", "", "toolbar");
|
|
2903
3753
|
const refresh = element("button", "");
|
|
2904
3754
|
refresh.type = "button";
|
|
@@ -2912,25 +3762,24 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
2912
3762
|
content.tabIndex = -1;
|
|
2913
3763
|
refresh.setAttribute("aria-controls", content.id);
|
|
2914
3764
|
cancel.setAttribute("aria-controls", content.id);
|
|
2915
|
-
const language = createPreferenceControl("language",
|
|
2916
|
-
|
|
2917
|
-
["zh-CN", "language.chinese"],
|
|
2918
|
-
["en", "language.english"]
|
|
2919
|
-
], (value) => preferences.setLanguage(value));
|
|
2920
|
-
const theme = createPreferenceControl("theme", [
|
|
2921
|
-
["system", "theme.system"],
|
|
2922
|
-
["light", "theme.light"],
|
|
2923
|
-
["dark", "theme.dark"]
|
|
2924
|
-
], (value) => preferences.setTheme(value));
|
|
3765
|
+
const language = createPreferenceControl("language", (value) => preferences.setLanguage(value));
|
|
3766
|
+
const theme = createPreferenceControl("theme", (value) => preferences.setTheme(value));
|
|
2925
3767
|
preferencesBar.append(language.root, theme.root);
|
|
2926
3768
|
toolbar.append(indicator, refresh, cancel);
|
|
2927
|
-
commandbar.append(
|
|
2928
|
-
|
|
3769
|
+
commandbar.append(toolbar);
|
|
3770
|
+
const headerActions = element("div", "", "topbar-actions");
|
|
3771
|
+
headerActions.append(commandbar, preferencesBar);
|
|
3772
|
+
header.append(headerLabel, headerActions);
|
|
2929
3773
|
const skip = element("a", "", "skip-link");
|
|
2930
3774
|
skip.href = "#management-content";
|
|
2931
3775
|
const footer = element("footer", "", "muted");
|
|
2932
|
-
|
|
3776
|
+
const copyFeedback = element("span", "", "visually-hidden");
|
|
3777
|
+
copyFeedback.setAttribute("role", "status");
|
|
3778
|
+
copyFeedback.setAttribute("aria-live", "polite");
|
|
3779
|
+
workspace.append(header, content, copyFeedback, footer);
|
|
3780
|
+
root.replaceChildren(skip, sidebar, workspace);
|
|
2933
3781
|
let route = parseRoute(location.hash);
|
|
3782
|
+
let filter = { query: "", status: "all" };
|
|
2934
3783
|
let reader;
|
|
2935
3784
|
let disposed = false;
|
|
2936
3785
|
let i18n = createLocalizer("en");
|
|
@@ -2956,6 +3805,9 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
2956
3805
|
}
|
|
2957
3806
|
const pairing = management ? createPairingActions({
|
|
2958
3807
|
transport: management,
|
|
3808
|
+
committed() {
|
|
3809
|
+
resourcePanel?.refresh();
|
|
3810
|
+
},
|
|
2959
3811
|
authorizationLost(problem) {
|
|
2960
3812
|
if (disposed) {
|
|
2961
3813
|
return;
|
|
@@ -2969,20 +3821,62 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
2969
3821
|
if (pairing) {
|
|
2970
3822
|
footer.before(pairing.region);
|
|
2971
3823
|
}
|
|
3824
|
+
const resourcePanel = resources ? createResourcePanel({
|
|
3825
|
+
transport: resources,
|
|
3826
|
+
committed(machine, pairingId) {
|
|
3827
|
+
if (machine) {
|
|
3828
|
+
location.hash = "#view=devices";
|
|
3829
|
+
} else if (pairingId) {
|
|
3830
|
+
pairing?.remove(pairingId);
|
|
3831
|
+
}
|
|
3832
|
+
},
|
|
3833
|
+
authorizationLost(problem) {
|
|
3834
|
+
reader.dispose();
|
|
3835
|
+
actions?.clear();
|
|
3836
|
+
pairing?.clear();
|
|
3837
|
+
paint({ snapshot: null, pending: false, stale: true, ageMs: 0, problem }, route);
|
|
3838
|
+
}
|
|
3839
|
+
}) : undefined;
|
|
3840
|
+
if (resourcePanel) {
|
|
3841
|
+
footer.before(resourcePanel.region);
|
|
3842
|
+
}
|
|
2972
3843
|
function paint(state, current) {
|
|
2973
3844
|
refresh.disabled = state.pending || ["unauthenticated", "forbidden", "not_found"].includes(state.problem ?? "");
|
|
2974
3845
|
cancel.disabled = !state.pending;
|
|
2975
3846
|
indicator.textContent = i18n.t(state.pending ? "reader.pending" : state.snapshot ? state.stale ? "reader.stale" : "reader.snapshot" : "reader.empty");
|
|
2976
3847
|
content.setAttribute("aria-busy", String(state.pending));
|
|
2977
3848
|
indicator.dataset.state = state.pending ? "pending" : state.stale ? "stale" : state.snapshot ? "current" : "empty";
|
|
2978
|
-
sectionLink.setAttribute("aria-current", current.machineId === null ? "page" : "location");
|
|
2979
3849
|
if (["unauthenticated", "forbidden", "not_found"].includes(state.problem ?? "")) {
|
|
2980
3850
|
actions?.clear();
|
|
2981
3851
|
pairing?.clear();
|
|
2982
3852
|
}
|
|
2983
3853
|
pairing?.update(i18n, !!state.snapshot && !state.stale && state.snapshot.value.status.mode === "active");
|
|
2984
3854
|
actions?.update(i18n, !!state.snapshot && !state.stale && state.snapshot.value.status.mode === "active", state.snapshot ? state.snapshot.observedAt + state.ageMs : undefined);
|
|
2985
|
-
|
|
3855
|
+
const section = current.machineId ? "devices" : current.section ?? "overview";
|
|
3856
|
+
content.dataset.section = section;
|
|
3857
|
+
for (const item of navLinks) {
|
|
3858
|
+
if (item.section === section) {
|
|
3859
|
+
item.link.setAttribute("aria-current", "page");
|
|
3860
|
+
} else {
|
|
3861
|
+
item.link.removeAttribute("aria-current");
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
title.textContent = i18n.t("nav." + section);
|
|
3865
|
+
const active = !!state.snapshot && !state.stale && state.snapshot.value.status.mode === "active";
|
|
3866
|
+
sidebarStatus.dataset.state = active ? "current" : "unknown";
|
|
3867
|
+
statusValue.textContent = i18n.t(active ? "gateway.active" : state.snapshot ? state.stale ? "reader.stale" : "gateway.degraded" : "reader.empty");
|
|
3868
|
+
if (pairing) {
|
|
3869
|
+
pairing.region.hidden = section !== "pairing" || !active;
|
|
3870
|
+
}
|
|
3871
|
+
renderPage(content, state, current, i18n, !!actions, filter);
|
|
3872
|
+
const authorized = active && !["unauthenticated", "forbidden", "not_found"].includes(state.problem ?? "");
|
|
3873
|
+
resourcePanel?.update(current, i18n, authorized);
|
|
3874
|
+
if (resourcePanel && section === "connect" && authorized) {
|
|
3875
|
+
const guide = content.querySelector(".connection-guide");
|
|
3876
|
+
if (guide) {
|
|
3877
|
+
guide.hidden = true;
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
2986
3880
|
}
|
|
2987
3881
|
function applyPreferences(state) {
|
|
2988
3882
|
const languageChanged = i18n.language !== state.language;
|
|
@@ -2994,15 +3888,23 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
2994
3888
|
document.documentElement.dataset.theme = state.theme;
|
|
2995
3889
|
document.title = t("app.title");
|
|
2996
3890
|
brand.setAttribute("aria-label", t("app.navigation"));
|
|
2997
|
-
title.textContent = t("
|
|
2998
|
-
|
|
3891
|
+
title.textContent = t("nav.console");
|
|
3892
|
+
sidebarCaption.textContent = t("app.console");
|
|
3893
|
+
navHeading.textContent = t("nav.workspace");
|
|
3894
|
+
resourceHeading.textContent = t("nav.resources");
|
|
3895
|
+
navigation.setAttribute("aria-label", t("app.subtitle"));
|
|
3896
|
+
docsLabel.textContent = t("nav.docs");
|
|
3897
|
+
docs.href = `https://github.com/ArnoChenFx/rovloom/blob/main/docs/QUICK-START${state.language === "zh-CN" ? "-zh" : ""}.md`;
|
|
3898
|
+
for (const item of navLinks) {
|
|
3899
|
+
item.label.textContent = t("nav." + item.section);
|
|
3900
|
+
}
|
|
2999
3901
|
commandbar.setAttribute("aria-label", t("app.subtitle"));
|
|
3000
3902
|
refresh.textContent = t("action.refresh");
|
|
3001
3903
|
cancel.textContent = t("action.cancel");
|
|
3002
3904
|
skip.textContent = t("app.skip");
|
|
3003
3905
|
footer.textContent = t(actions ? "app.managedFooter" : "app.footer");
|
|
3004
|
-
language.update(i18n, state.
|
|
3005
|
-
theme.update(i18n, state.
|
|
3906
|
+
language.update(i18n, state.language);
|
|
3907
|
+
theme.update(i18n, state.theme);
|
|
3006
3908
|
if (languageChanged && reader) {
|
|
3007
3909
|
paint(reader.state(), route);
|
|
3008
3910
|
}
|
|
@@ -3024,6 +3926,7 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3024
3926
|
content.focus();
|
|
3025
3927
|
}
|
|
3026
3928
|
function refreshPage() {
|
|
3929
|
+
resourcePanel?.refresh();
|
|
3027
3930
|
reader.refresh();
|
|
3028
3931
|
}
|
|
3029
3932
|
function cancelRead() {
|
|
@@ -3064,11 +3967,41 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3064
3967
|
actions.open({ action, machine_id: device.machine_id }, button);
|
|
3065
3968
|
}
|
|
3066
3969
|
}
|
|
3970
|
+
function searchDevices(event) {
|
|
3971
|
+
if (event.target instanceof HTMLInputElement && event.target.name === "device-search") {
|
|
3972
|
+
filter.query = event.target.value;
|
|
3973
|
+
paint(reader.state(), route);
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
function filterDevices(event) {
|
|
3977
|
+
const button = event.target instanceof Element ? event.target.closest("[data-device-filter]") : null;
|
|
3978
|
+
const selected = button?.dataset.deviceFilter;
|
|
3979
|
+
if (selected === "all" || selected === "online" || selected === "attention") {
|
|
3980
|
+
filter.status = selected;
|
|
3981
|
+
paint(reader.state(), route);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
async function copy(event) {
|
|
3985
|
+
const button = event.target instanceof Element ? event.target.closest("button[data-copy]") : null;
|
|
3986
|
+
if (!button || button.disabled || !root.contains(button)) {
|
|
3987
|
+
return;
|
|
3988
|
+
}
|
|
3989
|
+
const value = button.dataset.copy;
|
|
3990
|
+
if (value === undefined) {
|
|
3991
|
+
return;
|
|
3992
|
+
}
|
|
3993
|
+
copyFeedback.textContent = "";
|
|
3994
|
+
const success = await copyText(value);
|
|
3995
|
+
if (!disposed) {
|
|
3996
|
+
copyFeedback.textContent = i18n.t(success ? "copy.done" : "copy.failed");
|
|
3997
|
+
button.title = copyFeedback.textContent;
|
|
3998
|
+
button.dataset.copied = String(success);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
3067
4001
|
function navigate() {
|
|
4002
|
+
filter = { query: "", status: "all" };
|
|
3068
4003
|
actions?.suspend();
|
|
3069
4004
|
pairing?.suspend();
|
|
3070
|
-
language.close();
|
|
3071
|
-
theme.close();
|
|
3072
4005
|
if (location.hash === "#management-content") {
|
|
3073
4006
|
content.focus();
|
|
3074
4007
|
return;
|
|
@@ -3096,9 +4029,8 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3096
4029
|
function hide() {
|
|
3097
4030
|
actions?.suspend();
|
|
3098
4031
|
pairing?.suspend();
|
|
3099
|
-
language.close();
|
|
3100
|
-
theme.close();
|
|
3101
4032
|
reader.dispose();
|
|
4033
|
+
resourcePanel?.clear();
|
|
3102
4034
|
content.replaceChildren();
|
|
3103
4035
|
}
|
|
3104
4036
|
function dispose() {
|
|
@@ -3107,9 +4039,13 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3107
4039
|
}
|
|
3108
4040
|
disposed = true;
|
|
3109
4041
|
reader.dispose();
|
|
4042
|
+
resourcePanel?.dispose();
|
|
3110
4043
|
actions?.dispose();
|
|
3111
4044
|
pairing?.dispose();
|
|
3112
4045
|
content.removeEventListener("click", deviceAction);
|
|
4046
|
+
content.removeEventListener("click", filterDevices);
|
|
4047
|
+
content.removeEventListener("input", searchDevices);
|
|
4048
|
+
root.removeEventListener("click", copy);
|
|
3113
4049
|
skip.removeEventListener("click", skipContent);
|
|
3114
4050
|
refresh.removeEventListener("click", refreshPage);
|
|
3115
4051
|
cancel.removeEventListener("click", cancelRead);
|
|
@@ -3126,6 +4062,9 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3126
4062
|
refresh.addEventListener("click", refreshPage);
|
|
3127
4063
|
cancel.addEventListener("click", cancelRead);
|
|
3128
4064
|
content.addEventListener("click", deviceAction);
|
|
4065
|
+
content.addEventListener("click", filterDevices);
|
|
4066
|
+
content.addEventListener("input", searchDevices);
|
|
4067
|
+
root.addEventListener("click", copy);
|
|
3129
4068
|
window.addEventListener("hashchange", navigate);
|
|
3130
4069
|
document.addEventListener("visibilitychange", visibility);
|
|
3131
4070
|
window.addEventListener("pageshow", show);
|
|
@@ -3517,4 +4456,4 @@ export {
|
|
|
3517
4456
|
unavailableRead
|
|
3518
4457
|
};
|
|
3519
4458
|
|
|
3520
|
-
//# debugId=
|
|
4459
|
+
//# debugId=9FAF58405B4DF80264756E2164756E21
|