@le-space/ui 0.7.0 → 0.9.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/styles.css CHANGED
@@ -42,6 +42,11 @@
42
42
  --relay-surface: rgba(11, 14, 21, 0.55);
43
43
  --relay-surface-border: var(--relay-horizon);
44
44
  --relay-field-bg: rgba(11, 14, 21, 0.85);
45
+ /* Opaque on purpose. A select's drop-down takes the control's own colours on
46
+ the platforms that honour author styling, and it opens over the page rather
47
+ than over the panel - a translucent background would composite against
48
+ whatever is behind it and leave light text on a light list. */
49
+ --relay-menu-bg: var(--relay-deep-space);
45
50
  --relay-accent: var(--relay-coral);
46
51
  --relay-accent-contrast: #0b0e15;
47
52
  --relay-link: var(--relay-cyan);
@@ -81,6 +86,7 @@
81
86
  --relay-surface: rgba(20, 27, 46, 0.04);
82
87
  --relay-surface-border: #d9e0ec;
83
88
  --relay-field-bg: #ffffff;
89
+ --relay-menu-bg: #ffffff;
84
90
  --relay-accent: var(--relay-coral);
85
91
  --relay-accent-contrast: #ffffff;
86
92
  --relay-link: var(--relay-cyan);
@@ -20,6 +20,7 @@ export let version = ''
20
20
  export let apiHost = undefined
21
21
  export let apiHosts = undefined
22
22
  export let crnListUrl = undefined
23
+ export let crnSource = undefined
23
24
  export let schedulerApiHost = undefined
24
25
  export let twoN6ApiHost = undefined
25
26
 
@@ -35,6 +36,7 @@ export let apiHosts = undefined
35
36
  apiHost,
36
37
  apiHosts,
37
38
  crnListUrl,
39
+ crnSource,
38
40
  schedulerApiHost,
39
41
  twoN6ApiHost
40
42
  })
@@ -91,6 +93,44 @@ export let apiHosts = undefined
91
93
  )
92
94
  $: orphanRegistrations = state.orphanBootstrapRegistrations ?? []
93
95
 
