@le-space/ui 0.1.54 → 0.1.56

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/shared/index.js CHANGED
@@ -17,6 +17,16 @@ var RELAY_PING_IDLE_STATE = {
17
17
  lastReceivedAt: null,
18
18
  error: null
19
19
  };
20
+ var IDLE_DEPLOYMENT_PROGRESS = {
21
+ stage: "idle",
22
+ label: "Ready",
23
+ progress: 0,
24
+ status: "info",
25
+ itemHash: null,
26
+ detail: null,
27
+ error: null,
28
+ timestamp: Date.now()
29
+ };
20
30
  var REFRESH_INTERVAL_MS = 3e4;
21
31
  var RELAY_PING_INTERVAL_MS = 2e4;
22
32
 
@@ -696,18 +706,34 @@ function resolveManifestSource(args) {
696
706
  }
697
707
 
698
708
  // src/shared/wallet-controller.ts
709
+ import { keccak_256 } from "@noble/hashes/sha3";
699
710
  function getEthereumProvider() {
700
711
  return globalThis.window?.ethereum ?? null;
701
712
  }
713
+ function toChecksumAddress(address) {
714
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
715
+ throw new Error("Invalid EVM address.");
716
+ }
717
+ const normalized = address.slice(2).toLowerCase();
718
+ const hash = Array.from(keccak_256(new TextEncoder().encode(normalized))).map((byte) => byte.toString(16).padStart(2, "0")).join("");
719
+ let result = "0x";
720
+ for (let index = 0; index < normalized.length; index += 1) {
721
+ result += Number.parseInt(hash[index], 16) >= 8 ? normalized[index].toUpperCase() : normalized[index];
722
+ }
723
+ return result;
724
+ }
702
725
  async function connectWallet(provider = getEthereumProvider()) {
703
726
  if (!provider) {
704
727
  throw new Error("MetaMask provider not found.");
705
728
  }
706
- const accounts = await provider.request({ method: "eth_requestAccounts" });
729
+ const accounts = await provider.request({
730
+ method: "eth_requestAccounts"
731
+ });
707
732
  const chainId = await provider.request({ method: "eth_chainId" });
733
+ const address = accounts[0] ? toChecksumAddress(accounts[0]) : null;
708
734
  return {
709
- connected: Boolean(accounts[0]),
710
- address: accounts[0] ?? null,
735
+ connected: Boolean(address),
736
+ address,
711
737
  chainId,
712
738
  isMetaMask: Boolean(provider.isMetaMask)
713
739
  };
@@ -963,6 +989,16 @@ async function createUnsignedForgetMessage(args) {
963
989
  };
964
990
  }
965
991
  async function forgetAlephMessages(args) {
992
+ args.onProgress?.({
993
+ stage: "building-delete-message",
994
+ label: "Building delete message",
995
+ progress: 18,
996
+ status: "info",
997
+ itemHash: null,
998
+ detail: [...new Set((args.hashes ?? []).filter(Boolean))].join(", ") || null,
999
+ error: null,
1000
+ timestamp: Date.now()
1001
+ });
966
1002
  const unsignedMessage = await createUnsignedForgetMessage({
967
1003
  sender: args.sender,
968
1004
  hashes: args.hashes,
@@ -972,18 +1008,49 @@ async function forgetAlephMessages(args) {
972
1008
  channel: args.channel,
973
1009
  now: args.now
974
1010
  });
1011
+ args.onProgress?.({
1012
+ stage: "signing-delete-message",
1013
+ label: "Waiting for delete signature",
1014
+ progress: 42,
1015
+ status: "info",
1016
+ itemHash: unsignedMessage.item_hash,
1017
+ detail: unsignedMessage.item_hash,
1018
+ error: null,
1019
+ timestamp: Date.now()
1020
+ });
975
1021
  const message = await signAlephMessage(unsignedMessage, args.signer);
1022
+ args.onProgress?.({
1023
+ stage: "broadcasting-delete",
1024
+ label: "Broadcasting delete to Aleph",
1025
+ progress: 68,
1026
+ status: "warning",
1027
+ itemHash: message.item_hash,
1028
+ detail: message.item_hash,
1029
+ error: null,
1030
+ timestamp: Date.now()
1031
+ });
976
1032
  const { response, httpStatus } = await broadcastAlephMessage2(message, {
977
1033
  apiHost: args.apiHost,
978
1034
  sync: args.sync,
979
1035
  fetch: args.fetch
980
1036
  });
1037
+ const status = normalizeBroadcastStatus(httpStatus, response?.message_status);
1038
+ args.onProgress?.({
1039
+ stage: status === "rejected" ? "error" : "delete-completed",
1040
+ label: status === "rejected" ? "Delete rejected by Aleph" : "Delete submitted to Aleph",
1041
+ progress: 100,
1042
+ status: status === "rejected" ? "error" : "success",
1043
+ itemHash: message.item_hash,
1044
+ detail: status === "rejected" ? String(response?.details ?? "Aleph rejected this delete request.") : null,
1045
+ error: status === "rejected" ? String(response?.details ?? "Aleph rejected this delete request.") : null,
1046
+ timestamp: Date.now()
1047
+ });
981
1048
  return {
982
1049
  sender: args.sender,
983
1050
  itemHash: message.item_hash,
984
1051
  response,
985
1052
  httpStatus,
986
- status: normalizeBroadcastStatus(httpStatus, response?.message_status)
1053
+ status
987
1054
  };
988
1055
  }
989
1056
 
@@ -1090,6 +1157,16 @@ async function createUnsignedInstanceMessage(args) {
1090
1157
  };
1091
1158
  }
