@le-space/ui 0.3.31 → 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 +308 -52
- package/shared/index.d.ts +22 -2
- package/shared/index.js +126 -4
- 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) {
|
|
@@ -3044,7 +3044,8 @@ function defaultState(props = {}) {
|
|
|
3044
3044
|
connectingWallet: false,
|
|
3045
3045
|
refreshing: false,
|
|
3046
3046
|
deploying: false,
|
|
3047
|
-
deletingInstanceHash: null
|
|
3047
|
+
deletingInstanceHash: null,
|
|
3048
|
+
deletingRegistrationHash: null
|
|
3048
3049
|
},
|
|
3049
3050
|
statusText: "Ready",
|
|
3050
3051
|
errorText: null,
|
|
@@ -3070,6 +3071,8 @@ function defaultState(props = {}) {
|
|
|
3070
3071
|
crns: [],
|
|
3071
3072
|
selectedCrn: null,
|
|
3072
3073
|
instances: [],
|
|
3074
|
+
bootstrapRegistrations: [],
|
|
3075
|
+
orphanBootstrapRegistrations: [],
|
|
3073
3076
|
lastDeploymentHash: null,
|
|
3074
3077
|
deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
|
|
3075
3078
|
};
|
|
@@ -3174,6 +3177,20 @@ function relayProfileForManifest(manifest) {
|
|
|
3174
3177
|
}
|
|
3175
3178
|
return null;
|
|
3176
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
|
+
}
|
|
3177
3194
|
async function inspectInstanceRuntime(args) {
|
|
3178
3195
|
const details = {
|
|
3179
3196
|
messageStatus: String(
|
|
@@ -4137,10 +4154,16 @@ var SponsorRelayController = class {
|
|
|
4137
4154
|
const manifest = manifestState.manifest;
|
|
4138
4155
|
let balance = this.state.balance;
|
|
4139
4156
|
let instances = [];
|
|
4157
|
+
let bootstrapRegistrations = [];
|
|
4158
|
+
let orphanBootstrapRegistrations = [];
|
|
4140
4159
|
if (this.state.wallet.address) {
|
|
4141
|
-
const [nextBalance, rawInstances] = await Promise.all([
|
|
4160
|
+
const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
|
|
4142
4161
|
this.client.fetchBalance(this.state.wallet.address),
|
|
4143
|
-
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(() => [])
|
|
4144
4167
|
]);
|
|
4145
4168
|
balance = nextBalance;
|
|
4146
4169
|
instances = await Promise.all(
|
|
@@ -4200,6 +4223,37 @@ var SponsorRelayController = class {
|
|
|
4200
4223
|
};
|
|
4201
4224
|
})
|
|
4202
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
|
+
);
|
|
4203
4257
|
}
|
|
4204
4258
|
let rootfsVerified = false;
|
|
4205
4259
|
let rootfsResolution = null;
|
|
@@ -4230,6 +4284,8 @@ var SponsorRelayController = class {
|
|
|
4230
4284
|
balance,
|
|
4231
4285
|
crns,
|
|
4232
4286
|
instances,
|
|
4287
|
+
bootstrapRegistrations,
|
|
4288
|
+
orphanBootstrapRegistrations,
|
|
4233
4289
|
busy: { refreshing: false },
|
|
4234
4290
|
statusText: "Relay sponsor data ready"
|
|
4235
4291
|
});
|
|
@@ -4739,6 +4795,72 @@ var SponsorRelayController = class {
|
|
|
4739
4795
|
});
|
|
4740
4796
|
}
|
|
4741
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
|
+
}
|
|
4742
4864
|
};
|
|
4743
4865
|
function createSponsorRelayController(props = {}) {
|
|
4744
4866
|
return new SponsorRelayController(props);
|
|
@@ -4938,6 +5060,10 @@ function SponsorRelayFab(props) {
|
|
|
4938
5060
|
const versionLabel = resolvedVersion.startsWith("v") ? resolvedVersion : `v${resolvedVersion}`;
|
|
4939
5061
|
const launcherMode = props.launcherMode ?? "floating";
|
|
4940
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 ?? [];
|
|
4941
5067
|
const [successFlash, setSuccessFlash] = React.useState(false);
|
|
4942
5068
|
const [hovered, setHovered] = React.useState(false);
|
|
4943
5069
|
const launcherRef = React.useRef(null);
|
|
@@ -5576,66 +5702,196 @@ function SponsorRelayFab(props) {
|
|
|
5576
5702
|
"Latest deployment: ",
|
|
5577
5703
|
shortHash(state.lastDeploymentHash)
|
|
5578
5704
|
] }) : null,
|
|
5579
|
-
state.showInstances ? /* @__PURE__ */
|
|
5705
|
+
state.showInstances ? /* @__PURE__ */ jsxs(
|
|
5580
5706
|
"div",
|
|
5581
5707
|
{
|
|
5582
5708
|
style: { marginTop: "1rem", display: "grid", gap: "0.7rem" },
|
|
5583
|
-
children:
|
|
5584
|
-
|
|
5585
|
-
|
|
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(
|
|
5586
5826
|
"div",
|
|
5587
5827
|
{
|
|
5588
5828
|
style: {
|
|
5589
5829
|
display: "grid",
|
|
5590
|
-
gap: "0.
|
|
5591
|
-
|
|
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))"
|
|
5592
5835
|
},
|
|
5593
5836
|
children: [
|
|
5594
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5595
|
-
"
|
|
5596
|
-
|
|
5597
|
-
] }),
|
|
5598
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5599
|
-
"Host IPv4: ",
|
|
5600
|
-
entry.details.hostIpv4 ?? "-"
|
|
5601
|
-
] }),
|
|
5602
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5603
|
-
"IPv6: ",
|
|
5604
|
-
entry.details.ipv6 ?? "-"
|
|
5605
|
-
] }),
|
|
5606
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5607
|
-
"VM IPv4: ",
|
|
5608
|
-
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." })
|
|
5609
5840
|
] }),
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
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
|
+
})
|
|
5635
5891
|
]
|
|
5636
5892
|
}
|
|
5637
|
-
)
|
|
5638
|
-
]
|
|
5893
|
+
) : null
|
|
5894
|
+
]
|
|
5639
5895
|
}
|
|
5640
5896
|
) : null
|
|
5641
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) {
|
|
@@ -3043,7 +3043,8 @@ function defaultState(props = {}) {
|
|
|
3043
3043
|
connectingWallet: false,
|
|
3044
3044
|
refreshing: false,
|
|
3045
3045
|
deploying: false,
|
|
3046
|
-
deletingInstanceHash: null
|
|
3046
|
+
deletingInstanceHash: null,
|
|
3047
|
+
deletingRegistrationHash: null
|
|
3047
3048
|
},
|
|
3048
3049
|
statusText: "Ready",
|
|
3049
3050
|
errorText: null,
|
|
@@ -3069,6 +3070,8 @@ function defaultState(props = {}) {
|
|
|
3069
3070
|
crns: [],
|
|
3070
3071
|
selectedCrn: null,
|
|
3071
3072
|
instances: [],
|
|
3073
|
+
bootstrapRegistrations: [],
|
|
3074
|
+
orphanBootstrapRegistrations: [],
|
|
3072
3075
|
lastDeploymentHash: null,
|
|
3073
3076
|
deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
|
|
3074
3077
|
};
|
|
@@ -3173,6 +3176,20 @@ function relayProfileForManifest(manifest) {
|
|
|
3173
3176
|
}
|
|
3174
3177
|
return null;
|
|
3175
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
|
+
}
|
|
3176
3193
|
async function inspectInstanceRuntime(args) {
|
|
3177
3194
|
const details = {
|
|
3178
3195
|
messageStatus: String(
|
|
@@ -4136,10 +4153,16 @@ var SponsorRelayController = class {
|
|
|
4136
4153
|
const manifest = manifestState.manifest;
|
|
4137
4154
|
let balance = this.state.balance;
|
|
4138
4155
|
let instances = [];
|
|
4156
|
+
let bootstrapRegistrations = [];
|
|
4157
|
+
let orphanBootstrapRegistrations = [];
|
|
4139
4158
|
if (this.state.wallet.address) {
|
|
4140
|
-
const [nextBalance, rawInstances] = await Promise.all([
|
|
4159
|
+
const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
|
|
4141
4160
|
this.client.fetchBalance(this.state.wallet.address),
|
|
4142
|
-
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(() => [])
|
|
4143
4166
|
]);
|
|
4144
4167
|
balance = nextBalance;
|
|
4145
4168
|
instances = await Promise.all(
|
|
@@ -4199,6 +4222,37 @@ var SponsorRelayController = class {
|
|
|
4199
4222
|
};
|
|
4200
4223
|
})
|
|
4201
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
|
+
);
|
|
4202
4256
|
}
|
|
4203
4257
|
let rootfsVerified = false;
|
|
4204
4258
|
let rootfsResolution = null;
|
|
@@ -4229,6 +4283,8 @@ var SponsorRelayController = class {
|
|
|
4229
4283
|
balance,
|
|
4230
4284
|
crns,
|
|
4231
4285
|
instances,
|
|
4286
|
+
bootstrapRegistrations,
|
|
4287
|
+
orphanBootstrapRegistrations,
|
|
4232
4288
|
busy: { refreshing: false },
|
|
4233
4289
|
statusText: "Relay sponsor data ready"
|
|
4234
4290
|
});
|
|
@@ -4738,6 +4794,72 @@ var SponsorRelayController = class {
|
|
|
4738
4794
|
});
|
|
4739
4795
|
}
|
|
4740
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
|
+
}
|
|
4741
4863
|
};
|
|
4742
4864
|
function createSponsorRelayController(props = {}) {
|
|
4743
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;
|