@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/package.json +3 -3
- package/react/index.d.ts +62 -0
- package/react/index.js +907 -165
- package/shared/index.d.ts +72 -2
- package/shared/index.js +477 -41
- package/styles.css +6 -0
- package/svelte/SponsorRelayFab.svelte +286 -0
- package/svelte/styles/theme.css +6 -0
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;
|
package/svelte/styles/theme.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);
|