rovloom 0.4.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 +21 -41
- package/dist/src/admin/index.js +1 -1
- package/dist/src/{chunk-smpevv7n.js → chunk-0dqx2mmz.js} +9 -16
- package/dist/src/{chunk-z1vhdr3c.js → chunk-0qgk7mhz.js} +16 -15
- package/dist/src/{chunk-qfyefed3.js → chunk-1bf0wmgk.js} +1 -1
- package/dist/src/{chunk-ymsx7hhd.js → chunk-3dvn33c4.js} +61 -62
- package/dist/src/chunk-5b8g62kw.js +438 -0
- package/dist/src/chunk-5j0gj6w2.js +7 -0
- package/dist/src/{chunk-v1httsp6.js → chunk-8ykrqhxh.js} +10 -4
- package/dist/src/{chunk-76wgt2g5.js → chunk-9zkq0kc8.js} +14 -8
- package/dist/src/{chunk-6hxy1fcg.js → chunk-bspzg8er.js} +2 -2
- package/dist/src/{chunk-dtj9y82v.js → chunk-d2kz25f2.js} +269 -39
- package/dist/src/{chunk-khmgp03q.js → chunk-ez7d5dac.js} +77 -16
- package/dist/src/{chunk-emcefrc1.js → chunk-g27b2gmx.js} +3 -4
- package/dist/src/{chunk-keg92d31.js → chunk-gyvqqnjb.js} +34 -59
- package/dist/src/{chunk-bjdqj43d.js → chunk-hpzr94hm.js} +23 -6
- package/dist/src/{chunk-t3pn1435.js → chunk-jf4r0r44.js} +4 -4
- package/dist/src/{chunk-4etdq0cp.js → chunk-jka7v3wb.js} +4 -7
- package/dist/src/{chunk-5vx1b9z8.js → chunk-m9c1pnv9.js} +5 -5
- package/dist/src/{chunk-7k9g0x5e.js → chunk-mg0fd6ph.js} +1 -1
- package/dist/src/{chunk-1fyfrgte.js → chunk-n6wtjyfb.js} +10 -18
- package/dist/src/{chunk-nyn75b8v.js → chunk-qjh9x872.js} +30 -1
- package/dist/src/{chunk-xmkgq3nh.js → chunk-s3ydr65k.js} +2 -2
- package/dist/src/{chunk-4af38z2b.js → chunk-srfbjwtk.js} +4 -3
- package/dist/src/{chunk-8x6ygvhh.js → chunk-ssp57p43.js} +27 -31
- package/dist/src/{chunk-qab3m6nh.js → chunk-wxqhscsw.js} +43 -7
- package/dist/src/{chunk-7q96e161.js → chunk-x0td5wn4.js} +6 -12
- package/dist/src/{chunk-ydmabg5t.js → chunk-ytpfmcp1.js} +7 -6
- package/dist/src/{chunk-wdr3sdsm.js → chunk-yv9px0f5.js} +60 -32
- package/dist/src/{chunk-se5daacy.js → chunk-z7kkfrgz.js} +1 -1
- package/dist/src/{chunk-fd9n51mc.js → chunk-zjkjjm81.js} +87 -61
- package/dist/src/cli/index.js +27 -19
- package/dist/src/cli/main.js +18 -13
- 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 +1107 -158
- package/package.json +1 -1
package/dist/src/webui/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_PAIRING_POLICY_REF
|
|
4
|
+
} from "../chunk-5j0gj6w2.js";
|
|
2
5
|
import {
|
|
3
6
|
ReadError2,
|
|
4
7
|
createSnapshotReader2,
|
|
@@ -10,6 +13,111 @@ import {
|
|
|
10
13
|
|
|
11
14
|
// src/webui/i18n.ts
|
|
12
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",
|
|
13
121
|
"provider.disable": "\u7981\u7528\u6B64 provider",
|
|
14
122
|
"provider.target": "Provider \u76EE\u6807",
|
|
15
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",
|
|
@@ -18,8 +126,9 @@ var chinese = Object.freeze({
|
|
|
18
126
|
"app.subtitle": "\u8BBE\u5907\u4E0E\u8FDE\u63A5\u7BA1\u7406",
|
|
19
127
|
"app.console": "Gateway \u63A7\u5236\u53F0",
|
|
20
128
|
"app.skip": "\u8DF3\u5230\u7BA1\u7406\u5185\u5BB9",
|
|
21
|
-
"app.managedFooter": "\
|
|
22
|
-
"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}",
|
|
23
132
|
"language.label": "\u8BED\u8A00",
|
|
24
133
|
"language.system": "\u8DDF\u968F\u6D4F\u89C8\u5668",
|
|
25
134
|
"language.chinese": "\u7B80\u4F53\u4E2D\u6587",
|
|
@@ -57,7 +166,7 @@ var chinese = Object.freeze({
|
|
|
57
166
|
"manage.nativeEffect": "\u6B64\u64CD\u4F5C\u4E0D\u4FDD\u8BC1\u505C\u6B62\u6216\u56DE\u6EDA\u539F\u751F\u5DE5\u4F5C\u3002",
|
|
58
167
|
"manage.action": "\u64CD\u4F5C",
|
|
59
168
|
"pairing.title": "\u5728\u7EBF\u6DFB\u52A0\u8BBE\u5907",
|
|
60
|
-
"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",
|
|
61
170
|
"pairing.name": "\u65B0\u8BBE\u5907\u540D\u79F0",
|
|
62
171
|
"pairing.policy": "\u6388\u6743\u653F\u7B56\u5F15\u7528",
|
|
63
172
|
"pairing.create": "\u65B0\u5EFA\u914D\u5BF9\u7533\u8BF7",
|
|
@@ -67,7 +176,7 @@ var chinese = Object.freeze({
|
|
|
67
176
|
"pairing.cancel": "\u53D6\u6D88",
|
|
68
177
|
"pairing.created": "\u7533\u8BF7\u5DF2\u521B\u5EFA\u3002\u628A\u7533\u8BF7\u7F16\u53F7\u4EA4\u7ED9\u65B0\u8BBE\u5907\uFF1A",
|
|
69
178
|
"pairing.waitingClaim": "\u7B49\u5F85\u8BBE\u5907\u63D0\u4EA4\u8BA4\u9886\u2026",
|
|
70
|
-
"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",
|
|
71
180
|
"pairing.userCode": "\u6838\u5BF9\u7801",
|
|
72
181
|
"pairing.approve": "\u6279\u51C6\u7B7E\u53D1\u51ED\u636E",
|
|
73
182
|
"pairing.approved": "\u5DF2\u6279\u51C6\uFF0C\u51ED\u636E\u5DF2\u6258\u7BA1\u4EA4\u4ED8\uFF0C\u7B49\u5F85\u8BBE\u5907\u53D6\u56DE\u2026",
|
|
@@ -99,7 +208,7 @@ var chinese = Object.freeze({
|
|
|
99
208
|
"action.cancel": "\u53D6\u6D88\u8BFB\u53D6",
|
|
100
209
|
"reader.pending": "\u6B63\u5728\u8BFB\u53D6\u2026",
|
|
101
210
|
"reader.stale": "\u5FEB\u7167\u5DF2\u8FC7\u671F",
|
|
102
|
-
"reader.snapshot": "\
|
|
211
|
+
"reader.snapshot": "\u5DF2\u66F4\u65B0",
|
|
103
212
|
"reader.empty": "\u65E0\u53EF\u7528\u5FEB\u7167",
|
|
104
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",
|
|
105
214
|
"error.forbidden": "\u5F53\u524D\u7BA1\u7406\u5458\u6CA1\u6709\u6B64\u8303\u56F4\u7684\u67E5\u770B\u6743\u9650\u3002",
|
|
@@ -242,6 +351,111 @@ var chinese = Object.freeze({
|
|
|
242
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"
|
|
243
352
|
});
|
|
244
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.",
|
|
245
459
|
"provider.disable": "Disable this provider",
|
|
246
460
|
"provider.target": "Provider target",
|
|
247
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.",
|
|
@@ -250,8 +464,9 @@ var english = Object.freeze({
|
|
|
250
464
|
"app.subtitle": "Device and connection management",
|
|
251
465
|
"app.console": "Gateway console",
|
|
252
466
|
"app.skip": "Skip to management content",
|
|
253
|
-
"app.managedFooter": "
|
|
254
|
-
"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}",
|
|
255
470
|
"language.label": "Language",
|
|
256
471
|
"language.system": "Browser language",
|
|
257
472
|
"language.chinese": "\u7B80\u4F53\u4E2D\u6587",
|
|
@@ -289,7 +504,7 @@ var english = Object.freeze({
|
|
|
289
504
|
"manage.nativeEffect": "This operation does not guarantee stopping or rolling back native work.",
|
|
290
505
|
"manage.action": "Action",
|
|
291
506
|
"pairing.title": "Add a device online",
|
|
292
|
-
"pairing.intro": "
|
|
507
|
+
"pairing.intro": "Give your workstation a name. Create an application, then follow the steps on the right.",
|
|
293
508
|
"pairing.name": "New device name",
|
|
294
509
|
"pairing.policy": "Authorization policy reference",
|
|
295
510
|
"pairing.create": "Create pairing application",
|
|
@@ -331,7 +546,7 @@ var english = Object.freeze({
|
|
|
331
546
|
"action.cancel": "Cancel read",
|
|
332
547
|
"reader.pending": "Reading\u2026",
|
|
333
548
|
"reader.stale": "Snapshot expired",
|
|
334
|
-
"reader.snapshot": "
|
|
549
|
+
"reader.snapshot": "Up to date",
|
|
335
550
|
"reader.empty": "No snapshot",
|
|
336
551
|
"error.unauthenticated": "The management session is missing or expired. Sign in through the protected administrator entry.",
|
|
337
552
|
"error.forbidden": "The current administrator cannot view this scope.",
|
|
@@ -505,6 +720,84 @@ function createLocalizer(language) {
|
|
|
505
720
|
};
|
|
506
721
|
}
|
|
507
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
|
+
|
|
508
801
|
// src/webui/view.ts
|
|
509
802
|
var connectionNames = {
|
|
510
803
|
connecting: "connection.connecting",
|
|
@@ -576,6 +869,9 @@ function routeHref(route) {
|
|
|
576
869
|
if (route.providerCursor !== null) {
|
|
577
870
|
parameters.set("providers", route.providerCursor);
|
|
578
871
|
}
|
|
872
|
+
if (route.machineId === null && route.section) {
|
|
873
|
+
parameters.set("view", route.section);
|
|
874
|
+
}
|
|
579
875
|
return `#${parameters.toString()}`;
|
|
580
876
|
}
|
|
581
877
|
function parseRoute(hash) {
|
|
@@ -584,7 +880,7 @@ function parseRoute(hash) {
|
|
|
584
880
|
return empty;
|
|
585
881
|
}
|
|
586
882
|
const parameters = new URLSearchParams(hash.replace(/^#/, ""));
|
|
587
|
-
for (const key of ["machine", "cursor", "providers"]) {
|
|
883
|
+
for (const key of ["machine", "cursor", "providers", "view"]) {
|
|
588
884
|
if (parameters.getAll(key).length > 1 || (parameters.get(key)?.length ?? 0) > 1024) {
|
|
589
885
|
return empty;
|
|
590
886
|
}
|
|
@@ -593,7 +889,8 @@ function parseRoute(hash) {
|
|
|
593
889
|
return {
|
|
594
890
|
machineId,
|
|
595
891
|
cursor: machineId ? null : parameters.get("cursor") || null,
|
|
596
|
-
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") } : {}
|
|
597
894
|
};
|
|
598
895
|
}
|
|
599
896
|
function current(observedAt, freshness) {
|
|
@@ -621,7 +918,7 @@ function listObservation(key, observedAt, freshness) {
|
|
|
621
918
|
function currentDeviceLink(device, freshness) {
|
|
622
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";
|
|
623
920
|
}
|
|
624
|
-
function deviceBadges(device, freshness) {
|
|
921
|
+
function deviceBadges(device, freshness, compact = false) {
|
|
625
922
|
const { i18n } = freshness;
|
|
626
923
|
const { t } = i18n;
|
|
627
924
|
const result = node("div", "", "badges");
|
|
@@ -658,7 +955,9 @@ function deviceBadges(device, freshness) {
|
|
|
658
955
|
} else {
|
|
659
956
|
result.append(badge(t("device.authUnknown", { reason: i18n.reason(authentication.reason_code) }), "warning"));
|
|
660
957
|
}
|
|
661
|
-
|
|
958
|
+
if (!compact) {
|
|
959
|
+
result.append(badge(t("device.readyUnknown")));
|
|
960
|
+
}
|
|
662
961
|
return result;
|
|
663
962
|
}
|
|
664
963
|
function unavailableActions(kind, i18n) {
|
|
@@ -700,20 +999,154 @@ function deviceActions(page, freshness, connected) {
|
|
|
700
999
|
}
|
|
701
1000
|
return section;
|
|
702
1001
|
}
|
|
703
|
-
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) {
|
|
704
1079
|
const { i18n } = freshness;
|
|
705
1080
|
const { t } = i18n;
|
|
706
1081
|
const heading = node("header", "", "page-heading");
|
|
707
|
-
|
|
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);
|
|
708
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
|
+
});
|
|
709
1136
|
if (page.devices.items.length === 0) {
|
|
710
1137
|
const empty = node("div", "", "empty");
|
|
711
|
-
|
|
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);
|
|
712
1145
|
section.append(empty);
|
|
713
1146
|
}
|
|
714
1147
|
const columns = node("div", "", "device-columns");
|
|
715
1148
|
columns.setAttribute("aria-hidden", "true");
|
|
716
|
-
for (const key of ["devices.identity", "devices.health", "devices.activity", "devices.
|
|
1149
|
+
for (const key of ["devices.identity", "devices.health", "devices.activity", "devices.record"]) {
|
|
717
1150
|
columns.append(node("span", t(key)));
|
|
718
1151
|
}
|
|
719
1152
|
if (page.devices.items.length) {
|
|
@@ -721,32 +1154,60 @@ function deviceList(page, freshness) {
|
|
|
721
1154
|
}
|
|
722
1155
|
const list = node("ul", "", "device-list");
|
|
723
1156
|
list.setAttribute("aria-label", t("devices.heading"));
|
|
724
|
-
for (const device of
|
|
1157
|
+
for (const device of items) {
|
|
725
1158
|
const row = node("li", "", "device-card");
|
|
726
1159
|
const identity = node("div", "", "device-identity");
|
|
727
1160
|
const link = node("a", device.name, "device-name");
|
|
728
1161
|
link.href = routeHref({ machineId: device.machine_id, cursor: null, providerCursor: null });
|
|
729
|
-
|
|
730
|
-
|
|
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"));
|
|
731
1170
|
const health = node("div", "", "device-health");
|
|
732
|
-
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));
|
|
733
1172
|
const heartbeat = device.connection.verification === "verified" ? device.connection.value.last_heartbeat_at : null;
|
|
734
1173
|
const activity = node("div", "", "device-activity");
|
|
735
1174
|
activity.append(node("span", t("devices.activity"), "cell-label"));
|
|
736
1175
|
activity.append(node("strong", heartbeat === null ? t("time.noHeartbeat") : i18n.age(heartbeat, freshness.now), "activity-age"));
|
|
737
1176
|
activity.append(node("p", t("devices.received", { time: i18n.timestamp(heartbeat) }), "muted"));
|
|
738
1177
|
const observations = node("div", "", "device-observations");
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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);
|
|
743
1191
|
row.append(identity, health, activity, observations);
|
|
744
1192
|
list.append(row);
|
|
745
1193
|
}
|
|
746
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
|
+
}
|
|
747
1203
|
if (page.devices.next_cursor !== undefined) {
|
|
748
1204
|
const next = node("a", t("devices.next"), "button-link");
|
|
749
|
-
next.href = routeHref({
|
|
1205
|
+
next.href = routeHref({
|
|
1206
|
+
machineId: null,
|
|
1207
|
+
cursor: page.devices.next_cursor,
|
|
1208
|
+
providerCursor: null,
|
|
1209
|
+
section: route.section
|
|
1210
|
+
});
|
|
750
1211
|
section.append(next);
|
|
751
1212
|
}
|
|
752
1213
|
return { heading, body: section };
|
|
@@ -891,11 +1352,15 @@ function syncChildren(parent, desired) {
|
|
|
891
1352
|
parent.removeChild(parent.lastChild);
|
|
892
1353
|
}
|
|
893
1354
|
}
|
|
894
|
-
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" }) {
|
|
895
1356
|
const { t } = i18n;
|
|
896
1357
|
const focused = content.contains(document.activeElement) && document.activeElement instanceof HTMLAnchorElement ? document.activeElement.getAttribute("href") : null;
|
|
897
1358
|
const metadataWasOpen = content.querySelector(".gateway-metadata")?.open ?? false;
|
|
898
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
|
+
]));
|
|
899
1364
|
const view = document.createDocumentFragment();
|
|
900
1365
|
if (state.problem) {
|
|
901
1366
|
const message = node("p", t(problemNames[state.problem]), "notice warning");
|
|
@@ -924,8 +1389,28 @@ function renderPage(content, state, route, i18n = createLocalizer("zh-CN"), mana
|
|
|
924
1389
|
gatewayHeading.append(node("h2", t("gateway.heading")));
|
|
925
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"));
|
|
926
1391
|
gatewayHeading.append(badge(state.stale ? t("reader.stale") : t("gateway.fresh"), state.stale ? "warning" : "neutral"));
|
|
927
|
-
gateway
|
|
928
|
-
|
|
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([
|
|
929
1414
|
[t("gateway.listener"), t("gateway.listenerUnknown")],
|
|
930
1415
|
[
|
|
931
1416
|
t("gateway.authentication"),
|
|
@@ -944,22 +1429,28 @@ function renderPage(content, state, route, i18n = createLocalizer("zh-CN"), mana
|
|
|
944
1429
|
})
|
|
945
1430
|
]
|
|
946
1431
|
], "gateway-facts"));
|
|
947
|
-
const metadata = node("details", "", "gateway-metadata");
|
|
948
|
-
metadata.open = metadataWasOpen;
|
|
949
|
-
metadata.append(node("summary", t("gateway.metadata")));
|
|
950
|
-
metadata.append(facts([
|
|
951
|
-
[t("gateway.generation"), status.recovery_generation],
|
|
952
|
-
[t("gateway.policy"), status.policy_revision]
|
|
953
|
-
]));
|
|
954
1432
|
gateway.append(metadata);
|
|
955
1433
|
if (status.mode !== "active") {
|
|
956
1434
|
gateway.append(node("p", t("gateway.degradedNote"), "notice warning"));
|
|
957
1435
|
}
|
|
958
|
-
const section = route.machineId
|
|
959
|
-
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);
|
|
960
1448
|
}
|
|
961
1449
|
const identity = routeHref(route);
|
|
962
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
|
+
}
|
|
963
1454
|
syncChildren(content, view);
|
|
964
1455
|
} else {
|
|
965
1456
|
content.replaceChildren(view);
|
|
@@ -1077,9 +1568,8 @@ function createUiPreferences(changed, host = browserHost) {
|
|
|
1077
1568
|
}
|
|
1078
1569
|
|
|
1079
1570
|
// src/webui/preference-control.ts
|
|
1080
|
-
var
|
|
1571
|
+
var paths2 = {
|
|
1081
1572
|
language: "M2 5h12M7 2h1M12 5c-1 6-4 9-8 11M5 8c2 4 4 6 7 8M13 22l5-11 5 11M15 18h6",
|
|
1082
|
-
system: "M3 4h18v12H3zM8 21h8M12 16v5",
|
|
1083
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",
|
|
1084
1574
|
dark: "M20.9 13A9 9 0 0 1 11 3.1 9 9 0 1 0 20.9 13Z"
|
|
1085
1575
|
};
|
|
@@ -1094,102 +1584,56 @@ function icon(name) {
|
|
|
1094
1584
|
svg.setAttribute("stroke-linecap", "round");
|
|
1095
1585
|
svg.setAttribute("stroke-linejoin", "round");
|
|
1096
1586
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1097
|
-
path.setAttribute("d",
|
|
1587
|
+
path.setAttribute("d", paths2[name]);
|
|
1098
1588
|
svg.append(path);
|
|
1099
1589
|
return svg;
|
|
1100
1590
|
}
|
|
1101
|
-
function createPreferenceControl(kind,
|
|
1591
|
+
function createPreferenceControl(kind, onChoose) {
|
|
1102
1592
|
const root = document.createElement("div");
|
|
1103
1593
|
root.className = "preference";
|
|
1104
1594
|
root.dataset.preference = kind;
|
|
1105
1595
|
const trigger = document.createElement("button");
|
|
1106
1596
|
trigger.type = "button";
|
|
1107
1597
|
trigger.className = "preference-trigger";
|
|
1108
|
-
trigger.setAttribute("aria-expanded", "false");
|
|
1109
1598
|
const current = document.createElement("span");
|
|
1110
1599
|
current.className = "visually-hidden";
|
|
1111
1600
|
current.id = `rovloom-${kind}-current`;
|
|
1112
1601
|
trigger.setAttribute("aria-describedby", current.id);
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
trigger.setAttribute("aria-controls", panel.id);
|
|
1119
|
-
const buttons = choices.map(([value]) => {
|
|
1120
|
-
const button = document.createElement("button");
|
|
1121
|
-
button.type = "button";
|
|
1122
|
-
button.dataset.choice = value;
|
|
1123
|
-
panel.append(button);
|
|
1124
|
-
return button;
|
|
1125
|
-
});
|
|
1126
|
-
root.append(trigger, current, panel);
|
|
1127
|
-
function close(restoreFocus = false) {
|
|
1128
|
-
panel.hidden = true;
|
|
1129
|
-
trigger.setAttribute("aria-expanded", "false");
|
|
1130
|
-
if (restoreFocus) {
|
|
1131
|
-
trigger.focus();
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
function click(event) {
|
|
1135
|
-
const target = event.target instanceof Element ? event.target.closest("button") : null;
|
|
1136
|
-
if (target === trigger) {
|
|
1137
|
-
panel.hidden = !panel.hidden;
|
|
1138
|
-
trigger.setAttribute("aria-expanded", String(!panel.hidden));
|
|
1139
|
-
} else if (target instanceof HTMLButtonElement && buttons.includes(target)) {
|
|
1140
|
-
onChoose(target.dataset.choice);
|
|
1141
|
-
close(true);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
function keydown(event) {
|
|
1145
|
-
if (event.key === "Escape" && !panel.hidden) {
|
|
1146
|
-
event.preventDefault();
|
|
1147
|
-
close(true);
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
event.preventDefault();
|
|
1154
|
-
const index = buttons.indexOf(document.activeElement);
|
|
1155
|
-
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;
|
|
1156
|
-
panel.hidden = false;
|
|
1157
|
-
trigger.setAttribute("aria-expanded", "true");
|
|
1158
|
-
buttons[next].focus();
|
|
1159
|
-
}
|
|
1160
|
-
function outside(event) {
|
|
1161
|
-
if (event.target instanceof Node && !root.contains(event.target)) {
|
|
1162
|
-
close();
|
|
1602
|
+
root.append(trigger, current);
|
|
1603
|
+
let next = "";
|
|
1604
|
+
function click() {
|
|
1605
|
+
if (next) {
|
|
1606
|
+
onChoose(next);
|
|
1163
1607
|
}
|
|
1164
1608
|
}
|
|
1165
1609
|
function update(i18n, value) {
|
|
1166
|
-
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 })}`;
|
|
1167
1616
|
trigger.setAttribute("aria-label", label);
|
|
1168
|
-
panel.setAttribute("aria-label", label);
|
|
1169
|
-
for (let index = 0;index < buttons.length; index++) {
|
|
1170
|
-
const [choice, key] = choices[index];
|
|
1171
|
-
const button = buttons[index];
|
|
1172
|
-
button.textContent = i18n.t(key);
|
|
1173
|
-
button.setAttribute("aria-pressed", String(choice === value));
|
|
1174
|
-
if (choice === value) {
|
|
1175
|
-
current.textContent = i18n.t(key);
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
1617
|
trigger.title = `${label}: ${current.textContent}`;
|
|
1179
|
-
trigger.
|
|
1618
|
+
trigger.dataset.value = value;
|
|
1619
|
+
trigger.dataset.nextValue = next;
|
|
1620
|
+
trigger.replaceChildren(icon(language ? "language" : value === "dark" ? "dark" : "light"));
|
|
1180
1621
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
root
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1622
|
+
trigger.addEventListener("click", click);
|
|
1623
|
+
return {
|
|
1624
|
+
root,
|
|
1625
|
+
update,
|
|
1626
|
+
dispose() {
|
|
1627
|
+
next = "";
|
|
1628
|
+
trigger.removeEventListener("click", click);
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/webui/request-id.ts
|
|
1634
|
+
function managementRequestId() {
|
|
1635
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
1636
|
+
return "request_" + Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1193
1637
|
}
|
|
1194
1638
|
|
|
1195
1639
|
// src/webui/write-contract.ts
|
|
@@ -1695,7 +2139,7 @@ function createDeviceManagementFlow(options) {
|
|
|
1695
2139
|
let active;
|
|
1696
2140
|
let generation = 0;
|
|
1697
2141
|
let disposed = false;
|
|
1698
|
-
const requestId = options.requestId ??
|
|
2142
|
+
const requestId = options.requestId ?? managementRequestId;
|
|
1699
2143
|
async function execute(operation, args, signal) {
|
|
1700
2144
|
if (signal.aborted) {
|
|
1701
2145
|
throw new ManagementClientError("aborted");
|
|
@@ -2320,7 +2764,7 @@ function createPairingManagementFlow(options) {
|
|
|
2320
2764
|
let active;
|
|
2321
2765
|
let generation = 0;
|
|
2322
2766
|
let disposed = false;
|
|
2323
|
-
const requestId = options.requestId ??
|
|
2767
|
+
const requestId = options.requestId ?? managementRequestId;
|
|
2324
2768
|
async function execute(operation, args, signal) {
|
|
2325
2769
|
if (signal.aborted) {
|
|
2326
2770
|
throw new ManagementClientError("aborted");
|
|
@@ -2543,6 +2987,18 @@ var errors2 = {
|
|
|
2543
2987
|
outcome_unconfirmed: "manage.unknown",
|
|
2544
2988
|
aborted: "manage.cancelled"
|
|
2545
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
|
+
}
|
|
2546
3002
|
function node3(tag, className = "") {
|
|
2547
3003
|
const element = document.createElement(tag);
|
|
2548
3004
|
element.className = className;
|
|
@@ -2550,6 +3006,18 @@ function node3(tag, className = "") {
|
|
|
2550
3006
|
}
|
|
2551
3007
|
function createPairingActions(options) {
|
|
2552
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");
|
|
2553
3021
|
const heading = node3("h2");
|
|
2554
3022
|
const intro = node3("p", "muted");
|
|
2555
3023
|
const form = node3("form", "pairing-form");
|
|
@@ -2561,7 +3029,10 @@ function createPairingActions(options) {
|
|
|
2561
3029
|
nameInput.maxLength = 63;
|
|
2562
3030
|
nameInput.pattern = "[a-z0-9][a-z0-9._-]{0,62}";
|
|
2563
3031
|
nameInput.autocomplete = "off";
|
|
2564
|
-
|
|
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);
|
|
2565
3036
|
const policyLabel = node3("label");
|
|
2566
3037
|
const policyCaption = node3("span");
|
|
2567
3038
|
const policyInput = node3("input");
|
|
@@ -2569,10 +3040,14 @@ function createPairingActions(options) {
|
|
|
2569
3040
|
policyInput.required = true;
|
|
2570
3041
|
policyInput.maxLength = 128;
|
|
2571
3042
|
policyInput.autocomplete = "off";
|
|
3043
|
+
policyInput.value = DEFAULT_PAIRING_POLICY_REF;
|
|
2572
3044
|
policyLabel.append(policyCaption, policyInput);
|
|
2573
3045
|
const create = node3("button", "pairing-confirm");
|
|
2574
3046
|
create.type = "submit";
|
|
2575
|
-
|
|
3047
|
+
const advanced = node3("details", "pairing-advanced");
|
|
3048
|
+
const advancedSummary = node3("summary");
|
|
3049
|
+
advanced.append(advancedSummary, policyLabel);
|
|
3050
|
+
form.append(nameLabel, advanced, create);
|
|
2576
3051
|
const reviewBox = node3("div", "pairing-review");
|
|
2577
3052
|
reviewBox.hidden = true;
|
|
2578
3053
|
const reviewText = node3("p", "pairing-status");
|
|
@@ -2590,7 +3065,21 @@ function createPairingActions(options) {
|
|
|
2590
3065
|
message.setAttribute("role", "status");
|
|
2591
3066
|
message.setAttribute("aria-live", "polite");
|
|
2592
3067
|
const list = node3("ul", "pairing-entries");
|
|
2593
|
-
|
|
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);
|
|
2594
3083
|
let i18n;
|
|
2595
3084
|
let available = false;
|
|
2596
3085
|
let disposed = false;
|
|
@@ -2695,6 +3184,11 @@ function createPairingActions(options) {
|
|
|
2695
3184
|
if (!i18n) {
|
|
2696
3185
|
return;
|
|
2697
3186
|
}
|
|
3187
|
+
const signature = entryRenderSignature(i18n.language, entry);
|
|
3188
|
+
if (entry.rendered === signature) {
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
entry.rendered = signature;
|
|
2698
3192
|
const t = i18n.t.bind(i18n);
|
|
2699
3193
|
entry.item.replaceChildren();
|
|
2700
3194
|
const head = node3("div", "pairing-entry-head");
|
|
@@ -2704,19 +3198,21 @@ function createPairingActions(options) {
|
|
|
2704
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");
|
|
2705
3199
|
head.append(title, status);
|
|
2706
3200
|
entry.item.append(head);
|
|
3201
|
+
status.classList.add("badge");
|
|
2707
3202
|
const facts = node3("dl", "facts");
|
|
2708
3203
|
const idTerm = node3("dt");
|
|
2709
3204
|
idTerm.textContent = t("pairing.application");
|
|
2710
3205
|
const idValue = node3("dd");
|
|
2711
3206
|
const idCode = node3("code");
|
|
2712
3207
|
idCode.textContent = entry.pairing_id;
|
|
2713
|
-
idValue.append(idCode);
|
|
3208
|
+
idValue.append(idCode, copyControl(entry.pairing_id, t("copy.id")));
|
|
2714
3209
|
const machineTerm = node3("dt");
|
|
2715
3210
|
machineTerm.textContent = t("pairing.machine");
|
|
2716
3211
|
const machineValue = node3("dd");
|
|
2717
3212
|
machineValue.textContent = entry.machine_id;
|
|
2718
3213
|
facts.append(idTerm, idValue, machineTerm, machineValue);
|
|
2719
3214
|
entry.item.append(facts);
|
|
3215
|
+
updateSteps();
|
|
2720
3216
|
if (entry.status === "pending" && entry.claim?.status === "pending") {
|
|
2721
3217
|
const codeLabel = node3("p", "muted");
|
|
2722
3218
|
codeLabel.textContent = t("pairing.userCode");
|
|
@@ -2738,6 +3234,19 @@ function createPairingActions(options) {
|
|
|
2738
3234
|
entry.item.append(codeLabel, code, approve);
|
|
2739
3235
|
}
|
|
2740
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
|
+
}
|
|
2741
3250
|
function addEntry(entry) {
|
|
2742
3251
|
const existing = entries.get(entry.pairing_id);
|
|
2743
3252
|
if (existing) {
|
|
@@ -2746,6 +3255,7 @@ function createPairingActions(options) {
|
|
|
2746
3255
|
return;
|
|
2747
3256
|
}
|
|
2748
3257
|
entries.set(entry.pairing_id, entry);
|
|
3258
|
+
options.committed?.();
|
|
2749
3259
|
list.append(entry.item);
|
|
2750
3260
|
renderEntry(entry);
|
|
2751
3261
|
}
|
|
@@ -2827,7 +3337,19 @@ function createPairingActions(options) {
|
|
|
2827
3337
|
available = isAvailable;
|
|
2828
3338
|
region.hidden = !isAvailable;
|
|
2829
3339
|
const t = localizer.t.bind(localizer);
|
|
2830
|
-
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");
|
|
2831
3353
|
intro.textContent = t("pairing.intro");
|
|
2832
3354
|
nameCaption.textContent = t("pairing.name");
|
|
2833
3355
|
policyCaption.textContent = t("pairing.policy");
|
|
@@ -2846,6 +3368,17 @@ function createPairingActions(options) {
|
|
|
2846
3368
|
suspend() {
|
|
2847
3369
|
stopPolling();
|
|
2848
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
|
+
},
|
|
2849
3382
|
clear() {
|
|
2850
3383
|
stopPolling();
|
|
2851
3384
|
flow?.dispose();
|
|
@@ -2864,8 +3397,305 @@ function createPairingActions(options) {
|
|
|
2864
3397
|
};
|
|
2865
3398
|
}
|
|
2866
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
|
+
|
|
2867
3697
|
// src/webui/page.ts
|
|
2868
|
-
function element(tag, text, className = "") {
|
|
3698
|
+
function element(tag, text = "", className = "") {
|
|
2869
3699
|
const result = document.createElement(tag);
|
|
2870
3700
|
result.textContent = text;
|
|
2871
3701
|
result.className = className;
|
|
@@ -2874,21 +3704,51 @@ function element(tag, text, className = "") {
|
|
|
2874
3704
|
async function unavailableRead() {
|
|
2875
3705
|
throw new ReadError2("unavailable");
|
|
2876
3706
|
}
|
|
2877
|
-
function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
2878
|
-
const
|
|
3707
|
+
function mountWebUi(root, read = unavailableRead, clock, management, resources) {
|
|
3708
|
+
const sidebar = element("aside", "", "sidebar");
|
|
2879
3709
|
const brand = element("a", "", "brand");
|
|
2880
3710
|
brand.href = "#";
|
|
2881
3711
|
const logo = element("img", "", "brand-logo");
|
|
2882
|
-
logo.src = "./rovloom-
|
|
3712
|
+
logo.src = "./rovloom-logo.png";
|
|
2883
3713
|
logo.alt = "Rovloom";
|
|
2884
|
-
logo.width =
|
|
2885
|
-
logo.height =
|
|
2886
|
-
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"));
|
|
2887
3747
|
const title = element("span", "", "brand-context");
|
|
3748
|
+
headerLabel.append(title);
|
|
2888
3749
|
const preferencesBar = element("div", "", "preferences");
|
|
2889
|
-
const commandbar = element("
|
|
2890
|
-
|
|
2891
|
-
sectionLink.href = "#";
|
|
3750
|
+
const commandbar = element("div", "", "commandbar");
|
|
3751
|
+
commandbar.setAttribute("role", "toolbar");
|
|
2892
3752
|
const toolbar = element("div", "", "toolbar");
|
|
2893
3753
|
const refresh = element("button", "");
|
|
2894
3754
|
refresh.type = "button";
|
|
@@ -2902,25 +3762,24 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
2902
3762
|
content.tabIndex = -1;
|
|
2903
3763
|
refresh.setAttribute("aria-controls", content.id);
|
|
2904
3764
|
cancel.setAttribute("aria-controls", content.id);
|
|
2905
|
-
const language = createPreferenceControl("language",
|
|
2906
|
-
|
|
2907
|
-
["zh-CN", "language.chinese"],
|
|
2908
|
-
["en", "language.english"]
|
|
2909
|
-
], (value) => preferences.setLanguage(value));
|
|
2910
|
-
const theme = createPreferenceControl("theme", [
|
|
2911
|
-
["system", "theme.system"],
|
|
2912
|
-
["light", "theme.light"],
|
|
2913
|
-
["dark", "theme.dark"]
|
|
2914
|
-
], (value) => preferences.setTheme(value));
|
|
3765
|
+
const language = createPreferenceControl("language", (value) => preferences.setLanguage(value));
|
|
3766
|
+
const theme = createPreferenceControl("theme", (value) => preferences.setTheme(value));
|
|
2915
3767
|
preferencesBar.append(language.root, theme.root);
|
|
2916
3768
|
toolbar.append(indicator, refresh, cancel);
|
|
2917
|
-
commandbar.append(
|
|
2918
|
-
|
|
3769
|
+
commandbar.append(toolbar);
|
|
3770
|
+
const headerActions = element("div", "", "topbar-actions");
|
|
3771
|
+
headerActions.append(commandbar, preferencesBar);
|
|
3772
|
+
header.append(headerLabel, headerActions);
|
|
2919
3773
|
const skip = element("a", "", "skip-link");
|
|
2920
3774
|
skip.href = "#management-content";
|
|
2921
3775
|
const footer = element("footer", "", "muted");
|
|
2922
|
-
|
|
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);
|
|
2923
3781
|
let route = parseRoute(location.hash);
|
|
3782
|
+
let filter = { query: "", status: "all" };
|
|
2924
3783
|
let reader;
|
|
2925
3784
|
let disposed = false;
|
|
2926
3785
|
let i18n = createLocalizer("en");
|
|
@@ -2946,6 +3805,9 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
2946
3805
|
}
|
|
2947
3806
|
const pairing = management ? createPairingActions({
|
|
2948
3807
|
transport: management,
|
|
3808
|
+
committed() {
|
|
3809
|
+
resourcePanel?.refresh();
|
|
3810
|
+
},
|
|
2949
3811
|
authorizationLost(problem) {
|
|
2950
3812
|
if (disposed) {
|
|
2951
3813
|
return;
|
|
@@ -2959,20 +3821,62 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
2959
3821
|
if (pairing) {
|
|
2960
3822
|
footer.before(pairing.region);
|
|
2961
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
|
+
}
|
|
2962
3843
|
function paint(state, current) {
|
|
2963
3844
|
refresh.disabled = state.pending || ["unauthenticated", "forbidden", "not_found"].includes(state.problem ?? "");
|
|
2964
3845
|
cancel.disabled = !state.pending;
|
|
2965
3846
|
indicator.textContent = i18n.t(state.pending ? "reader.pending" : state.snapshot ? state.stale ? "reader.stale" : "reader.snapshot" : "reader.empty");
|
|
2966
3847
|
content.setAttribute("aria-busy", String(state.pending));
|
|
2967
3848
|
indicator.dataset.state = state.pending ? "pending" : state.stale ? "stale" : state.snapshot ? "current" : "empty";
|
|
2968
|
-
sectionLink.setAttribute("aria-current", current.machineId === null ? "page" : "location");
|
|
2969
3849
|
if (["unauthenticated", "forbidden", "not_found"].includes(state.problem ?? "")) {
|
|
2970
3850
|
actions?.clear();
|
|
2971
3851
|
pairing?.clear();
|
|
2972
3852
|
}
|
|
2973
3853
|
pairing?.update(i18n, !!state.snapshot && !state.stale && state.snapshot.value.status.mode === "active");
|
|
2974
3854
|
actions?.update(i18n, !!state.snapshot && !state.stale && state.snapshot.value.status.mode === "active", state.snapshot ? state.snapshot.observedAt + state.ageMs : undefined);
|
|
2975
|
-
|
|
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
|
+
}
|
|
2976
3880
|
}
|
|
2977
3881
|
function applyPreferences(state) {
|
|
2978
3882
|
const languageChanged = i18n.language !== state.language;
|
|
@@ -2984,15 +3888,23 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
2984
3888
|
document.documentElement.dataset.theme = state.theme;
|
|
2985
3889
|
document.title = t("app.title");
|
|
2986
3890
|
brand.setAttribute("aria-label", t("app.navigation"));
|
|
2987
|
-
title.textContent = t("
|
|
2988
|
-
|
|
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
|
+
}
|
|
2989
3901
|
commandbar.setAttribute("aria-label", t("app.subtitle"));
|
|
2990
3902
|
refresh.textContent = t("action.refresh");
|
|
2991
3903
|
cancel.textContent = t("action.cancel");
|
|
2992
3904
|
skip.textContent = t("app.skip");
|
|
2993
3905
|
footer.textContent = t(actions ? "app.managedFooter" : "app.footer");
|
|
2994
|
-
language.update(i18n, state.
|
|
2995
|
-
theme.update(i18n, state.
|
|
3906
|
+
language.update(i18n, state.language);
|
|
3907
|
+
theme.update(i18n, state.theme);
|
|
2996
3908
|
if (languageChanged && reader) {
|
|
2997
3909
|
paint(reader.state(), route);
|
|
2998
3910
|
}
|
|
@@ -3014,6 +3926,7 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3014
3926
|
content.focus();
|
|
3015
3927
|
}
|
|
3016
3928
|
function refreshPage() {
|
|
3929
|
+
resourcePanel?.refresh();
|
|
3017
3930
|
reader.refresh();
|
|
3018
3931
|
}
|
|
3019
3932
|
function cancelRead() {
|
|
@@ -3054,11 +3967,41 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3054
3967
|
actions.open({ action, machine_id: device.machine_id }, button);
|
|
3055
3968
|
}
|
|
3056
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
|
+
}
|
|
3057
4001
|
function navigate() {
|
|
4002
|
+
filter = { query: "", status: "all" };
|
|
3058
4003
|
actions?.suspend();
|
|
3059
4004
|
pairing?.suspend();
|
|
3060
|
-
language.close();
|
|
3061
|
-
theme.close();
|
|
3062
4005
|
if (location.hash === "#management-content") {
|
|
3063
4006
|
content.focus();
|
|
3064
4007
|
return;
|
|
@@ -3086,9 +4029,8 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3086
4029
|
function hide() {
|
|
3087
4030
|
actions?.suspend();
|
|
3088
4031
|
pairing?.suspend();
|
|
3089
|
-
language.close();
|
|
3090
|
-
theme.close();
|
|
3091
4032
|
reader.dispose();
|
|
4033
|
+
resourcePanel?.clear();
|
|
3092
4034
|
content.replaceChildren();
|
|
3093
4035
|
}
|
|
3094
4036
|
function dispose() {
|
|
@@ -3097,9 +4039,13 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3097
4039
|
}
|
|
3098
4040
|
disposed = true;
|
|
3099
4041
|
reader.dispose();
|
|
4042
|
+
resourcePanel?.dispose();
|
|
3100
4043
|
actions?.dispose();
|
|
3101
4044
|
pairing?.dispose();
|
|
3102
4045
|
content.removeEventListener("click", deviceAction);
|
|
4046
|
+
content.removeEventListener("click", filterDevices);
|
|
4047
|
+
content.removeEventListener("input", searchDevices);
|
|
4048
|
+
root.removeEventListener("click", copy);
|
|
3103
4049
|
skip.removeEventListener("click", skipContent);
|
|
3104
4050
|
refresh.removeEventListener("click", refreshPage);
|
|
3105
4051
|
cancel.removeEventListener("click", cancelRead);
|
|
@@ -3116,6 +4062,9 @@ function mountWebUi(root, read = unavailableRead, clock, management) {
|
|
|
3116
4062
|
refresh.addEventListener("click", refreshPage);
|
|
3117
4063
|
cancel.addEventListener("click", cancelRead);
|
|
3118
4064
|
content.addEventListener("click", deviceAction);
|
|
4065
|
+
content.addEventListener("click", filterDevices);
|
|
4066
|
+
content.addEventListener("input", searchDevices);
|
|
4067
|
+
root.addEventListener("click", copy);
|
|
3119
4068
|
window.addEventListener("hashchange", navigate);
|
|
3120
4069
|
document.addEventListener("visibilitychange", visibility);
|
|
3121
4070
|
window.addEventListener("pageshow", show);
|
|
@@ -3507,4 +4456,4 @@ export {
|
|
|
3507
4456
|
unavailableRead
|
|
3508
4457
|
};
|
|
3509
4458
|
|
|
3510
|
-
//# debugId=
|
|
4459
|
+
//# debugId=9FAF58405B4DF80264756E2164756E21
|