Microservice to bring 2FA to self hosted PDSes
at feature/admin-rbac 991 lines 20 kB view raw
1:root, 2:root.light-mode { 3 --brand-color: rgb(16, 131, 254); 4 --primary-color: rgb(7, 10, 13); 5 --secondary-color: rgb(66, 86, 108); 6 --bg-primary-color: rgb(255, 255, 255); 7 --bg-secondary-color: rgb(240, 242, 245); 8 --border-color: rgb(220, 225, 230); 9 --danger-color: rgb(220, 38, 38); 10 --success-color: rgb(22, 163, 74); 11 --warning-color: rgb(234, 179, 8); 12 --table-stripe: rgba(0, 0, 0, 0.02); 13} 14 15@media (prefers-color-scheme: dark) { 16 :root { 17 --brand-color: rgb(16, 131, 254); 18 --primary-color: rgb(255, 255, 255); 19 --secondary-color: rgb(133, 152, 173); 20 --bg-primary-color: rgb(7, 10, 13); 21 --bg-secondary-color: rgb(13, 18, 23); 22 --border-color: rgb(40, 45, 55); 23 --table-stripe: rgba(255, 255, 255, 0.02); 24 } 25} 26 27:root.dark-mode { 28 --brand-color: rgb(16, 131, 254); 29 --primary-color: rgb(255, 255, 255); 30 --secondary-color: rgb(133, 152, 173); 31 --bg-primary-color: rgb(7, 10, 13); 32 --bg-secondary-color: rgb(13, 18, 23); 33 --border-color: rgb(40, 45, 55); 34 --table-stripe: rgba(255, 255, 255, 0.02); 35} 36 37/* --- Reset & Base ------------------------------------------- */ 38 39* { 40 margin: 0; 41 padding: 0; 42 box-sizing: border-box; 43} 44 45body { 46 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; 47 background: var(--bg-secondary-color); 48 color: var(--primary-color); 49 text-rendering: optimizeLegibility; 50 -webkit-font-smoothing: antialiased; 51} 52 53/* --- Layout: Sidebar + Main --------------------------------- */ 54 55.layout { 56 display: flex; 57 min-height: 100vh; 58} 59 60.sidebar { 61 width: 220px; 62 background: var(--bg-primary-color); 63 border-right: 1px solid var(--border-color); 64 padding: 20px 0; 65 position: fixed; 66 top: 0; 67 left: 0; 68 bottom: 0; 69 overflow-y: auto; 70 display: flex; 71 flex-direction: column; 72} 73 74.sidebar-title { 75 font-size: 0.8125rem; 76 font-weight: 700; 77 padding: 0 20px; 78 margin-bottom: 4px; 79 white-space: nowrap; 80 overflow: hidden; 81 text-overflow: ellipsis; 82} 83 84.sidebar-subtitle { 85 font-size: 0.6875rem; 86 color: var(--secondary-color); 87 padding: 0 20px; 88 margin-bottom: 20px; 89} 90 91.sidebar nav { 92 flex: 1; 93} 94 95.sidebar nav a { 96 display: block; 97 padding: 8px 20px; 98 font-size: 0.8125rem; 99 color: var(--secondary-color); 100 text-decoration: none; 101 transition: background 0.1s, color 0.1s; 102} 103 104.sidebar nav a:hover { 105 background: var(--bg-secondary-color); 106 color: var(--primary-color); 107} 108 109.sidebar nav a.active { 110 color: var(--brand-color); 111 font-weight: 500; 112} 113 114.sidebar-footer { 115 padding: 16px 20px 0; 116 border-top: 1px solid var(--border-color); 117 margin-top: 16px; 118} 119 120.sidebar-footer .session-info { 121 font-size: 0.75rem; 122 color: var(--secondary-color); 123 margin-bottom: 8px; 124} 125 126.sidebar-footer form { 127 display: inline; 128} 129 130.sidebar-footer button { 131 background: none; 132 border: none; 133 font-size: 0.75rem; 134 color: var(--secondary-color); 135 cursor: pointer; 136 padding: 0; 137 text-decoration: underline; 138} 139 140.sidebar-footer button:hover { 141 color: var(--primary-color); 142} 143 144.main { 145 margin-left: 220px; 146 flex: 1; 147 padding: 32px; 148 max-width: 960px; 149} 150 151/* --- Common Components -------------------------------------- */ 152 153.page-title { 154 font-size: 1.5rem; 155 font-weight: 700; 156 margin-bottom: 24px; 157} 158 159.page-subtitle { 160 font-size: 0.8125rem; 161 color: var(--secondary-color); 162 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 163 margin-top: -20px; 164 margin-bottom: 24px; 165 word-break: break-all; 166} 167 168.page-description { 169 font-size: 0.875rem; 170 color: var(--secondary-color); 171 margin-top: -16px; 172 margin-bottom: 24px; 173} 174 175.flash-success { 176 background: rgba(22, 163, 74, 0.1); 177 color: var(--success-color); 178 border: 1px solid rgba(22, 163, 74, 0.2); 179 border-radius: 8px; 180 padding: 10px 14px; 181 font-size: 0.875rem; 182 margin-bottom: 20px; 183} 184 185.flash-error { 186 background: rgba(220, 38, 38, 0.1); 187 color: var(--danger-color); 188 border: 1px solid rgba(220, 38, 38, 0.2); 189 border-radius: 8px; 190 padding: 10px 14px; 191 font-size: 0.875rem; 192 margin-bottom: 20px; 193} 194 195/* --- Buttons ------------------------------------------------ */ 196 197.btn { 198 display: inline-flex; 199 align-items: center; 200 justify-content: center; 201 padding: 10px 20px; 202 font-size: 0.875rem; 203 font-weight: 500; 204 border: none; 205 border-radius: 8px; 206 cursor: pointer; 207 transition: opacity 0.15s; 208 text-decoration: none; 209} 210 211.btn:hover { 212 opacity: 0.85; 213} 214 215.btn-primary { 216 background: var(--brand-color); 217 color: #fff; 218} 219 220.btn-danger { 221 background: var(--danger-color); 222 color: #fff; 223 border-color: var(--danger-color); 224} 225 226.btn-warning { 227 background: var(--warning-color); 228 color: #000; 229 border-color: var(--warning-color); 230} 231 232.btn-small { 233 padding: 6px 12px; 234 font-size: 0.75rem; 235} 236 237.btn-outline-danger { 238 background: transparent; 239 color: var(--danger-color); 240 border: 1px solid var(--danger-color); 241} 242 243/* --- Detail Sections ---------------------------------------- */ 244 245.detail-section { 246 background: var(--bg-primary-color); 247 border: 1px solid var(--border-color); 248 border-radius: 10px; 249 padding: 20px; 250 margin-bottom: 16px; 251} 252 253.detail-section h3 { 254 font-size: 0.875rem; 255 font-weight: 600; 256 margin-bottom: 12px; 257} 258 259.detail-row { 260 display: flex; 261 justify-content: space-between; 262 align-items: center; 263 padding: 8px 0; 264 font-size: 0.8125rem; 265 border-bottom: 1px solid var(--border-color); 266} 267 268.detail-row:last-child { 269 border-bottom: none; 270} 271 272.detail-row .label { 273 color: var(--secondary-color); 274 flex-shrink: 0; 275} 276 277.detail-row .value { 278 font-weight: 500; 279 word-break: break-all; 280 text-align: right; 281 max-width: 65%; 282} 283 284.detail-row .value a { 285 color: var(--brand-color); 286 text-decoration: none; 287} 288 289.detail-row .value a:hover { 290 text-decoration: underline; 291} 292 293/* --- Tables ------------------------------------------------- */ 294 295.table-container { 296 background: var(--bg-primary-color); 297 border: 1px solid var(--border-color); 298 border-radius: 10px; 299 overflow: hidden; 300} 301 302table { 303 width: 100%; 304 border-collapse: collapse; 305} 306 307thead th { 308 text-align: left; 309 padding: 12px 16px; 310 font-size: 0.75rem; 311 font-weight: 600; 312 color: var(--secondary-color); 313 text-transform: uppercase; 314 letter-spacing: 0.5px; 315 border-bottom: 1px solid var(--border-color); 316} 317 318tbody tr { 319 border-bottom: 1px solid var(--border-color); 320} 321 322tbody tr:last-child { 323 border-bottom: none; 324} 325 326tbody tr:nth-child(even) { 327 background: var(--table-stripe); 328} 329 330tbody td { 331 padding: 10px 16px; 332 font-size: 0.8125rem; 333} 334 335tbody td a { 336 color: var(--brand-color); 337 text-decoration: none; 338} 339 340tbody td a:hover { 341 text-decoration: underline; 342} 343 344/* --- Badges ------------------------------------------------- */ 345 346.badge { 347 display: inline-block; 348 padding: 2px 8px; 349 border-radius: 4px; 350 font-size: 0.75rem; 351 font-weight: 500; 352} 353 354.badge-success { 355 background: rgba(22, 163, 74, 0.1); 356 color: var(--success-color); 357} 358 359.badge-danger { 360 background: rgba(220, 38, 38, 0.1); 361 color: var(--danger-color); 362} 363 364.badge-warning { 365 background: rgba(234, 179, 8, 0.1); 366 color: var(--warning-color); 367} 368 369/* --- Forms -------------------------------------------------- */ 370 371.form-card { 372 background: var(--bg-primary-color); 373 border: 1px solid var(--border-color); 374 border-radius: 10px; 375 padding: 24px; 376 max-width: 480px; 377} 378 379.form-group { 380 margin-bottom: 16px; 381} 382 383.form-group label { 384 display: block; 385 font-size: 0.8125rem; 386 font-weight: 500; 387 margin-bottom: 6px; 388 color: var(--primary-color); 389} 390 391.form-group input { 392 width: 100%; 393 padding: 10px 12px; 394 font-size: 0.875rem; 395 border: 1px solid var(--border-color); 396 border-radius: 8px; 397 background: var(--bg-primary-color); 398 color: var(--primary-color); 399 outline: none; 400 transition: border-color 0.15s; 401} 402 403.form-group input:focus { 404 border-color: var(--brand-color); 405} 406 407.form-group .hint { 408 font-size: 0.75rem; 409 color: var(--secondary-color); 410 margin-top: 4px; 411} 412 413.search-form { 414 display: flex; 415 gap: 8px; 416 margin-bottom: 24px; 417} 418 419.search-form input { 420 flex: 1; 421 padding: 10px 12px; 422 font-size: 0.875rem; 423 border: 1px solid var(--border-color); 424 border-radius: 8px; 425 background: var(--bg-primary-color); 426 color: var(--primary-color); 427 outline: none; 428} 429 430.search-form input:focus { 431 border-color: var(--brand-color); 432} 433 434.create-form { 435 display: flex; 436 gap: 8px; 437 align-items: flex-end; 438 margin-bottom: 24px; 439} 440 441.create-form .form-group { 442 display: flex; 443 flex-direction: column; 444 gap: 4px; 445} 446 447.create-form label { 448 font-size: 0.75rem; 449 font-weight: 500; 450 color: var(--secondary-color); 451} 452 453.create-form input[type="number"] { 454 padding: 10px 12px; 455 font-size: 0.875rem; 456 border: 1px solid var(--border-color); 457 border-radius: 8px; 458 background: var(--bg-primary-color); 459 color: var(--primary-color); 460 outline: none; 461 width: 120px; 462} 463 464.create-form input[type="number"]:focus { 465 border-color: var(--brand-color); 466} 467 468/* --- Utility ------------------------------------------------ */ 469 470.mono-text { 471 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 472 font-size: 0.75rem; 473} 474 475.inline-form { 476 display: inline; 477} 478 479.empty-state { 480 text-align: center; 481 padding: 40px 20px; 482 color: var(--secondary-color); 483 font-size: 0.875rem; 484} 485 486.load-more { 487 text-align: center; 488 padding: 16px; 489} 490 491.load-more a { 492 color: var(--brand-color); 493 text-decoration: none; 494 font-size: 0.875rem; 495 font-weight: 500; 496} 497 498.load-more a:hover { 499 text-decoration: underline; 500} 501 502.pagination { 503 display: flex; 504 align-items: center; 505 justify-content: center; 506 gap: 16px; 507 padding: 16px 0; 508} 509 510.pagination-info { 511 font-size: 0.8125rem; 512 color: var(--secondary-color); 513} 514 515/* --- Dashboard: Cards --------------------------------------- */ 516 517.cards { 518 display: grid; 519 grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); 520 gap: 16px; 521 margin-bottom: 32px; 522} 523 524.card { 525 background: var(--bg-primary-color); 526 border: 1px solid var(--border-color); 527 border-radius: 10px; 528 padding: 20px; 529} 530 531.card-label { 532 font-size: 0.75rem; 533 font-weight: 500; 534 color: var(--secondary-color); 535 text-transform: uppercase; 536 letter-spacing: 0.5px; 537 margin-bottom: 6px; 538} 539 540.card-value { 541 font-size: 1.25rem; 542 font-weight: 600; 543} 544 545.card-value.success { 546 color: var(--success-color); 547} 548 549.card-value.danger { 550 color: var(--danger-color); 551} 552 553/* --- Accounts: DID Cell ------------------------------------- */ 554 555.did-cell { 556 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 557 font-size: 0.75rem; 558 color: var(--secondary-color); 559} 560 561/* --- Account Detail ----------------------------------------- */ 562 563.actions { 564 display: flex; 565 flex-wrap: wrap; 566 gap: 8px; 567 margin-top: 8px; 568} 569 570.actions form { 571 display: inline; 572} 573 574.actions .btn { 575 padding: 8px 16px; 576 font-size: 0.8125rem; 577 border: 1px solid var(--border-color); 578 background: var(--bg-primary-color); 579 color: var(--primary-color); 580} 581 582.actions .btn-primary { 583 background: var(--brand-color); 584 color: #fff; 585 border-color: var(--brand-color); 586} 587 588.actions .btn-danger { 589 background: var(--danger-color); 590 color: #fff; 591 border-color: var(--danger-color); 592} 593 594.actions .btn-warning { 595 background: var(--warning-color); 596 color: #000; 597 border-color: var(--warning-color); 598} 599 600.password-box { 601 background: rgba(22, 163, 74, 0.08); 602 border: 1px solid rgba(22, 163, 74, 0.2); 603 border-radius: 10px; 604 padding: 16px 20px; 605 margin-bottom: 16px; 606} 607 608.password-box .pw-label { 609 font-size: 0.75rem; 610 font-weight: 600; 611 color: var(--success-color); 612 margin-bottom: 6px; 613} 614 615.password-box .pw-value { 616 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 617 font-size: 1rem; 618 font-weight: 600; 619 user-select: all; 620} 621 622.back-link { 623 display: inline-block; 624 color: var(--brand-color); 625 text-decoration: none; 626 font-size: 0.8125rem; 627 margin-bottom: 16px; 628} 629 630.back-link:hover { 631 text-decoration: underline; 632} 633 634.collection-list { 635 max-height: 200px; 636 overflow-y: auto; 637 padding: 8px 0; 638} 639 640.collection-item { 641 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 642 font-size: 0.75rem; 643 padding: 4px 0; 644 color: var(--secondary-color); 645 border-bottom: 1px solid var(--border-color); 646} 647 648.collection-item:last-child { 649 border-bottom: none; 650} 651 652.threat-sig { 653 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 654 font-size: 0.75rem; 655 padding: 4px 0; 656 color: var(--secondary-color); 657} 658 659/* --- Create Account ----------------------------------------- */ 660 661.success-card { 662 background: var(--bg-primary-color); 663 border: 1px solid var(--border-color); 664 border-radius: 10px; 665 padding: 24px; 666 max-width: 480px; 667} 668 669.success-card h3 { 670 font-size: 1rem; 671 font-weight: 600; 672 color: var(--success-color); 673 margin-bottom: 16px; 674} 675 676.success-card .detail-row .value { 677 display: flex; 678 align-items: center; 679 gap: 6px; 680} 681 682.password-highlight { 683 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 684 background: rgba(22, 163, 74, 0.08); 685 padding: 2px 6px; 686 border-radius: 4px; 687 user-select: all; 688} 689 690.copy-btn { 691 background: none; 692 border: 1px solid var(--border-color); 693 border-radius: 4px; 694 padding: 2px 6px; 695 font-size: 0.6875rem; 696 cursor: pointer; 697 color: var(--secondary-color); 698 transition: color 0.15s, border-color 0.15s; 699 white-space: nowrap; 700} 701 702.copy-btn:hover { 703 color: var(--primary-color); 704 border-color: var(--primary-color); 705} 706 707/* --- Invite Codes ------------------------------------------- */ 708 709.code-box { 710 background: rgba(22, 163, 74, 0.08); 711 border: 1px solid rgba(22, 163, 74, 0.2); 712 border-radius: 10px; 713 padding: 16px 20px; 714 margin-bottom: 24px; 715} 716 717.code-box .code-label { 718 font-size: 0.75rem; 719 font-weight: 600; 720 color: var(--success-color); 721 margin-bottom: 6px; 722} 723 724.code-box .code-value { 725 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 726 font-size: 1rem; 727 font-weight: 600; 728 user-select: all; 729} 730 731.code-cell { 732 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; 733 font-size: 0.75rem; 734} 735 736/* --- Login Page --------------------------------------------- */ 737 738.login-card { 739 background: var(--bg-primary-color); 740 border: 1px solid var(--border-color); 741 border-radius: 12px; 742 padding: 40px; 743 width: 100%; 744 max-width: 400px; 745 margin: 20px; 746} 747 748.login-title { 749 font-size: 1.5rem; 750 font-weight: 700; 751 text-align: center; 752 margin-bottom: 4px; 753} 754 755.login-subtitle { 756 font-size: 0.875rem; 757 color: var(--secondary-color); 758 text-align: center; 759 margin-bottom: 32px; 760} 761 762.login-card .form-group { 763 margin-bottom: 20px; 764} 765 766.login-card .form-group label { 767 font-size: 0.875rem; 768} 769 770.login-card .btn-primary { 771 width: 100%; 772} 773 774.error-msg { 775 background: rgba(220, 38, 38, 0.1); 776 color: var(--danger-color); 777 border: 1px solid rgba(220, 38, 38, 0.2); 778 border-radius: 8px; 779 padding: 10px 14px; 780 font-size: 0.875rem; 781 margin-bottom: 20px; 782} 783 784/* --- Error Page ---------------------------------------------- */ 785 786.centered { 787 display: flex; 788 align-items: center; 789 justify-content: center; 790 min-height: 100vh; 791} 792 793.error-card { 794 background: var(--bg-primary-color); 795 border: 1px solid var(--border-color); 796 border-radius: 12px; 797 padding: 40px; 798 text-align: center; 799 max-width: 480px; 800 width: 100%; 801 margin: 20px; 802} 803 804.error-card--inset { 805 margin: 60px auto; 806} 807 808.error-icon { 809 font-size: 2.5rem; 810 margin-bottom: 16px; 811 color: var(--danger-color); 812} 813 814.error-title { 815 font-size: 1.25rem; 816 font-weight: 700; 817 margin-bottom: 8px; 818} 819 820.error-message { 821 font-size: 0.875rem; 822 color: var(--secondary-color); 823 margin-bottom: 24px; 824 line-height: 1.5; 825} 826 827.error-link { 828 display: inline-flex; 829 align-items: center; 830 justify-content: center; 831 padding: 10px 20px; 832 font-size: 0.875rem; 833 font-weight: 500; 834 border: none; 835 border-radius: 8px; 836 cursor: pointer; 837 text-decoration: none; 838 background: var(--brand-color); 839 color: #fff; 840 transition: opacity 0.15s; 841} 842 843.error-link:hover { 844 opacity: 0.85; 845} 846 847/* --- Mobile: Topbar + Hamburger ----------------------------- */ 848 849.topbar { 850 display: none; 851 position: fixed; 852 top: 0; 853 left: 0; 854 right: 0; 855 height: 52px; 856 background: var(--bg-primary-color); 857 border-bottom: 1px solid var(--border-color); 858 align-items: center; 859 padding: 0 16px; 860 gap: 12px; 861 z-index: 100; 862} 863 864.topbar-title { 865 font-size: 0.875rem; 866 font-weight: 600; 867 overflow: hidden; 868 text-overflow: ellipsis; 869 white-space: nowrap; 870 flex: 1; 871} 872 873.hamburger { 874 background: none; 875 border: none; 876 cursor: pointer; 877 padding: 4px; 878 display: flex; 879 flex-direction: column; 880 gap: 5px; 881 flex-shrink: 0; 882} 883 884.hamburger span { 885 display: block; 886 width: 22px; 887 height: 2px; 888 background: var(--primary-color); 889 border-radius: 2px; 890 transition: transform 0.25s ease, opacity 0.25s ease; 891} 892 893body.sidebar-open .hamburger span:nth-child(1) { 894 transform: translateY(7px) rotate(45deg); 895} 896 897body.sidebar-open .hamburger span:nth-child(2) { 898 opacity: 0; 899} 900 901body.sidebar-open .hamburger span:nth-child(3) { 902 transform: translateY(-7px) rotate(-45deg); 903} 904 905.sidebar-overlay { 906 display: none; 907 position: fixed; 908 inset: 0; 909 background: rgba(0, 0, 0, 0.4); 910 z-index: 149; 911} 912 913/* --- Responsive --------------------------------------------- */ 914 915@media (max-width: 768px) { 916 .topbar { 917 display: flex; 918 } 919 920 .sidebar { 921 transform: translateX(-220px); 922 transition: transform 0.25s ease; 923 z-index: 150; 924 } 925 926 body.sidebar-open .sidebar { 927 transform: translateX(0); 928 } 929 930 body.sidebar-open .sidebar-overlay { 931 display: block; 932 } 933 934 .main { 935 margin-left: 0; 936 padding: 24px 16px; 937 padding-top: 76px; /* 52px topbar + 24px breathing room */ 938 } 939 940 /* Tables scroll horizontally */ 941 .table-container { 942 overflow-x: auto; 943 -webkit-overflow-scrolling: touch; 944 } 945 946 /* Cards: single column */ 947 .cards { 948 grid-template-columns: 1fr; 949 } 950 951 /* Form cards: full width */ 952 .form-card, 953 .success-card { 954 max-width: 100%; 955 } 956 957 /* Search / create forms: stack vertically */ 958 .search-form, 959 .create-form { 960 flex-direction: column; 961 align-items: stretch; 962 } 963 964 .create-form input[type="number"] { 965 width: 100%; 966 } 967 968 /* Detail rows wrap on narrow screens */ 969 .detail-row { 970 flex-wrap: wrap; 971 gap: 4px; 972 } 973 974 .detail-row .value { 975 max-width: 100%; 976 text-align: left; 977 } 978 979 /* Actions wrap tightly */ 980 .actions { 981 flex-direction: column; 982 align-items: flex-start; 983 } 984 985 .actions form, 986 .actions .btn { 987 width: 100%; 988 display: block; 989 text-align: center; 990 } 991}