dsh-remote-tunnel-easy 1.4.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/LICENSE +21 -0
- package/README.md +166 -0
- package/assets/logo.svg +81 -0
- package/cordis.patch.yml +12 -0
- package/index.js +792 -0
- package/lib/client.js +617 -0
- package/lib/cloudflared.js +196 -0
- package/lib/qrcode.js +591 -0
- package/lib/update.js +221 -0
- package/package.json +51 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-remote-tunnel-easy",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
|
|
10
|
+
//#region css
|
|
11
|
+
const css = [
|
|
12
|
+
".dshmh-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;min-width:0;list-style:none;transition:border-color .16s,background .16s;overflow:hidden}",
|
|
13
|
+
".dshmh-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
|
|
14
|
+
".dshmh-header{width:100%;color:inherit;cursor:pointer;text-align:left;font:inherit;background:0 0;border:0;align-items:center;gap:8px;padding:10px 14px;display:flex}",
|
|
15
|
+
".dshmh-header:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}",
|
|
16
|
+
".dshmh-headText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex;overflow:hidden}",
|
|
17
|
+
".dshmh-name{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;font-weight:600;overflow:hidden}",
|
|
18
|
+
".dshmh-description{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;font-size:12px;overflow:hidden}",
|
|
19
|
+
".dshmh-pending{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;flex:none;font-size:12px}",
|
|
20
|
+
".dshmh-chevron{color:var(--dsw-alias-label-tertiary);flex:none;font-size:13px;transition:transform .12s}",
|
|
21
|
+
".dshmh-chevronOpen{transform:rotate(180deg)}",
|
|
22
|
+
".dshmh-body{flex-direction:column;gap:14px;padding:0 14px 14px;display:flex}",
|
|
23
|
+
".dshmh-qrRow{display:flex;gap:16px;align-items:center;flex-wrap:wrap}",
|
|
24
|
+
".dshmh-qr{border:1px solid var(--dsw-alias-border-l2);background:#ffffff;border-radius:6px;padding:10px;flex:none}",
|
|
25
|
+
".dshmh-qr svg{display:block}",
|
|
26
|
+
".dshmh-linkBlock{flex-direction:column;gap:8px;min-width:0;flex:1;display:flex}",
|
|
27
|
+
".dshmh-link{color:var(--dsw-alias-state-business-primary);font-size:12px;text-decoration:none;word-break:break-all}",
|
|
28
|
+
".dshmh-link:hover{text-decoration:underline}",
|
|
29
|
+
".dshmh-hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px}",
|
|
30
|
+
".dshmh-failed{color:var(--dsw-alias-state-error-primary);font-size:12px}",
|
|
31
|
+
".dshmh-field{flex-direction:column;gap:4px;min-width:0;display:flex}",
|
|
32
|
+
".dshmh-label{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}",
|
|
33
|
+
".dshmh-input{border:1px solid var(--dsw-alias-border-l2);font:inherit;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border-radius:6px;padding:6px 8px;font-size:13px;transition:border-color .13s,box-shadow .13s;width:100%}",
|
|
34
|
+
".dshmh-input:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed)}",
|
|
35
|
+
".dshmh-input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}",
|
|
36
|
+
".dshmh-input:disabled{opacity:.6;cursor:default}",
|
|
37
|
+
".dshmh-check{gap:6px;align-items:center;display:flex;color:var(--dsw-alias-label-primary);font-size:13px;cursor:pointer}",
|
|
38
|
+
".dshmh-check input{accent-color:var(--dsw-alias-state-business-primary)}",
|
|
39
|
+
".dshmh-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex;flex-wrap:wrap}",
|
|
40
|
+
".dshmh-btn{font:inherit;cursor:pointer;border-radius:6px;padding:5px 12px;font-size:13px;transition:background-color .13s,border-color .13s,color .13s}",
|
|
41
|
+
".dshmh-save{border:1px solid var(--dsw-alias-button-info-fill);background:var(--dsw-alias-button-info-fill);color:var(--dsw-alias-label-primary-foreground)}",
|
|
42
|
+
".dshmh-save:hover:not(:disabled){border-color:var(--dsw-alias-button-info-hover);background:var(--dsw-alias-button-info-hover)}",
|
|
43
|
+
".dshmh-save:disabled{opacity:.5;cursor:default}",
|
|
44
|
+
].join("");
|
|
45
|
+
const tagId = "dsh-remote-tunnel-easy/card.css";
|
|
46
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
47
|
+
const tag = document.createElement("style");
|
|
48
|
+
tag.dataset.plugin = "dsh-remote-tunnel-easy";
|
|
49
|
+
tag.dataset.pluginCss = tagId;
|
|
50
|
+
tag.textContent = css;
|
|
51
|
+
document.head.appendChild(tag);
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
|
|
55
|
+
const BRIDGE_PREFIX = "/api/dsh-remote-handoff-settings";
|
|
56
|
+
const NS = "remote-handoff";
|
|
57
|
+
const lang = typeof navigator !== "undefined" && /^zh\b/i.test(navigator.language ?? "") ? "zh" : "en";
|
|
58
|
+
const I18N = {
|
|
59
|
+
zh: {
|
|
60
|
+
description: "手机免配置访问 —— 扫描二维码即可在手机上打开当前会话",
|
|
61
|
+
starting: "隧道启动中…",
|
|
62
|
+
installing: "正在准备 cloudflared…",
|
|
63
|
+
ready: "扫描二维码在手机上打开当前会话",
|
|
64
|
+
failed: "隧道不可用",
|
|
65
|
+
openLink: "在手机上打开链接 ↗",
|
|
66
|
+
copy: "复制链接",
|
|
67
|
+
copied: "已复制",
|
|
68
|
+
regenerate: "重新生成",
|
|
69
|
+
regenerating: "正在重新生成…",
|
|
70
|
+
target: "隧道目标(本机 harness 地址)",
|
|
71
|
+
cloudflaredPath: "cloudflared 路径",
|
|
72
|
+
sessionHandoff: "会话握手(扫描后进入当前会话)",
|
|
73
|
+
save: "保存",
|
|
74
|
+
saving: "保存中…",
|
|
75
|
+
unsaved: "未保存",
|
|
76
|
+
saveFailed: "保存失败",
|
|
77
|
+
pluginUpdate: "插件更新",
|
|
78
|
+
checkUpdate: "检查更新",
|
|
79
|
+
checkingUpdate: "检查中…",
|
|
80
|
+
upToDate: "已是最新",
|
|
81
|
+
updateAvailable: "有可用更新",
|
|
82
|
+
updating: "更新中…",
|
|
83
|
+
updated: "已更新 — 重启 dsh web 生效",
|
|
84
|
+
updateFailed: "更新失败",
|
|
85
|
+
restartRequired: "重启 dsh web 以应用更新",
|
|
86
|
+
unavailable: "设置不可用 —— remote-handoff 桥接未暴露。",
|
|
87
|
+
},
|
|
88
|
+
en: {
|
|
89
|
+
description: "Hassle-free remote access — scan the QR to open the current session on your phone",
|
|
90
|
+
starting: "Starting tunnel…",
|
|
91
|
+
installing: "Preparing cloudflared…",
|
|
92
|
+
ready: "Scan the QR to open the current session on your phone",
|
|
93
|
+
failed: "Tunnel unavailable",
|
|
94
|
+
openLink: "Open link on phone ↗",
|
|
95
|
+
copy: "Copy link",
|
|
96
|
+
copied: "Copied",
|
|
97
|
+
regenerate: "Regenerate",
|
|
98
|
+
regenerating: "Regenerating…",
|
|
99
|
+
target: "Tunnel target (local harness address)",
|
|
100
|
+
cloudflaredPath: "cloudflared path",
|
|
101
|
+
sessionHandoff: "Session handoff (scan opens the current session)",
|
|
102
|
+
save: "Save",
|
|
103
|
+
saving: "Saving…",
|
|
104
|
+
unsaved: "Unsaved",
|
|
105
|
+
saveFailed: "Save failed",
|
|
106
|
+
pluginUpdate: "Plugin update",
|
|
107
|
+
checkUpdate: "Check for updates",
|
|
108
|
+
checkingUpdate: "Checking…",
|
|
109
|
+
upToDate: "Up to date",
|
|
110
|
+
updateAvailable: "Update available",
|
|
111
|
+
updating: "Updating…",
|
|
112
|
+
updated: "Updated — restart dsh web to apply",
|
|
113
|
+
updateFailed: "Update failed",
|
|
114
|
+
restartRequired: "Restart dsh web to apply",
|
|
115
|
+
unavailable: "Settings unavailable — remote-handoff bridge not exposed.",
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
async function bridgeDescribe() {
|
|
120
|
+
const response = await fetch(`${BRIDGE_PREFIX}/describe`, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: { "content-type": "application/json" },
|
|
123
|
+
body: "{}",
|
|
124
|
+
});
|
|
125
|
+
return response.json();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function bridgeMutate(payload) {
|
|
129
|
+
const response = await fetch(`${BRIDGE_PREFIX}/mutate`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: { "content-type": "application/json" },
|
|
132
|
+
body: JSON.stringify(payload),
|
|
133
|
+
});
|
|
134
|
+
return response.json();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function bridgeStatus() {
|
|
138
|
+
const response = await fetch(`${BRIDGE_PREFIX}/status`, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { "content-type": "application/json" },
|
|
141
|
+
body: "{}",
|
|
142
|
+
});
|
|
143
|
+
return response.json();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function bridgeRegenerate() {
|
|
147
|
+
const response = await fetch(`${BRIDGE_PREFIX}/regenerate`, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: { "content-type": "application/json" },
|
|
150
|
+
body: "{}",
|
|
151
|
+
});
|
|
152
|
+
return response.json();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function bridgeUpdateStatus() {
|
|
156
|
+
const response = await fetch(`${BRIDGE_PREFIX}/update-status`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { "content-type": "application/json" },
|
|
159
|
+
body: "{}",
|
|
160
|
+
});
|
|
161
|
+
return response.json();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function bridgeCheckUpdate() {
|
|
165
|
+
const response = await fetch(`${BRIDGE_PREFIX}/check-update`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { "content-type": "application/json" },
|
|
168
|
+
body: "{}",
|
|
169
|
+
});
|
|
170
|
+
return response.json();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function bridgeUpdate() {
|
|
174
|
+
const response = await fetch(`${BRIDGE_PREFIX}/update`, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: { "content-type": "application/json" },
|
|
177
|
+
body: "{}",
|
|
178
|
+
});
|
|
179
|
+
return response.json();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Render a QR matrix (count×count 0/1 cells) to a compact SVG element tree. */
|
|
183
|
+
function qrSvg(matrix, size) {
|
|
184
|
+
const cell = size / matrix.count;
|
|
185
|
+
const dark = [];
|
|
186
|
+
for (let r = 0; r < matrix.count; r++) {
|
|
187
|
+
for (let c = 0; c < matrix.count; c++) {
|
|
188
|
+
if (matrix.modules[r][c]) {
|
|
189
|
+
dark.push(
|
|
190
|
+
react_jsx_runtime.jsx("rect", {
|
|
191
|
+
key: r + ":" + c,
|
|
192
|
+
x: c * cell,
|
|
193
|
+
y: r * cell,
|
|
194
|
+
width: cell,
|
|
195
|
+
height: cell,
|
|
196
|
+
fill: "#000000",
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return react_jsx_runtime.jsx("svg", {
|
|
203
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
204
|
+
viewBox: "0 0 " + size + " " + size,
|
|
205
|
+
width: size,
|
|
206
|
+
height: size,
|
|
207
|
+
children: [
|
|
208
|
+
react_jsx_runtime.jsx("rect", { x: 0, y: 0, width: size, height: size, fill: "#ffffff" }),
|
|
209
|
+
dark,
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function RemoteHandoffCard() {
|
|
215
|
+
const [open, setOpen] = react.useState(false);
|
|
216
|
+
const [state, setState] = react.useState({ status: "loading" });
|
|
217
|
+
const [tunnelStatus, setTunnelStatus] = react.useState({ state: "starting" });
|
|
218
|
+
const [target, setTarget] = react.useState("");
|
|
219
|
+
const [autoTarget, setAutoTarget] = react.useState("");
|
|
220
|
+
const [cloudflaredPath, setCloudflaredPath] = react.useState("");
|
|
221
|
+
const [sessionHandoff, setSessionHandoff] = react.useState(true);
|
|
222
|
+
const [dirty, setDirty] = react.useState(false);
|
|
223
|
+
const [saving, setSaving] = react.useState(false);
|
|
224
|
+
const [failed, setFailed] = react.useState(false);
|
|
225
|
+
const [copied, setCopied] = react.useState(false);
|
|
226
|
+
const [regenerating, setRegenerating] = react.useState(false);
|
|
227
|
+
const [updateStatus, setUpdateStatus] = react.useState({ state: "idle", currentVersion: null, latestVersion: null });
|
|
228
|
+
const [checking, setChecking] = react.useState(false);
|
|
229
|
+
const [updating, setUpdating] = react.useState(false);
|
|
230
|
+
|
|
231
|
+
const load = react.useCallback(async () => {
|
|
232
|
+
try {
|
|
233
|
+
const result = await bridgeDescribe();
|
|
234
|
+
if (result.ok) {
|
|
235
|
+
const view = result.value.namespaces.find((n) => n.ns === NS);
|
|
236
|
+
if (view) {
|
|
237
|
+
const v = view.value ?? {};
|
|
238
|
+
setTarget(v.tunnelTarget ?? "");
|
|
239
|
+
setCloudflaredPath(v.cloudflaredPath ?? "");
|
|
240
|
+
setSessionHandoff(v.sessionHandoff !== false);
|
|
241
|
+
setState({ status: "ready", writable: result.value.writable });
|
|
242
|
+
} else {
|
|
243
|
+
setState({ status: "unavailable" });
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
setState({ status: "unavailable" });
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
setState({ status: "unavailable" });
|
|
250
|
+
}
|
|
251
|
+
}, []);
|
|
252
|
+
|
|
253
|
+
const pollStatus = react.useCallback(async () => {
|
|
254
|
+
try {
|
|
255
|
+
const result = await bridgeStatus();
|
|
256
|
+
if (result.ok) {
|
|
257
|
+
setTunnelStatus(result.value);
|
|
258
|
+
// Track the Host-resolved auto target so the input can prefill and
|
|
259
|
+
// stay truthful when the persisted `tunnelTarget` is empty ("auto").
|
|
260
|
+
if (result.value && typeof result.value.resolvedTarget === "string") {
|
|
261
|
+
setAutoTarget(result.value.resolvedTarget);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch {}
|
|
265
|
+
}, []);
|
|
266
|
+
|
|
267
|
+
const regenerate = async () => {
|
|
268
|
+
setRegenerating(true);
|
|
269
|
+
try {
|
|
270
|
+
await bridgeRegenerate();
|
|
271
|
+
} catch {
|
|
272
|
+
// Network/lifecycle failure: the next pollStatus reveals the state.
|
|
273
|
+
}
|
|
274
|
+
pollStatus();
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Clear the "regenerating" flag once the Host reports a terminal state
|
|
278
|
+
// (ready with a fresh URL, or failed) so the button re-enables.
|
|
279
|
+
react.useEffect(() => {
|
|
280
|
+
if (regenerating && (tunnelStatus.state === "ready" || tunnelStatus.state === "failed")) {
|
|
281
|
+
setRegenerating(false);
|
|
282
|
+
}
|
|
283
|
+
}, [regenerating, tunnelStatus.state]);
|
|
284
|
+
|
|
285
|
+
// Fast-poll while regenerating so the new QR appears promptly.
|
|
286
|
+
react.useEffect(() => {
|
|
287
|
+
if (!regenerating) return;
|
|
288
|
+
const t = setInterval(pollStatus, 700);
|
|
289
|
+
return () => clearInterval(t);
|
|
290
|
+
}, [regenerating, pollStatus]);
|
|
291
|
+
|
|
292
|
+
react.useEffect(() => {
|
|
293
|
+
load();
|
|
294
|
+
}, [load]);
|
|
295
|
+
|
|
296
|
+
react.useEffect(() => {
|
|
297
|
+
pollStatus();
|
|
298
|
+
const t = setInterval(pollStatus, 5000);
|
|
299
|
+
return () => clearInterval(t);
|
|
300
|
+
}, [pollStatus]);
|
|
301
|
+
|
|
302
|
+
const refreshUpdateStatus = react.useCallback(async () => {
|
|
303
|
+
try {
|
|
304
|
+
const result = await bridgeUpdateStatus();
|
|
305
|
+
if (result.ok) setUpdateStatus(result.value);
|
|
306
|
+
} catch {}
|
|
307
|
+
}, []);
|
|
308
|
+
|
|
309
|
+
const doCheckUpdate = async () => {
|
|
310
|
+
setChecking(true);
|
|
311
|
+
try {
|
|
312
|
+
const result = await bridgeCheckUpdate();
|
|
313
|
+
if (result.ok) setUpdateStatus(result.value);
|
|
314
|
+
else if (result.value) setUpdateStatus(result.value);
|
|
315
|
+
else if (result.error || result.message) setUpdateStatus((prev) => ({ ...prev, state: "failed", error: result.message || result.error }));
|
|
316
|
+
} catch {}
|
|
317
|
+
setChecking(false);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const doUpdate = async () => {
|
|
321
|
+
setUpdating(true);
|
|
322
|
+
try {
|
|
323
|
+
const result = await bridgeUpdate();
|
|
324
|
+
if (result.ok) setUpdateStatus(result.value);
|
|
325
|
+
else if (result.value) setUpdateStatus(result.value);
|
|
326
|
+
else if (result.error || result.message) setUpdateStatus((prev) => ({ ...prev, state: "failed", error: result.message || result.error }));
|
|
327
|
+
} catch {}
|
|
328
|
+
setUpdating(false);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
react.useEffect(() => {
|
|
332
|
+
refreshUpdateStatus();
|
|
333
|
+
}, [refreshUpdateStatus]);
|
|
334
|
+
|
|
335
|
+
const save = async () => {
|
|
336
|
+
setSaving(true);
|
|
337
|
+
setFailed(false);
|
|
338
|
+
try {
|
|
339
|
+
const ops = [
|
|
340
|
+
{ op: "set", path: ["tunnelTarget"], value: target },
|
|
341
|
+
{ op: "set", path: ["cloudflaredPath"], value: cloudflaredPath },
|
|
342
|
+
{ op: "set", path: ["sessionHandoff"], value: sessionHandoff },
|
|
343
|
+
];
|
|
344
|
+
const result = await bridgeMutate({ ns: NS, ops });
|
|
345
|
+
if (result.ok) {
|
|
346
|
+
setDirty(false);
|
|
347
|
+
load();
|
|
348
|
+
} else {
|
|
349
|
+
setFailed(true);
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
setFailed(true);
|
|
353
|
+
} finally {
|
|
354
|
+
setSaving(false);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const copyLink = async () => {
|
|
359
|
+
if (!tunnelStatus.accessUrl) return;
|
|
360
|
+
try {
|
|
361
|
+
await navigator.clipboard.writeText(tunnelStatus.accessUrl);
|
|
362
|
+
setCopied(true);
|
|
363
|
+
setTimeout(() => setCopied(false), 1500);
|
|
364
|
+
} catch {}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
if (state.status === "loading") return null;
|
|
368
|
+
if (state.status === "unavailable") {
|
|
369
|
+
const zh = lang === "zh";
|
|
370
|
+
return react_jsx_runtime.jsx("li", {
|
|
371
|
+
className: "dshmh-card",
|
|
372
|
+
children: react_jsx_runtime.jsx("div", {
|
|
373
|
+
className: "dshmh-body",
|
|
374
|
+
style: { padding: "14px" },
|
|
375
|
+
children: react_jsx_runtime.jsx("span", {
|
|
376
|
+
className: "dshmh-failed",
|
|
377
|
+
children: zh ? I18N.zh.unavailable : I18N.en.unavailable,
|
|
378
|
+
}),
|
|
379
|
+
}),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const t = lang === "zh" ? I18N.zh : I18N.en;
|
|
384
|
+
const hasQr = tunnelStatus.state === "ready" && tunnelStatus.matrix;
|
|
385
|
+
const installing = tunnelStatus.state !== "ready" && tunnelStatus.state !== "failed"
|
|
386
|
+
&& typeof tunnelStatus.phase === "string" && tunnelStatus.phase !== "starting tunnel…";
|
|
387
|
+
const statusLabel = tunnelStatus.state === "ready"
|
|
388
|
+
? t.ready
|
|
389
|
+
: tunnelStatus.state === "failed" ? t.failed : (installing ? t.installing : t.starting);
|
|
390
|
+
const targetValue = target || autoTarget;
|
|
391
|
+
|
|
392
|
+
return react_jsx_runtime.jsx("li", {
|
|
393
|
+
className: open ? "dshmh-card dshmh-cardOpen" : "dshmh-card",
|
|
394
|
+
children: [
|
|
395
|
+
react_jsx_runtime.jsx("button", {
|
|
396
|
+
type: "button",
|
|
397
|
+
className: "dshmh-header",
|
|
398
|
+
"aria-expanded": open,
|
|
399
|
+
onClick: () => setOpen(!open),
|
|
400
|
+
children: [
|
|
401
|
+
react_jsx_runtime.jsx("span", {
|
|
402
|
+
className: "dshmh-headText",
|
|
403
|
+
children: [
|
|
404
|
+
react_jsx_runtime.jsx("span", { className: "dshmh-name", children: "DSH Remote" }),
|
|
405
|
+
react_jsx_runtime.jsx("span", { className: "dshmh-description", children: t.description }),
|
|
406
|
+
],
|
|
407
|
+
}),
|
|
408
|
+
dirty ? react_jsx_runtime.jsx("span", { className: "dshmh-pending", children: t.unsaved }) : null,
|
|
409
|
+
react_jsx_runtime.jsx("span", { className: "dshmh-pending", children: statusLabel }),
|
|
410
|
+
react_jsx_runtime.jsx("span", {
|
|
411
|
+
className: open ? "dshmh-chevron dshmh-chevronOpen" : "dshmh-chevron",
|
|
412
|
+
children: "▾",
|
|
413
|
+
}),
|
|
414
|
+
],
|
|
415
|
+
}),
|
|
416
|
+
open
|
|
417
|
+
? react_jsx_runtime.jsx("div", {
|
|
418
|
+
className: "dshmh-body",
|
|
419
|
+
children: [
|
|
420
|
+
tunnelStatus.state === "failed"
|
|
421
|
+
? react_jsx_runtime.jsx("span", {
|
|
422
|
+
className: "dshmh-failed",
|
|
423
|
+
children: tunnelStatus.error ? t.failed + ": " + tunnelStatus.error : t.failed,
|
|
424
|
+
})
|
|
425
|
+
: null,
|
|
426
|
+
hasQr
|
|
427
|
+
? react_jsx_runtime.jsx("div", {
|
|
428
|
+
className: "dshmh-qrRow",
|
|
429
|
+
children: [
|
|
430
|
+
react_jsx_runtime.jsx("div", {
|
|
431
|
+
className: "dshmh-qr",
|
|
432
|
+
children: qrSvg(tunnelStatus.matrix, 176),
|
|
433
|
+
}),
|
|
434
|
+
react_jsx_runtime.jsx("div", {
|
|
435
|
+
className: "dshmh-linkBlock",
|
|
436
|
+
children: [
|
|
437
|
+
react_jsx_runtime.jsx("a", {
|
|
438
|
+
className: "dshmh-link",
|
|
439
|
+
href: tunnelStatus.accessUrl,
|
|
440
|
+
target: "_blank",
|
|
441
|
+
rel: "noopener noreferrer",
|
|
442
|
+
children: tunnelStatus.accessUrl,
|
|
443
|
+
}),
|
|
444
|
+
react_jsx_runtime.jsx("div", {
|
|
445
|
+
style: { display: "flex", gap: "8px", flexWrap: "wrap" },
|
|
446
|
+
children: [
|
|
447
|
+
react_jsx_runtime.jsx("a", {
|
|
448
|
+
className: "dshmh-link",
|
|
449
|
+
href: tunnelStatus.accessUrl,
|
|
450
|
+
target: "_blank",
|
|
451
|
+
rel: "noopener noreferrer",
|
|
452
|
+
children: t.openLink,
|
|
453
|
+
}),
|
|
454
|
+
react_jsx_runtime.jsx("button", {
|
|
455
|
+
className: "dshmh-link",
|
|
456
|
+
type: "button",
|
|
457
|
+
style: { background: "none", border: "none", cursor: "pointer", padding: 0 },
|
|
458
|
+
onClick: copyLink,
|
|
459
|
+
children: copied ? t.copied : t.copy,
|
|
460
|
+
}),
|
|
461
|
+
react_jsx_runtime.jsx("button", {
|
|
462
|
+
className: "dshmh-btn dshmh-save",
|
|
463
|
+
type: "button",
|
|
464
|
+
onClick: regenerate,
|
|
465
|
+
disabled: regenerating,
|
|
466
|
+
children: regenerating ? t.regenerating : t.regenerate,
|
|
467
|
+
}),
|
|
468
|
+
],
|
|
469
|
+
}),
|
|
470
|
+
],
|
|
471
|
+
}),
|
|
472
|
+
],
|
|
473
|
+
})
|
|
474
|
+
: null,
|
|
475
|
+
react_jsx_runtime.jsx("div", {
|
|
476
|
+
className: "dshmh-field",
|
|
477
|
+
children: [
|
|
478
|
+
react_jsx_runtime.jsx("span", { className: "dshmh-label", children: t.target }),
|
|
479
|
+
react_jsx_runtime.jsx("input", {
|
|
480
|
+
className: "dshmh-input",
|
|
481
|
+
value: targetValue,
|
|
482
|
+
placeholder: autoTarget || "http://127.0.0.1:3080",
|
|
483
|
+
disabled: saving,
|
|
484
|
+
onChange: (e) => { setTarget(e.target.value); setDirty(true); },
|
|
485
|
+
}),
|
|
486
|
+
],
|
|
487
|
+
}),
|
|
488
|
+
react_jsx_runtime.jsx("div", {
|
|
489
|
+
className: "dshmh-field",
|
|
490
|
+
children: [
|
|
491
|
+
react_jsx_runtime.jsx("span", { className: "dshmh-label", children: t.cloudflaredPath }),
|
|
492
|
+
react_jsx_runtime.jsx("input", {
|
|
493
|
+
className: "dshmh-input",
|
|
494
|
+
value: cloudflaredPath,
|
|
495
|
+
disabled: saving,
|
|
496
|
+
onChange: (e) => { setCloudflaredPath(e.target.value); setDirty(true); },
|
|
497
|
+
}),
|
|
498
|
+
],
|
|
499
|
+
}),
|
|
500
|
+
react_jsx_runtime.jsx("label", {
|
|
501
|
+
className: "dshmh-check",
|
|
502
|
+
children: [
|
|
503
|
+
react_jsx_runtime.jsx("input", {
|
|
504
|
+
type: "checkbox",
|
|
505
|
+
checked: sessionHandoff,
|
|
506
|
+
disabled: saving,
|
|
507
|
+
onChange: (e) => { setSessionHandoff(e.target.checked); setDirty(true); },
|
|
508
|
+
}),
|
|
509
|
+
t.sessionHandoff,
|
|
510
|
+
],
|
|
511
|
+
}),
|
|
512
|
+
react_jsx_runtime.jsx("div", {
|
|
513
|
+
className: "dshmh-field",
|
|
514
|
+
children: [
|
|
515
|
+
react_jsx_runtime.jsx("span", { className: "dshmh-label", children: t.pluginUpdate }),
|
|
516
|
+
react_jsx_runtime.jsx("div", {
|
|
517
|
+
style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" },
|
|
518
|
+
children: [
|
|
519
|
+
updateStatus.state === "idle"
|
|
520
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: updateStatus.currentVersion ? "v" + updateStatus.currentVersion : "" })
|
|
521
|
+
: updateStatus.state === "checking"
|
|
522
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: t.checkingUpdate })
|
|
523
|
+
: updateStatus.state === "available"
|
|
524
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: "v" + updateStatus.currentVersion + " → v" + updateStatus.latestVersion })
|
|
525
|
+
: updateStatus.state === "up-to-date"
|
|
526
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: t.upToDate + (updateStatus.currentVersion ? " (v" + updateStatus.currentVersion + ")" : "") })
|
|
527
|
+
: updateStatus.state === "updating"
|
|
528
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: t.updating })
|
|
529
|
+
: updateStatus.state === "updated"
|
|
530
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: t.updated })
|
|
531
|
+
: updateStatus.state === "failed"
|
|
532
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-failed", children: (updateStatus.error ? t.updateFailed + ": " + updateStatus.error : t.updateFailed) })
|
|
533
|
+
: react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: updateStatus.currentVersion ? "v" + updateStatus.currentVersion : "" }),
|
|
534
|
+
updateStatus.state === "available"
|
|
535
|
+
? react_jsx_runtime.jsx("button", {
|
|
536
|
+
className: "dshmh-btn dshmh-save",
|
|
537
|
+
type: "button",
|
|
538
|
+
onClick: doUpdate,
|
|
539
|
+
disabled: updating || checking,
|
|
540
|
+
children: t.updateAvailable,
|
|
541
|
+
})
|
|
542
|
+
: updateStatus.state === "up-to-date" || updateStatus.state === "idle"
|
|
543
|
+
? react_jsx_runtime.jsx("button", {
|
|
544
|
+
className: "dshmh-btn dshmh-save",
|
|
545
|
+
type: "button",
|
|
546
|
+
onClick: doCheckUpdate,
|
|
547
|
+
disabled: checking || updating,
|
|
548
|
+
children: checking ? t.checkingUpdate : t.checkUpdate,
|
|
549
|
+
})
|
|
550
|
+
: updateStatus.state === "failed"
|
|
551
|
+
? react_jsx_runtime.jsx("button", {
|
|
552
|
+
className: "dshmh-btn dshmh-save",
|
|
553
|
+
type: "button",
|
|
554
|
+
onClick: doCheckUpdate,
|
|
555
|
+
disabled: checking || updating,
|
|
556
|
+
children: t.checkUpdate,
|
|
557
|
+
})
|
|
558
|
+
: updateStatus.state === "updated"
|
|
559
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: t.restartRequired })
|
|
560
|
+
: updateStatus.state === "checking" || updateStatus.state === "updating"
|
|
561
|
+
? null
|
|
562
|
+
: react_jsx_runtime.jsx("button", {
|
|
563
|
+
className: "dshmh-btn dshmh-save",
|
|
564
|
+
type: "button",
|
|
565
|
+
onClick: doCheckUpdate,
|
|
566
|
+
disabled: checking || updating,
|
|
567
|
+
children: t.checkUpdate,
|
|
568
|
+
}),
|
|
569
|
+
],
|
|
570
|
+
}),
|
|
571
|
+
updateStatus.requiresRestart
|
|
572
|
+
? react_jsx_runtime.jsx("span", { className: "dshmh-hint", children: t.restartRequired })
|
|
573
|
+
: null,
|
|
574
|
+
],
|
|
575
|
+
}),
|
|
576
|
+
react_jsx_runtime.jsx("div", {
|
|
577
|
+
className: "dshmh-footer",
|
|
578
|
+
children: [
|
|
579
|
+
failed ? react_jsx_runtime.jsx("span", { className: "dshmh-failed", children: t.saveFailed }) : null,
|
|
580
|
+
react_jsx_runtime.jsx("button", {
|
|
581
|
+
className: "dshmh-btn dshmh-save",
|
|
582
|
+
type: "button",
|
|
583
|
+
onClick: save,
|
|
584
|
+
disabled: saving || !dirty,
|
|
585
|
+
children: saving ? t.saving : t.save,
|
|
586
|
+
}),
|
|
587
|
+
],
|
|
588
|
+
}),
|
|
589
|
+
],
|
|
590
|
+
})
|
|
591
|
+
: null,
|
|
592
|
+
],
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const inject = ["slots"];
|
|
597
|
+
|
|
598
|
+
function apply(ctx) {
|
|
599
|
+
ctx.slots.inject("settings.plugin.item", () =>
|
|
600
|
+
ctx.slots.register(
|
|
601
|
+
{
|
|
602
|
+
name: "settings.plugin.item",
|
|
603
|
+
key: "remote-handoff",
|
|
604
|
+
id: "dsh-remote-tunnel-easy",
|
|
605
|
+
order: 130,
|
|
606
|
+
inject: () => ({}),
|
|
607
|
+
},
|
|
608
|
+
RemoteHandoffCard
|
|
609
|
+
)
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
exports.apply = apply;
|
|
614
|
+
exports.inject = inject;
|
|
615
|
+
return module.exports;
|
|
616
|
+
},
|
|
617
|
+
});
|