@le-space/ui 0.3.30 → 0.3.32
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 +20 -0
- package/react/index.js +393 -95
- package/shared/index.d.ts +22 -2
- package/shared/index.js +211 -47
- package/svelte/SponsorRelayFab.svelte +96 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.32",
|
|
4
4
|
"description": "Floating Aleph sponsor relay UI components for Svelte and React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@le-space/browser": "0.3.
|
|
40
|
-
"@le-space/core": "0.3.
|
|
39
|
+
"@le-space/browser": "0.3.32",
|
|
40
|
+
"@le-space/core": "0.3.32",
|
|
41
41
|
"viem": "^2.38.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
package/react/index.d.ts
CHANGED
|
@@ -243,6 +243,22 @@ interface CompactInstanceRecord {
|
|
|
243
243
|
instance: InstanceMessage;
|
|
244
244
|
details: CompactInstanceDetails;
|
|
245
245
|
}
|
|
246
|
+
interface CompactBootstrapRegistrationRecord {
|
|
247
|
+
messageHash: string | null;
|
|
248
|
+
hash: string | null;
|
|
249
|
+
itemHash: string | null;
|
|
250
|
+
address: string | null;
|
|
251
|
+
time: number | null;
|
|
252
|
+
instanceItemHash: string | null;
|
|
253
|
+
confirmed: boolean;
|
|
254
|
+
content: {
|
|
255
|
+
peerId: string;
|
|
256
|
+
registrationId?: string;
|
|
257
|
+
multiaddrs: string[];
|
|
258
|
+
browserMultiaddrs?: string[];
|
|
259
|
+
updatedAt: number;
|
|
260
|
+
} | null;
|
|
261
|
+
}
|
|
246
262
|
interface SponsorRelayState {
|
|
247
263
|
ready: boolean;
|
|
248
264
|
open: boolean;
|
|
@@ -260,6 +276,7 @@ interface SponsorRelayState {
|
|
|
260
276
|
refreshing: boolean;
|
|
261
277
|
deploying: boolean;
|
|
262
278
|
deletingInstanceHash: string | null;
|
|
279
|
+
deletingRegistrationHash: string | null;
|
|
263
280
|
};
|
|
264
281
|
statusText: string;
|
|
265
282
|
errorText: string | null;
|
|
@@ -273,6 +290,8 @@ interface SponsorRelayState {
|
|
|
273
290
|
crns: Crn[];
|
|
274
291
|
selectedCrn: Crn | null;
|
|
275
292
|
instances: CompactInstanceRecord[];
|
|
293
|
+
bootstrapRegistrations: CompactBootstrapRegistrationRecord[];
|
|
294
|
+
orphanBootstrapRegistrations: CompactBootstrapRegistrationRecord[];
|
|
276
295
|
lastDeploymentHash: string | null;
|
|
277
296
|
deploymentProgress: DeploymentProgressEvent;
|
|
278
297
|
}
|
|
@@ -324,6 +343,7 @@ declare class SponsorRelayController {
|
|
|
324
343
|
refresh(): Promise<void>;
|
|
325
344
|
deploy(): Promise<void>;
|
|
326
345
|
deleteInstance(instanceHash: string): Promise<void>;
|
|
346
|
+
deleteBootstrapRegistration(registrationHash: string): Promise<void>;
|
|
327
347
|
}
|
|
328
348
|
|
|
329
349
|
declare function SponsorRelayFab(props: SponsorRelayProps): react_jsx_runtime.JSX.Element;
|
package/react/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
|
28
28
|
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
29
29
|
|
|
30
30
|
// src/shared/package-version.ts
|
|
31
|
-
var UI_PACKAGE_VERSION = "0.3.
|
|
31
|
+
var UI_PACKAGE_VERSION = "0.3.32";
|
|
32
32
|
|
|
33
33
|
// src/shared/format.ts
|
|
34
34
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -2869,7 +2869,14 @@ function normalizeVmBootstrapConfigSignalRecord(value) {
|
|
|
2869
2869
|
instanceItemHash,
|
|
2870
2870
|
updatedAt,
|
|
2871
2871
|
publisherAddress: typeof candidate.publisherAddress === "string" && candidate.publisherAddress.trim() ? candidate.publisherAddress.trim() : null,
|
|
2872
|
-
authorization: candidate.authorization
|
|
2872
|
+
authorization: candidate.authorization,
|
|
2873
|
+
peerId: typeof candidate.peerId === "string" && candidate.peerId.trim() ? candidate.peerId.trim() : null,
|
|
2874
|
+
probeMultiaddrs: Array.isArray(candidate.probeMultiaddrs) ? candidate.probeMultiaddrs.filter(
|
|
2875
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2876
|
+
) : [],
|
|
2877
|
+
browserBootstrapMultiaddrs: Array.isArray(candidate.browserBootstrapMultiaddrs) ? candidate.browserBootstrapMultiaddrs.filter(
|
|
2878
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2879
|
+
) : []
|
|
2873
2880
|
};
|
|
2874
2881
|
}
|
|
2875
2882
|
async function fetchVmBootstrapConfigSignals(args) {
|
|
@@ -3037,7 +3044,8 @@ function defaultState(props = {}) {
|
|
|
3037
3044
|
connectingWallet: false,
|
|
3038
3045
|
refreshing: false,
|
|
3039
3046
|
deploying: false,
|
|
3040
|
-
deletingInstanceHash: null
|
|
3047
|
+
deletingInstanceHash: null,
|
|
3048
|
+
deletingRegistrationHash: null
|
|
3041
3049
|
},
|
|
3042
3050
|
statusText: "Ready",
|
|
3043
3051
|
errorText: null,
|
|
@@ -3063,6 +3071,8 @@ function defaultState(props = {}) {
|
|
|
3063
3071
|
crns: [],
|
|
3064
3072
|
selectedCrn: null,
|
|
3065
3073
|
instances: [],
|
|
3074
|
+
bootstrapRegistrations: [],
|
|
3075
|
+
orphanBootstrapRegistrations: [],
|
|
3066
3076
|
lastDeploymentHash: null,
|
|
3067
3077
|
deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
|
|
3068
3078
|
};
|
|
@@ -3167,6 +3177,20 @@ function relayProfileForManifest(manifest) {
|
|
|
3167
3177
|
}
|
|
3168
3178
|
return null;
|
|
3169
3179
|
}
|
|
3180
|
+
function registrationIdInstanceItemHash(registrationId) {
|
|
3181
|
+
const normalized = String(registrationId ?? "").trim();
|
|
3182
|
+
if (!normalized) return null;
|
|
3183
|
+
const parts = normalized.split(":").map((part) => part.trim()).filter(Boolean);
|
|
3184
|
+
const candidate = parts.at(-1) ?? "";
|
|
3185
|
+
return /^[0-9a-f]{64}$/i.test(candidate) ? candidate : null;
|
|
3186
|
+
}
|
|
3187
|
+
function relayBootstrapInstanceItemHash(entry) {
|
|
3188
|
+
const authorizedInstanceItemHash = typeof entry?.content?.authorization?.payload?.instanceItemHash === "string" ? entry.content.authorization.payload.instanceItemHash.trim() : "";
|
|
3189
|
+
if (authorizedInstanceItemHash) {
|
|
3190
|
+
return authorizedInstanceItemHash;
|
|
3191
|
+
}
|
|
3192
|
+
return registrationIdInstanceItemHash(entry?.content?.registrationId);
|
|
3193
|
+
}
|
|
3170
3194
|
async function inspectInstanceRuntime(args) {
|
|
3171
3195
|
const details = {
|
|
3172
3196
|
messageStatus: String(
|
|
@@ -3401,6 +3425,7 @@ var SponsorRelayController = class {
|
|
|
3401
3425
|
if (!setupPort) {
|
|
3402
3426
|
throw new Error("Relay runtime did not expose the temporary setup endpoint.");
|
|
3403
3427
|
}
|
|
3428
|
+
let bootstrapSignalRelayMetadata = null;
|
|
3404
3429
|
if (profile === "orbitdb-relay-pinner") {
|
|
3405
3430
|
this.emitProgress({
|
|
3406
3431
|
stage: "deployment-confirmed",
|
|
@@ -3501,11 +3526,6 @@ var SponsorRelayController = class {
|
|
|
3501
3526
|
}
|
|
3502
3527
|
runtime = latestRuntime;
|
|
3503
3528
|
mappedPorts2 = runtime.mappedPorts ?? mappedPorts2;
|
|
3504
|
-
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3505
|
-
throw new Error(
|
|
3506
|
-
"Aleph reserved a 2n6 proxy URL, but it never became active in time. Aborting guest HTTPS configuration to avoid a broken Caddy setup."
|
|
3507
|
-
);
|
|
3508
|
-
}
|
|
3509
3529
|
}
|
|
3510
3530
|
if (!runtime.hostIpv4) {
|
|
3511
3531
|
throw new Error("Relay runtime lost its host IPv4 address while waiting for HTTPS activation.");
|
|
@@ -3587,46 +3607,85 @@ var SponsorRelayController = class {
|
|
|
3587
3607
|
"The relay VM did not confirm that it applied the Aleph bootstrap config in time."
|
|
3588
3608
|
);
|
|
3589
3609
|
}
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
detail: "Waiting for the relay to report its final public multiaddrs."
|
|
3598
|
-
});
|
|
3599
|
-
const relayMetadata = await fetchRelayMetadataForRuntime({
|
|
3600
|
-
runtime,
|
|
3601
|
-
fetch: (url, init) => fetch(url, init),
|
|
3602
|
-
preferSecureMetadata: profile === "uc-go-peer",
|
|
3603
|
-
attempts: 80,
|
|
3604
|
-
delayMs: 3e3,
|
|
3605
|
-
timeoutMs: 24e4,
|
|
3606
|
-
onAttempt: (result) => {
|
|
3607
|
-
const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
|
|
3608
|
-
const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
|
|
3609
|
-
this.trace("deploy:relay-metadata-attempt", {
|
|
3610
|
+
bootstrapSignalRelayMetadata = typeof bootstrapConfigSignal.peerId === "string" && bootstrapConfigSignal.peerId.length > 0 && Array.isArray(bootstrapConfigSignal.probeMultiaddrs) && bootstrapConfigSignal.probeMultiaddrs.length > 0 ? {
|
|
3611
|
+
peerId: bootstrapConfigSignal.peerId,
|
|
3612
|
+
probeMultiaddrs: bootstrapConfigSignal.probeMultiaddrs,
|
|
3613
|
+
browserBootstrapMultiaddrs: bootstrapConfigSignal.browserBootstrapMultiaddrs ?? []
|
|
3614
|
+
} : null;
|
|
3615
|
+
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3616
|
+
this.trace("deploy:bootstrap-proxy-fallback", {
|
|
3610
3617
|
itemHash: args.itemHash,
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
metadataUrl: result.metadataUrl,
|
|
3615
|
-
hostIpv4: result.hostIpv4,
|
|
3616
|
-
setupPort: result.setupPort,
|
|
3617
|
-
ok: result.ok,
|
|
3618
|
-
status: result.status,
|
|
3619
|
-
ready: result.ready,
|
|
3620
|
-
error: result.error ?? null,
|
|
3621
|
-
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3622
|
-
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3623
|
-
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3624
|
-
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3625
|
-
metadata?.browser_bootstrap_multiaddrs
|
|
3626
|
-
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3618
|
+
deploymentToken: args.deploymentToken,
|
|
3619
|
+
proxyUrl: runtime.proxyUrl,
|
|
3620
|
+
bootstrapSignalRelayMetadata
|
|
3627
3621
|
});
|
|
3622
|
+
this.emitProgress({
|
|
3623
|
+
stage: "deployment-confirmed",
|
|
3624
|
+
label: "Continuing without HTTPS route",
|
|
3625
|
+
progress: 94,
|
|
3626
|
+
status: "warning",
|
|
3627
|
+
itemHash: args.itemHash,
|
|
3628
|
+
detail: "The 2n6 HTTPS route stayed inactive, so guest setup continued without Caddy. Secure browser routing can be enabled later when the proxy becomes available."
|
|
3629
|
+
});
|
|
3630
|
+
runtime = {
|
|
3631
|
+
...runtime,
|
|
3632
|
+
proxyUrl: null,
|
|
3633
|
+
webAccess: runtime.webAccess ? {
|
|
3634
|
+
...runtime.webAccess,
|
|
3635
|
+
active: false
|
|
3636
|
+
} : runtime.webAccess
|
|
3637
|
+
};
|
|
3628
3638
|
}
|
|
3629
|
-
}
|
|
3639
|
+
}
|
|
3640
|
+
let relayMetadata = bootstrapSignalRelayMetadata;
|
|
3641
|
+
if (relayMetadata) {
|
|
3642
|
+
this.trace("deploy:relay-metadata-from-bootstrap-signal", {
|
|
3643
|
+
itemHash: args.itemHash,
|
|
3644
|
+
peerId: relayMetadata.peerId,
|
|
3645
|
+
probeMultiaddrCount: relayMetadata.probeMultiaddrs.length,
|
|
3646
|
+
browserBootstrapMultiaddrCount: relayMetadata.browserBootstrapMultiaddrs.length
|
|
3647
|
+
});
|
|
3648
|
+
} else {
|
|
3649
|
+
this.emitProgress({
|
|
3650
|
+
stage: "publishing-bootstrap",
|
|
3651
|
+
label: "Reading relay multiaddrs",
|
|
3652
|
+
progress: 95,
|
|
3653
|
+
status: "info",
|
|
3654
|
+
itemHash: args.itemHash,
|
|
3655
|
+
detail: "Waiting for the relay to report its final public multiaddrs."
|
|
3656
|
+
});
|
|
3657
|
+
relayMetadata = await fetchRelayMetadataForRuntime({
|
|
3658
|
+
runtime,
|
|
3659
|
+
fetch: (url, init) => fetch(url, init),
|
|
3660
|
+
preferSecureMetadata: profile === "uc-go-peer" && Boolean(runtime.proxyUrl),
|
|
3661
|
+
attempts: 80,
|
|
3662
|
+
delayMs: 3e3,
|
|
3663
|
+
timeoutMs: 24e4,
|
|
3664
|
+
onAttempt: (result) => {
|
|
3665
|
+
const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
|
|
3666
|
+
const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
|
|
3667
|
+
this.trace("deploy:relay-metadata-attempt", {
|
|
3668
|
+
itemHash: args.itemHash,
|
|
3669
|
+
attempt: result.attempt,
|
|
3670
|
+
attempts: result.attempts,
|
|
3671
|
+
requestUrl: result.requestUrl,
|
|
3672
|
+
metadataUrl: result.metadataUrl,
|
|
3673
|
+
hostIpv4: result.hostIpv4,
|
|
3674
|
+
setupPort: result.setupPort,
|
|
3675
|
+
ok: result.ok,
|
|
3676
|
+
status: result.status,
|
|
3677
|
+
ready: result.ready,
|
|
3678
|
+
error: result.error ?? null,
|
|
3679
|
+
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3680
|
+
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3681
|
+
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3682
|
+
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3683
|
+
metadata?.browser_bootstrap_multiaddrs
|
|
3684
|
+
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3685
|
+
});
|
|
3686
|
+
}
|
|
3687
|
+
});
|
|
3688
|
+
}
|
|
3630
3689
|
if (!relayMetadata) {
|
|
3631
3690
|
throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
|
|
3632
3691
|
}
|
|
@@ -4095,10 +4154,16 @@ var SponsorRelayController = class {
|
|
|
4095
4154
|
const manifest = manifestState.manifest;
|
|
4096
4155
|
let balance = this.state.balance;
|
|
4097
4156
|
let instances = [];
|
|
4157
|
+
let bootstrapRegistrations = [];
|
|
4158
|
+
let orphanBootstrapRegistrations = [];
|
|
4098
4159
|
if (this.state.wallet.address) {
|
|
4099
|
-
const [nextBalance, rawInstances] = await Promise.all([
|
|
4160
|
+
const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
|
|
4100
4161
|
this.client.fetchBalance(this.state.wallet.address),
|
|
4101
|
-
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
|
|
4162
|
+
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([]),
|
|
4163
|
+
fetchAlephBootstrapPosts({
|
|
4164
|
+
apiHost: this.client.apiHost,
|
|
4165
|
+
fetch
|
|
4166
|
+
}).catch(() => [])
|
|
4102
4167
|
]);
|
|
4103
4168
|
balance = nextBalance;
|
|
4104
4169
|
instances = await Promise.all(
|
|
@@ -4158,6 +4223,37 @@ var SponsorRelayController = class {
|
|
|
4158
4223
|
};
|
|
4159
4224
|
})
|
|
4160
4225
|
);
|
|
4226
|
+
const normalizedWalletAddress = this.state.wallet.address.toLowerCase();
|
|
4227
|
+
const activeInstanceHashes = new Set(
|
|
4228
|
+
instances.map((entry) => entry.instance.item_hash)
|
|
4229
|
+
);
|
|
4230
|
+
bootstrapRegistrations = selectCurrentRelayBootstrapPosts(rawBootstrapPosts).filter(
|
|
4231
|
+
(entry) => entry.address?.toLowerCase() === normalizedWalletAddress
|
|
4232
|
+
).map((entry) => {
|
|
4233
|
+
const instanceItemHash = relayBootstrapInstanceItemHash(entry);
|
|
4234
|
+
const messageHash = entry.itemHash ?? entry.hash ?? null;
|
|
4235
|
+
return {
|
|
4236
|
+
messageHash,
|
|
4237
|
+
hash: entry.hash ?? null,
|
|
4238
|
+
itemHash: entry.itemHash ?? null,
|
|
4239
|
+
address: entry.address ?? null,
|
|
4240
|
+
time: entry.time ?? null,
|
|
4241
|
+
instanceItemHash,
|
|
4242
|
+
confirmed: Boolean(
|
|
4243
|
+
instanceItemHash && activeInstanceHashes.has(instanceItemHash)
|
|
4244
|
+
),
|
|
4245
|
+
content: entry.content ? {
|
|
4246
|
+
peerId: entry.content.peerId,
|
|
4247
|
+
registrationId: entry.content.registrationId,
|
|
4248
|
+
multiaddrs: Array.isArray(entry.content.multiaddrs) ? entry.content.multiaddrs : [],
|
|
4249
|
+
browserMultiaddrs: Array.isArray(entry.content.browserMultiaddrs) ? entry.content.browserMultiaddrs : [],
|
|
4250
|
+
updatedAt: entry.content.updatedAt
|
|
4251
|
+
} : null
|
|
4252
|
+
};
|
|
4253
|
+
});
|
|
4254
|
+
orphanBootstrapRegistrations = bootstrapRegistrations.filter(
|
|
4255
|
+
(entry) => !entry.confirmed
|
|
4256
|
+
);
|
|
4161
4257
|
}
|
|
4162
4258
|
let rootfsVerified = false;
|
|
4163
4259
|
let rootfsResolution = null;
|
|
@@ -4188,6 +4284,8 @@ var SponsorRelayController = class {
|
|
|
4188
4284
|
balance,
|
|
4189
4285
|
crns,
|
|
4190
4286
|
instances,
|
|
4287
|
+
bootstrapRegistrations,
|
|
4288
|
+
orphanBootstrapRegistrations,
|
|
4191
4289
|
busy: { refreshing: false },
|
|
4192
4290
|
statusText: "Relay sponsor data ready"
|
|
4193
4291
|
});
|
|
@@ -4697,6 +4795,72 @@ var SponsorRelayController = class {
|
|
|
4697
4795
|
});
|
|
4698
4796
|
}
|
|
4699
4797
|
}
|
|
4798
|
+
async deleteBootstrapRegistration(registrationHash) {
|
|
4799
|
+
this.trace("registration-delete:start", { registrationHash });
|
|
4800
|
+
if (!this.state.wallet.address) {
|
|
4801
|
+
this.patch({
|
|
4802
|
+
errorText: "Connect MetaMask before forgetting registrations."
|
|
4803
|
+
});
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
this.patch({
|
|
4807
|
+
busy: { deletingRegistrationHash: registrationHash },
|
|
4808
|
+
errorText: null,
|
|
4809
|
+
statusText: `Forgetting registration ${registrationHash}`
|
|
4810
|
+
});
|
|
4811
|
+
try {
|
|
4812
|
+
this.emitProgress({
|
|
4813
|
+
stage: "validating",
|
|
4814
|
+
label: "Validating registration cleanup",
|
|
4815
|
+
progress: 6,
|
|
4816
|
+
status: "warning",
|
|
4817
|
+
itemHash: registrationHash,
|
|
4818
|
+
detail: "Preparing Aleph FORGET message for the orphan bootstrap registration."
|
|
4819
|
+
});
|
|
4820
|
+
await forgetAlephMessages({
|
|
4821
|
+
sender: this.state.wallet.address,
|
|
4822
|
+
hashes: [registrationHash],
|
|
4823
|
+
reason: "Deleted orphan relay bootstrap registration from Sponsor Relay panel",
|
|
4824
|
+
signer: personalSign,
|
|
4825
|
+
hasher: sha256Hex,
|
|
4826
|
+
fetch: asJsonFetch,
|
|
4827
|
+
apiHost: this.client.apiHost,
|
|
4828
|
+
onProgress: (event) => {
|
|
4829
|
+
this.emitProgress(event);
|
|
4830
|
+
}
|
|
4831
|
+
});
|
|
4832
|
+
this.patch({
|
|
4833
|
+
busy: { deletingRegistrationHash: null },
|
|
4834
|
+
statusText: `Deletion submitted for ${registrationHash}`
|
|
4835
|
+
});
|
|
4836
|
+
this.emitProgress({
|
|
4837
|
+
stage: "refreshing-instances",
|
|
4838
|
+
label: "Refreshing registrations after delete",
|
|
4839
|
+
progress: 92,
|
|
4840
|
+
status: "info",
|
|
4841
|
+
itemHash: registrationHash,
|
|
4842
|
+
detail: "Reloading deployments and relay bootstrap registrations."
|
|
4843
|
+
});
|
|
4844
|
+
await this.refresh();
|
|
4845
|
+
this.trace("registration-delete:submitted", { registrationHash });
|
|
4846
|
+
this.emitProgress({
|
|
4847
|
+
stage: "completed",
|
|
4848
|
+
label: "Registration cleanup completed",
|
|
4849
|
+
progress: 100,
|
|
4850
|
+
status: "success",
|
|
4851
|
+
itemHash: registrationHash,
|
|
4852
|
+
detail: "Orphan relay bootstrap registration forgotten and state refreshed.",
|
|
4853
|
+
error: null
|
|
4854
|
+
});
|
|
4855
|
+
} catch (error) {
|
|
4856
|
+
this.trace("registration-delete:error", error);
|
|
4857
|
+
this.patch({
|
|
4858
|
+
busy: { deletingRegistrationHash: null },
|
|
4859
|
+
errorText: error instanceof Error ? error.message : String(error),
|
|
4860
|
+
statusText: "Registration cleanup failed"
|
|
4861
|
+
});
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4700
4864
|
};
|
|
4701
4865
|
function createSponsorRelayController(props = {}) {
|
|
4702
4866
|
return new SponsorRelayController(props);
|
|
@@ -4896,6 +5060,10 @@ function SponsorRelayFab(props) {
|
|
|
4896
5060
|
const versionLabel = resolvedVersion.startsWith("v") ? resolvedVersion : `v${resolvedVersion}`;
|
|
4897
5061
|
const launcherMode = props.launcherMode ?? "floating";
|
|
4898
5062
|
const indicator = launcherIndicator(state);
|
|
5063
|
+
const confirmedRegistrationByInstanceHash = new Map(
|
|
5064
|
+
(state.bootstrapRegistrations ?? []).filter((entry) => entry.confirmed && entry.instanceItemHash).map((entry) => [entry.instanceItemHash, entry])
|
|
5065
|
+
);
|
|
5066
|
+
const orphanRegistrations = state.orphanBootstrapRegistrations ?? [];
|
|
4899
5067
|
const [successFlash, setSuccessFlash] = React.useState(false);
|
|
4900
5068
|
const [hovered, setHovered] = React.useState(false);
|
|
4901
5069
|
const launcherRef = React.useRef(null);
|
|
@@ -5534,66 +5702,196 @@ function SponsorRelayFab(props) {
|
|
|
5534
5702
|
"Latest deployment: ",
|
|
5535
5703
|
shortHash(state.lastDeploymentHash)
|
|
5536
5704
|
] }) : null,
|
|
5537
|
-
state.showInstances ? /* @__PURE__ */
|
|
5705
|
+
state.showInstances ? /* @__PURE__ */ jsxs(
|
|
5538
5706
|
"div",
|
|
5539
5707
|
{
|
|
5540
5708
|
style: { marginTop: "1rem", display: "grid", gap: "0.7rem" },
|
|
5541
|
-
children:
|
|
5542
|
-
|
|
5543
|
-
|
|
5709
|
+
children: [
|
|
5710
|
+
state.instances.map((entry) => {
|
|
5711
|
+
const confirmedRegistration = confirmedRegistrationByInstanceHash.get(
|
|
5712
|
+
entry.instance.item_hash
|
|
5713
|
+
) ?? null;
|
|
5714
|
+
return /* @__PURE__ */ jsxs("details", { open: true, children: [
|
|
5715
|
+
/* @__PURE__ */ jsx("summary", { children: /* @__PURE__ */ jsxs(
|
|
5716
|
+
"span",
|
|
5717
|
+
{
|
|
5718
|
+
style: {
|
|
5719
|
+
display: "inline-flex",
|
|
5720
|
+
alignItems: "center",
|
|
5721
|
+
gap: "0.55rem",
|
|
5722
|
+
flexWrap: "wrap"
|
|
5723
|
+
},
|
|
5724
|
+
children: [
|
|
5725
|
+
(entry.instance.content?.metadata?.name ?? "relay") + " \xB7 " + shortHash(entry.instance.item_hash),
|
|
5726
|
+
confirmedRegistration ? /* @__PURE__ */ jsxs(
|
|
5727
|
+
"span",
|
|
5728
|
+
{
|
|
5729
|
+
style: {
|
|
5730
|
+
display: "inline-flex",
|
|
5731
|
+
alignItems: "center",
|
|
5732
|
+
gap: "0.32rem",
|
|
5733
|
+
padding: "0.16rem 0.45rem",
|
|
5734
|
+
borderRadius: "999px",
|
|
5735
|
+
background: "rgba(34, 197, 94, 0.16)",
|
|
5736
|
+
color: "#86efac",
|
|
5737
|
+
fontSize: "0.7rem",
|
|
5738
|
+
fontWeight: 700
|
|
5739
|
+
},
|
|
5740
|
+
children: [
|
|
5741
|
+
/* @__PURE__ */ jsx(
|
|
5742
|
+
"span",
|
|
5743
|
+
{
|
|
5744
|
+
"aria-hidden": "true",
|
|
5745
|
+
style: {
|
|
5746
|
+
width: "0.45rem",
|
|
5747
|
+
height: "0.45rem",
|
|
5748
|
+
borderRadius: "999px",
|
|
5749
|
+
background: "#22c55e",
|
|
5750
|
+
boxShadow: "0 0 0 3px rgba(34, 197, 94, 0.18)"
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5753
|
+
),
|
|
5754
|
+
"Registration confirmed"
|
|
5755
|
+
]
|
|
5756
|
+
}
|
|
5757
|
+
) : null
|
|
5758
|
+
]
|
|
5759
|
+
}
|
|
5760
|
+
) }),
|
|
5761
|
+
/* @__PURE__ */ jsxs(
|
|
5762
|
+
"div",
|
|
5763
|
+
{
|
|
5764
|
+
style: {
|
|
5765
|
+
display: "grid",
|
|
5766
|
+
gap: "0.35rem",
|
|
5767
|
+
marginTop: "0.55rem"
|
|
5768
|
+
},
|
|
5769
|
+
children: [
|
|
5770
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5771
|
+
"Status: ",
|
|
5772
|
+
entry.details.messageStatus
|
|
5773
|
+
] }),
|
|
5774
|
+
confirmedRegistration ? /* @__PURE__ */ jsxs("div", { children: [
|
|
5775
|
+
"Bootstrap registration:",
|
|
5776
|
+
" ",
|
|
5777
|
+
shortHash(
|
|
5778
|
+
confirmedRegistration.messageHash ?? confirmedRegistration.content?.registrationId ?? "confirmed",
|
|
5779
|
+
14,
|
|
5780
|
+
8
|
|
5781
|
+
)
|
|
5782
|
+
] }) : null,
|
|
5783
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5784
|
+
"Host IPv4: ",
|
|
5785
|
+
entry.details.hostIpv4 ?? "-"
|
|
5786
|
+
] }),
|
|
5787
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5788
|
+
"IPv6: ",
|
|
5789
|
+
entry.details.ipv6 ?? "-"
|
|
5790
|
+
] }),
|
|
5791
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5792
|
+
"VM IPv4: ",
|
|
5793
|
+
entry.details.vmIpv4 ?? "-"
|
|
5794
|
+
] }),
|
|
5795
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5796
|
+
"SSH: ",
|
|
5797
|
+
entry.details.sshCommand ?? "-"
|
|
5798
|
+
] }),
|
|
5799
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5800
|
+
"Ports: ",
|
|
5801
|
+
joinMappedPorts(entry.details.mappedPorts)
|
|
5802
|
+
] }),
|
|
5803
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5804
|
+
"Submitted:",
|
|
5805
|
+
" ",
|
|
5806
|
+
formatDateTime(
|
|
5807
|
+
entry.instance.reception_time ?? entry.instance.time
|
|
5808
|
+
)
|
|
5809
|
+
] }),
|
|
5810
|
+
/* @__PURE__ */ jsx(
|
|
5811
|
+
"button",
|
|
5812
|
+
{
|
|
5813
|
+
type: "button",
|
|
5814
|
+
onClick: () => void controller.deleteInstance(
|
|
5815
|
+
entry.instance.item_hash
|
|
5816
|
+
),
|
|
5817
|
+
children: state.busy.deletingInstanceHash === entry.instance.item_hash ? "Deleting\u2026" : "Delete"
|
|
5818
|
+
}
|
|
5819
|
+
)
|
|
5820
|
+
]
|
|
5821
|
+
}
|
|
5822
|
+
)
|
|
5823
|
+
] }, entry.instance.item_hash);
|
|
5824
|
+
}),
|
|
5825
|
+
orphanRegistrations.length > 0 ? /* @__PURE__ */ jsxs(
|
|
5544
5826
|
"div",
|
|
5545
5827
|
{
|
|
5546
5828
|
style: {
|
|
5547
5829
|
display: "grid",
|
|
5548
|
-
gap: "0.
|
|
5549
|
-
|
|
5830
|
+
gap: "0.7rem",
|
|
5831
|
+
padding: "0.85rem",
|
|
5832
|
+
borderRadius: "1rem",
|
|
5833
|
+
border: "1px solid rgba(248, 113, 113, 0.22)",
|
|
5834
|
+
background: "linear-gradient(180deg, rgba(127, 29, 29, 0.18), rgba(69, 10, 10, 0.12))"
|
|
5550
5835
|
},
|
|
5551
5836
|
children: [
|
|
5552
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5553
|
-
"
|
|
5554
|
-
|
|
5555
|
-
] }),
|
|
5556
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5557
|
-
"Host IPv4: ",
|
|
5558
|
-
entry.details.hostIpv4 ?? "-"
|
|
5559
|
-
] }),
|
|
5560
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5561
|
-
"IPv6: ",
|
|
5562
|
-
entry.details.ipv6 ?? "-"
|
|
5563
|
-
] }),
|
|
5564
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5565
|
-
"VM IPv4: ",
|
|
5566
|
-
entry.details.vmIpv4 ?? "-"
|
|
5837
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.2rem" }, children: [
|
|
5838
|
+
/* @__PURE__ */ jsx("strong", { children: "Orphan bootstrap registrations" }),
|
|
5839
|
+
/* @__PURE__ */ jsx("small", { style: { color: "#fecaca", lineHeight: 1.35 }, children: "Current-wallet registrations without a matching instance. Forget them directly from here." })
|
|
5567
5840
|
] }),
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5841
|
+
orphanRegistrations.map((entry) => {
|
|
5842
|
+
const registrationHash = entry.messageHash ?? entry.hash ?? null;
|
|
5843
|
+
return /* @__PURE__ */ jsxs(
|
|
5844
|
+
"div",
|
|
5845
|
+
{
|
|
5846
|
+
style: {
|
|
5847
|
+
display: "grid",
|
|
5848
|
+
gap: "0.35rem",
|
|
5849
|
+
padding: "0.75rem",
|
|
5850
|
+
borderRadius: "0.9rem",
|
|
5851
|
+
background: "rgba(15, 23, 42, 0.22)",
|
|
5852
|
+
border: "1px solid rgba(255,255,255,0.08)"
|
|
5853
|
+
},
|
|
5854
|
+
children: [
|
|
5855
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 700 }, children: (entry.content?.registrationId ?? "registration") + " \xB7 " + shortHash(registrationHash ?? "unknown") }),
|
|
5856
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5857
|
+
"Peer: ",
|
|
5858
|
+
entry.content?.peerId ?? "-"
|
|
5859
|
+
] }),
|
|
5860
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5861
|
+
"Linked instance:",
|
|
5862
|
+
" ",
|
|
5863
|
+
entry.instanceItemHash ? shortHash(entry.instanceItemHash) : "missing"
|
|
5864
|
+
] }),
|
|
5865
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5866
|
+
"Browser multiaddrs:",
|
|
5867
|
+
" ",
|
|
5868
|
+
String(entry.content?.browserMultiaddrs?.length ?? 0)
|
|
5869
|
+
] }),
|
|
5870
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5871
|
+
"Updated:",
|
|
5872
|
+
" ",
|
|
5873
|
+
formatDateTime(entry.content?.updatedAt ?? entry.time)
|
|
5874
|
+
] }),
|
|
5875
|
+
/* @__PURE__ */ jsx(
|
|
5876
|
+
"button",
|
|
5877
|
+
{
|
|
5878
|
+
type: "button",
|
|
5879
|
+
disabled: !registrationHash || state.busy.deletingRegistrationHash === registrationHash,
|
|
5880
|
+
onClick: () => registrationHash ? void controller.deleteBootstrapRegistration(
|
|
5881
|
+
registrationHash
|
|
5882
|
+
) : void 0,
|
|
5883
|
+
children: state.busy.deletingRegistrationHash === registrationHash ? "Forgetting\u2026" : "Forget registration"
|
|
5884
|
+
}
|
|
5885
|
+
)
|
|
5886
|
+
]
|
|
5887
|
+
},
|
|
5888
|
+
registrationHash ?? entry.content?.registrationId ?? `orphan-${entry.content?.peerId ?? "unknown"}`
|
|
5889
|
+
);
|
|
5890
|
+
})
|
|
5593
5891
|
]
|
|
5594
5892
|
}
|
|
5595
|
-
)
|
|
5596
|
-
]
|
|
5893
|
+
) : null
|
|
5894
|
+
]
|
|
5597
5895
|
}
|
|
5598
5896
|
) : null
|
|
5599
5897
|
] })
|
package/shared/index.d.ts
CHANGED
|
@@ -241,6 +241,22 @@ interface CompactInstanceRecord {
|
|
|
241
241
|
instance: InstanceMessage;
|
|
242
242
|
details: CompactInstanceDetails;
|
|
243
243
|
}
|
|
244
|
+
interface CompactBootstrapRegistrationRecord {
|
|
245
|
+
messageHash: string | null;
|
|
246
|
+
hash: string | null;
|
|
247
|
+
itemHash: string | null;
|
|
248
|
+
address: string | null;
|
|
249
|
+
time: number | null;
|
|
250
|
+
instanceItemHash: string | null;
|
|
251
|
+
confirmed: boolean;
|
|
252
|
+
content: {
|
|
253
|
+
peerId: string;
|
|
254
|
+
registrationId?: string;
|
|
255
|
+
multiaddrs: string[];
|
|
256
|
+
browserMultiaddrs?: string[];
|
|
257
|
+
updatedAt: number;
|
|
258
|
+
} | null;
|
|
259
|
+
}
|
|
244
260
|
interface SponsorRelayState {
|
|
245
261
|
ready: boolean;
|
|
246
262
|
open: boolean;
|
|
@@ -258,6 +274,7 @@ interface SponsorRelayState {
|
|
|
258
274
|
refreshing: boolean;
|
|
259
275
|
deploying: boolean;
|
|
260
276
|
deletingInstanceHash: string | null;
|
|
277
|
+
deletingRegistrationHash: string | null;
|
|
261
278
|
};
|
|
262
279
|
statusText: string;
|
|
263
280
|
errorText: string | null;
|
|
@@ -271,6 +288,8 @@ interface SponsorRelayState {
|
|
|
271
288
|
crns: Crn[];
|
|
272
289
|
selectedCrn: Crn | null;
|
|
273
290
|
instances: CompactInstanceRecord[];
|
|
291
|
+
bootstrapRegistrations: CompactBootstrapRegistrationRecord[];
|
|
292
|
+
orphanBootstrapRegistrations: CompactBootstrapRegistrationRecord[];
|
|
274
293
|
lastDeploymentHash: string | null;
|
|
275
294
|
deploymentProgress: DeploymentProgressEvent;
|
|
276
295
|
}
|
|
@@ -290,7 +309,7 @@ declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
|
|
|
290
309
|
declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
|
|
291
310
|
declare const DEPLOYMENT_PENDING_WARNING_MS: number;
|
|
292
311
|
|
|
293
|
-
declare const UI_PACKAGE_VERSION = "0.3.
|
|
312
|
+
declare const UI_PACKAGE_VERSION = "0.3.32";
|
|
294
313
|
|
|
295
314
|
interface RootfsRequiredPortForward {
|
|
296
315
|
port: number;
|
|
@@ -372,6 +391,7 @@ declare class SponsorRelayController {
|
|
|
372
391
|
refresh(): Promise<void>;
|
|
373
392
|
deploy(): Promise<void>;
|
|
374
393
|
deleteInstance(instanceHash: string): Promise<void>;
|
|
394
|
+
deleteBootstrapRegistration(registrationHash: string): Promise<void>;
|
|
375
395
|
}
|
|
376
396
|
declare function createSponsorRelayController(props?: SponsorRelayProps): SponsorRelayController;
|
|
377
397
|
|
|
@@ -382,4 +402,4 @@ interface DeploymentProgressEmitter {
|
|
|
382
402
|
}
|
|
383
403
|
declare function createDeploymentProgressEmitter(): DeploymentProgressEmitter;
|
|
384
404
|
|
|
385
|
-
export { type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, ROOTFS_MISSING_STATE, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, UI_PACKAGE_VERSION, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
|
|
405
|
+
export { type CompactBootstrapRegistrationRecord, type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, ROOTFS_MISSING_STATE, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, UI_PACKAGE_VERSION, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
|
package/shared/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
|
27
27
|
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
28
28
|
|
|
29
29
|
// src/shared/package-version.ts
|
|
30
|
-
var UI_PACKAGE_VERSION = "0.3.
|
|
30
|
+
var UI_PACKAGE_VERSION = "0.3.32";
|
|
31
31
|
|
|
32
32
|
// src/shared/format.ts
|
|
33
33
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -2868,7 +2868,14 @@ function normalizeVmBootstrapConfigSignalRecord(value) {
|
|
|
2868
2868
|
instanceItemHash,
|
|
2869
2869
|
updatedAt,
|
|
2870
2870
|
publisherAddress: typeof candidate.publisherAddress === "string" && candidate.publisherAddress.trim() ? candidate.publisherAddress.trim() : null,
|
|
2871
|
-
authorization: candidate.authorization
|
|
2871
|
+
authorization: candidate.authorization,
|
|
2872
|
+
peerId: typeof candidate.peerId === "string" && candidate.peerId.trim() ? candidate.peerId.trim() : null,
|
|
2873
|
+
probeMultiaddrs: Array.isArray(candidate.probeMultiaddrs) ? candidate.probeMultiaddrs.filter(
|
|
2874
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2875
|
+
) : [],
|
|
2876
|
+
browserBootstrapMultiaddrs: Array.isArray(candidate.browserBootstrapMultiaddrs) ? candidate.browserBootstrapMultiaddrs.filter(
|
|
2877
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2878
|
+
) : []
|
|
2872
2879
|
};
|
|
2873
2880
|
}
|
|
2874
2881
|
async function fetchVmBootstrapConfigSignals(args) {
|
|
@@ -3036,7 +3043,8 @@ function defaultState(props = {}) {
|
|
|
3036
3043
|
connectingWallet: false,
|
|
3037
3044
|
refreshing: false,
|
|
3038
3045
|
deploying: false,
|
|
3039
|
-
deletingInstanceHash: null
|
|
3046
|
+
deletingInstanceHash: null,
|
|
3047
|
+
deletingRegistrationHash: null
|
|
3040
3048
|
},
|
|
3041
3049
|
statusText: "Ready",
|
|
3042
3050
|
errorText: null,
|
|
@@ -3062,6 +3070,8 @@ function defaultState(props = {}) {
|
|
|
3062
3070
|
crns: [],
|
|
3063
3071
|
selectedCrn: null,
|
|
3064
3072
|
instances: [],
|
|
3073
|
+
bootstrapRegistrations: [],
|
|
3074
|
+
orphanBootstrapRegistrations: [],
|
|
3065
3075
|
lastDeploymentHash: null,
|
|
3066
3076
|
deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
|
|
3067
3077
|
};
|
|
@@ -3166,6 +3176,20 @@ function relayProfileForManifest(manifest) {
|
|
|
3166
3176
|
}
|
|
3167
3177
|
return null;
|
|
3168
3178
|
}
|
|
3179
|
+
function registrationIdInstanceItemHash(registrationId) {
|
|
3180
|
+
const normalized = String(registrationId ?? "").trim();
|
|
3181
|
+
if (!normalized) return null;
|
|
3182
|
+
const parts = normalized.split(":").map((part) => part.trim()).filter(Boolean);
|
|
3183
|
+
const candidate = parts.at(-1) ?? "";
|
|
3184
|
+
return /^[0-9a-f]{64}$/i.test(candidate) ? candidate : null;
|
|
3185
|
+
}
|
|
3186
|
+
function relayBootstrapInstanceItemHash(entry) {
|
|
3187
|
+
const authorizedInstanceItemHash = typeof entry?.content?.authorization?.payload?.instanceItemHash === "string" ? entry.content.authorization.payload.instanceItemHash.trim() : "";
|
|
3188
|
+
if (authorizedInstanceItemHash) {
|
|
3189
|
+
return authorizedInstanceItemHash;
|
|
3190
|
+
}
|
|
3191
|
+
return registrationIdInstanceItemHash(entry?.content?.registrationId);
|
|
3192
|
+
}
|
|
3169
3193
|
async function inspectInstanceRuntime(args) {
|
|
3170
3194
|
const details = {
|
|
3171
3195
|
messageStatus: String(
|
|
@@ -3400,6 +3424,7 @@ var SponsorRelayController = class {
|
|
|
3400
3424
|
if (!setupPort) {
|
|
3401
3425
|
throw new Error("Relay runtime did not expose the temporary setup endpoint.");
|
|
3402
3426
|
}
|
|
3427
|
+
let bootstrapSignalRelayMetadata = null;
|
|
3403
3428
|
if (profile === "orbitdb-relay-pinner") {
|
|
3404
3429
|
this.emitProgress({
|
|
3405
3430
|
stage: "deployment-confirmed",
|
|
@@ -3500,11 +3525,6 @@ var SponsorRelayController = class {
|
|
|
3500
3525
|
}
|
|
3501
3526
|
runtime = latestRuntime;
|
|
3502
3527
|
mappedPorts2 = runtime.mappedPorts ?? mappedPorts2;
|
|
3503
|
-
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3504
|
-
throw new Error(
|
|
3505
|
-
"Aleph reserved a 2n6 proxy URL, but it never became active in time. Aborting guest HTTPS configuration to avoid a broken Caddy setup."
|
|
3506
|
-
);
|
|
3507
|
-
}
|
|
3508
3528
|
}
|
|
3509
3529
|
if (!runtime.hostIpv4) {
|
|
3510
3530
|
throw new Error("Relay runtime lost its host IPv4 address while waiting for HTTPS activation.");
|
|
@@ -3586,46 +3606,85 @@ var SponsorRelayController = class {
|
|
|
3586
3606
|
"The relay VM did not confirm that it applied the Aleph bootstrap config in time."
|
|
3587
3607
|
);
|
|
3588
3608
|
}
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
detail: "Waiting for the relay to report its final public multiaddrs."
|
|
3597
|
-
});
|
|
3598
|
-
const relayMetadata = await fetchRelayMetadataForRuntime({
|
|
3599
|
-
runtime,
|
|
3600
|
-
fetch: (url, init) => fetch(url, init),
|
|
3601
|
-
preferSecureMetadata: profile === "uc-go-peer",
|
|
3602
|
-
attempts: 80,
|
|
3603
|
-
delayMs: 3e3,
|
|
3604
|
-
timeoutMs: 24e4,
|
|
3605
|
-
onAttempt: (result) => {
|
|
3606
|
-
const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
|
|
3607
|
-
const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
|
|
3608
|
-
this.trace("deploy:relay-metadata-attempt", {
|
|
3609
|
+
bootstrapSignalRelayMetadata = typeof bootstrapConfigSignal.peerId === "string" && bootstrapConfigSignal.peerId.length > 0 && Array.isArray(bootstrapConfigSignal.probeMultiaddrs) && bootstrapConfigSignal.probeMultiaddrs.length > 0 ? {
|
|
3610
|
+
peerId: bootstrapConfigSignal.peerId,
|
|
3611
|
+
probeMultiaddrs: bootstrapConfigSignal.probeMultiaddrs,
|
|
3612
|
+
browserBootstrapMultiaddrs: bootstrapConfigSignal.browserBootstrapMultiaddrs ?? []
|
|
3613
|
+
} : null;
|
|
3614
|
+
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3615
|
+
this.trace("deploy:bootstrap-proxy-fallback", {
|
|
3609
3616
|
itemHash: args.itemHash,
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
metadataUrl: result.metadataUrl,
|
|
3614
|
-
hostIpv4: result.hostIpv4,
|
|
3615
|
-
setupPort: result.setupPort,
|
|
3616
|
-
ok: result.ok,
|
|
3617
|
-
status: result.status,
|
|
3618
|
-
ready: result.ready,
|
|
3619
|
-
error: result.error ?? null,
|
|
3620
|
-
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3621
|
-
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3622
|
-
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3623
|
-
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3624
|
-
metadata?.browser_bootstrap_multiaddrs
|
|
3625
|
-
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3617
|
+
deploymentToken: args.deploymentToken,
|
|
3618
|
+
proxyUrl: runtime.proxyUrl,
|
|
3619
|
+
bootstrapSignalRelayMetadata
|
|
3626
3620
|
});
|
|
3621
|
+
this.emitProgress({
|
|
3622
|
+
stage: "deployment-confirmed",
|
|
3623
|
+
label: "Continuing without HTTPS route",
|
|
3624
|
+
progress: 94,
|
|
3625
|
+
status: "warning",
|
|
3626
|
+
itemHash: args.itemHash,
|
|
3627
|
+
detail: "The 2n6 HTTPS route stayed inactive, so guest setup continued without Caddy. Secure browser routing can be enabled later when the proxy becomes available."
|
|
3628
|
+
});
|
|
3629
|
+
runtime = {
|
|
3630
|
+
...runtime,
|
|
3631
|
+
proxyUrl: null,
|
|
3632
|
+
webAccess: runtime.webAccess ? {
|
|
3633
|
+
...runtime.webAccess,
|
|
3634
|
+
active: false
|
|
3635
|
+
} : runtime.webAccess
|
|
3636
|
+
};
|
|
3627
3637
|
}
|
|
3628
|
-
}
|
|
3638
|
+
}
|
|
3639
|
+
let relayMetadata = bootstrapSignalRelayMetadata;
|
|
3640
|
+
if (relayMetadata) {
|
|
3641
|
+
this.trace("deploy:relay-metadata-from-bootstrap-signal", {
|
|
3642
|
+
itemHash: args.itemHash,
|
|
3643
|
+
peerId: relayMetadata.peerId,
|
|
3644
|
+
probeMultiaddrCount: relayMetadata.probeMultiaddrs.length,
|
|
3645
|
+
browserBootstrapMultiaddrCount: relayMetadata.browserBootstrapMultiaddrs.length
|
|
3646
|
+
});
|
|
3647
|
+
} else {
|
|
3648
|
+
this.emitProgress({
|
|
3649
|
+
stage: "publishing-bootstrap",
|
|
3650
|
+
label: "Reading relay multiaddrs",
|
|
3651
|
+
progress: 95,
|
|
3652
|
+
status: "info",
|
|
3653
|
+
itemHash: args.itemHash,
|
|
3654
|
+
detail: "Waiting for the relay to report its final public multiaddrs."
|
|
3655
|
+
});
|
|
3656
|
+
relayMetadata = await fetchRelayMetadataForRuntime({
|
|
3657
|
+
runtime,
|
|
3658
|
+
fetch: (url, init) => fetch(url, init),
|
|
3659
|
+
preferSecureMetadata: profile === "uc-go-peer" && Boolean(runtime.proxyUrl),
|
|
3660
|
+
attempts: 80,
|
|
3661
|
+
delayMs: 3e3,
|
|
3662
|
+
timeoutMs: 24e4,
|
|
3663
|
+
onAttempt: (result) => {
|
|
3664
|
+
const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
|
|
3665
|
+
const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
|
|
3666
|
+
this.trace("deploy:relay-metadata-attempt", {
|
|
3667
|
+
itemHash: args.itemHash,
|
|
3668
|
+
attempt: result.attempt,
|
|
3669
|
+
attempts: result.attempts,
|
|
3670
|
+
requestUrl: result.requestUrl,
|
|
3671
|
+
metadataUrl: result.metadataUrl,
|
|
3672
|
+
hostIpv4: result.hostIpv4,
|
|
3673
|
+
setupPort: result.setupPort,
|
|
3674
|
+
ok: result.ok,
|
|
3675
|
+
status: result.status,
|
|
3676
|
+
ready: result.ready,
|
|
3677
|
+
error: result.error ?? null,
|
|
3678
|
+
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3679
|
+
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3680
|
+
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3681
|
+
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3682
|
+
metadata?.browser_bootstrap_multiaddrs
|
|
3683
|
+
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3629
3688
|
if (!relayMetadata) {
|
|
3630
3689
|
throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
|
|
3631
3690
|
}
|
|
@@ -4094,10 +4153,16 @@ var SponsorRelayController = class {
|
|
|
4094
4153
|
const manifest = manifestState.manifest;
|
|
4095
4154
|
let balance = this.state.balance;
|
|
4096
4155
|
let instances = [];
|
|
4156
|
+
let bootstrapRegistrations = [];
|
|
4157
|
+
let orphanBootstrapRegistrations = [];
|
|
4097
4158
|
if (this.state.wallet.address) {
|
|
4098
|
-
const [nextBalance, rawInstances] = await Promise.all([
|
|
4159
|
+
const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
|
|
4099
4160
|
this.client.fetchBalance(this.state.wallet.address),
|
|
4100
|
-
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
|
|
4161
|
+
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([]),
|
|
4162
|
+
fetchAlephBootstrapPosts({
|
|
4163
|
+
apiHost: this.client.apiHost,
|
|
4164
|
+
fetch
|
|
4165
|
+
}).catch(() => [])
|
|
4101
4166
|
]);
|
|
4102
4167
|
balance = nextBalance;
|
|
4103
4168
|
instances = await Promise.all(
|
|
@@ -4157,6 +4222,37 @@ var SponsorRelayController = class {
|
|
|
4157
4222
|
};
|
|
4158
4223
|
})
|
|
4159
4224
|
);
|
|
4225
|
+
const normalizedWalletAddress = this.state.wallet.address.toLowerCase();
|
|
4226
|
+
const activeInstanceHashes = new Set(
|
|
4227
|
+
instances.map((entry) => entry.instance.item_hash)
|
|
4228
|
+
);
|
|
4229
|
+
bootstrapRegistrations = selectCurrentRelayBootstrapPosts(rawBootstrapPosts).filter(
|
|
4230
|
+
(entry) => entry.address?.toLowerCase() === normalizedWalletAddress
|
|
4231
|
+
).map((entry) => {
|
|
4232
|
+
const instanceItemHash = relayBootstrapInstanceItemHash(entry);
|
|
4233
|
+
const messageHash = entry.itemHash ?? entry.hash ?? null;
|
|
4234
|
+
return {
|
|
4235
|
+
messageHash,
|
|
4236
|
+
hash: entry.hash ?? null,
|
|
4237
|
+
itemHash: entry.itemHash ?? null,
|
|
4238
|
+
address: entry.address ?? null,
|
|
4239
|
+
time: entry.time ?? null,
|
|
4240
|
+
instanceItemHash,
|
|
4241
|
+
confirmed: Boolean(
|
|
4242
|
+
instanceItemHash && activeInstanceHashes.has(instanceItemHash)
|
|
4243
|
+
),
|
|
4244
|
+
content: entry.content ? {
|
|
4245
|
+
peerId: entry.content.peerId,
|
|
4246
|
+
registrationId: entry.content.registrationId,
|
|
4247
|
+
multiaddrs: Array.isArray(entry.content.multiaddrs) ? entry.content.multiaddrs : [],
|
|
4248
|
+
browserMultiaddrs: Array.isArray(entry.content.browserMultiaddrs) ? entry.content.browserMultiaddrs : [],
|
|
4249
|
+
updatedAt: entry.content.updatedAt
|
|
4250
|
+
} : null
|
|
4251
|
+
};
|
|
4252
|
+
});
|
|
4253
|
+
orphanBootstrapRegistrations = bootstrapRegistrations.filter(
|
|
4254
|
+
(entry) => !entry.confirmed
|
|
4255
|
+
);
|
|
4160
4256
|
}
|
|
4161
4257
|
let rootfsVerified = false;
|
|
4162
4258
|
let rootfsResolution = null;
|
|
@@ -4187,6 +4283,8 @@ var SponsorRelayController = class {
|
|
|
4187
4283
|
balance,
|
|
4188
4284
|
crns,
|
|
4189
4285
|
instances,
|
|
4286
|
+
bootstrapRegistrations,
|
|
4287
|
+
orphanBootstrapRegistrations,
|
|
4190
4288
|
busy: { refreshing: false },
|
|
4191
4289
|
statusText: "Relay sponsor data ready"
|
|
4192
4290
|
});
|
|
@@ -4696,6 +4794,72 @@ var SponsorRelayController = class {
|
|
|
4696
4794
|
});
|
|
4697
4795
|
}
|
|
4698
4796
|
}
|
|
4797
|
+
async deleteBootstrapRegistration(registrationHash) {
|
|
4798
|
+
this.trace("registration-delete:start", { registrationHash });
|
|
4799
|
+
if (!this.state.wallet.address) {
|
|
4800
|
+
this.patch({
|
|
4801
|
+
errorText: "Connect MetaMask before forgetting registrations."
|
|
4802
|
+
});
|
|
4803
|
+
return;
|
|
4804
|
+
}
|
|
4805
|
+
this.patch({
|
|
4806
|
+
busy: { deletingRegistrationHash: registrationHash },
|
|
4807
|
+
errorText: null,
|
|
4808
|
+
statusText: `Forgetting registration ${registrationHash}`
|
|
4809
|
+
});
|
|
4810
|
+
try {
|
|
4811
|
+
this.emitProgress({
|
|
4812
|
+
stage: "validating",
|
|
4813
|
+
label: "Validating registration cleanup",
|
|
4814
|
+
progress: 6,
|
|
4815
|
+
status: "warning",
|
|
4816
|
+
itemHash: registrationHash,
|
|
4817
|
+
detail: "Preparing Aleph FORGET message for the orphan bootstrap registration."
|
|
4818
|
+
});
|
|
4819
|
+
await forgetAlephMessages({
|
|
4820
|
+
sender: this.state.wallet.address,
|
|
4821
|
+
hashes: [registrationHash],
|
|
4822
|
+
reason: "Deleted orphan relay bootstrap registration from Sponsor Relay panel",
|
|
4823
|
+
signer: personalSign,
|
|
4824
|
+
hasher: sha256Hex,
|
|
4825
|
+
fetch: asJsonFetch,
|
|
4826
|
+
apiHost: this.client.apiHost,
|
|
4827
|
+
onProgress: (event) => {
|
|
4828
|
+
this.emitProgress(event);
|
|
4829
|
+
}
|
|
4830
|
+
});
|
|
4831
|
+
this.patch({
|
|
4832
|
+
busy: { deletingRegistrationHash: null },
|
|
4833
|
+
statusText: `Deletion submitted for ${registrationHash}`
|
|
4834
|
+
});
|
|
4835
|
+
this.emitProgress({
|
|
4836
|
+
stage: "refreshing-instances",
|
|
4837
|
+
label: "Refreshing registrations after delete",
|
|
4838
|
+
progress: 92,
|
|
4839
|
+
status: "info",
|
|
4840
|
+
itemHash: registrationHash,
|
|
4841
|
+
detail: "Reloading deployments and relay bootstrap registrations."
|
|
4842
|
+
});
|
|
4843
|
+
await this.refresh();
|
|
4844
|
+
this.trace("registration-delete:submitted", { registrationHash });
|
|
4845
|
+
this.emitProgress({
|
|
4846
|
+
stage: "completed",
|
|
4847
|
+
label: "Registration cleanup completed",
|
|
4848
|
+
progress: 100,
|
|
4849
|
+
status: "success",
|
|
4850
|
+
itemHash: registrationHash,
|
|
4851
|
+
detail: "Orphan relay bootstrap registration forgotten and state refreshed.",
|
|
4852
|
+
error: null
|
|
4853
|
+
});
|
|
4854
|
+
} catch (error) {
|
|
4855
|
+
this.trace("registration-delete:error", error);
|
|
4856
|
+
this.patch({
|
|
4857
|
+
busy: { deletingRegistrationHash: null },
|
|
4858
|
+
errorText: error instanceof Error ? error.message : String(error),
|
|
4859
|
+
statusText: "Registration cleanup failed"
|
|
4860
|
+
});
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4699
4863
|
};
|
|
4700
4864
|
function createSponsorRelayController(props = {}) {
|
|
4701
4865
|
return new SponsorRelayController(props);
|
|
@@ -49,6 +49,12 @@ export let apiHost = undefined
|
|
|
49
49
|
$: pollingDetail = state.busy.refreshing
|
|
50
50
|
? (state.statusText || 'Refreshing relay sponsor data from Aleph and the selected CRN.')
|
|
51
51
|
: (state.deploymentProgress.detail || state.statusText || 'Waiting for the next confirmed relay state from Aleph.')
|
|
52
|
+
$: confirmedRegistrationByInstanceHash = new Map(
|
|
53
|
+
(state.bootstrapRegistrations ?? [])
|
|
54
|
+
.filter((entry) => entry.confirmed && entry.instanceItemHash)
|
|
55
|
+
.map((entry) => [entry.instanceItemHash, entry]),
|
|
56
|
+
)
|
|
57
|
+
$: orphanRegistrations = state.orphanBootstrapRegistrations ?? []
|
|
52
58
|
|
|
53
59
|
onMount(async () => {
|
|
54
60
|
const unsubscribe = controller.subscribe((next) => {
|
|
@@ -220,12 +226,19 @@ export let apiHost = undefined
|
|
|
220
226
|
|
|
221
227
|
{#each state.instances as entry}
|
|
222
228
|
<AccordionSection title={`${entry.instance.content?.metadata?.name ?? 'relay'} · ${shortHash(entry.instance.item_hash)}`} open={true}>
|
|
229
|
+
{@const confirmedRegistration = confirmedRegistrationByInstanceHash.get(entry.instance.item_hash)}
|
|
223
230
|
<div class="instance-topline">
|
|
224
231
|
<div class="chip-row">
|
|
225
232
|
<span class="chip">{entry.details.messageStatus}</span>
|
|
226
233
|
{#if entry.details.crnUrl}
|
|
227
234
|
<span class="chip">{entry.details.crnUrl.replace(/^https?:\/\//, '')}</span>
|
|
228
235
|
{/if}
|
|
236
|
+
{#if confirmedRegistration}
|
|
237
|
+
<span class="chip chip-confirmed">
|
|
238
|
+
<span class="chip-dot-confirmed"></span>
|
|
239
|
+
Registration confirmed
|
|
240
|
+
</span>
|
|
241
|
+
{/if}
|
|
229
242
|
</div>
|
|
230
243
|
<button
|
|
231
244
|
class="delete"
|
|
@@ -267,6 +280,13 @@ export let apiHost = undefined
|
|
|
267
280
|
<strong>{joinMappedPorts(entry.details.mappedPorts)}</strong>
|
|
268
281
|
</div>
|
|
269
282
|
|
|
283
|
+
{#if confirmedRegistration}
|
|
284
|
+
<div class="mono-block">
|
|
285
|
+
<span>Bootstrap Registration</span>
|
|
286
|
+
<strong>{shortHash(confirmedRegistration.messageHash ?? confirmedRegistration.content?.registrationId ?? 'confirmed', 14, 8)}</strong>
|
|
287
|
+
</div>
|
|
288
|
+
{/if}
|
|
289
|
+
|
|
270
290
|
<div class="link-row">
|
|
271
291
|
<CopyButton text={entry.instance.item_hash} label="Copy hash" />
|
|
272
292
|
{#if entry.details.webUrl}
|
|
@@ -281,6 +301,34 @@ export let apiHost = undefined
|
|
|
281
301
|
{/if}
|
|
282
302
|
</AccordionSection>
|
|
283
303
|
{/each}
|
|
304
|
+
|
|
305
|
+
{#if orphanRegistrations.length > 0}
|
|
306
|
+
<div class="orphan-box">
|
|
307
|
+
<div class="orphan-head">
|
|
308
|
+
<strong>Orphan bootstrap registrations</strong>
|
|
309
|
+
<small>Current-wallet registrations without a matching instance. Forget them directly from here.</small>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
{#each orphanRegistrations as entry}
|
|
313
|
+
{@const registrationHash = entry.messageHash ?? entry.hash}
|
|
314
|
+
<div class="orphan-card">
|
|
315
|
+
<div class="orphan-title">{entry.content?.registrationId ?? 'registration'} · {shortHash(registrationHash ?? 'unknown')}</div>
|
|
316
|
+
<div>Peer: {entry.content?.peerId ?? '-'}</div>
|
|
317
|
+
<div>Linked instance: {entry.instanceItemHash ? shortHash(entry.instanceItemHash) : 'missing'}</div>
|
|
318
|
+
<div>Browser multiaddrs: {String(entry.content?.browserMultiaddrs?.length ?? 0)}</div>
|
|
319
|
+
<div>Updated: {formatDateTime(entry.content?.updatedAt ?? entry.time)}</div>
|
|
320
|
+
<button
|
|
321
|
+
class="delete"
|
|
322
|
+
type="button"
|
|
323
|
+
disabled={!registrationHash || state.busy.deletingRegistrationHash === registrationHash}
|
|
324
|
+
on:click={() => registrationHash && controller.deleteBootstrapRegistration(registrationHash)}
|
|
325
|
+
>
|
|
326
|
+
{state.busy.deletingRegistrationHash === registrationHash ? 'Forgetting…' : 'Forget registration'}
|
|
327
|
+
</button>
|
|
328
|
+
</div>
|
|
329
|
+
{/each}
|
|
330
|
+
</div>
|
|
331
|
+
{/if}
|
|
284
332
|
</section>
|
|
285
333
|
{/if}
|
|
286
334
|
</aside>
|
|
@@ -401,6 +449,22 @@ export let apiHost = undefined
|
|
|
401
449
|
color: #ffd4d4;
|
|
402
450
|
}
|
|
403
451
|
|
|
452
|
+
.chip-confirmed {
|
|
453
|
+
background: rgba(34, 197, 94, 0.16);
|
|
454
|
+
color: #86efac;
|
|
455
|
+
display: inline-flex;
|
|
456
|
+
align-items: center;
|
|
457
|
+
gap: 0.32rem;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.chip-dot-confirmed {
|
|
461
|
+
width: 0.45rem;
|
|
462
|
+
height: 0.45rem;
|
|
463
|
+
border-radius: 999px;
|
|
464
|
+
background: #22c55e;
|
|
465
|
+
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
|
|
466
|
+
}
|
|
467
|
+
|
|
404
468
|
.status-strip,
|
|
405
469
|
.metrics {
|
|
406
470
|
display: grid;
|
|
@@ -425,6 +489,38 @@ export let apiHost = undefined
|
|
|
425
489
|
gap: 0.7rem;
|
|
426
490
|
}
|
|
427
491
|
|
|
492
|
+
.orphan-box {
|
|
493
|
+
display: grid;
|
|
494
|
+
gap: 0.7rem;
|
|
495
|
+
padding: 0.85rem;
|
|
496
|
+
border-radius: 1rem;
|
|
497
|
+
border: 1px solid rgba(248, 113, 113, 0.22);
|
|
498
|
+
background: linear-gradient(180deg, rgba(127, 29, 29, 0.18), rgba(69, 10, 10, 0.12));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.orphan-head {
|
|
502
|
+
display: grid;
|
|
503
|
+
gap: 0.2rem;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.orphan-head small {
|
|
507
|
+
color: #fecaca;
|
|
508
|
+
line-height: 1.35;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.orphan-card {
|
|
512
|
+
display: grid;
|
|
513
|
+
gap: 0.35rem;
|
|
514
|
+
padding: 0.75rem;
|
|
515
|
+
border-radius: 0.9rem;
|
|
516
|
+
background: rgba(15, 23, 42, 0.22);
|
|
517
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.orphan-title {
|
|
521
|
+
font-weight: 700;
|
|
522
|
+
}
|
|
523
|
+
|
|
428
524
|
.alert {
|
|
429
525
|
margin: 0.8rem 0 0;
|
|
430
526
|
padding: 0.75rem 0.85rem;
|