@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/react/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/react/SponsorRelayFab.tsx
2
+ import React from "react";
3
+
1
4
  // src/shared/constants.ts
2
5
  var DEFAULT_INSTANCE_NAME = "sponsor-relay";
3
6
  var DEFAULT_MANIFEST_URL = "./rootfs-manifest.json";
@@ -17,6 +20,16 @@ var RELAY_PING_IDLE_STATE = {
17
20
  lastReceivedAt: null,
18
21
  error: null
19
22
  };
23
+ var IDLE_DEPLOYMENT_PROGRESS = {
24
+ stage: "idle",
25
+ label: "Ready",
26
+ progress: 0,
27
+ status: "info",
28
+ itemHash: null,
29
+ detail: null,
30
+ error: null,
31
+ timestamp: Date.now()
32
+ };
20
33
  var REFRESH_INTERVAL_MS = 3e4;
21
34
  var RELAY_PING_INTERVAL_MS = 2e4;
22
35
 
@@ -696,18 +709,34 @@ function resolveManifestSource(args) {
696
709
  }
697
710
 
698
711
  // src/shared/wallet-controller.ts
712
+ import { keccak_256 } from "@noble/hashes/sha3";
699
713
  function getEthereumProvider() {
700
714
  return globalThis.window?.ethereum ?? null;
701
715
  }
716
+ function toChecksumAddress(address) {
717
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
718
+ throw new Error("Invalid EVM address.");
719
+ }
720
+ const normalized = address.slice(2).toLowerCase();
721
+ const hash = Array.from(keccak_256(new TextEncoder().encode(normalized))).map((byte) => byte.toString(16).padStart(2, "0")).join("");
722
+ let result = "0x";
723
+ for (let index = 0; index < normalized.length; index += 1) {
724
+ result += Number.parseInt(hash[index], 16) >= 8 ? normalized[index].toUpperCase() : normalized[index];
725
+ }
726
+ return result;
727
+ }
702
728
  async function connectWallet(provider = getEthereumProvider()) {
703
729
  if (!provider) {
704
730
  throw new Error("MetaMask provider not found.");
705
731
  }
706
- const accounts = await provider.request({ method: "eth_requestAccounts" });
732
+ const accounts = await provider.request({
733
+ method: "eth_requestAccounts"
734
+ });
707
735
  const chainId = await provider.request({ method: "eth_chainId" });
736
+ const address = accounts[0] ? toChecksumAddress(accounts[0]) : null;
708
737
  return {
709
- connected: Boolean(accounts[0]),
710
- address: accounts[0] ?? null,
738
+ connected: Boolean(address),
739
+ address,
711
740
  chainId,
712
741
  isMetaMask: Boolean(provider.isMetaMask)
713
742
  };
@@ -963,6 +992,16 @@ async function createUnsignedForgetMessage(args) {
963
992
  };
964
993
  }
965
994
  async function forgetAlephMessages(args) {
995
+ args.onProgress?.({
996
+ stage: "building-delete-message",
997
+ label: "Building delete message",
998
+ progress: 18,
999
+ status: "info",
1000
+ itemHash: null,
1001
+ detail: [...new Set((args.hashes ?? []).filter(Boolean))].join(", ") || null,
1002
+ error: null,
1003
+ timestamp: Date.now()
1004
+ });
966
1005
  const unsignedMessage = await createUnsignedForgetMessage({
967
1006
  sender: args.sender,
968
1007
  hashes: args.hashes,
@@ -972,18 +1011,49 @@ async function forgetAlephMessages(args) {
972
1011
  channel: args.channel,
973
1012
  now: args.now
974
1013
  });
1014
+ args.onProgress?.({
1015
+ stage: "signing-delete-message",
1016
+ label: "Waiting for delete signature",
1017
+ progress: 42,
1018
+ status: "info",
1019
+ itemHash: unsignedMessage.item_hash,
1020
+ detail: unsignedMessage.item_hash,
1021
+ error: null,
1022
+ timestamp: Date.now()
1023
+ });
975
1024
  const message = await signAlephMessage(unsignedMessage, args.signer);
1025
+ args.onProgress?.({
1026
+ stage: "broadcasting-delete",
1027
+ label: "Broadcasting delete to Aleph",
1028
+ progress: 68,
1029
+ status: "warning",
1030
+ itemHash: message.item_hash,
1031
+ detail: message.item_hash,
1032
+ error: null,
1033
+ timestamp: Date.now()
1034
+ });
976
1035
  const { response, httpStatus } = await broadcastAlephMessage2(message, {
977
1036
  apiHost: args.apiHost,
978
1037
  sync: args.sync,
979
1038
  fetch: args.fetch
980
1039
  });
1040
+ const status = normalizeBroadcastStatus(httpStatus, response?.message_status);
1041
+ args.onProgress?.({
1042
+ stage: status === "rejected" ? "error" : "delete-completed",
1043
+ label: status === "rejected" ? "Delete rejected by Aleph" : "Delete submitted to Aleph",
1044
+ progress: 100,
1045
+ status: status === "rejected" ? "error" : "success",
1046
+ itemHash: message.item_hash,
1047
+ detail: status === "rejected" ? String(response?.details ?? "Aleph rejected this delete request.") : null,
1048
+ error: status === "rejected" ? String(response?.details ?? "Aleph rejected this delete request.") : null,
1049
+ timestamp: Date.now()
1050
+ });
981
1051
  return {
982
1052
  sender: args.sender,
983
1053
  itemHash: message.item_hash,
984
1054
  response,
985
1055
  httpStatus,
986
- status: normalizeBroadcastStatus(httpStatus, response?.message_status)
1056
+ status
987
1057
  };
988
1058
  }
989
1059
 
@@ -1090,6 +1160,16 @@ async function createUnsignedInstanceMessage(args) {
1090
1160
  };
1091
1161
  }
