ui: better Microsoft Dev Tunnels login design
linuztx committed
Jan 1, 2026 at 20:12 UTC
038bc712d91e26346c4d651b9c0b9e5f993dcc14
2 files changed
+200
-35
webui/components/settings/tunnel/tunnel-section.html
+192
-35
@@ -36,21 +36,42 @@
36
</select>
37
</div>
38
</div>
39
- <!-- Loading spinner for tunnel operations -->
40
- <div class="loading-spinner" x-show="$store.tunnelStore.isLoading">
39
+ <!-- Loading spinner for tunnel operations (hidden when Microsoft login code is displayed) -->
40
+ <div class="loading-spinner" x-show="$store.tunnelStore.isLoading && !$store.tunnelStore.microsoftLoginCode">
41
<span class="icon material-symbols-outlined spin">progress_activity</span>
42
<span x-text="$store.tunnelStore.loadingText || 'Processing tunnel request...'"></span>
43
</div>
44
<!-- Microsoft login code display -->
45
- <div class="microsoft-login-box" x-show="$store.tunnelStore.microsoftLoginCode">
46
- <div class="microsoft-login-title">Microsoft Login Required</div>
47
- <div class="microsoft-login-instructions">
48
- Open <a :href="$store.tunnelStore.microsoftLoginUrl" target="_blank" x-text="$store.tunnelStore.microsoftLoginUrl"></a> and enter the code:
45
+ <div class="microsoft-login-box" x-show="$store.tunnelStore.microsoftLoginCode" x-transition>
46
+ <div class="microsoft-login-header">
47
+ <span class="microsoft-login-icon material-symbols-outlined">key</span>
48
+ <div class="microsoft-login-title">Microsoft Login Required</div>
49
+ </div>
50
+ <div class="microsoft-login-steps">
51
+ <div class="microsoft-login-step">
52
+ <span class="step-number">1</span>
53
+ <span class="step-text">Open the authentication page:</span>
54
+ </div>
55
+ <a class="microsoft-login-link" :href="$store.tunnelStore.microsoftLoginUrl" target="_blank">
56
+ <span class="icon material-symbols-outlined">open_in_new</span>
57
+ <span x-text="$store.tunnelStore.microsoftLoginUrl"></span>
58
+ </a>
59
+ <div class="microsoft-login-step">
60
+ <span class="step-number">2</span>
61
+ <span class="step-text">Enter this code:</span>
62
+ </div>
63
+ </div>
64
+ <div class="microsoft-login-code-container">
65
+ <div class="microsoft-login-code" x-text="$store.tunnelStore.microsoftLoginCode"></div>
66
+ <button class="btn btn-copy-code" @click="$store.tunnelStore.copyLoginCode()" :class="$store.tunnelStore.codeCopied ? 'copied' : ''">
67
+ <span class="icon material-symbols-outlined" x-text="$store.tunnelStore.codeCopied ? 'check' : 'content_copy'"></span>
68
+ <span x-text="$store.tunnelStore.codeCopied ? 'Copied!' : 'Copy Code'"></span>
69
+ </button>
70
+ </div>
71
+ <div class="microsoft-login-footer">
72
+ <span class="icon material-symbols-outlined spin-slow">sync</span>
73
+ <span>Waiting for authentication to complete...</span>
74
</div>
50
- <div class="microsoft-login-code" x-text="$store.tunnelStore.microsoftLoginCode"></div>
51
- <button class="btn btn-copy-code" @click="$store.tunnelStore.copyLoginCode()">
52
- <span class="icon material-symbols-outlined">content_copy</span> Copy Code
53
- </button>
75
</div>
76
77
<!-- Tunnel content when not loading -->
@@ -379,63 +400,199 @@
400
401
/* Microsoft Login Box */
402
.microsoft-login-box {
382
- margin: 1rem 0;
383
- padding: 1.25rem;
384
- background: linear-gradient(135deg, rgba(0, 120, 212, 0.15), rgba(0, 120, 212, 0.05));
385
- border: 1px solid rgba(0, 120, 212, 0.4);
386
- border-radius: 8px;
403
+ margin: 1.5rem 0;
404
+ padding: 1.5rem;
405
+ background: linear-gradient(135deg, rgba(0, 120, 212, 0.12), rgba(0, 120, 212, 0.04));
406
+ border: 1px solid rgba(0, 120, 212, 0.35);
407
+ border-radius: 12px;
408
text-align: center;
409
+ box-shadow: 0 4px 20px rgba(0, 120, 212, 0.1);
410
+ }
411
+
412
+ .microsoft-login-header {
413
+ display: flex;
414
+ align-items: center;
415
+ justify-content: center;
416
+ gap: 0.5rem;
417
+ margin-bottom: 1.25rem;
418
+ }
419
+
420
+ .microsoft-login-icon {
421
+ font-size: 1.5rem;
422
+ color: #0078d4;
423
+ background: rgba(0, 120, 212, 0.15);
424
+ padding: 0.5rem;
425
+ border-radius: 50%;
426
}
427
428
.microsoft-login-title {
391
- font-size: 1rem;
429
+ font-size: 1.1rem;
430
font-weight: 600;
431
color: #0078d4;
394
- margin-bottom: 0.75rem;
432
}
433
397
- .microsoft-login-instructions {
434
+ .microsoft-login-steps {
435
+ text-align: left;
436
+ margin-bottom: 1rem;
437
+ }
438
+
439
+ .microsoft-login-step {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 0.75rem;
443
+ margin: 0.75rem 0 0.5rem 0;
444
font-size: 0.9rem;
445
+ color: var(--text-color);
446
+ }
447
+
448
+ .step-number {
449
+ display: flex;
450
+ align-items: center;
451
+ justify-content: center;
452
+ width: 24px;
453
+ height: 24px;
454
+ background-color: #0078d4;
455
+ color: white;
456
+ border-radius: 50%;
457
+ font-size: 0.8rem;
458
+ font-weight: 600;
459
+ flex-shrink: 0;
460
+ }
461
+
462
+ .step-text {
463
color: var(--text-color-secondary);
400
- margin-bottom: 0.75rem;
401
- line-height: 1.5;
464
}
465
404
- .microsoft-login-instructions a {
466
+ .microsoft-login-link {
467
+ display: flex;
468
+ align-items: center;
469
+ justify-content: center;
470
+ gap: 0.5rem;
471
+ padding: 0.75rem 1rem;
472
+ margin: 0.5rem 0 0.75rem 2.25rem;
473
+ background-color: rgba(0, 120, 212, 0.1);
474
+ border: 1px solid rgba(0, 120, 212, 0.3);
475
+ border-radius: 8px;
476
color: #0078d4;
406
- text-decoration: underline;
477
+ text-decoration: none;
478
+ font-size: 0.85rem;
479
+ transition: all 0.2s ease;
480
+ word-break: break-all;
481
+ }
482
+
483
+ .microsoft-login-link:hover {
484
+ background-color: rgba(0, 120, 212, 0.2);
485
+ border-color: rgba(0, 120, 212, 0.5);
486
+ transform: translateY(-1px);
487
+ }
488
+
489
+ .microsoft-login-link .icon {
490
+ font-size: 1.1rem;
491
+ flex-shrink: 0;
492
+ }
493
+
494
+ .microsoft-login-code-container {
495
+ display: flex;
496
+ flex-direction: column;
497
+ align-items: center;
498
+ gap: 0.75rem;
499
+ margin: 1rem 0;
500
+ padding: 1rem;
501
+ background-color: var(--bg-color-secondary);
502
+ border-radius: 10px;
503
+ border: 1px solid var(--border-color);
504
}
505
506
.microsoft-login-code {
410
- font-family: 'Courier New', monospace;
411
- font-size: 1.75rem;
412
- font-weight: bold;
413
- letter-spacing: 0.15em;
507
+ font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
508
+ font-size: 2rem;
509
+ font-weight: 700;
510
+ letter-spacing: 0.2em;
511
color: var(--text-color);
415
- background-color: var(--bg-color-secondary);
512
+ background: linear-gradient(135deg, rgba(0, 120, 212, 0.1), transparent);
513
padding: 0.75rem 1.5rem;
417
- border-radius: 6px;
418
- display: inline-block;
419
- margin-bottom: 0.75rem;
420
- border: 1px solid var(--border-color);
514
+ border-radius: 8px;
515
+ border: 2px dashed rgba(0, 120, 212, 0.4);
516
user-select: all;
517
+ cursor: text;
518
}
519
520
.btn-copy-code {
521
background-color: #0078d4;
522
color: white;
523
border: none;
428
- padding: 0.5rem 1rem;
429
- border-radius: 4px;
524
+ padding: 0.6rem 1.25rem;
525
+ border-radius: 6px;
526
cursor: pointer;
431
- font-size: 0.875rem;
527
+ font-size: 0.9rem;
528
+ font-weight: 500;
529
display: inline-flex;
530
align-items: center;
434
- gap: 0.25rem;
531
+ gap: 0.4rem;
532
+ transition: all 0.2s ease;
533
}
534
535
.btn-copy-code:hover {
536
background-color: #106ebe;
537
+ transform: translateY(-1px);
538
+ box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3);
539
+ }
540
+
541
+ .btn-copy-code.copied {
542
+ background-color: #28a745;
543
+ }
544
+
545
+ .btn-copy-code.copied:hover {
546
+ background-color: #218838;
547
+ }
548
+
549
+ .microsoft-login-footer {
550
+ display: flex;
551
+ align-items: center;
552
+ justify-content: center;
553
+ gap: 0.5rem;
554
+ margin-top: 1rem;
555
+ padding-top: 1rem;
556
+ border-top: 1px solid rgba(0, 120, 212, 0.2);
557
+ font-size: 0.85rem;
558
+ color: var(--text-color-secondary);
559
+ font-style: italic;
560
+ }
561
+
562
+ .microsoft-login-footer .icon {
563
+ font-size: 1.1rem;
564
+ color: #0078d4;
565
+ }
566
+
567
+ @keyframes spin-slow {
568
+ from { transform: rotate(0deg); }
569
+ to { transform: rotate(360deg); }
570
+ }
571
+
572
+ .spin-slow {
573
+ animation: spin-slow 2s linear infinite;
574
+ }
575
+
576
+ /* Responsive adjustments for Microsoft login */
577
+ @media (max-width: 480px) {
578
+ .microsoft-login-box {
579
+ padding: 1rem;
580
+ }
581
+
582
+ .microsoft-login-code {
583
+ font-size: 1.5rem;
584
+ letter-spacing: 0.15em;
585
+ padding: 0.5rem 1rem;
586
+ }
587
+
588
+ .microsoft-login-link {
589
+ margin-left: 0;
590
+ font-size: 0.8rem;
591
+ }
592
+
593
+ .microsoft-login-step {
594
+ font-size: 0.85rem;
595
+ }
596
}
597
</style>
598
webui/components/settings/tunnel/tunnel-store.js
+8
@@ -11,6 +11,7 @@ const model = {
11
provider: "cloudflared",
12
microsoftLoginCode: "",
13
microsoftLoginUrl: "",
14
+ codeCopied: false,
15
notificationPollInterval: null,
16
hasError: false,
17
@@ -21,12 +22,18 @@ const model = {
22
clearMicrosoftLogin() {
23
this.microsoftLoginCode = "";
24
this.microsoftLoginUrl = "";
25
+ this.codeCopied = false;
26
},
27
28
copyLoginCode() {
29
if (!this.microsoftLoginCode) return;
30
navigator.clipboard.writeText(this.microsoftLoginCode).then(() => {
31
+ this.codeCopied = true;
32
window.toastFrontendInfo("Login code copied to clipboard!", "Clipboard");
33
+ // Reset after 3 seconds
34
+ setTimeout(() => {
35
+ this.codeCopied = false;
36
+ }, 3000);
37
}).catch((err) => {
38
console.error("Failed to copy code: ", err);
39
window.toastFrontendError("Failed to copy login code", "Clipboard Error");
@@ -52,6 +59,7 @@ const model = {
59
this.loadingText = n.message;
60
break;
61
case "creating_tunnel":
62
+ this.clearMicrosoftLogin();
63
this.loadingText = n.message;
64
break;
65
case "info":