main
html 513 lines 16.2 KB
Raw
1 <html>
2 <head>
3 <title>Remote Control</title>
4 <script type="module">
5 import { store } from "/components/settings/tunnel/tunnel-store.js";
6 </script>
7 </head>
8
9 <body>
10 <div x-data>
11 <template x-if="$store.tunnelStore">
12 <div
13 id="tunnel-settings-section"
14 class="remote-link-surface"
15 x-init="$store.tunnelStore.init()"
16 x-destroy="$store.tunnelStore.cleanup()"
17 >
18 <header class="remote-link-hero">
19 <div class="remote-link-hero-icon" aria-hidden="true">
20 <x-icon name="share"></x-icon>
21 </div>
22 <div>
23 <div class="section-title">Remote Control</div>
24 <div class="section-description">
25 Create temporary HTTPS access for this Agent Zero instance, then open it from another browser or scan it from your phone.
26 </div>
27 </div>
28 </header>
29
30 <div class="remote-link-safety-note">
31 <x-icon aria-hidden="true" name="lock"></x-icon>
32 <span>Use sign-in before sharing remote control access. The URL can reach your instance while access is active.</span>
33 </div>
34
35 <div class="field remote-link-provider-field">
36 <div class="field-label">
37 <div class="field-title">Link provider</div>
38 <div class="field-description">
39 Cloudflare Tunnel is the quickest shareable URL. Tailscale Funnel creates a public HTTPS URL after this container joins your tailnet and may ask you to approve sign-in or Funnel access. Microsoft Dev Tunnels may ask you to approve a GitHub device login.
40 </div>
41 </div>
42 <div class="field-control">
43 <select
44 id="tunnel-provider"
45 x-model="$store.tunnelStore.provider"
46 :disabled="$store.tunnelStore.isLoading"
47 >
48 <option value="cloudflared">Cloudflare Tunnel</option>
49 <option value="tailscale">Tailscale</option>
50 <option value="microsoft">Microsoft Dev Tunnels</option>
51 <option value="serveo">Serveo</option>
52 </select>
53 </div>
54 </div>
55
56 <div class="remote-link-loading" x-show="$store.tunnelStore.isLoading" style="display: none;">
57 <x-icon class="spin" aria-hidden="true" name="progress_activity"></x-icon>
58 <span x-text="$store.tunnelStore.loadingText || 'Preparing remote control...'"></span>
59 </div>
60
61 <div class="microsoft-login-box" x-show="$store.tunnelStore.loginActionVisible" x-transition style="display: none;">
62 <div class="microsoft-login-header">
63 <x-icon aria-hidden="true" name="key"></x-icon>
64 <div>
65 <div class="microsoft-login-title" x-text="$store.tunnelStore.loginActionTitle"></div>
66 <div class="microsoft-login-copy" x-text="$store.tunnelStore.loginActionCopy"></div>
67 </div>
68 </div>
69 <a class="microsoft-login-link" x-show="$store.tunnelStore.microsoftLoginUrl" :href="$store.tunnelStore.microsoftLoginUrl" target="_blank" rel="noopener noreferrer">
70 <x-icon aria-hidden="true" name="open_in_new"></x-icon>
71 <span x-text="$store.tunnelStore.microsoftLoginUrl"></span>
72 </a>
73 <div class="microsoft-login-code-row" x-show="$store.tunnelStore.microsoftLoginCode">
74 <div class="microsoft-login-code" x-text="$store.tunnelStore.microsoftLoginCode"></div>
75 <button
76 type="button"
77 class="btn btn-field btn-copy-code"
78 @click="$store.tunnelStore.copyLoginCode()"
79 :class="$store.tunnelStore.codeCopied ? 'copied' : ''"
80 >
81 <x-icon aria-hidden="true" :name="$store.tunnelStore.codeCopied ? 'check' : 'content_copy'"></x-icon>
82 <span x-text="$store.tunnelStore.codeCopied ? 'Copied' : 'Copy code'"></span>
83 </button>
84 </div>
85 </div>
86
87 <div x-show="!$store.tunnelStore.isLoading">
88 <div class="remote-link-live" x-show="$store.tunnelStore.linkGenerated" style="display: none;">
89 <div class="remote-link-url-row">
90 <input
91 type="text"
92 class="remote-link-input"
93 :value="$store.tunnelStore.tunnelLink"
94 readonly
95 aria-label="Remote Control URL"
96 >
97 <button
98 type="button"
99 class="remote-link-action copy-link-button"
100 @click="$store.tunnelStore.copyToClipboard()"
101 :class="{
102 'copy-success': $store.tunnelStore.copyState === 'success',
103 'copy-error': $store.tunnelStore.copyState === 'error'
104 }"
105 >
106 <x-icon aria-hidden="true" :name="$store.tunnelStore.copyLinkIcon"></x-icon>
107 <span x-text="$store.tunnelStore.copyLinkLabel"></span>
108 </button>
109 <button
110 type="button"
111 class="remote-link-action refresh-link-button"
112 @click="$store.tunnelStore.refreshLink()"
113 :disabled="$store.tunnelStore.isLoading"
114 >
115 <x-icon aria-hidden="true" name="refresh"></x-icon>
116 <span>New URL</span>
117 </button>
118 </div>
119
120 <div class="remote-link-share-layout">
121 <div class="remote-link-qr-panel">
122 <div class="tunnel-qr-code" id="qrcode-tunnel"></div>
123 <div>
124 <div class="remote-link-qr-title">Open on mobile</div>
125 <div class="remote-link-qr-copy">Scan the code with your phone to connect without retyping the URL.</div>
126 </div>
127 </div>
128 <div class="remote-link-status-panel">
129 <x-icon aria-hidden="true" name="verified"></x-icon>
130 <div>
131 <div class="remote-link-status-title">Remote Control is active</div>
132 <div class="remote-link-status-copy">
133 It stays available until you stop it or the container restarts.
134 </div>
135 </div>
136 </div>
137 </div>
138
139 <div class="remote-link-footer-actions">
140 <button
141 type="button"
142 class="btn btn-cancel"
143 @click="$store.tunnelStore.stopTunnel()"
144 :disabled="$store.tunnelStore.isLoading"
145 >
146 <x-icon aria-hidden="true" name="stop_circle"></x-icon>
147 <span>Stop Remote Control</span>
148 </button>
149 </div>
150 </div>
151
152 <div class="remote-link-empty" x-show="!$store.tunnelStore.linkGenerated">
153 <x-icon aria-hidden="true" name="link"></x-icon>
154 <strong>No Remote Control yet</strong>
155 <span>Create one when you want to reach Agent Zero from another device or share access temporarily.</span>
156 <button
157 type="button"
158 class="btn btn-ok remote-link-primary"
159 @click="$store.tunnelStore.generateLink()"
160 :disabled="$store.tunnelStore.isLoading"
161 >
162 <x-icon aria-hidden="true" name="share"></x-icon>
163 <span>Start Remote Control</span>
164 </button>
165 </div>
166 </div>
167 </div>
168 </template>
169 </div>
170
171 <style>
172 .modal-inner.remote-link-modal {
173 width: min(92vw, 760px);
174 max-height: min(88vh, 820px);
175 }
176
177 .modal-inner.remote-link-modal .modal-scroll {
178 padding: 0;
179 }
180
181 .modal-inner.remote-link-modal .modal-bd {
182 padding: 22px 24px 26px;
183 }
184
185 .remote-link-surface {
186 width: 100%;
187 margin: 0 auto;
188 }
189
190 .remote-link-hero {
191 display: grid;
192 grid-template-columns: auto minmax(0, 1fr);
193 gap: 14px;
194 align-items: start;
195 margin-bottom: 16px;
196 }
197
198 .remote-link-hero-icon {
199 display: grid;
200 place-items: center;
201 width: 42px;
202 height: 42px;
203 border-radius: 8px;
204 background: color-mix(in srgb, var(--color-primary) 16%, transparent);
205 color: color-mix(in srgb, var(--color-primary) 82%, var(--color-text));
206 }
207
208 .remote-link-hero-icon .material-symbols-outlined {
209 font-size: 24px;
210 }
211
212 .remote-link-safety-note,
213 .remote-link-loading,
214 .remote-link-status-panel,
215 .remote-link-qr-panel {
216 display: flex;
217 align-items: center;
218 gap: 10px;
219 border: 1px solid color-mix(in srgb, var(--color-border) 68%, transparent);
220 border-radius: 8px;
221 background: color-mix(in srgb, var(--color-panel) 58%, transparent);
222 }
223
224 .remote-link-safety-note {
225 margin: 10px 0 14px;
226 padding: 10px 12px;
227 color: var(--color-text);
228 font-size: 0.86rem;
229 line-height: 1.35;
230 }
231
232 .remote-link-safety-note .material-symbols-outlined {
233 color: #f2bf4b;
234 font-size: 20px;
235 }
236
237 .remote-link-provider-field {
238 margin-bottom: 12px;
239 }
240
241 .remote-link-loading {
242 justify-content: center;
243 min-height: 46px;
244 margin: 16px 0;
245 padding: 10px 12px;
246 color: var(--color-text);
247 opacity: 0.86;
248 }
249
250 .remote-link-url-row {
251 display: grid;
252 grid-template-columns: minmax(0, 1fr) auto auto;
253 gap: 8px;
254 align-items: center;
255 margin: 16px 0 12px;
256 }
257
258 .remote-link-input {
259 min-height: 38px;
260 font-family: "Roboto Mono", monospace;
261 font-size: 0.82rem;
262 }
263
264 .remote-link-action,
265 .remote-link-primary,
266 .btn-copy-code,
267 .remote-link-footer-actions .btn {
268 display: inline-flex;
269 align-items: center;
270 justify-content: center;
271 gap: 6px;
272 white-space: nowrap;
273 }
274
275 .remote-link-action {
276 min-height: 38px;
277 padding: 0 11px;
278 border: 1px solid color-mix(in srgb, var(--color-border) 76%, transparent);
279 border-radius: 7px;
280 background: color-mix(in srgb, var(--color-background) 72%, transparent);
281 color: var(--color-text);
282 cursor: pointer;
283 font: inherit;
284 font-size: 0.82rem;
285 font-weight: 700;
286 }
287
288 .remote-link-action:hover:not(:disabled) {
289 border-color: color-mix(in srgb, var(--color-primary) 34%, var(--color-border));
290 background: color-mix(in srgb, var(--color-background-hover) 74%, transparent);
291 }
292
293 .remote-link-action:disabled {
294 cursor: not-allowed;
295 opacity: 0.52;
296 }
297
298 .remote-link-share-layout {
299 display: grid;
300 grid-template-columns: minmax(0, 1.35fr) minmax(220px, 0.8fr);
301 gap: 10px;
302 margin-top: 10px;
303 }
304
305 .remote-link-qr-panel,
306 .remote-link-status-panel {
307 padding: 12px;
308 align-items: center;
309 }
310
311 .remote-link-status-panel {
312 align-items: flex-start;
313 }
314
315 .remote-link-status-panel > .material-symbols-outlined {
316 color: #63c98b;
317 font-size: 22px;
318 }
319
320 .tunnel-qr-code {
321 display: grid;
322 place-items: center;
323 width: 116px;
324 min-width: 116px;
325 height: 116px;
326 padding: 8px;
327 border-radius: 8px;
328 background: #fff;
329 }
330
331 .tunnel-qr-code canvas,
332 .tunnel-qr-code img {
333 max-width: 100%;
334 max-height: 100%;
335 border-radius: 4px;
336 }
337
338 .remote-link-qr-title,
339 .remote-link-status-title,
340 .microsoft-login-title {
341 color: color-mix(in srgb, var(--color-text) 88%, var(--color-primary));
342 font-weight: 800;
343 }
344
345 .remote-link-qr-copy,
346 .remote-link-status-copy,
347 .microsoft-login-copy {
348 margin-top: 4px;
349 color: var(--color-text);
350 font-size: 0.84rem;
351 line-height: 1.35;
352 opacity: 0.76;
353 }
354
355 .remote-link-footer-actions {
356 display: flex;
357 justify-content: flex-end;
358 margin-top: 14px;
359 }
360
361 .remote-link-empty {
362 display: flex;
363 flex-direction: column;
364 align-items: center;
365 justify-content: center;
366 gap: 8px;
367 min-height: 190px;
368 margin-top: 14px;
369 padding: 22px;
370 border: 1px dashed color-mix(in srgb, var(--color-border) 78%, transparent);
371 border-radius: 8px;
372 text-align: center;
373 color: var(--color-text);
374 }
375
376 .remote-link-empty > .material-symbols-outlined {
377 color: var(--color-primary);
378 font-size: 30px;
379 }
380
381 .remote-link-empty strong {
382 font-size: 1rem;
383 }
384
385 .remote-link-empty > span:not(.material-symbols-outlined) {
386 max-width: 46ch;
387 opacity: 0.74;
388 line-height: 1.35;
389 }
390
391 .remote-link-primary {
392 margin-top: 6px;
393 }
394
395 .copy-success {
396 border-color: color-mix(in srgb, #63c98b 58%, var(--color-border)) !important;
397 color: #63c98b !important;
398 background: color-mix(in srgb, #63c98b 12%, transparent) !important;
399 }
400
401 .copy-error {
402 border-color: color-mix(in srgb, #ff6b7a 56%, var(--color-border)) !important;
403 color: #ff6b7a !important;
404 background: color-mix(in srgb, #ff6b7a 12%, transparent) !important;
405 }
406
407 .microsoft-login-box {
408 margin: 14px 0;
409 padding: 14px;
410 border: 1px solid color-mix(in srgb, #4da3ff 36%, var(--color-border));
411 border-radius: 8px;
412 background:
413 linear-gradient(120deg, color-mix(in srgb, #4da3ff 12%, transparent), transparent 62%),
414 color-mix(in srgb, var(--color-panel) 68%, transparent);
415 }
416
417 .microsoft-login-header {
418 display: flex;
419 align-items: flex-start;
420 gap: 10px;
421 margin-bottom: 12px;
422 }
423
424 .microsoft-login-header .material-symbols-outlined {
425 color: #4da3ff;
426 font-size: 22px;
427 }
428
429 .microsoft-login-link {
430 display: flex;
431 align-items: center;
432 gap: 6px;
433 min-width: 0;
434 margin-bottom: 12px;
435 padding: 9px 10px;
436 border: 1px solid color-mix(in srgb, #4da3ff 34%, var(--color-border));
437 border-radius: 7px;
438 color: color-mix(in srgb, #4da3ff 82%, var(--color-text));
439 background: color-mix(in srgb, #4da3ff 8%, transparent);
440 text-decoration: none;
441 word-break: break-all;
442 }
443
444 .microsoft-login-code-row {
445 display: flex;
446 flex-wrap: wrap;
447 align-items: center;
448 gap: 10px;
449 }
450
451 .microsoft-login-code {
452 min-height: 38px;
453 padding: 7px 12px;
454 border: 1px dashed color-mix(in srgb, #4da3ff 44%, var(--color-border));
455 border-radius: 7px;
456 font-family: "Roboto Mono", monospace;
457 font-size: 1.1rem;
458 font-weight: 800;
459 color: var(--color-text);
460 background: color-mix(in srgb, var(--color-background) 56%, transparent);
461 user-select: all;
462 }
463
464 .spin {
465 animation: remote-link-spin 0.8s linear infinite;
466 }
467
468 @keyframes remote-link-spin {
469 from { transform: rotate(0deg); }
470 to { transform: rotate(360deg); }
471 }
472
473 @media (max-width: 720px) {
474 .modal-inner.remote-link-modal .modal-bd {
475 padding: 16px;
476 }
477
478 .remote-link-url-row,
479 .remote-link-share-layout {
480 grid-template-columns: 1fr;
481 }
482
483 .remote-link-action {
484 width: 100%;
485 }
486
487 .remote-link-qr-panel {
488 align-items: center;
489 }
490 }
491
492 @media (max-width: 520px) {
493 .remote-link-hero {
494 grid-template-columns: 1fr;
495 }
496
497 .remote-link-hero-icon {
498 width: 38px;
499 height: 38px;
500 }
501
502 .remote-link-qr-panel {
503 flex-direction: column;
504 text-align: center;
505 }
506
507 .remote-link-footer-actions .btn {
508 width: 100%;
509 }
510 }
511 </style>
512 </body>
513 </html>