96
+ // Which tab each expanded deployment card is showing, keyed by item hash.
97
+ let instanceTabs = {}
98
+
99
+ function instanceTab(itemHash) {
100
+ return instanceTabs[itemHash] ?? 'general'
101
+ }
102
+
103
+ function selectInstanceTab(itemHash, tab) {
104
+ instanceTabs = { ...instanceTabs, [itemHash]: tab }
105
+ if (tab !== 'general') {
106
+ // Addresses are live state — AutoTLS entries turn up a minute or two after
107
+ // the deploy reports success — so load on open and let the user refresh.
108
+ controller.loadInstanceAddresses(itemHash)
109
+ }
110
+ }
111
+
112
+ function addressesOf(itemHash) {
113
+ return state.instanceAddresses?.[itemHash] ?? null
114
+ }
115
+
116
+ /** Label the tab by what the deployment actually publishes, not by profile. */
117
+ function addressTabLabel(itemHash) {
118
+ const addresses = addressesOf(itemHash)
119
+ if (addresses?.groups?.length) return 'Multiaddresses'
120
+ if (addresses?.pwaEnv) return 'Endpoints'
121
+ return 'Multiaddresses'
122
+ }
123
+
124
+ function copyAllText(addresses) {
125
+ return (addresses?.groups ?? []).flatMap((group) => group.addresses).join('\n')
126
+ }
127
+
128
+ function pwaEnvText(addresses) {
129
+ return Object.entries(addresses?.pwaEnv ?? {})
130
+ .map(([key, value]) => `${key}=${value}`)
131
+ .join('\n')
132
+ }
133
+
94
134
  onMount(async () => {
95
135
  const unsubscribe = controller.subscribe((next) => {
96
136
  state = next
@@ -359,6 +399,38 @@ export let apiHosts = undefined
359
399
  </button>
360
400
  </div>
361
401
 
402
+ {@const itemHash = entry.instance.item_hash}
403
+ {@const addresses = addressesOf(itemHash)}
404
+ {@const tab = instanceTab(itemHash)}
405
+
406
+ <div class="tab-bar" role="tablist">
407
+ <button
408
+ type="button"
409
+ role="tab"
410
+ class="tab"
411
+ class:tab-active={tab === 'general'}
412
+ aria-selected={tab === 'general'}
413
+ on:click={() => selectInstanceTab(itemHash, 'general')}
414
+ >General</button>
415
+ <button
416
+ type="button"
417
+ role="tab"
418
+ class="tab"
419
+ class:tab-active={tab === 'addresses'}
420
+ aria-selected={tab === 'addresses'}
421
+ on:click={() => selectInstanceTab(itemHash, 'addresses')}
422
+ >{addressTabLabel(itemHash)}</button>
423
+ <button
424
+ type="button"
425
+ role="tab"
426
+ class="tab"
427
+ class:tab-active={tab === 'health'}
428
+ aria-selected={tab === 'health'}
429
+ on:click={() => selectInstanceTab(itemHash, 'health')}
430
+ >Health</button>
431
+ </div>
432
+
433
+ {#if tab === 'general'}
362
434
  <div class="instance-grid">
363
435
  <div>
364
436
  <span>Host IPv4</span>
@@ -389,12 +461,109 @@ export let apiHosts = undefined
389
461
  <strong>{joinMappedPorts(entry.details.mappedPorts)}</strong>
390
462
  </div>
391
463
 
464
+ {#if entry.details.proxyHostname}
465
+ <div class="mono-block">
466
+ <span>Proxy hostname</span>
467
+ <strong>{entry.details.proxyHostname}</strong>
468
+ <CopyButton text={entry.details.proxyHostname} />
469
+ </div>
470
+ {/if}
471
+
392
472
  {#if bootstrapUiEnabled() && confirmedRegistration}
393
473
  <div class="mono-block">
394
474
  <span>Bootstrap Registration</span>
395
475
  <strong>{shortHash(confirmedRegistration.messageHash ?? confirmedRegistration.content?.registrationId ?? 'confirmed', 14, 8)}</strong>
396
476
  </div>
397
477
  {/if}
478
+ {/if}
479
+
480
+ {#if tab === 'addresses'}
481
+ <div class="tab-head">
482
+ <span>{addresses?.sourceUrl ?? 'Reading from the deployment'}</span>
483
+ <button
484
+ type="button"
485
+ class="refresh"
486
+ disabled={addresses?.status === 'loading'}
487
+ on:click={() => controller.loadInstanceAddresses(itemHash, { force: true })}
488
+ >{addresses?.status === 'loading' ? 'Loading…' : 'Refresh'}</button>
489
+ </div>
490
+
491
+ {#if addresses?.status === 'loading' && !addresses?.groups?.length}
492
+ <p class="tab-note">Reading the deployment's address document…</p>
493
+ {:else if addresses?.status === 'unsupported'}
494
+ <p class="tab-note">{addresses.error}</p>
495
+ {:else if addresses?.status === 'error'}
496
+ <p class="alert error">{addresses.error}</p>
497
+ {:else if addresses?.groups?.length}
498
+ {#if addresses.peerId}
499
+ <div class="mono-block">
500
+ <span>Peer ID</span>
501
+ <strong>{addresses.peerId}</strong>
502
+ <CopyButton text={addresses.peerId} />
503
+ </div>
504
+ {/if}
505
+
506
+ {#each addresses.groups as group}
507
+ <div class="address-group">
508
+ <div class="address-group-head">
509
+ <strong>{group.label}</strong>
510
+ <CopyButton text={group.addresses.join('\n')} label="Copy group" />
511
+ </div>
512
+ {#each group.addresses as address}
513
+ <div class="address-row">
514
+ <code>{address}</code>
515
+ <CopyButton text={address} />
516
+ </div>
517
+ {/each}
518
+ </div>
519
+ {/each}
520
+
521
+ <div class="link-row">
522
+ <CopyButton text={copyAllText(addresses)} label="Copy all" />
523
+ <CopyButton text={JSON.stringify(addresses.payload, null, 2)} label="Copy JSON" />
524
+ </div>
525
+ {:else if addresses?.pwaEnv}
526
+ <div class="address-group">
527
+ <div class="address-group-head">
528
+ <strong>PWA environment</strong>
529
+ <CopyButton text={pwaEnvText(addresses)} label="Copy block" />
530
+ </div>
531
+ {#each Object.entries(addresses.pwaEnv) as [key, value]}
532
+ <div class="address-row">
533
+ <code>{key}={value}</code>
534
+ <CopyButton text={`${key}=${value}`} />
535
+ </div>
536
+ {/each}
537
+ </div>
538
+ {:else}
539
+ <p class="tab-note">This deployment reports no addresses yet.</p>
540
+ {/if}
541
+ {/if}
542
+
543
+ {#if tab === 'health'}
544
+ <div class="tab-head">
545
+ <span>{addresses?.sourceUrl ?? 'Reading from the deployment'}</span>
546
+ <button
547
+ type="button"
548
+ class="refresh"
549
+ disabled={addresses?.status === 'loading'}
550
+ on:click={() => controller.loadInstanceAddresses(itemHash, { force: true })}
551
+ >{addresses?.status === 'loading' ? 'Loading…' : 'Refresh'}</button>
552
+ </div>
553
+
554
+ {#if addresses?.payload}
555
+ <pre class="payload">{JSON.stringify(addresses.payload, null, 2)}</pre>
556
+ <div class="link-row">
557
+ <CopyButton text={JSON.stringify(addresses.payload, null, 2)} label="Copy JSON" />
558
+ </div>
559
+ {:else if addresses?.status === 'loading'}
560
+ <p class="tab-note">Reading the deployment's health document…</p>
561
+ {:else if addresses?.error}
562
+ <p class="tab-note">{addresses.error}</p>
563
+ {:else}
564
+ <p class="tab-note">No health document read yet.</p>
565
+ {/if}
566
+ {/if}
398
567
 
399
568
  <div class="link-row">
400
569
  <CopyButton text={entry.instance.item_hash} label="Copy hash" />
@@ -800,6 +969,14 @@ export let apiHosts = undefined
800
969
  font: 500 0.8125rem/1.4 var(--relay-font-body);
801
970
  }
802
971
 
972
+ /* The list, not the closed control. Without this it inherits the select's
973
+ translucent field background and keeps --relay-text, which on a light
974
+ drop-down is light-on-light. */
975
+ option {
976
+ background: var(--relay-menu-bg);
977
+ color: var(--relay-text);
978
+ }
979
+
803
980
  input:focus-visible,
804
981
  select:focus-visible,
805
982
  textarea:focus-visible {
@@ -830,6 +1007,115 @@ export let apiHosts = undefined
830
1007
  gap: 0.3rem;
831
1008
  }
832
1009
 
1010
+ .tab-bar {
1011
+ display: flex;
1012
+ gap: 0.35rem;
1013
+ margin: 0.75rem 0 0.5rem;
1014
+ border-bottom: 1px solid var(--relay-surface-border);
1015
+ }
1016
+
1017
+ .tab {
1018
+ border: 0;
1019
+ border-bottom: 2px solid transparent;
1020
+ background: transparent;
1021
+ color: var(--relay-muted);
1022
+ padding: 0.35rem 0.6rem;
1023
+ font: 700 0.6875rem/1 var(--relay-font-mono);
1024
+ text-transform: uppercase;
1025
+ letter-spacing: 0.06em;
1026
+ cursor: pointer;
1027
+ }
1028
+
1029
+ .tab:hover {
1030
+ color: var(--relay-text);
1031
+ }
1032
+
1033
+ .tab-active {
1034
+ color: var(--relay-text);
1035
+ border-bottom-color: var(--relay-cyan);
1036
+ }
1037
+
1038
+ .tab-head {
1039
+ display: flex;
1040
+ align-items: center;
1041
+ justify-content: space-between;
1042
+ gap: 0.5rem;
1043
+ margin-bottom: 0.5rem;
1044
+ }
1045
+
1046
+ .tab-head span {
1047
+ color: var(--relay-muted);
1048
+ font: 400 0.6875rem/1.4 var(--relay-font-mono);
1049
+ overflow-wrap: anywhere;
1050
+ }
1051
+
1052
+ .refresh {
1053
+ border: 1px solid var(--relay-surface-border);
1054
+ border-radius: 999px;
1055
+ background: transparent;
1056
+ color: var(--relay-muted);
1057
+ padding: 0.28rem 0.7rem;
1058
+ font: 700 0.6563rem/1 var(--relay-font-mono);
1059
+ text-transform: uppercase;
1060
+ letter-spacing: 0.06em;
1061
+ cursor: pointer;
1062
+ }
1063
+
1064
+ .refresh:hover:not(:disabled) {
1065
+ color: var(--relay-text);
1066
+ border-color: var(--relay-cyan);
1067
+ }
1068
+
1069
+ .refresh:disabled {
1070
+ opacity: 0.6;
1071
+ cursor: default;
1072
+ }
1073
+
1074
+ .tab-note {
1075
+ color: var(--relay-muted);
1076
+ font-size: 0.75rem;
1077
+ margin: 0.5rem 0;
1078
+ }
1079
+
1080
+ .address-group {
1081
+ margin-bottom: 0.75rem;
1082
+ }
1083
+
1084
+ .address-group-head {
1085
+ display: flex;
1086
+ align-items: center;
1087
+ justify-content: space-between;
1088
+ gap: 0.5rem;
1089
+ margin-bottom: 0.3rem;
1090
+ }
1091
+
1092
+ .address-group-head strong {
1093
+ font-size: 0.75rem;
1094
+ }
1095
+
1096
+ .address-row {
1097
+ display: flex;
1098
+ align-items: center;
1099
+ justify-content: space-between;
1100
+ gap: 0.5rem;
1101
+ padding: 0.25rem 0;
1102
+ border-top: 1px solid var(--relay-surface-border);
1103
+ }
1104
+
1105
+ .address-row code {
1106
+ font: 400 0.6875rem/1.4 var(--relay-font-mono);
1107
+ overflow-wrap: anywhere;
1108
+ }
1109
+
1110
+ .payload {
1111
+ max-height: 20rem;
1112
+ overflow: auto;
1113
+ padding: 0.5rem;
1114
+ border: 1px solid var(--relay-surface-border);
1115
+ border-radius: 0.5rem;
1116
+ font: 400 0.6875rem/1.5 var(--relay-font-mono);
1117
+ }
1118
+
833
1119
  .deployment-box strong {
834
1120
  font-family: var(--relay-font-mono);
835
1121
  font-size: 0.75rem;
@@ -42,6 +42,11 @@
42
42
  --relay-surface: rgba(11, 14, 21, 0.55);
43
43
  --relay-surface-border: var(--relay-horizon);
44
44
  --relay-field-bg: rgba(11, 14, 21, 0.85);
45
+ /* Opaque on purpose. A select's drop-down takes the control's own colours on
46
+ the platforms that honour author styling, and it opens over the page rather
47
+ than over the panel - a translucent background would composite against
48
+ whatever is behind it and leave light text on a light list. */
49
+ --relay-menu-bg: var(--relay-deep-space);
45
50
  --relay-accent: var(--relay-coral);
46
51
  --relay-accent-contrast: #0b0e15;
47
52
  --relay-link: var(--relay-cyan);
@@ -81,6 +86,7 @@
81
86
  --relay-surface: rgba(20, 27, 46, 0.04);
82
87
  --relay-surface-border: #d9e0ec;
83
88
  --relay-field-bg: #ffffff;
89
+ --relay-menu-bg: #ffffff;
84
90
  --relay-accent: var(--relay-coral);
85
91
  --relay-accent-contrast: #ffffff;
86
92
  --relay-link: var(--relay-cyan);