1092
1162
  async function deployInstance(args) {
1163
+ args.onProgress?.({
1164
+ stage: "building-message",
1165
+ label: "Building deployment message",
1166
+ progress: 32,
1167
+ status: "info",
1168
+ detail: args.content.metadata?.name ?? null,
1169
+ itemHash: null,
1170
+ error: null,
1171
+ timestamp: Date.now()
1172
+ });
1093
1173
  const unsignedMessage = await createUnsignedInstanceMessage({
1094
1174
  sender: args.sender,
1095
1175
  content: args.content,
@@ -1097,17 +1177,47 @@ async function deployInstance(args) {
1097
1177
  channel: args.channel,
1098
1178
  now: args.now
1099
1179
  });
1180
+ args.onProgress?.({
1181
+ stage: "signing-message",
1182
+ label: "Waiting for MetaMask signature",
1183
+ progress: 48,
1184
+ status: "info",
1185
+ detail: unsignedMessage.item_hash,
1186
+ itemHash: unsignedMessage.item_hash,
1187
+ error: null,
1188
+ timestamp: Date.now()
1189
+ });
1100
1190
  const signature = await args.signer(unsignedMessage.sender, [unsignedMessage.chain, unsignedMessage.sender, unsignedMessage.type, unsignedMessage.item_hash].join("\n"));
1101
1191
  const message = {
1102
1192
  ...unsignedMessage,
1103
1193
  signature: signature.startsWith("0x") ? signature : `0x${signature}`
1104
1194
  };
1195
+ args.onProgress?.({
1196
+ stage: "broadcasting",
1197
+ label: "Broadcasting to Aleph",
1198
+ progress: 64,
1199
+ status: "info",
1200
+ detail: message.item_hash,
1201
+ itemHash: message.item_hash,
1202
+ error: null,
1203
+ timestamp: Date.now()
1204
+ });
1105
1205
  const { response, httpStatus } = await broadcastAlephMessage2(message, {
1106
1206
  apiHost: args.apiHost,
1107
1207
  sync: args.sync,
1108
1208
  fetch: args.fetch
1109
1209
  });
1110
1210
  const status = httpStatus === 202 ? "pending" : typeof response.message_status === "string" ? response.message_status : "unknown";
1211
+ args.onProgress?.({
1212
+ stage: status === "processed" ? "deployment-confirmed" : status === "rejected" ? "deployment-rejected" : "waiting-for-aleph",
1213
+ label: status === "processed" ? "Deployment accepted by Aleph" : status === "rejected" ? "Deployment rejected by Aleph" : "Deployment submitted to Aleph",
1214
+ progress: status === "processed" ? 88 : status === "rejected" ? 100 : 78,
1215
+ status: status === "processed" ? "success" : status === "rejected" ? "error" : "warning",
1216
+ detail: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1217
+ itemHash: message.item_hash,
1218
+ error: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1219
+ timestamp: Date.now()
1220
+ });
1111
1221
  return {
1112
1222
  itemHash: message.item_hash,
1113
1223
  httpStatus,
@@ -1118,6 +1228,107 @@ async function deployInstance(args) {
1118
1228
  };
1119
1229
  }
1120
1230
 
1231
+ // ../core/src/guest.ts
1232
+ async function notifyCrnAllocation2(args) {
1233
+ const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
1234
+ if (!normalizedCrnUrl) {
1235
+ return {
1236
+ status: "skipped",
1237
+ reason: "No CRN URL available for allocation notification."
1238
+ };
1239
+ }
1240
+ try {
1241
+ const response = await args.fetch(
1242
+ `${normalizedCrnUrl}/control/allocation/notify`,
1243
+ {
1244
+ method: "POST",
1245
+ headers: {
1246
+ "content-type": "application/json"
1247
+ },
1248
+ body: JSON.stringify({ instance: args.itemHash })
1249
+ }
1250
+ );
1251
+ const payload = await response.json().catch(() => null);
1252
+ const responseText = payload == null ? "" : typeof payload === "string" ? payload : JSON.stringify(payload);
1253
+ if (!response.ok) {
1254
+ return {
1255
+ status: "unconfirmed",
1256
+ reason: `CRN allocation notify returned ${response.status}${responseText ? ` ${responseText}` : ""}.`,
1257
+ payload
1258
+ };
1259
+ }
1260
+ return {
1261
+ status: "confirmed",
1262
+ payload
1263
+ };
1264
+ } catch (error) {
1265
+ return {
1266
+ status: "unconfirmed",
1267
+ reason: error instanceof Error ? error.message : String(error)
1268
+ };
1269
+ }
1270
+ }
1271
+ function isRetryableAllocationNotifyReason(reason) {
1272
+ const normalized = String(reason ?? "").toLowerCase();
1273
+ return normalized.includes("503") && (normalized.includes("node hash not yet discovered") || normalized.includes("cannot accept targeted allocations"));
1274
+ }
1275
+ async function notifyCrnAllocationWithRetry(args) {
1276
+ const attempts = Math.max(1, Number(args.attempts ?? 6));
1277
+ const delayMs = Math.max(0, Number(args.delayMs ?? 2e3));
1278
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1279
+ let lastResult = null;
1280
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1281
+ args.onProgress?.({
1282
+ stage: "notifying-crn",
1283
+ label: "Notifying selected CRN",
1284
+ progress: 84,
1285
+ status: "info",
1286
+ itemHash: args.itemHash,
1287
+ detail: `${attempt + 1}/${attempts} ${String(args.crnUrl ?? "").trim() || "selected CRN"}`,
1288
+ error: null,
1289
+ timestamp: Date.now()
1290
+ });
1291
+ const result = await notifyCrnAllocation2({
1292
+ crnUrl: args.crnUrl,
1293
+ itemHash: args.itemHash,
1294
+ fetch: args.fetch
1295
+ });
1296
+ if (result.status === "confirmed" || result.status === "skipped") {
1297
+ return result;
1298
+ }
1299
+ lastResult = result;
1300
+ if (!isRetryableAllocationNotifyReason(result.reason)) {
1301
+ return result;
1302
+ }
1303
+ if (attempt < attempts - 1) {
1304
+ await sleep(delayMs);
1305
+ }
1306
+ }
1307
+ return lastResult ?? {
1308
+ status: "unconfirmed",
1309
+ reason: "CRN allocation notify was not confirmed."
1310
+ };
1311
+ }
1312
+
1313
+ // src/shared/events.ts
1314
+ function createDeploymentProgressEmitter() {
1315
+ const listeners = /* @__PURE__ */ new Set();
1316
+ return {
1317
+ emit(event) {
1318
+ listeners.forEach((listener) => listener(event));
1319
+ },
1320
+ subscribe(listener) {
1321
+ listeners.add(listener);
1322
+ return () => {
1323
+ listeners.delete(listener);
1324
+ };
1325
+ },
1326
+ clear() {
1327
+ listeners.clear();
1328
+ }
1329
+ };
1330
+ }
1331
+
1121
1332
  // src/shared/controller.ts
1122
1333
  function defaultState(props = {}) {
1123
1334
  return {
@@ -1167,18 +1378,24 @@ function defaultState(props = {}) {
1167
1378
  selectedCrn: null,
1168
1379
  instances: [],
1169
1380
  relayPing: RELAY_PING_IDLE_STATE,
1170
- lastDeploymentHash: null
1381
+ lastDeploymentHash: null,
1382
+ deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
1171
1383
  };
1172
1384
  }
1173
1385
  async function sha256Hex(payload) {
1174
- const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
1386
+ const digest = await globalThis.crypto.subtle.digest(
1387
+ "SHA-256",
1388
+ new TextEncoder().encode(payload)
1389
+ );
1175
1390
  return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1176
1391
  }
1177
1392
  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
- }));
1393
+ return Object.entries(execution?.networking?.mapped_ports ?? {}).map(
1394
+ ([port, mapping]) => ({
1395
+ label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
1396
+ hostPort: mapping.host ?? null
1397
+ })
1398
+ );
1182
1399
  }
