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":