1092
1159
  async function deployInstance(args) {
1160
+ args.onProgress?.({
1161
+ stage: "building-message",
1162
+ label: "Building deployment message",
1163
+ progress: 32,
1164
+ status: "info",
1165
+ detail: args.content.metadata?.name ?? null,
1166
+ itemHash: null,
1167
+ error: null,
1168
+ timestamp: Date.now()
1169
+ });
1093
1170
  const unsignedMessage = await createUnsignedInstanceMessage({
1094
1171
  sender: args.sender,
1095
1172
  content: args.content,
@@ -1097,17 +1174,47 @@ async function deployInstance(args) {
1097
1174
  channel: args.channel,
1098
1175
  now: args.now
1099
1176
  });
1177
+ args.onProgress?.({
1178
+ stage: "signing-message",
1179
+ label: "Waiting for MetaMask signature",
1180
+ progress: 48,
1181
+ status: "info",
1182
+ detail: unsignedMessage.item_hash,
1183
+ itemHash: unsignedMessage.item_hash,
1184
+ error: null,
1185
+ timestamp: Date.now()
1186
+ });
1100
1187
  const signature = await args.signer(unsignedMessage.sender, [unsignedMessage.chain, unsignedMessage.sender, unsignedMessage.type, unsignedMessage.item_hash].join("\n"));
1101
1188
  const message = {
1102
1189
  ...unsignedMessage,
1103
1190
  signature: signature.startsWith("0x") ? signature : `0x${signature}`
1104
1191
  };
1192
+ args.onProgress?.({
1193
+ stage: "broadcasting",
1194
+ label: "Broadcasting to Aleph",
1195
+ progress: 64,
1196
+ status: "info",
1197
+ detail: message.item_hash,
1198
+ itemHash: message.item_hash,
1199
+ error: null,
1200
+ timestamp: Date.now()
1201
+ });
1105
1202
  const { response, httpStatus } = await broadcastAlephMessage2(message, {
1106
1203
  apiHost: args.apiHost,
1107
1204
  sync: args.sync,
1108
1205
  fetch: args.fetch
1109
1206
  });
1110
1207
  const status = httpStatus === 202 ? "pending" : typeof response.message_status === "string" ? response.message_status : "unknown";
1208
+ args.onProgress?.({
1209
+ stage: status === "processed" ? "deployment-confirmed" : status === "rejected" ? "deployment-rejected" : "waiting-for-aleph",
1210
+ label: status === "processed" ? "Deployment accepted by Aleph" : status === "rejected" ? "Deployment rejected by Aleph" : "Deployment submitted to Aleph",
1211
+ progress: status === "processed" ? 88 : status === "rejected" ? 100 : 78,
1212
+ status: status === "processed" ? "success" : status === "rejected" ? "error" : "warning",
1213
+ detail: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1214
+ itemHash: message.item_hash,
1215
+ error: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1216
+ timestamp: Date.now()
1217
+ });
1111
1218
  return {
1112
1219
  itemHash: message.item_hash,
1113
1220
  httpStatus,
@@ -1118,6 +1225,107 @@ async function deployInstance(args) {
1118
1225
  };
1119
1226
  }
1120
1227
 
1228
+ // ../core/src/guest.ts
1229
+ async function notifyCrnAllocation2(args) {
1230
+ const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
1231
+ if (!normalizedCrnUrl) {
1232
+ return {
1233
+ status: "skipped",
1234
+ reason: "No CRN URL available for allocation notification."
1235
+ };
1236
+ }
1237
+ try {
1238
+ const response = await args.fetch(
1239
+ `${normalizedCrnUrl}/control/allocation/notify`,
1240
+ {
1241
+ method: "POST",
1242
+ headers: {
1243
+ "content-type": "application/json"
1244
+ },
1245
+ body: JSON.stringify({ instance: args.itemHash })
1246
+ }
1247
+ );
1248
+ const payload = await response.json().catch(() => null);
1249
+ const responseText = payload == null ? "" : typeof payload === "string" ? payload : JSON.stringify(payload);
1250
+ if (!response.ok) {
1251
+ return {
1252
+ status: "unconfirmed",
1253
+ reason: `CRN allocation notify returned ${response.status}${responseText ? ` ${responseText}` : ""}.`,
1254
+ payload
1255
+ };
1256
+ }
1257
+ return {
1258
+ status: "confirmed",
1259
+ payload
1260
+ };
1261
+ } catch (error) {
1262
+ return {
1263
+ status: "unconfirmed",
1264
+ reason: error instanceof Error ? error.message : String(error)
1265
+ };
1266
+ }
1267
+ }
1268
+ function isRetryableAllocationNotifyReason(reason) {
1269
+ const normalized = String(reason ?? "").toLowerCase();
1270
+ return normalized.includes("503") && (normalized.includes("node hash not yet discovered") || normalized.includes("cannot accept targeted allocations"));
1271
+ }
1272
+ async function notifyCrnAllocationWithRetry(args) {
1273
+ const attempts = Math.max(1, Number(args.attempts ?? 6));
1274
+ const delayMs = Math.max(0, Number(args.delayMs ?? 2e3));
1275
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1276
+ let lastResult = null;
1277
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1278
+ args.onProgress?.({
1279
+ stage: "notifying-crn",
1280
+ label: "Notifying selected CRN",
1281
+ progress: 84,
1282
+ status: "info",
1283
+ itemHash: args.itemHash,
1284
+ detail: `${attempt + 1}/${attempts} ${String(args.crnUrl ?? "").trim() || "selected CRN"}`,
1285
+ error: null,
1286
+ timestamp: Date.now()
1287
+ });
1288
+ const result = await notifyCrnAllocation2({
1289
+ crnUrl: args.crnUrl,
1290
+ itemHash: args.itemHash,
1291
+ fetch: args.fetch
1292
+ });
1293
+ if (result.status === "confirmed" || result.status === "skipped") {
1294
+ return result;
1295
+ }
1296
+ lastResult = result;
1297
+ if (!isRetryableAllocationNotifyReason(result.reason)) {
1298
+ return result;
1299
+ }
1300
+ if (attempt < attempts - 1) {
1301
+ await sleep(delayMs);
1302
+ }
1303
+ }
1304
+ return lastResult ?? {
1305
+ status: "unconfirmed",
1306
+ reason: "CRN allocation notify was not confirmed."
1307
+ };
1308
+ }
1309
+
1310
+ // src/shared/events.ts
1311
+ function createDeploymentProgressEmitter() {
1312
+ const listeners = /* @__PURE__ */ new Set();
1313
+ return {
1314
+ emit(event) {
1315
+ listeners.forEach((listener) => listener(event));
1316
+ },
1317
+ subscribe(listener) {
1318
+ listeners.add(listener);
1319
+ return () => {
1320
+ listeners.delete(listener);
1321
+ };
1322
+ },
1323
+ clear() {
1324
+ listeners.clear();
1325
+ }
1326
+ };
1327
+ }
1328
+
1121
1329
  // src/shared/controller.ts
1122
1330
  function defaultState(props = {}) {
1123
1331
  return {
@@ -1167,18 +1375,24 @@ function defaultState(props = {}) {
1167
1375
  selectedCrn: null,
1168
1376
  instances: [],
1169
1377
  relayPing: RELAY_PING_IDLE_STATE,
1170
- lastDeploymentHash: null
1378
+ lastDeploymentHash: null,
1379
+ deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
1171
1380
  };
1172
1381
  }
1173
1382
  async function sha256Hex(payload) {
1174
- const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
1383
+ const digest = await globalThis.crypto.subtle.digest(
1384
+ "SHA-256",
1385
+ new TextEncoder().encode(payload)
1386
+ );
1175
1387
  return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1176
1388
  }
1177
1389
  function mappedPorts(execution) {
1178
- return Object.entries(execution?.networking?.mapped_ports ?? {}).map(([port, mapping]) => ({
1179
- label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
1180
- hostPort: mapping.host ?? null
1181
- }));
1390
+ return Object.entries(execution?.networking?.mapped_ports ?? {}).map(
1391
+ ([port, mapping]) => ({
1392
+ label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
1393
+ hostPort: mapping.host ?? null
1394
+ })
1395
+ );
1182
1396
  }
1183
1397
  function rootfsHealth(args) {
1184
1398
  if (!args.manifestState.valid || !args.manifestState.manifest) {
@@ -1236,7 +1450,9 @@ async function resolveManifest(args) {
1236
1450
  }
1237
1451
  async function inspectInstanceRuntime(args) {
1238
1452
  const details = {
1239
- messageStatus: String(args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")).toLowerCase(),
1453
+ messageStatus: String(
1454
+ args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")
1455
+ ).toLowerCase(),
1240
1456
  allocationSource: null,
1241
1457
  crnUrl: null,
1242
1458
  hostIpv4: null,
@@ -1267,11 +1483,15 @@ async function inspectInstanceRuntime(args) {
1267
1483
  details.allocationSource = allocation?.source ?? null;
1268
1484
  details.crnUrl = allocation?.crnUrl ?? null;
1269
1485
  details.ipv6 = allocation?.vmIpv6 ?? null;
1270
- details.webUrl = await args.client.fetch2n6WebAccessUrl(args.instance.item_hash);
1486
+ details.webUrl = await args.client.fetch2n6WebAccessUrl(
1487
+ args.instance.item_hash
1488
+ );
1271
1489
  if (!allocation?.crnUrl) {
1272
1490
  return details;
1273
1491
  }
1274
- const executionLookup = await args.client.fetchCrnExecutionMap(allocation.crnUrl);
1492
+ const executionLookup = await args.client.fetchCrnExecutionMap(
1493
+ allocation.crnUrl
1494
+ );
1275
1495
  const executionPayload = executionLookup.payload?.[args.instance.item_hash];
1276
1496
  if (!executionPayload) {
1277
1497
  return details;
@@ -1299,7 +1519,10 @@ function compatibleCrnsForTier(crns, state) {
1299
1519
  if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
1300
1520
  return [];
1301
1521
  }
1302
- const spec = tierSpec(state.pricingSummary.pricing, state.pricingSummary.tier);
1522
+ const spec = tierSpec(
1523
+ state.pricingSummary.pricing,
1524
+ state.pricingSummary.tier
1525
+ );
1303
1526
  return filterDeployableCrns(crns, { spec });
1304
1527
  }
1305
1528
  async function pingPeer(libp2p) {
@@ -1361,6 +1584,7 @@ var SponsorRelayController = class {
1361
1584
  pingTimer = null;
1362
1585
  stopWalletWatch = null;
1363
1586
  props;
1587
+ progressEmitter = createDeploymentProgressEmitter();
1364
1588
  constructor(props = {}) {
1365
1589
  this.props = props;
1366
1590
  this.state = defaultState(props);
@@ -1371,6 +1595,9 @@ var SponsorRelayController = class {
1371
1595
  twoN6ApiHost: props.twoN6ApiHost
1372
1596
  });
1373
1597
  }
1598
+ subscribeToDeploymentProgress(listener) {
1599
+ return this.progressEmitter.subscribe(listener);
1600
+ }
1374
1601
  subscribe(subscriber) {
1375
1602
  this.subscribers.add(subscriber);
1376
1603
  return () => {
@@ -1395,6 +1622,18 @@ var SponsorRelayController = class {
1395
1622
  };
1396
1623
  this.emit();
1397
1624
  }
1625
+ emitProgress(event) {
1626
+ const nextEvent = {
1627
+ ...event,
1628
+ timestamp: Date.now()
1629
+ };
1630
+ this.patch({
1631
+ deploymentProgress: nextEvent,
1632
+ statusText: event.error ?? event.detail ?? event.label,
1633
+ errorText: event.status === "error" ? event.error ?? event.detail ?? event.label : this.state.errorText
1634
+ });
1635
+ this.progressEmitter.emit(nextEvent);
1636
+ }
1398
1637
  async init() {
1399
1638
  this.stopWalletWatch = watchWallet(() => {
1400
1639
  void this.refreshWalletDerivedState();
@@ -1514,11 +1753,25 @@ var SponsorRelayController = class {
1514
1753
  statusText: "Refreshing relay sponsor data"
1515
1754
  });
1516
1755
  try {
1756
+ this.emitProgress({
1757
+ stage: "loading-manifest",
1758
+ label: "Loading manifest",
1759
+ progress: 8,
1760
+ status: "info",
1761
+ detail: this.state.manifestUrl
1762
+ });
1517
1763
  const manifestState = await resolveManifest({
1518
1764
  manifestUrl: this.state.manifestUrl,
1519
1765
  manifestJson: this.state.manifestJson
1520
1766
  });
1521
1767
  const manifest = manifestState.manifest;
1768
+ this.emitProgress({
1769
+ stage: "loading-pricing",
1770
+ label: "Loading pricing and CRNs",
1771
+ progress: 18,
1772
+ status: "info",
1773
+ detail: "Fetching live Aleph pricing and compatible CRNs."
1774
+ });
1522
1775
  const [pricingSummary, crns] = await Promise.all([
1523
1776
  fetchInstancePricing(this.client.apiHost),
1524
1777
  this.client.fetchCrns()
@@ -1526,6 +1779,13 @@ var SponsorRelayController = class {
1526
1779
  let balance = this.state.balance;
1527
1780
  let instances = [];
1528
1781
  if (this.state.wallet.address) {
1782
+ this.emitProgress({
1783
+ stage: "loading-balance",
1784
+ label: "Loading wallet balance",
1785
+ progress: 28,
1786
+ status: "info",
1787
+ detail: this.state.wallet.address
1788
+ });
1529
1789
  const [nextBalance, rawInstances] = await Promise.all([
1530
1790
  this.client.fetchBalance(this.state.wallet.address),
1531
1791
  this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
@@ -1545,8 +1805,21 @@ var SponsorRelayController = class {
1545
1805
  let rootfsVerified = false;
1546
1806
  let rootfsResolution = null;
1547
1807
  if (manifestState.valid && manifest) {
1548
- rootfsVerified = await verifyRootfsExists(manifest.rootfsItemHash, this.client.apiHost);
1549
- rootfsResolution = await resolveRootfsReference(manifest.rootfsItemHash, this.client.apiHost);
1808
+ this.emitProgress({
1809
+ stage: "verifying-rootfs",
1810
+ label: "Verifying rootfs",
1811
+ progress: 38,
1812
+ status: "info",
1813
+ detail: manifest.rootfsItemHash
1814
+ });
1815
+ rootfsVerified = await verifyRootfsExists(
1816
+ manifest.rootfsItemHash,
1817
+ this.client.apiHost
1818
+ );
1819
+ rootfsResolution = await resolveRootfsReference(
1820
+ manifest.rootfsItemHash,
1821
+ this.client.apiHost
1822
+ );
1550
1823
  }
1551
1824
  this.patch({
1552
1825
  manifestState,
@@ -1569,12 +1842,26 @@ var SponsorRelayController = class {
1569
1842
  statusText: "Relay sponsor data ready"
1570
1843
  });
1571
1844
  this.recomputePricingSummary();
1845
+ this.emitProgress({
1846
+ stage: "completed",
1847
+ label: "Data ready",
1848
+ progress: 100,
1849
+ status: "success",
1850
+ detail: "Manifest, pricing, and wallet state are ready."
1851
+ });
1572
1852
  } catch (error) {
1573
1853
  this.patch({
1574
1854
  busy: { refreshing: false },
1575
1855
  errorText: error instanceof Error ? error.message : String(error),
1576
1856
  statusText: "Refresh failed"
1577
1857
  });
1858
+ this.emitProgress({
1859
+ stage: "error",
1860
+ label: "Refresh failed",
1861
+ progress: 100,
1862
+ status: "error",
1863
+ error: error instanceof Error ? error.message : String(error)
1864
+ });
1578
1865
  }
1579
1866
  }
1580
1867
  async refreshRelayPing() {
@@ -1587,7 +1874,9 @@ var SponsorRelayController = class {
1587
1874
  return;
1588
1875
  }
1589
1876
  if (!this.state.manifest || !this.state.rootfsVerified || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
1590
- this.patch({ errorText: "Manifest, rootfs, and pricing must be ready before deploying." });
1877
+ this.patch({
1878
+ errorText: "Manifest, rootfs, and pricing must be ready before deploying."
1879
+ });
1591
1880
  return;
1592
1881
  }
1593
1882
  this.patch({
@@ -1596,13 +1885,33 @@ var SponsorRelayController = class {
1596
1885
  statusText: "Broadcasting deployment"
1597
1886
  });
1598
1887
  try {
1599
- const spec = tierSpec(this.state.pricingSummary.pricing, this.state.pricingSummary.tier);
1888
+ this.emitProgress({
1889
+ stage: "validating",
1890
+ label: "Validating deployment",
1891
+ progress: 5,
1892
+ status: "info",
1893
+ detail: "Checking wallet, manifest, rootfs, pricing, and SSH key."
1894
+ });
1895
+ const spec = tierSpec(
1896
+ this.state.pricingSummary.pricing,
1897
+ this.state.pricingSummary.tier
1898
+ );
1899
+ this.emitProgress({
1900
+ stage: "selecting-crn",
1901
+ label: "Selecting CRN",
1902
+ progress: 18,
1903
+ status: "info",
1904
+ detail: this.state.selectedCrn?.name ?? this.state.selectedCrn?.hash ?? "Auto-selected compatible CRN"
1905
+ });
1600
1906
  const content = createInstanceContent({
1601
1907
  address: this.state.wallet.address,
1602
1908
  name: this.state.instanceName.trim(),
1603
1909
  sshPublicKey: this.state.sshPublicKey.trim(),
1604
1910
  rootfsItemHash: this.state.manifest.rootfsItemHash,
1605
- rootfsSizeMiB: Math.max(this.state.manifest.rootfsSizeMiB, spec.diskMiB),
1911
+ rootfsSizeMiB: Math.max(
1912
+ this.state.manifest.rootfsSizeMiB,
1913
+ spec.diskMiB
1914
+ ),
1606
1915
  vcpus: spec.vcpus,
1607
1916
  memoryMiB: spec.memoryMiB,
1608
1917
  rootfsVersion: this.state.manifest.version,
@@ -1615,20 +1924,57 @@ var SponsorRelayController = class {
1615
1924
  signer: personalSign,
1616
1925
  fetch: (url, init) => fetch(url, init),
1617
1926
  apiHost: this.client.apiHost,
1618
- sync: false
1927
+ sync: false,
1928
+ onProgress: (event) => {
1929
+ this.emitProgress(event);
1930
+ }
1619
1931
  });
1620
1932
  this.patch({
1621
1933
  busy: { deploying: false },
1622
1934
  statusText: `Deployment submitted: ${result.itemHash}`,
1623
1935
  lastDeploymentHash: result.itemHash
1624
1936
  });
1937
+ if (result.status === "processed" && this.state.selectedCrn?.address) {
1938
+ await notifyCrnAllocationWithRetry({
1939
+ crnUrl: this.state.selectedCrn.address,
1940
+ itemHash: result.itemHash,
1941
+ fetch: (url, init) => fetch(url, init),
1942
+ onProgress: (event) => {
1943
+ this.emitProgress(event);
1944
+ }
1945
+ });
1946
+ }
1947
+ this.emitProgress({
1948
+ stage: "refreshing-instances",
1949
+ label: "Refreshing instances",
1950
+ progress: 92,
1951
+ status: "info",
1952
+ itemHash: result.itemHash,
1953
+ detail: "Reloading deployments and runtime state."
1954
+ });
1625
1955
  await this.refresh();
1956
+ this.emitProgress({
1957
+ stage: "completed",
1958
+ label: "Deployment completed",
1959
+ progress: 100,
1960
+ status: result.status === "rejected" ? "error" : "success",
1961
+ itemHash: result.itemHash,
1962
+ detail: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : "Deployment submitted and synced back into the panel.",
1963
+ error: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : null
1964
+ });
1626
1965
  } catch (error) {
1627
1966
  this.patch({
1628
1967
  busy: { deploying: false },
1629
1968
  errorText: error instanceof Error ? error.message : String(error),
1630
1969
  statusText: "Deployment failed"
1631
1970
  });
1971
+ this.emitProgress({
1972
+ stage: "error",
1973
+ label: "Deployment failed",
1974
+ progress: 100,
1975
+ status: "error",
1976
+ error: error instanceof Error ? error.message : String(error)
1977
+ });
1632
1978
  }
1633
1979
  }
1634
1980
  async deleteInstance(instanceHash) {
@@ -1642,6 +1988,14 @@ var SponsorRelayController = class {
1642
1988
  statusText: `Deleting ${instanceHash}`
1643
1989
  });
1644
1990
  try {
1991
+ this.emitProgress({
1992
+ stage: "validating",
1993
+ label: "Validating delete request",
1994
+ progress: 6,
1995
+ status: "warning",
1996
+ itemHash: instanceHash,
1997
+ detail: "Preparing Aleph FORGET message."
1998
+ });
1645
1999
  await forgetAlephMessages({
1646
2000
  sender: this.state.wallet.address,
1647
2001
  hashes: [instanceHash],
@@ -1653,13 +2007,33 @@ var SponsorRelayController = class {
1653
2007
  status: response.status,
1654
2008
  json: async () => await response.json()
1655
2009
  })),
1656
- apiHost: this.client.apiHost
2010
+ apiHost: this.client.apiHost,
2011
+ onProgress: (event) => {
2012
+ this.emitProgress(event);
2013
+ }
1657
2014
  });
1658
2015
  this.patch({
1659
2016
  busy: { deletingInstanceHash: null },
1660
2017
  statusText: `Deletion submitted for ${instanceHash}`
1661
2018
  });
2019
+ this.emitProgress({
2020
+ stage: "refreshing-instances",
2021
+ label: "Refreshing instances after delete",
2022
+ progress: 92,
2023
+ status: "info",
2024
+ itemHash: instanceHash,
2025
+ detail: "Reloading current deployments."
2026
+ });
1662
2027
  await this.refresh();
2028
+ this.emitProgress({
2029
+ stage: "completed",
2030
+ label: "Delete completed",
2031
+ progress: 100,
2032
+ status: "success",
2033
+ itemHash: instanceHash,
2034
+ detail: "Delete request submitted and deployments refreshed.",
2035
+ error: null
2036
+ });
1663
2037
  } catch (error) {
1664
2038
  this.patch({
1665
2039
  busy: { deletingInstanceHash: null },
@@ -1676,6 +2050,7 @@ export {
1676
2050
  DEFAULT_INSTANCE_NAME,
1677
2051
  DEFAULT_MANIFEST_URL,
1678
2052
  DEFAULT_TIER_ID,
2053
+ IDLE_DEPLOYMENT_PROGRESS,
1679
2054
  REFRESH_INTERVAL_MS,
1680
2055
  RELAY_PING_IDLE_STATE,
1681
2056
  RELAY_PING_INTERVAL_MS,
@@ -1683,6 +2058,7 @@ export {
1683
2058
  SponsorRelayController,
1684
2059
  buildSshCommand,
1685
2060
  connectWallet,
2061
+ createDeploymentProgressEmitter,
1686
2062
  createSponsorRelayController,
1687
2063
  formatDateTime,
1688
2064
  formatNumber,
@@ -1691,5 +2067,6 @@ export {
1691
2067
  personalSign,
1692
2068
  resolveManifestSource,
1693
2069
  shortHash,
2070
+ toChecksumAddress,
1694
2071
  watchWallet
1695
2072
  };
Binary file