1183
1400
  function rootfsHealth(args) {
1184
1401
  if (!args.manifestState.valid || !args.manifestState.manifest) {
@@ -1236,7 +1453,9 @@ async function resolveManifest(args) {
1236
1453
  }
1237
1454
  async function inspectInstanceRuntime(args) {
1238
1455
  const details = {
1239
- messageStatus: String(args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")).toLowerCase(),
1456
+ messageStatus: String(
1457
+ args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")
1458
+ ).toLowerCase(),
1240
1459
  allocationSource: null,
1241
1460
  crnUrl: null,
1242
1461
  hostIpv4: null,
@@ -1267,11 +1486,15 @@ async function inspectInstanceRuntime(args) {
1267
1486
  details.allocationSource = allocation?.source ?? null;
1268
1487
  details.crnUrl = allocation?.crnUrl ?? null;
1269
1488
  details.ipv6 = allocation?.vmIpv6 ?? null;
1270
- details.webUrl = await args.client.fetch2n6WebAccessUrl(args.instance.item_hash);
1489
+ details.webUrl = await args.client.fetch2n6WebAccessUrl(
1490
+ args.instance.item_hash
1491
+ );
1271
1492
  if (!allocation?.crnUrl) {
1272
1493
  return details;
1273
1494
  }
1274
- const executionLookup = await args.client.fetchCrnExecutionMap(allocation.crnUrl);
1495
+ const executionLookup = await args.client.fetchCrnExecutionMap(
1496
+ allocation.crnUrl
1497
+ );
1275
1498
  const executionPayload = executionLookup.payload?.[args.instance.item_hash];
1276
1499
  if (!executionPayload) {
1277
1500
  return details;
@@ -1299,7 +1522,10 @@ function compatibleCrnsForTier(crns, state) {
1299
1522
  if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
1300
1523
  return [];
1301
1524
  }
1302
- const spec = tierSpec(state.pricingSummary.pricing, state.pricingSummary.tier);
1525
+ const spec = tierSpec(
1526
+ state.pricingSummary.pricing,
1527
+ state.pricingSummary.tier
1528
+ );
1303
1529
  return filterDeployableCrns(crns, { spec });
1304
1530
  }
1305
1531
  async function pingPeer(libp2p) {
@@ -1361,6 +1587,7 @@ var SponsorRelayController = class {
1361
1587
  pingTimer = null;
1362
1588
  stopWalletWatch = null;
1363
1589
  props;
1590
+ progressEmitter = createDeploymentProgressEmitter();
1364
1591
  constructor(props = {}) {
1365
1592
  this.props = props;
1366
1593
  this.state = defaultState(props);
@@ -1371,6 +1598,9 @@ var SponsorRelayController = class {
1371
1598
  twoN6ApiHost: props.twoN6ApiHost
1372
1599
  });
1373
1600
  }
1601
+ subscribeToDeploymentProgress(listener) {
1602
+ return this.progressEmitter.subscribe(listener);
1603
+ }
1374
1604
  subscribe(subscriber) {
1375
1605
  this.subscribers.add(subscriber);
1376
1606
  return () => {
@@ -1395,6 +1625,18 @@ var SponsorRelayController = class {
1395
1625
  };
1396
1626
  this.emit();
1397
1627
  }
1628
+ emitProgress(event) {
1629
+ const nextEvent = {
1630
+ ...event,
1631
+ timestamp: Date.now()
1632
+ };
1633
+ this.patch({
1634
+ deploymentProgress: nextEvent,
1635
+ statusText: event.error ?? event.detail ?? event.label,
1636
+ errorText: event.status === "error" ? event.error ?? event.detail ?? event.label : this.state.errorText
1637
+ });
1638
+ this.progressEmitter.emit(nextEvent);
1639
+ }
1398
1640
  async init() {
1399
1641
  this.stopWalletWatch = watchWallet(() => {
1400
1642
  void this.refreshWalletDerivedState();
@@ -1514,11 +1756,25 @@ var SponsorRelayController = class {
1514
1756
  statusText: "Refreshing relay sponsor data"
1515
1757
  });
1516
1758
  try {
1759
+ this.emitProgress({
1760
+ stage: "loading-manifest",
1761
+ label: "Loading manifest",
1762
+ progress: 8,
1763
+ status: "info",
1764
+ detail: this.state.manifestUrl
1765
+ });
1517
1766
  const manifestState = await resolveManifest({
1518
1767
  manifestUrl: this.state.manifestUrl,
1519
1768
  manifestJson: this.state.manifestJson
1520
1769
  });
1521
1770
  const manifest = manifestState.manifest;
1771
+ this.emitProgress({
1772
+ stage: "loading-pricing",
1773
+ label: "Loading pricing and CRNs",
1774
+ progress: 18,
1775
+ status: "info",
1776
+ detail: "Fetching live Aleph pricing and compatible CRNs."
1777
+ });
1522
1778
  const [pricingSummary, crns] = await Promise.all([
1523
1779
  fetchInstancePricing(this.client.apiHost),
1524
1780
  this.client.fetchCrns()
@@ -1526,6 +1782,13 @@ var SponsorRelayController = class {
1526
1782
  let balance = this.state.balance;
1527
1783
  let instances = [];
1528
1784
  if (this.state.wallet.address) {
1785
+ this.emitProgress({
1786
+ stage: "loading-balance",
1787
+ label: "Loading wallet balance",
1788
+ progress: 28,
1789
+ status: "info",
1790
+ detail: this.state.wallet.address
1791
+ });
1529
1792
  const [nextBalance, rawInstances] = await Promise.all([
1530
1793
  this.client.fetchBalance(this.state.wallet.address),
1531
1794
  this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
@@ -1545,8 +1808,21 @@ var SponsorRelayController = class {
1545
1808
  let rootfsVerified = false;
1546
1809
  let rootfsResolution = null;
1547
1810
  if (manifestState.valid && manifest) {
1548
- rootfsVerified = await verifyRootfsExists(manifest.rootfsItemHash, this.client.apiHost);
1549
- rootfsResolution = await resolveRootfsReference(manifest.rootfsItemHash, this.client.apiHost);
1811
+ this.emitProgress({
1812
+ stage: "verifying-rootfs",
1813
+ label: "Verifying rootfs",
1814
+ progress: 38,
1815
+ status: "info",
1816
+ detail: manifest.rootfsItemHash
1817
+ });
1818
+ rootfsVerified = await verifyRootfsExists(
1819
+ manifest.rootfsItemHash,
1820
+ this.client.apiHost
1821
+ );
1822
+ rootfsResolution = await resolveRootfsReference(
1823
+ manifest.rootfsItemHash,
1824
+ this.client.apiHost
1825
+ );
1550
1826
  }
1551
1827
  this.patch({
1552
1828
  manifestState,
@@ -1569,12 +1845,26 @@ var SponsorRelayController = class {
1569
1845
  statusText: "Relay sponsor data ready"
1570
1846
  });
1571
1847
  this.recomputePricingSummary();
1848
+ this.emitProgress({
1849
+ stage: "completed",
1850
+ label: "Data ready",
1851
+ progress: 100,
1852
+ status: "success",
1853
+ detail: "Manifest, pricing, and wallet state are ready."
1854
+ });
1572
1855
  } catch (error) {
1573
1856
  this.patch({
1574
1857
  busy: { refreshing: false },
1575
1858
  errorText: error instanceof Error ? error.message : String(error),
1576
1859
  statusText: "Refresh failed"
1577
1860
  });
1861
+ this.emitProgress({
1862
+ stage: "error",
1863
+ label: "Refresh failed",
1864
+ progress: 100,
1865
+ status: "error",
1866
+ error: error instanceof Error ? error.message : String(error)
1867
+ });
1578
1868
  }
1579
1869
  }
1580
1870
  async refreshRelayPing() {
@@ -1587,7 +1877,9 @@ var SponsorRelayController = class {
1587
1877
  return;
1588
1878
  }
1589
1879
  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." });
1880
+ this.patch({
1881
+ errorText: "Manifest, rootfs, and pricing must be ready before deploying."
1882
+ });
1591
1883
  return;
1592
1884
  }
1593
1885
  this.patch({
@@ -1596,13 +1888,33 @@ var SponsorRelayController = class {
1596
1888
  statusText: "Broadcasting deployment"
1597
1889
  });
1598
1890
  try {
1599
- const spec = tierSpec(this.state.pricingSummary.pricing, this.state.pricingSummary.tier);
1891
+ this.emitProgress({
1892
+ stage: "validating",
1893
+ label: "Validating deployment",
1894
+ progress: 5,
1895
+ status: "info",
1896
+ detail: "Checking wallet, manifest, rootfs, pricing, and SSH key."
1897
+ });
1898
+ const spec = tierSpec(
1899
+ this.state.pricingSummary.pricing,
1900
+ this.state.pricingSummary.tier
1901
+ );
1902
+ this.emitProgress({
1903
+ stage: "selecting-crn",
1904
+ label: "Selecting CRN",
1905
+ progress: 18,
1906
+ status: "info",
1907
+ detail: this.state.selectedCrn?.name ?? this.state.selectedCrn?.hash ?? "Auto-selected compatible CRN"
1908
+ });
1600
1909
  const content = createInstanceContent({
1601
1910
  address: this.state.wallet.address,
1602
1911
  name: this.state.instanceName.trim(),
1603
1912
  sshPublicKey: this.state.sshPublicKey.trim(),
1604
1913
  rootfsItemHash: this.state.manifest.rootfsItemHash,
1605
- rootfsSizeMiB: Math.max(this.state.manifest.rootfsSizeMiB, spec.diskMiB),
1914
+ rootfsSizeMiB: Math.max(
1915
+ this.state.manifest.rootfsSizeMiB,
1916
+ spec.diskMiB
1917
+ ),
1606
1918
  vcpus: spec.vcpus,
1607
1919
  memoryMiB: spec.memoryMiB,
1608
1920
  rootfsVersion: this.state.manifest.version,
@@ -1615,20 +1927,57 @@ var SponsorRelayController = class {
1615
1927
  signer: personalSign,
1616
1928
  fetch: (url, init) => fetch(url, init),
1617
1929
  apiHost: this.client.apiHost,
1618
- sync: false
1930
+ sync: false,
1931
+ onProgress: (event) => {
1932
+ this.emitProgress(event);
1933
+ }
1619
1934
  });
1620
1935
  this.patch({
1621
1936
  busy: { deploying: false },
1622
1937
  statusText: `Deployment submitted: ${result.itemHash}`,
1623
1938
  lastDeploymentHash: result.itemHash
1624
1939
  });
1940
+ if (result.status === "processed" && this.state.selectedCrn?.address) {
1941
+ await notifyCrnAllocationWithRetry({
1942
+ crnUrl: this.state.selectedCrn.address,
1943
+ itemHash: result.itemHash,
1944
+ fetch: (url, init) => fetch(url, init),
1945
+ onProgress: (event) => {
1946
+ this.emitProgress(event);
1947
+ }
1948
+ });
1949
+ }
1950
+ this.emitProgress({
1951
+ stage: "refreshing-instances",
1952
+ label: "Refreshing instances",
1953
+ progress: 92,
1954
+ status: "info",
1955
+ itemHash: result.itemHash,
1956
+ detail: "Reloading deployments and runtime state."
1957
+ });
1625
1958
  await this.refresh();
1959
+ this.emitProgress({
1960
+ stage: "completed",
1961
+ label: "Deployment completed",
1962
+ progress: 100,
1963
+ status: result.status === "rejected" ? "error" : "success",
1964
+ itemHash: result.itemHash,
1965
+ detail: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : "Deployment submitted and synced back into the panel.",
1966
+ error: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : null
1967
+ });
1626
1968
  } catch (error) {
1627
1969
  this.patch({
1628
1970
  busy: { deploying: false },
1629
1971
  errorText: error instanceof Error ? error.message : String(error),
1630
1972
  statusText: "Deployment failed"
1631
1973
  });
1974
+ this.emitProgress({
1975
+ stage: "error",
1976
+ label: "Deployment failed",
1977
+ progress: 100,
1978
+ status: "error",
1979
+ error: error instanceof Error ? error.message : String(error)
1980
+ });
1632
1981
  }
1633
1982
  }
1634
1983
  async deleteInstance(instanceHash) {
@@ -1642,6 +1991,14 @@ var SponsorRelayController = class {
1642
1991
  statusText: `Deleting ${instanceHash}`
1643
1992
  });
1644
1993
  try {
1994
+ this.emitProgress({
1995
+ stage: "validating",
1996
+ label: "Validating delete request",
1997
+ progress: 6,
1998
+ status: "warning",
1999
+ itemHash: instanceHash,
2000
+ detail: "Preparing Aleph FORGET message."
2001
+ });
1645
2002
  await forgetAlephMessages({
1646
2003
  sender: this.state.wallet.address,
1647
2004
  hashes: [instanceHash],
@@ -1653,13 +2010,33 @@ var SponsorRelayController = class {
1653
2010
  status: response.status,
1654
2011
  json: async () => await response.json()
1655
2012
  })),
1656
- apiHost: this.client.apiHost
2013
+ apiHost: this.client.apiHost,
2014
+ onProgress: (event) => {
2015
+ this.emitProgress(event);
2016
+ }
1657
2017
  });
1658
2018
  this.patch({
1659
2019
  busy: { deletingInstanceHash: null },
1660
2020
  statusText: `Deletion submitted for ${instanceHash}`
1661
2021
  });
2022
+ this.emitProgress({
2023
+ stage: "refreshing-instances",
2024
+ label: "Refreshing instances after delete",
2025
+ progress: 92,
2026
+ status: "info",
2027
+ itemHash: instanceHash,
2028
+ detail: "Reloading current deployments."
2029
+ });
1662
2030
  await this.refresh();
2031
+ this.emitProgress({
2032
+ stage: "completed",
2033
+ label: "Delete completed",
2034
+ progress: 100,
2035
+ status: "success",
2036
+ itemHash: instanceHash,
2037
+ detail: "Delete request submitted and deployments refreshed.",
2038
+ error: null
2039
+ });
1663
2040
  } catch (error) {
1664
2041
  this.patch({
1665
2042
  busy: { deletingInstanceHash: null },
@@ -1708,13 +2085,10 @@ function useSponsorRelayController(props) {
1708
2085
 
1709
2086
  // src/react/SponsorRelayFab.tsx
1710
2087
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1711
- var panelStyle = {
2088
+ var basePanelStyle = {
1712
2089
  position: "fixed",
1713
- right: "1.4rem",
1714
- bottom: "11.5rem",
1715
2090
  zIndex: 9999,
1716
2091
  width: "min(28rem, calc(100vw - 2rem))",
1717
- maxHeight: "calc(100vh - 12.5rem)",
1718
2092
  overflow: "auto",
1719
2093
  border: "1px solid rgba(255,255,255,0.12)",
1720
2094
  borderRadius: "1.4rem",
@@ -1735,33 +2109,282 @@ var fieldStyle = {
1735
2109
  fontSize: "0.98rem",
1736
2110
  lineHeight: 1.45
1737
2111
  };
2112
+ function progressToneColor(status) {
2113
+ switch (status) {
2114
+ case "success":
2115
+ return "#37d67a";
2116
+ case "warning":
2117
+ return "#ffc83f";
2118
+ case "error":
2119
+ return "#e91315";
2120
+ default:
2121
+ return "#0176ce";
2122
+ }
2123
+ }
2124
+ function progressBadgeLabel(stage) {
2125
+ switch (stage) {
2126
+ case "building-delete-message":
2127
+ case "signing-delete-message":
2128
+ case "broadcasting-delete":
2129
+ case "delete-completed":
2130
+ return "DELETE";
2131
+ case "error":
2132
+ return "ERROR";
2133
+ case "completed":
2134
+ return "DONE";
2135
+ default:
2136
+ return "DEPLOY";
2137
+ }
2138
+ }
2139
+ function launcherIndicator(state) {
2140
+ if (state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed") {
2141
+ return {
2142
+ label: state.deploymentProgress.progress > 0 ? `${Math.round(state.deploymentProgress.progress)}%` : progressBadgeLabel(state.deploymentProgress.stage),
2143
+ detail: state.deploymentProgress.label,
2144
+ tone: state.deploymentProgress.status
2145
+ };
2146
+ }
2147
+ if (state.deploymentProgress.stage === "completed") {
2148
+ return {
2149
+ label: "DONE",
2150
+ detail: state.deploymentProgress.label,
2151
+ tone: state.deploymentProgress.status === "error" ? "error" : "success"
2152
+ };
2153
+ }
2154
+ if (state.errorText) {
2155
+ return {
2156
+ label: "ERR",
2157
+ detail: state.errorText,
2158
+ tone: "error"
2159
+ };
2160
+ }
2161
+ if (!state.wallet.connected) {
2162
+ return {
2163
+ label: "WALLET",
2164
+ detail: "Connect MetaMask",
2165
+ tone: "warning"
2166
+ };
2167
+ }
2168
+ if (state.rootfsHealth.tone === "error") {
2169
+ return {
2170
+ label: "ROOTFS",
2171
+ detail: state.rootfsHealth.label,
2172
+ tone: "error"
2173
+ };
2174
+ }
2175
+ if (state.rootfsHealth.tone === "caution") {
2176
+ return {
2177
+ label: "CHECK",
2178
+ detail: state.rootfsHealth.label,
2179
+ tone: "warning"
2180
+ };
2181
+ }
2182
+ if (state.busy.refreshing) {
2183
+ return {
2184
+ label: "SYNC",
2185
+ detail: "Refreshing relay state",
2186
+ tone: "info"
2187
+ };
2188
+ }
2189
+ return {
2190
+ label: "READY",
2191
+ detail: state.selectedCrn?.name ?? "Ready to deploy",
2192
+ tone: "success"
2193
+ };
2194
+ }
1738
2195
  function SponsorRelayFab(props) {
1739
2196
  const { controller, state } = useSponsorRelayController(props);
1740
2197
  const launcherMode = props.launcherMode ?? "floating";
2198
+ const indicator = launcherIndicator(state);
2199
+ const [successFlash, setSuccessFlash] = React.useState(false);
2200
+ const [hovered, setHovered] = React.useState(false);
2201
+ const launcherRef = React.useRef(null);
2202
+ const [inlinePanelStyle, setInlinePanelStyle] = React.useState(null);
2203
+ const [compactInlineLabel, setCompactInlineLabel] = React.useState(false);
2204
+ React.useEffect(() => {
2205
+ if (state.deploymentProgress.stage !== "completed" || state.deploymentProgress.status !== "success") {
2206
+ return;
2207
+ }
2208
+ setSuccessFlash(true);
2209
+ const timeout = window.setTimeout(() => {
2210
+ setSuccessFlash(false);
2211
+ }, 1400);
2212
+ return () => {
2213
+ window.clearTimeout(timeout);
2214
+ };
2215
+ }, [
2216
+ state.deploymentProgress.stage,
2217
+ state.deploymentProgress.status,
2218
+ state.deploymentProgress.timestamp
2219
+ ]);
2220
+ React.useEffect(() => {
2221
+ if (launcherMode !== "inline" || !state.open) {
2222
+ return;
2223
+ }
2224
+ const updateInlinePanelStyle = () => {
2225
+ const launcher = launcherRef.current;
2226
+ if (launcher == null) {
2227
+ return;
2228
+ }
2229
+ const rect = launcher.getBoundingClientRect();
2230
+ const viewportWidth = window.innerWidth;
2231
+ const viewportHeight = window.innerHeight;
2232
+ const anchorRight = Math.max(16, viewportWidth - rect.right);
2233
+ const top = Math.max(72, rect.bottom + 12);
2234
+ setInlinePanelStyle({
2235
+ ...basePanelStyle,
2236
+ top,
2237
+ right: anchorRight,
2238
+ maxHeight: `calc(100vh - ${Math.min(viewportHeight - 16, top + 16)}px)`,
2239
+ width: "min(30rem, calc(100vw - 1.5rem))"
2240
+ });
2241
+ };
2242
+ updateInlinePanelStyle();
2243
+ window.addEventListener("resize", updateInlinePanelStyle);
2244
+ window.addEventListener("scroll", updateInlinePanelStyle, true);
2245
+ return () => {
2246
+ window.removeEventListener("resize", updateInlinePanelStyle);
2247
+ window.removeEventListener("scroll", updateInlinePanelStyle, true);
2248
+ };
2249
+ }, [launcherMode, state.open]);
2250
+ React.useEffect(() => {
2251
+ if (launcherMode !== "inline") {
2252
+ setCompactInlineLabel(false);
2253
+ return;
2254
+ }
2255
+ const updateCompactInlineLabel = () => {
2256
+ setCompactInlineLabel(window.innerWidth < 1240);
2257
+ };
2258
+ updateCompactInlineLabel();
2259
+ window.addEventListener("resize", updateCompactInlineLabel);
2260
+ return () => {
2261
+ window.removeEventListener("resize", updateCompactInlineLabel);
2262
+ };
2263
+ }, [launcherMode]);
2264
+ const progressActive = state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed" && state.deploymentProgress.stage !== "error";
2265
+ const pulseScale = progressActive ? 1.03 : hovered ? 1.015 : 1;
2266
+ const pulseShadow = progressActive ? `0 0 0 4px ${progressToneColor(indicator.tone)}22, 0 12px 28px rgba(15, 23, 42, 0.22)` : successFlash ? `0 0 0 5px rgba(55, 214, 122, 0.22), 0 14px 32px rgba(55, 214, 122, 0.22)` : launcherMode === "inline" ? hovered ? "0 14px 30px rgba(79, 70, 229, 0.28)" : "0 10px 24px rgba(99, 102, 241, 0.22)" : void 0;
2267
+ const inlineButtonBackground = indicator.tone === "error" ? "linear-gradient(135deg, #b91c1c 0%, #dc2626 100%)" : indicator.tone === "warning" ? "linear-gradient(135deg, #7c3aed 0%, #8b5cf6 100%)" : indicator.tone === "success" ? "linear-gradient(135deg, #4f46e5 0%, #6366f1 100%)" : "linear-gradient(135deg, #4f46e5 0%, #6366f1 100%)";
2268
+ const inlineButtonBorder = indicator.tone === "error" ? "1px solid rgba(254, 202, 202, 0.45)" : indicator.tone === "warning" ? "1px solid rgba(196, 181, 253, 0.42)" : "1px solid rgba(199, 210, 254, 0.42)";
2269
+ const inlineBadgeBackground = indicator.tone === "error" ? "rgba(127, 29, 29, 0.88)" : indicator.tone === "warning" ? "rgba(88, 28, 135, 0.88)" : indicator.tone === "success" ? "rgba(49, 46, 129, 0.92)" : "rgba(30, 41, 59, 0.92)";
2270
+ const inlineBadgeBorder = indicator.tone === "error" ? "1px solid rgba(254, 202, 202, 0.34)" : indicator.tone === "warning" ? "1px solid rgba(233, 213, 255, 0.28)" : indicator.tone === "success" ? "1px solid rgba(191, 219, 254, 0.24)" : "1px solid rgba(191, 219, 254, 0.2)";
2271
+ const panelStyle = launcherMode === "inline" ? inlinePanelStyle ?? {
2272
+ ...basePanelStyle,
2273
+ top: "4.75rem",
2274
+ right: "1rem",
2275
+ maxHeight: "calc(100vh - 6rem)",
2276
+ width: "min(30rem, calc(100vw - 1.5rem))"
2277
+ } : {
2278
+ ...basePanelStyle,
2279
+ right: "1.4rem",
2280
+ bottom: "11.5rem",
2281
+ maxHeight: "calc(100vh - 12.5rem)"
2282
+ };
2283
+ const launcherLabel = launcherMode === "inline" && compactInlineLabel ? "Relay" : "Sponsor Relay";
1741
2284
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1742
- /* @__PURE__ */ jsx(
2285
+ /* @__PURE__ */ jsxs(
1743
2286
  "button",
1744
2287
  {
2288
+ ref: launcherRef,
1745
2289
  type: "button",
1746
2290
  onClick: () => controller.toggleOpen(),
2291
+ onMouseEnter: () => setHovered(true),
2292
+ onMouseLeave: () => setHovered(false),
2293
+ onFocus: () => setHovered(true),
2294
+ onBlur: () => setHovered(false),
2295
+ title: indicator.detail ?? "Sponsor Relay",
1747
2296
  style: {
1748
2297
  position: launcherMode === "floating" ? "fixed" : "relative",
1749
2298
  right: launcherMode === "floating" ? "1.4rem" : void 0,
1750
2299
  bottom: launcherMode === "floating" ? "5.8rem" : void 0,
1751
2300
  zIndex: launcherMode === "floating" ? 1e4 : "auto",
1752
2301
  borderRadius: "999px",
1753
- border: "2px solid rgba(255,255,255,0.9)",
1754
- background: "linear-gradient(135deg, #e91315 0%, #ffc83f 100%)",
2302
+ border: launcherMode === "floating" ? "2px solid rgba(255,255,255,0.9)" : inlineButtonBorder,
2303
+ background: launcherMode === "floating" ? "linear-gradient(135deg, #e91315 0%, #ffc83f 100%)" : inlineButtonBackground,
1755
2304
  color: "white",
1756
- padding: launcherMode === "floating" ? "0.9rem 1.2rem" : "0.55rem 0.9rem",
2305
+ minHeight: launcherMode === "floating" ? void 0 : "2.25rem",
2306
+ padding: launcherMode === "floating" ? "0.9rem 1.2rem" : "0.42rem 0.56rem 0.42rem 0.76rem",
1757
2307
  fontFamily: '"Epilogue", sans-serif',
1758
2308
  fontWeight: 700,
1759
- fontSize: launcherMode === "floating" ? "0.84rem" : "0.78rem",
2309
+ fontSize: launcherMode === "floating" ? "0.84rem" : "0.73rem",
1760
2310
  textTransform: "uppercase",
1761
2311
  letterSpacing: "0.08em",
1762
- cursor: "pointer"
2312
+ cursor: "pointer",
2313
+ display: "inline-flex",
2314
+ alignItems: "center",
2315
+ justifyContent: "center",
2316
+ gap: launcherMode === "floating" ? "0.45rem" : "0.34rem",
2317
+ boxShadow: pulseShadow,
2318
+ transform: `translateY(${hovered && !progressActive ? "-1px" : "0"}) scale(${pulseScale})`,
2319
+ transition: "transform 180ms ease, box-shadow 220ms ease, background 220ms ease, border-color 220ms ease"
1763
2320
  },
1764
- children: "Sponsor Relay"
2321
+ children: [
2322
+ /* @__PURE__ */ jsxs(
2323
+ "span",
2324
+ {
2325
+ style: {
2326
+ display: "inline-flex",
2327
+ alignItems: "center",
2328
+ gap: "0.36rem"
2329
+ },
2330
+ children: [
2331
+ /* @__PURE__ */ jsx(
2332
+ "span",
2333
+ {
2334
+ "aria-hidden": "true",
2335
+ style: {
2336
+ width: launcherMode === "floating" ? "0.5rem" : "0.42rem",
2337
+ height: launcherMode === "floating" ? "0.5rem" : "0.42rem",
2338
+ borderRadius: "999px",
2339
+ background: launcherMode === "floating" ? "rgba(255,255,255,0.96)" : "#f59e0b",
2340
+ boxShadow: launcherMode === "floating" ? "0 0 0 3px rgba(255,255,255,0.22)" : "0 0 0 3px rgba(245, 158, 11, 0.18)"
2341
+ }
2342
+ }
2343
+ ),
2344
+ /* @__PURE__ */ jsx("span", { children: launcherLabel })
2345
+ ]
2346
+ }
2347
+ ),
2348
+ /* @__PURE__ */ jsxs(
2349
+ "span",
2350
+ {
2351
+ style: {
2352
+ display: "inline-flex",
2353
+ alignItems: "center",
2354
+ gap: "0.36rem",
2355
+ padding: launcherMode === "floating" ? "0.2rem 0.42rem" : "0.21rem 0.5rem",
2356
+ minWidth: launcherMode === "floating" ? void 0 : "3.8rem",
2357
+ borderRadius: "999px",
2358
+ background: launcherMode === "floating" ? "rgba(15, 23, 42, 0.18)" : inlineBadgeBackground,
2359
+ border: launcherMode === "floating" ? "1px solid rgba(255,255,255,0.18)" : inlineBadgeBorder,
2360
+ fontSize: launcherMode === "floating" ? "0.62rem" : "0.61rem",
2361
+ fontWeight: launcherMode === "floating" ? 700 : 800,
2362
+ lineHeight: 1,
2363
+ letterSpacing: "0.08em",
2364
+ justifyContent: "center",
2365
+ boxShadow: launcherMode === "floating" ? void 0 : `inset 0 1px 0 rgba(255,255,255,0.08), 0 6px 16px ${progressToneColor(indicator.tone)}22`
2366
+ },
2367
+ children: [
2368
+ /* @__PURE__ */ jsx(
2369
+ "span",
2370
+ {
2371
+ "aria-hidden": "true",
2372
+ style: {
2373
+ width: "0.42rem",
2374
+ height: "0.42rem",
2375
+ borderRadius: "999px",
2376
+ background: progressToneColor(indicator.tone),
2377
+ boxShadow: `0 0 0 ${progressActive ? "4px" : "3px"} ${progressToneColor(indicator.tone)}22`,
2378
+ transform: `scale(${progressActive ? 1.12 : successFlash ? 1.18 : 1})`,
2379
+ transition: "transform 180ms ease, box-shadow 220ms ease"
2380
+ }
2381
+ }
2382
+ ),
2383
+ /* @__PURE__ */ jsx("span", { children: indicator.label })
2384
+ ]
2385
+ }
2386
+ )
2387
+ ]
1765
2388
  }
1766
2389
  ),
1767
2390
  state.open ? /* @__PURE__ */ jsxs("div", { children: [
@@ -1778,66 +2401,274 @@ function SponsorRelayFab(props) {
1778
2401
  }
1779
2402
  ),
1780
2403
  /* @__PURE__ */ jsxs("aside", { style: panelStyle, children: [
1781
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: "0.75rem", alignItems: "center" }, children: [
1782
- /* @__PURE__ */ jsxs("div", { children: [
1783
- /* @__PURE__ */ jsx("div", { style: { color: "#9fb2ca", fontSize: "0.72rem", textTransform: "uppercase", letterSpacing: "0.08em" }, children: "Aleph VM credit deployer" }),
1784
- /* @__PURE__ */ jsx("h2", { style: { margin: "0.2rem 0 0", fontFamily: '"Epilogue", sans-serif' }, children: "Sponsor Relay" })
1785
- ] }),
1786
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void controller.refresh(), children: state.busy.refreshing ? "Syncing" : "Refresh" })
1787
- ] }),
2404
+ /* @__PURE__ */ jsxs(
2405
+ "div",
2406
+ {
2407
+ style: {
2408
+ display: "flex",
2409
+ justifyContent: "space-between",
2410
+ gap: "0.75rem",
2411
+ alignItems: "center"
2412
+ },
2413
+ children: [
2414
+ /* @__PURE__ */ jsxs("div", { children: [
2415
+ /* @__PURE__ */ jsx(
2416
+ "div",
2417
+ {
2418
+ style: {
2419
+ color: "#9fb2ca",
2420
+ fontSize: "0.72rem",
2421
+ textTransform: "uppercase",
2422
+ letterSpacing: "0.08em"
2423
+ },
2424
+ children: "Aleph VM credit deployer"
2425
+ }
2426
+ ),
2427
+ /* @__PURE__ */ jsx(
2428
+ "h2",
2429
+ {
2430
+ style: {
2431
+ margin: "0.2rem 0 0",
2432
+ fontFamily: '"Epilogue", sans-serif'
2433
+ },
2434
+ children: "Sponsor Relay"
2435
+ }
2436
+ )
2437
+ ] }),
2438
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void controller.refresh(), children: state.busy.refreshing ? "Syncing" : "Refresh" })
2439
+ ]
2440
+ }
2441
+ ),
1788
2442
  /* @__PURE__ */ jsx("p", { style: { color: "#9fb2ca" }, children: state.statusText }),
1789
2443
  state.errorText ? /* @__PURE__ */ jsx("p", { style: { color: "#ffd9d9" }, children: state.errorText }) : null,
1790
- /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.75rem", marginTop: "0.9rem" }, children: [
1791
- /* @__PURE__ */ jsx(
1792
- "input",
1793
- {
1794
- style: fieldStyle,
1795
- value: state.manifestUrl,
1796
- onChange: (event) => controller.setManifestUrl(event.currentTarget.value),
1797
- placeholder: "Manifest URL"
1798
- }
1799
- ),
1800
- /* @__PURE__ */ jsx(
1801
- "input",
1802
- {
1803
- style: fieldStyle,
1804
- value: state.instanceName,
1805
- onChange: (event) => controller.setInstanceName(event.currentTarget.value),
1806
- placeholder: "Instance name"
1807
- }
1808
- ),
1809
- /* @__PURE__ */ jsx(
1810
- "select",
1811
- {
1812
- style: fieldStyle,
1813
- value: state.pricingSummary.tier?.id ?? state.tierId,
1814
- onChange: (event) => controller.setTierId(event.currentTarget.value),
1815
- children: (state.pricingSummary.pricing?.tiers ?? []).map((tier) => /* @__PURE__ */ jsx("option", { value: tier.id, children: tier.id }, tier.id))
1816
- }
1817
- ),
1818
- /* @__PURE__ */ jsx(
1819
- "textarea",
1820
- {
1821
- style: fieldStyle,
1822
- rows: 3,
1823
- value: state.sshPublicKey,
1824
- onChange: (event) => controller.setSshPublicKey(event.currentTarget.value),
1825
- placeholder: "SSH public key"
1826
- }
1827
- ),
1828
- /* @__PURE__ */ jsxs("details", { children: [
1829
- /* @__PURE__ */ jsx("summary", { children: "Paste Manifest" }),
1830
- /* @__PURE__ */ jsx(
1831
- "textarea",
1832
- {
1833
- style: { ...fieldStyle, marginTop: "0.65rem" },
1834
- rows: 7,
1835
- value: state.manifestJson,
1836
- onChange: (event) => controller.setManifestJson(event.currentTarget.value)
1837
- }
1838
- )
1839
- ] })
1840
- ] }),
2444
+ state.deploymentProgress.stage !== "idle" ? /* @__PURE__ */ jsxs(
2445
+ "div",
2446
+ {
2447
+ style: {
2448
+ marginTop: "0.85rem",
2449
+ padding: "0.75rem 0.8rem",
2450
+ borderRadius: "1rem",
2451
+ border: "1px solid rgba(255,255,255,0.1)",
2452
+ background: "linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.03))",
2453
+ boxShadow: "inset 0 1px 0 rgba(255,255,255,0.04)"
2454
+ },
2455
+ children: [
2456
+ /* @__PURE__ */ jsxs(
2457
+ "div",
2458
+ {
2459
+ style: {
2460
+ display: "flex",
2461
+ justifyContent: "space-between",
2462
+ gap: "0.75rem",
2463
+ alignItems: "center"
2464
+ },
2465
+ children: [
2466
+ /* @__PURE__ */ jsxs(
2467
+ "div",
2468
+ {
2469
+ style: {
2470
+ display: "flex",
2471
+ gap: "0.5rem",
2472
+ alignItems: "center",
2473
+ minWidth: 0
2474
+ },
2475
+ children: [
2476
+ /* @__PURE__ */ jsx(
2477
+ "span",
2478
+ {
2479
+ style: {
2480
+ display: "inline-flex",
2481
+ alignItems: "center",
2482
+ justifyContent: "center",
2483
+ minWidth: "3.9rem",
2484
+ padding: "0.22rem 0.45rem",
2485
+ borderRadius: "999px",
2486
+ background: `${progressToneColor(state.deploymentProgress.status)}22`,
2487
+ color: progressToneColor(
2488
+ state.deploymentProgress.status
2489
+ ),
2490
+ fontSize: "0.64rem",
2491
+ fontWeight: 800,
2492
+ letterSpacing: "0.08em",
2493
+ textTransform: "uppercase"
2494
+ },
2495
+ children: progressBadgeLabel(state.deploymentProgress.stage)
2496
+ }
2497
+ ),
2498
+ /* @__PURE__ */ jsx(
2499
+ "strong",
2500
+ {
2501
+ style: {
2502
+ fontSize: "0.84rem",
2503
+ lineHeight: 1.2,
2504
+ whiteSpace: "nowrap",
2505
+ overflow: "hidden",
2506
+ textOverflow: "ellipsis"
2507
+ },
2508
+ children: state.deploymentProgress.label
2509
+ }
2510
+ )
2511
+ ]
2512
+ }
2513
+ ),
2514
+ /* @__PURE__ */ jsxs(
2515
+ "span",
2516
+ {
2517
+ style: {
2518
+ fontSize: "0.72rem",
2519
+ color: "#9fb2ca",
2520
+ fontFamily: '"DM Mono", monospace'
2521
+ },
2522
+ children: [
2523
+ String(
2524
+ Math.round(state.deploymentProgress.progress)
2525
+ ).padStart(3, " "),
2526
+ "%"
2527
+ ]
2528
+ }
2529
+ )
2530
+ ]
2531
+ }
2532
+ ),
2533
+ /* @__PURE__ */ jsx(
2534
+ "div",
2535
+ {
2536
+ style: {
2537
+ marginTop: "0.5rem",
2538
+ width: "100%",
2539
+ height: "0.34rem",
2540
+ borderRadius: "999px",
2541
+ background: "rgba(255,255,255,0.08)",
2542
+ overflow: "hidden"
2543
+ },
2544
+ children: /* @__PURE__ */ jsx(
2545
+ "div",
2546
+ {
2547
+ style: {
2548
+ width: `${Math.max(0, Math.min(100, state.deploymentProgress.progress))}%`,
2549
+ height: "100%",
2550
+ borderRadius: "999px",
2551
+ background: progressToneColor(
2552
+ state.deploymentProgress.status
2553
+ ),
2554
+ transition: "width 180ms ease"
2555
+ }
2556
+ }
2557
+ )
2558
+ }
2559
+ ),
2560
+ /* @__PURE__ */ jsxs(
2561
+ "div",
2562
+ {
2563
+ style: {
2564
+ marginTop: "0.45rem",
2565
+ display: "grid",
2566
+ gap: "0.28rem"
2567
+ },
2568
+ children: [
2569
+ state.deploymentProgress.itemHash ? /* @__PURE__ */ jsxs(
2570
+ "div",
2571
+ {
2572
+ style: {
2573
+ color: "#9fb2ca",
2574
+ fontSize: "0.7rem",
2575
+ fontFamily: '"DM Mono", monospace',
2576
+ whiteSpace: "nowrap",
2577
+ overflow: "hidden",
2578
+ textOverflow: "ellipsis"
2579
+ },
2580
+ children: [
2581
+ "hash ",
2582
+ shortHash(state.deploymentProgress.itemHash, 10, 8)
2583
+ ]
2584
+ }
2585
+ ) : null,
2586
+ state.deploymentProgress.detail ? /* @__PURE__ */ jsx(
2587
+ "div",
2588
+ {
2589
+ style: {
2590
+ color: "#9fb2ca",
2591
+ fontSize: "0.72rem",
2592
+ lineHeight: 1.3,
2593
+ fontFamily: state.deploymentProgress.detail.includes("0x") || state.deploymentProgress.detail.includes("Qm") ? '"DM Mono", monospace' : void 0
2594
+ },
2595
+ children: state.deploymentProgress.detail
2596
+ }
2597
+ ) : null,
2598
+ state.deploymentProgress.error ? /* @__PURE__ */ jsx(
2599
+ "div",
2600
+ {
2601
+ style: {
2602
+ color: "#ffd9d9",
2603
+ fontSize: "0.72rem",
2604
+ lineHeight: 1.3
2605
+ },
2606
+ children: state.deploymentProgress.error
2607
+ }
2608
+ ) : null
2609
+ ]
2610
+ }
2611
+ )
2612
+ ]
2613
+ }
2614
+ ) : null,
2615
+ /* @__PURE__ */ jsxs(
2616
+ "div",
2617
+ {
2618
+ style: { display: "grid", gap: "0.75rem", marginTop: "0.9rem" },
2619
+ children: [
2620
+ /* @__PURE__ */ jsx(
2621
+ "input",
2622
+ {
2623
+ style: fieldStyle,
2624
+ value: state.manifestUrl,
2625
+ onChange: (event) => controller.setManifestUrl(event.currentTarget.value),
2626
+ placeholder: "Manifest URL"
2627
+ }
2628
+ ),
2629
+ /* @__PURE__ */ jsx(
2630
+ "input",
2631
+ {
2632
+ style: fieldStyle,
2633
+ value: state.instanceName,
2634
+ onChange: (event) => controller.setInstanceName(event.currentTarget.value),
2635
+ placeholder: "Instance name"
2636
+ }
2637
+ ),
2638
+ /* @__PURE__ */ jsx(
2639
+ "select",
2640
+ {
2641
+ style: fieldStyle,
2642
+ value: state.pricingSummary.tier?.id ?? state.tierId,
2643
+ onChange: (event) => controller.setTierId(event.currentTarget.value),
2644
+ children: (state.pricingSummary.pricing?.tiers ?? []).map((tier) => /* @__PURE__ */ jsx("option", { value: tier.id, children: tier.id }, tier.id))
2645
+ }
2646
+ ),
2647
+ /* @__PURE__ */ jsx(
2648
+ "textarea",
2649
+ {
2650
+ style: fieldStyle,
2651
+ rows: 3,
2652
+ value: state.sshPublicKey,
2653
+ onChange: (event) => controller.setSshPublicKey(event.currentTarget.value),
2654
+ placeholder: "SSH public key"
2655
+ }
2656
+ ),
2657
+ /* @__PURE__ */ jsxs("details", { children: [
2658
+ /* @__PURE__ */ jsx("summary", { children: "Paste Manifest" }),
2659
+ /* @__PURE__ */ jsx(
2660
+ "textarea",
2661
+ {
2662
+ style: { ...fieldStyle, marginTop: "0.65rem" },
2663
+ rows: 7,
2664
+ value: state.manifestJson,
2665
+ onChange: (event) => controller.setManifestJson(event.currentTarget.value)
2666
+ }
2667
+ )
2668
+ ] })
2669
+ ]
2670
+ }
2671
+ ),
1841
2672
  /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.55rem", marginTop: "1rem" }, children: [
1842
2673
  /* @__PURE__ */ jsxs("div", { children: [
1843
2674
  formatNumber(state.pricingSummary.availableCredits, 0),
@@ -1863,40 +2694,68 @@ function SponsorRelayFab(props) {
1863
2694
  "Latest deployment: ",
1864
2695
  shortHash(state.lastDeploymentHash)
1865
2696
  ] }) : null,
1866
- state.showInstances ? /* @__PURE__ */ jsx("div", { style: { marginTop: "1rem", display: "grid", gap: "0.7rem" }, children: state.instances.map((entry) => /* @__PURE__ */ jsxs("details", { open: true, children: [
1867
- /* @__PURE__ */ jsx("summary", { children: (entry.instance.content?.metadata?.name ?? "relay") + " \xB7 " + shortHash(entry.instance.item_hash) }),
1868
- /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.35rem", marginTop: "0.55rem" }, children: [
1869
- /* @__PURE__ */ jsxs("div", { children: [
1870
- "Status: ",
1871
- entry.details.messageStatus
1872
- ] }),
1873
- /* @__PURE__ */ jsxs("div", { children: [
1874
- "Host IPv4: ",
1875
- entry.details.hostIpv4 ?? "-"
1876
- ] }),
1877
- /* @__PURE__ */ jsxs("div", { children: [
1878
- "IPv6: ",
1879
- entry.details.ipv6 ?? "-"
1880
- ] }),
1881
- /* @__PURE__ */ jsxs("div", { children: [
1882
- "VM IPv4: ",
1883
- entry.details.vmIpv4 ?? "-"
1884
- ] }),
1885
- /* @__PURE__ */ jsxs("div", { children: [
1886
- "SSH: ",
1887
- entry.details.sshCommand ?? "-"
1888
- ] }),
1889
- /* @__PURE__ */ jsxs("div", { children: [
1890
- "Ports: ",
1891
- joinMappedPorts(entry.details.mappedPorts)
1892
- ] }),
1893
- /* @__PURE__ */ jsxs("div", { children: [
1894
- "Submitted: ",
1895
- formatDateTime(entry.instance.reception_time ?? entry.instance.time)
1896
- ] }),
1897
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void controller.deleteInstance(entry.instance.item_hash), children: state.busy.deletingInstanceHash === entry.instance.item_hash ? "Deleting\u2026" : "Delete" })
1898
- ] })
1899
- ] }, entry.instance.item_hash)) }) : null
2697
+ state.showInstances ? /* @__PURE__ */ jsx(
2698
+ "div",
2699
+ {
2700
+ style: { marginTop: "1rem", display: "grid", gap: "0.7rem" },
2701
+ children: state.instances.map((entry) => /* @__PURE__ */ jsxs("details", { open: true, children: [
2702
+ /* @__PURE__ */ jsx("summary", { children: (entry.instance.content?.metadata?.name ?? "relay") + " \xB7 " + shortHash(entry.instance.item_hash) }),
2703
+ /* @__PURE__ */ jsxs(
2704
+ "div",
2705
+ {
2706
+ style: {
2707
+ display: "grid",
2708
+ gap: "0.35rem",
2709
+ marginTop: "0.55rem"
2710
+ },
2711
+ children: [
2712
+ /* @__PURE__ */ jsxs("div", { children: [
2713
+ "Status: ",
2714
+ entry.details.messageStatus
2715
+ ] }),
2716
+ /* @__PURE__ */ jsxs("div", { children: [
2717
+ "Host IPv4: ",
2718
+ entry.details.hostIpv4 ?? "-"
2719
+ ] }),
2720
+ /* @__PURE__ */ jsxs("div", { children: [
2721
+ "IPv6: ",
2722
+ entry.details.ipv6 ?? "-"
2723
+ ] }),
2724
+ /* @__PURE__ */ jsxs("div", { children: [
2725
+ "VM IPv4: ",
2726
+ entry.details.vmIpv4 ?? "-"
2727
+ ] }),
2728
+ /* @__PURE__ */ jsxs("div", { children: [
2729
+ "SSH: ",
2730
+ entry.details.sshCommand ?? "-"
2731
+ ] }),
2732
+ /* @__PURE__ */ jsxs("div", { children: [
2733
+ "Ports: ",
2734
+ joinMappedPorts(entry.details.mappedPorts)
2735
+ ] }),
2736
+ /* @__PURE__ */ jsxs("div", { children: [
2737
+ "Submitted:",
2738
+ " ",
2739
+ formatDateTime(
2740
+ entry.instance.reception_time ?? entry.instance.time
2741
+ )
2742
+ ] }),
2743
+ /* @__PURE__ */ jsx(
2744
+ "button",
2745
+ {
2746
+ type: "button",
2747
+ onClick: () => void controller.deleteInstance(
2748
+ entry.instance.item_hash
2749
+ ),
2750
+ children: state.busy.deletingInstanceHash === entry.instance.item_hash ? "Deleting\u2026" : "Delete"
2751
+ }
2752
+ )
2753
+ ]
2754
+ }
2755
+ )
2756
+ ] }, entry.instance.item_hash))
2757
+ }
2758
+ ) : null
1900
2759
  ] })
1901
2760
  ] }) : null
1902
2761
  ] });