@le-space/ui 0.1.54 → 0.1.55

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,25 @@ async function deployInstance(args) {
1118
1228
  };
1119
1229
  }
1120
1230
 
1231
+ // src/shared/events.ts
1232
+ function createDeploymentProgressEmitter() {
1233
+ const listeners = /* @__PURE__ */ new Set();
1234
+ return {
1235
+ emit(event) {
1236
+ listeners.forEach((listener) => listener(event));
1237
+ },
1238
+ subscribe(listener) {
1239
+ listeners.add(listener);
1240
+ return () => {
1241
+ listeners.delete(listener);
1242
+ };
1243
+ },
1244
+ clear() {
1245
+ listeners.clear();
1246
+ }
1247
+ };
1248
+ }
1249
+
1121
1250
  // src/shared/controller.ts
1122
1251
  function defaultState(props = {}) {
1123
1252
  return {
@@ -1167,7 +1296,8 @@ function defaultState(props = {}) {
1167
1296
  selectedCrn: null,
1168
1297
  instances: [],
1169
1298
  relayPing: RELAY_PING_IDLE_STATE,
1170
- lastDeploymentHash: null
1299
+ lastDeploymentHash: null,
1300
+ deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
1171
1301
  };
1172
1302
  }
1173
1303
  async function sha256Hex(payload) {
@@ -1361,6 +1491,7 @@ var SponsorRelayController = class {
1361
1491
  pingTimer = null;
1362
1492
  stopWalletWatch = null;
1363
1493
  props;
1494
+ progressEmitter = createDeploymentProgressEmitter();
1364
1495
  constructor(props = {}) {
1365
1496
  this.props = props;
1366
1497
  this.state = defaultState(props);
@@ -1371,6 +1502,9 @@ var SponsorRelayController = class {
1371
1502
  twoN6ApiHost: props.twoN6ApiHost
1372
1503
  });
1373
1504
  }
1505
+ subscribeToDeploymentProgress(listener) {
1506
+ return this.progressEmitter.subscribe(listener);
1507
+ }
1374
1508
  subscribe(subscriber) {
1375
1509
  this.subscribers.add(subscriber);
1376
1510
  return () => {
@@ -1395,6 +1529,18 @@ var SponsorRelayController = class {
1395
1529
  };
1396
1530
  this.emit();
1397
1531
  }
1532
+ emitProgress(event) {
1533
+ const nextEvent = {
1534
+ ...event,
1535
+ timestamp: Date.now()
1536
+ };
1537
+ this.patch({
1538
+ deploymentProgress: nextEvent,
1539
+ statusText: event.error ?? event.detail ?? event.label,
1540
+ errorText: event.status === "error" ? event.error ?? event.detail ?? event.label : this.state.errorText
1541
+ });
1542
+ this.progressEmitter.emit(nextEvent);
1543
+ }
1398
1544
  async init() {
1399
1545
  this.stopWalletWatch = watchWallet(() => {
1400
1546
  void this.refreshWalletDerivedState();
@@ -1514,11 +1660,25 @@ var SponsorRelayController = class {
1514
1660
  statusText: "Refreshing relay sponsor data"
1515
1661
  });
1516
1662
  try {
1663
+ this.emitProgress({
1664
+ stage: "loading-manifest",
1665
+ label: "Loading manifest",
1666
+ progress: 8,
1667
+ status: "info",
1668
+ detail: this.state.manifestUrl
1669
+ });
1517
1670
  const manifestState = await resolveManifest({
1518
1671
  manifestUrl: this.state.manifestUrl,
1519
1672
  manifestJson: this.state.manifestJson
1520
1673
  });
1521
1674
  const manifest = manifestState.manifest;
1675
+ this.emitProgress({
1676
+ stage: "loading-pricing",
1677
+ label: "Loading pricing and CRNs",
1678
+ progress: 18,
1679
+ status: "info",
1680
+ detail: "Fetching live Aleph pricing and compatible CRNs."
1681
+ });
1522
1682
  const [pricingSummary, crns] = await Promise.all([
1523
1683
  fetchInstancePricing(this.client.apiHost),
1524
1684
  this.client.fetchCrns()
@@ -1526,6 +1686,13 @@ var SponsorRelayController = class {
1526
1686
  let balance = this.state.balance;
1527
1687
  let instances = [];
1528
1688
  if (this.state.wallet.address) {
1689
+ this.emitProgress({
1690
+ stage: "loading-balance",
1691
+ label: "Loading wallet balance",
1692
+ progress: 28,
1693
+ status: "info",
1694
+ detail: this.state.wallet.address
1695
+ });
1529
1696
  const [nextBalance, rawInstances] = await Promise.all([
1530
1697
  this.client.fetchBalance(this.state.wallet.address),
1531
1698
  this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
@@ -1545,6 +1712,13 @@ var SponsorRelayController = class {
1545
1712
  let rootfsVerified = false;
1546
1713
  let rootfsResolution = null;
1547
1714
  if (manifestState.valid && manifest) {
1715
+ this.emitProgress({
1716
+ stage: "verifying-rootfs",
1717
+ label: "Verifying rootfs",
1718
+ progress: 38,
1719
+ status: "info",
1720
+ detail: manifest.rootfsItemHash
1721
+ });
1548
1722
  rootfsVerified = await verifyRootfsExists(manifest.rootfsItemHash, this.client.apiHost);
1549
1723
  rootfsResolution = await resolveRootfsReference(manifest.rootfsItemHash, this.client.apiHost);
1550
1724
  }
@@ -1569,12 +1743,26 @@ var SponsorRelayController = class {
1569
1743
  statusText: "Relay sponsor data ready"
1570
1744
  });
1571
1745
  this.recomputePricingSummary();
1746
+ this.emitProgress({
1747
+ stage: "completed",
1748
+ label: "Data ready",
1749
+ progress: 100,
1750
+ status: "success",
1751
+ detail: "Manifest, pricing, and wallet state are ready."
1752
+ });
1572
1753
  } catch (error) {
1573
1754
  this.patch({
1574
1755
  busy: { refreshing: false },
1575
1756
  errorText: error instanceof Error ? error.message : String(error),
1576
1757
  statusText: "Refresh failed"
1577
1758
  });
1759
+ this.emitProgress({
1760
+ stage: "error",
1761
+ label: "Refresh failed",
1762
+ progress: 100,
1763
+ status: "error",
1764
+ error: error instanceof Error ? error.message : String(error)
1765
+ });
1578
1766
  }
1579
1767
  }
1580
1768
  async refreshRelayPing() {
@@ -1596,7 +1784,21 @@ var SponsorRelayController = class {
1596
1784
  statusText: "Broadcasting deployment"
1597
1785
  });
1598
1786
  try {
1787
+ this.emitProgress({
1788
+ stage: "validating",
1789
+ label: "Validating deployment",
1790
+ progress: 5,
1791
+ status: "info",
1792
+ detail: "Checking wallet, manifest, rootfs, pricing, and SSH key."
1793
+ });
1599
1794
  const spec = tierSpec(this.state.pricingSummary.pricing, this.state.pricingSummary.tier);
1795
+ this.emitProgress({
1796
+ stage: "selecting-crn",
1797
+ label: "Selecting CRN",
1798
+ progress: 18,
1799
+ status: "info",
1800
+ detail: this.state.selectedCrn?.name ?? this.state.selectedCrn?.hash ?? "Auto-selected compatible CRN"
1801
+ });
1600
1802
  const content = createInstanceContent({
1601
1803
  address: this.state.wallet.address,
1602
1804
  name: this.state.instanceName.trim(),
@@ -1615,20 +1817,47 @@ var SponsorRelayController = class {
1615
1817
  signer: personalSign,
1616
1818
  fetch: (url, init) => fetch(url, init),
1617
1819
  apiHost: this.client.apiHost,
1618
- sync: false
1820
+ sync: false,
1821
+ onProgress: (event) => {
1822
+ this.emitProgress(event);
1823
+ }
1619
1824
  });
1620
1825
  this.patch({
1621
1826
  busy: { deploying: false },
1622
1827
  statusText: `Deployment submitted: ${result.itemHash}`,
1623
1828
  lastDeploymentHash: result.itemHash
1624
1829
  });
1830
+ this.emitProgress({
1831
+ stage: "refreshing-instances",
1832
+ label: "Refreshing instances",
1833
+ progress: 92,
1834
+ status: "info",
1835
+ itemHash: result.itemHash,
1836
+ detail: "Reloading deployments and runtime state."
1837
+ });
1625
1838
  await this.refresh();
1839
+ this.emitProgress({
1840
+ stage: "completed",
1841
+ label: "Deployment completed",
1842
+ progress: 100,
1843
+ status: result.status === "rejected" ? "error" : "success",
1844
+ itemHash: result.itemHash,
1845
+ detail: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : "Deployment submitted and synced back into the panel.",
1846
+ error: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : null
1847
+ });
1626
1848
  } catch (error) {
1627
1849
  this.patch({
1628
1850
  busy: { deploying: false },
1629
1851
  errorText: error instanceof Error ? error.message : String(error),
1630
1852
  statusText: "Deployment failed"
1631
1853
  });
1854
+ this.emitProgress({
1855
+ stage: "error",
1856
+ label: "Deployment failed",
1857
+ progress: 100,
1858
+ status: "error",
1859
+ error: error instanceof Error ? error.message : String(error)
1860
+ });
1632
1861
  }
1633
1862
  }
1634
1863
  async deleteInstance(instanceHash) {
@@ -1642,6 +1871,14 @@ var SponsorRelayController = class {
1642
1871
  statusText: `Deleting ${instanceHash}`
1643
1872
  });
1644
1873
  try {
1874
+ this.emitProgress({
1875
+ stage: "validating",
1876
+ label: "Validating delete request",
1877
+ progress: 6,
1878
+ status: "warning",
1879
+ itemHash: instanceHash,
1880
+ detail: "Preparing Aleph FORGET message."
1881
+ });
1645
1882
  await forgetAlephMessages({
1646
1883
  sender: this.state.wallet.address,
1647
1884
  hashes: [instanceHash],
@@ -1653,13 +1890,33 @@ var SponsorRelayController = class {
1653
1890
  status: response.status,
1654
1891
  json: async () => await response.json()
1655
1892
  })),
1656
- apiHost: this.client.apiHost
1893
+ apiHost: this.client.apiHost,
1894
+ onProgress: (event) => {
1895
+ this.emitProgress(event);
1896
+ }
1657
1897
  });
1658
1898
  this.patch({
1659
1899
  busy: { deletingInstanceHash: null },
1660
1900
  statusText: `Deletion submitted for ${instanceHash}`
1661
1901
  });
1902
+ this.emitProgress({
1903
+ stage: "refreshing-instances",
1904
+ label: "Refreshing instances after delete",
1905
+ progress: 92,
1906
+ status: "info",
1907
+ itemHash: instanceHash,
1908
+ detail: "Reloading current deployments."
1909
+ });
1662
1910
  await this.refresh();
1911
+ this.emitProgress({
1912
+ stage: "completed",
1913
+ label: "Delete completed",
1914
+ progress: 100,
1915
+ status: "success",
1916
+ itemHash: instanceHash,
1917
+ detail: "Delete request submitted and deployments refreshed.",
1918
+ error: null
1919
+ });
1663
1920
  } catch (error) {
1664
1921
  this.patch({
1665
1922
  busy: { deletingInstanceHash: null },
@@ -1708,13 +1965,10 @@ function useSponsorRelayController(props) {
1708
1965
 
1709
1966
  // src/react/SponsorRelayFab.tsx
1710
1967
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1711
- var panelStyle = {
1968
+ var basePanelStyle = {
1712
1969
  position: "fixed",
1713
- right: "1.4rem",
1714
- bottom: "11.5rem",
1715
1970
  zIndex: 9999,
1716
1971
  width: "min(28rem, calc(100vw - 2rem))",
1717
- maxHeight: "calc(100vh - 12.5rem)",
1718
1972
  overflow: "auto",
1719
1973
  border: "1px solid rgba(255,255,255,0.12)",
1720
1974
  borderRadius: "1.4rem",
@@ -1735,33 +1989,282 @@ var fieldStyle = {
1735
1989
  fontSize: "0.98rem",
1736
1990
  lineHeight: 1.45
1737
1991
  };
1992
+ function progressToneColor(status) {
1993
+ switch (status) {
1994
+ case "success":
1995
+ return "#37d67a";
1996
+ case "warning":
1997
+ return "#ffc83f";
1998
+ case "error":
1999
+ return "#e91315";
2000
+ default:
2001
+ return "#0176ce";
2002
+ }
2003
+ }
2004
+ function progressBadgeLabel(stage) {
2005
+ switch (stage) {
2006
+ case "building-delete-message":
2007
+ case "signing-delete-message":
2008
+ case "broadcasting-delete":
2009
+ case "delete-completed":
2010
+ return "DELETE";
2011
+ case "error":
2012
+ return "ERROR";
2013
+ case "completed":
2014
+ return "DONE";
2015
+ default:
2016
+ return "DEPLOY";
2017
+ }
2018
+ }
2019
+ function launcherIndicator(state) {
2020
+ if (state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed") {
2021
+ return {
2022
+ label: state.deploymentProgress.progress > 0 ? `${Math.round(state.deploymentProgress.progress)}%` : progressBadgeLabel(state.deploymentProgress.stage),
2023
+ detail: state.deploymentProgress.label,
2024
+ tone: state.deploymentProgress.status
2025
+ };
2026
+ }
2027
+ if (state.deploymentProgress.stage === "completed") {
2028
+ return {
2029
+ label: "DONE",
2030
+ detail: state.deploymentProgress.label,
2031
+ tone: state.deploymentProgress.status === "error" ? "error" : "success"
2032
+ };
2033
+ }
2034
+ if (state.errorText) {
2035
+ return {
2036
+ label: "ERR",
2037
+ detail: state.errorText,
2038
+ tone: "error"
2039
+ };
2040
+ }
2041
+ if (!state.wallet.connected) {
2042
+ return {
2043
+ label: "WALLET",
2044
+ detail: "Connect MetaMask",
2045
+ tone: "warning"
2046
+ };
2047
+ }
2048
+ if (state.rootfsHealth.tone === "error") {
2049
+ return {
2050
+ label: "ROOTFS",
2051
+ detail: state.rootfsHealth.label,
2052
+ tone: "error"
2053
+ };
2054
+ }
2055
+ if (state.rootfsHealth.tone === "caution") {
2056
+ return {
2057
+ label: "CHECK",
2058
+ detail: state.rootfsHealth.label,
2059
+ tone: "warning"
2060
+ };
2061
+ }
2062
+ if (state.busy.refreshing) {
2063
+ return {
2064
+ label: "SYNC",
2065
+ detail: "Refreshing relay state",
2066
+ tone: "info"
2067
+ };
2068
+ }
2069
+ return {
2070
+ label: "READY",
2071
+ detail: state.selectedCrn?.name ?? "Ready to deploy",
2072
+ tone: "success"
2073
+ };
2074
+ }
1738
2075
  function SponsorRelayFab(props) {
1739
2076
  const { controller, state } = useSponsorRelayController(props);
1740
2077
  const launcherMode = props.launcherMode ?? "floating";
2078
+ const indicator = launcherIndicator(state);
2079
+ const [successFlash, setSuccessFlash] = React.useState(false);
2080
+ const [hovered, setHovered] = React.useState(false);
2081
+ const launcherRef = React.useRef(null);
2082
+ const [inlinePanelStyle, setInlinePanelStyle] = React.useState(null);
2083
+ const [compactInlineLabel, setCompactInlineLabel] = React.useState(false);
2084
+ React.useEffect(() => {
2085
+ if (state.deploymentProgress.stage !== "completed" || state.deploymentProgress.status !== "success") {
2086
+ return;
2087
+ }
2088
+ setSuccessFlash(true);
2089
+ const timeout = window.setTimeout(() => {
2090
+ setSuccessFlash(false);
2091
+ }, 1400);
2092
+ return () => {
2093
+ window.clearTimeout(timeout);
2094
+ };
2095
+ }, [
2096
+ state.deploymentProgress.stage,
2097
+ state.deploymentProgress.status,
2098
+ state.deploymentProgress.timestamp
2099
+ ]);
2100
+ React.useEffect(() => {
2101
+ if (launcherMode !== "inline" || !state.open) {
2102
+ return;
2103
+ }
2104
+ const updateInlinePanelStyle = () => {
2105
+ const launcher = launcherRef.current;
2106
+ if (launcher == null) {
2107
+ return;
2108
+ }
2109
+ const rect = launcher.getBoundingClientRect();
2110
+ const viewportWidth = window.innerWidth;
2111
+ const viewportHeight = window.innerHeight;
2112
+ const anchorRight = Math.max(16, viewportWidth - rect.right);
2113
+ const top = Math.max(72, rect.bottom + 12);
2114
+ setInlinePanelStyle({
2115
+ ...basePanelStyle,
2116
+ top,
2117
+ right: anchorRight,
2118
+ maxHeight: `calc(100vh - ${Math.min(viewportHeight - 16, top + 16)}px)`,
2119
+ width: "min(30rem, calc(100vw - 1.5rem))"
2120
+ });
2121
+ };
2122
+ updateInlinePanelStyle();
2123
+ window.addEventListener("resize", updateInlinePanelStyle);
2124
+ window.addEventListener("scroll", updateInlinePanelStyle, true);
2125
+ return () => {
2126
+ window.removeEventListener("resize", updateInlinePanelStyle);
2127
+ window.removeEventListener("scroll", updateInlinePanelStyle, true);
2128
+ };
2129
+ }, [launcherMode, state.open]);
2130
+ React.useEffect(() => {
2131
+ if (launcherMode !== "inline") {
2132
+ setCompactInlineLabel(false);
2133
+ return;
2134
+ }
2135
+ const updateCompactInlineLabel = () => {
2136
+ setCompactInlineLabel(window.innerWidth < 1240);
2137
+ };
2138
+ updateCompactInlineLabel();
2139
+ window.addEventListener("resize", updateCompactInlineLabel);
2140
+ return () => {
2141
+ window.removeEventListener("resize", updateCompactInlineLabel);
2142
+ };
2143
+ }, [launcherMode]);
2144
+ const progressActive = state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed" && state.deploymentProgress.stage !== "error";
2145
+ const pulseScale = progressActive ? 1.03 : hovered ? 1.015 : 1;
2146
+ 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;
2147
+ 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%)";
2148
+ 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)";
2149
+ 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)";
2150
+ 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)";
2151
+ const panelStyle = launcherMode === "inline" ? inlinePanelStyle ?? {
2152
+ ...basePanelStyle,
2153
+ top: "4.75rem",
2154
+ right: "1rem",
2155
+ maxHeight: "calc(100vh - 6rem)",
2156
+ width: "min(30rem, calc(100vw - 1.5rem))"
2157
+ } : {
2158
+ ...basePanelStyle,
2159
+ right: "1.4rem",
2160
+ bottom: "11.5rem",
2161
+ maxHeight: "calc(100vh - 12.5rem)"
2162
+ };
2163
+ const launcherLabel = launcherMode === "inline" && compactInlineLabel ? "Relay" : "Sponsor Relay";
1741
2164
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1742
- /* @__PURE__ */ jsx(
2165
+ /* @__PURE__ */ jsxs(
1743
2166
  "button",
1744
2167
  {
2168
+ ref: launcherRef,
1745
2169
  type: "button",
1746
2170
  onClick: () => controller.toggleOpen(),
2171
+ onMouseEnter: () => setHovered(true),
2172
+ onMouseLeave: () => setHovered(false),
2173
+ onFocus: () => setHovered(true),
2174
+ onBlur: () => setHovered(false),
2175
+ title: indicator.detail ?? "Sponsor Relay",
1747
2176
  style: {
1748
2177
  position: launcherMode === "floating" ? "fixed" : "relative",
1749
2178
  right: launcherMode === "floating" ? "1.4rem" : void 0,
1750
2179
  bottom: launcherMode === "floating" ? "5.8rem" : void 0,
1751
2180
  zIndex: launcherMode === "floating" ? 1e4 : "auto",
1752
2181
  borderRadius: "999px",
1753
- border: "2px solid rgba(255,255,255,0.9)",
1754
- background: "linear-gradient(135deg, #e91315 0%, #ffc83f 100%)",
2182
+ border: launcherMode === "floating" ? "2px solid rgba(255,255,255,0.9)" : inlineButtonBorder,
2183
+ background: launcherMode === "floating" ? "linear-gradient(135deg, #e91315 0%, #ffc83f 100%)" : inlineButtonBackground,
1755
2184
  color: "white",
1756
- padding: launcherMode === "floating" ? "0.9rem 1.2rem" : "0.55rem 0.9rem",
2185
+ minHeight: launcherMode === "floating" ? void 0 : "2.25rem",
2186
+ padding: launcherMode === "floating" ? "0.9rem 1.2rem" : "0.42rem 0.56rem 0.42rem 0.76rem",
1757
2187
  fontFamily: '"Epilogue", sans-serif',
1758
2188
  fontWeight: 700,
1759
- fontSize: launcherMode === "floating" ? "0.84rem" : "0.78rem",
2189
+ fontSize: launcherMode === "floating" ? "0.84rem" : "0.73rem",
1760
2190
  textTransform: "uppercase",
1761
2191
  letterSpacing: "0.08em",
1762
- cursor: "pointer"
2192
+ cursor: "pointer",
2193
+ display: "inline-flex",
2194
+ alignItems: "center",
2195
+ justifyContent: "center",
2196
+ gap: launcherMode === "floating" ? "0.45rem" : "0.34rem",
2197
+ boxShadow: pulseShadow,
2198
+ transform: `translateY(${hovered && !progressActive ? "-1px" : "0"}) scale(${pulseScale})`,
2199
+ transition: "transform 180ms ease, box-shadow 220ms ease, background 220ms ease, border-color 220ms ease"
1763
2200
  },
1764
- children: "Sponsor Relay"
2201
+ children: [
2202
+ /* @__PURE__ */ jsxs(
2203
+ "span",
2204
+ {
2205
+ style: {
2206
+ display: "inline-flex",
2207
+ alignItems: "center",
2208
+ gap: "0.36rem"
2209
+ },
2210
+ children: [
2211
+ /* @__PURE__ */ jsx(
2212
+ "span",
2213
+ {
2214
+ "aria-hidden": "true",
2215
+ style: {
2216
+ width: launcherMode === "floating" ? "0.5rem" : "0.42rem",
2217
+ height: launcherMode === "floating" ? "0.5rem" : "0.42rem",
2218
+ borderRadius: "999px",
2219
+ background: launcherMode === "floating" ? "rgba(255,255,255,0.96)" : "#f59e0b",
2220
+ boxShadow: launcherMode === "floating" ? "0 0 0 3px rgba(255,255,255,0.22)" : "0 0 0 3px rgba(245, 158, 11, 0.18)"
2221
+ }
2222
+ }
2223
+ ),
2224
+ /* @__PURE__ */ jsx("span", { children: launcherLabel })
2225
+ ]
2226
+ }
2227
+ ),
2228
+ /* @__PURE__ */ jsxs(
2229
+ "span",
2230
+ {
2231
+ style: {
2232
+ display: "inline-flex",
2233
+ alignItems: "center",
2234
+ gap: "0.36rem",
2235
+ padding: launcherMode === "floating" ? "0.2rem 0.42rem" : "0.21rem 0.5rem",
2236
+ minWidth: launcherMode === "floating" ? void 0 : "3.8rem",
2237
+ borderRadius: "999px",
2238
+ background: launcherMode === "floating" ? "rgba(15, 23, 42, 0.18)" : inlineBadgeBackground,
2239
+ border: launcherMode === "floating" ? "1px solid rgba(255,255,255,0.18)" : inlineBadgeBorder,
2240
+ fontSize: launcherMode === "floating" ? "0.62rem" : "0.61rem",
2241
+ fontWeight: launcherMode === "floating" ? 700 : 800,
2242
+ lineHeight: 1,
2243
+ letterSpacing: "0.08em",
2244
+ justifyContent: "center",
2245
+ boxShadow: launcherMode === "floating" ? void 0 : `inset 0 1px 0 rgba(255,255,255,0.08), 0 6px 16px ${progressToneColor(indicator.tone)}22`
2246
+ },
2247
+ children: [
2248
+ /* @__PURE__ */ jsx(
2249
+ "span",
2250
+ {
2251
+ "aria-hidden": "true",
2252
+ style: {
2253
+ width: "0.42rem",
2254
+ height: "0.42rem",
2255
+ borderRadius: "999px",
2256
+ background: progressToneColor(indicator.tone),
2257
+ boxShadow: `0 0 0 ${progressActive ? "4px" : "3px"} ${progressToneColor(indicator.tone)}22`,
2258
+ transform: `scale(${progressActive ? 1.12 : successFlash ? 1.18 : 1})`,
2259
+ transition: "transform 180ms ease, box-shadow 220ms ease"
2260
+ }
2261
+ }
2262
+ ),
2263
+ /* @__PURE__ */ jsx("span", { children: indicator.label })
2264
+ ]
2265
+ }
2266
+ )
2267
+ ]
1765
2268
  }
1766
2269
  ),
1767
2270
  state.open ? /* @__PURE__ */ jsxs("div", { children: [
@@ -1778,66 +2281,274 @@ function SponsorRelayFab(props) {
1778
2281
  }
1779
2282
  ),
1780
2283
  /* @__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
- ] }),
2284
+ /* @__PURE__ */ jsxs(
2285
+ "div",
2286
+ {
2287
+ style: {
2288
+ display: "flex",
2289
+ justifyContent: "space-between",
2290
+ gap: "0.75rem",
2291
+ alignItems: "center"
2292
+ },
2293
+ children: [
2294
+ /* @__PURE__ */ jsxs("div", { children: [
2295
+ /* @__PURE__ */ jsx(
2296
+ "div",
2297
+ {
2298
+ style: {
2299
+ color: "#9fb2ca",
2300
+ fontSize: "0.72rem",
2301
+ textTransform: "uppercase",
2302
+ letterSpacing: "0.08em"
2303
+ },
2304
+ children: "Aleph VM credit deployer"
2305
+ }
2306
+ ),
2307
+ /* @__PURE__ */ jsx(
2308
+ "h2",
2309
+ {
2310
+ style: {
2311
+ margin: "0.2rem 0 0",
2312
+ fontFamily: '"Epilogue", sans-serif'
2313
+ },
2314
+ children: "Sponsor Relay"
2315
+ }
2316
+ )
2317
+ ] }),
2318
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void controller.refresh(), children: state.busy.refreshing ? "Syncing" : "Refresh" })
2319
+ ]
2320
+ }
2321
+ ),
1788
2322
  /* @__PURE__ */ jsx("p", { style: { color: "#9fb2ca" }, children: state.statusText }),
1789
2323
  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
- ] }),
2324
+ state.deploymentProgress.stage !== "idle" ? /* @__PURE__ */ jsxs(
2325
+ "div",
2326
+ {
2327
+ style: {
2328
+ marginTop: "0.85rem",
2329
+ padding: "0.75rem 0.8rem",
2330
+ borderRadius: "1rem",
2331
+ border: "1px solid rgba(255,255,255,0.1)",
2332
+ background: "linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.03))",
2333
+ boxShadow: "inset 0 1px 0 rgba(255,255,255,0.04)"
2334
+ },
2335
+ children: [
2336
+ /* @__PURE__ */ jsxs(
2337
+ "div",
2338
+ {
2339
+ style: {
2340
+ display: "flex",
2341
+ justifyContent: "space-between",
2342
+ gap: "0.75rem",
2343
+ alignItems: "center"
2344
+ },
2345
+ children: [
2346
+ /* @__PURE__ */ jsxs(
2347
+ "div",
2348
+ {
2349
+ style: {
2350
+ display: "flex",
2351
+ gap: "0.5rem",
2352
+ alignItems: "center",
2353
+ minWidth: 0
2354
+ },
2355
+ children: [
2356
+ /* @__PURE__ */ jsx(
2357
+ "span",
2358
+ {
2359
+ style: {
2360
+ display: "inline-flex",
2361
+ alignItems: "center",
2362
+ justifyContent: "center",
2363
+ minWidth: "3.9rem",
2364
+ padding: "0.22rem 0.45rem",
2365
+ borderRadius: "999px",
2366
+ background: `${progressToneColor(state.deploymentProgress.status)}22`,
2367
+ color: progressToneColor(
2368
+ state.deploymentProgress.status
2369
+ ),
2370
+ fontSize: "0.64rem",
2371
+ fontWeight: 800,
2372
+ letterSpacing: "0.08em",
2373
+ textTransform: "uppercase"
2374
+ },
2375
+ children: progressBadgeLabel(state.deploymentProgress.stage)
2376
+ }
2377
+ ),
2378
+ /* @__PURE__ */ jsx(
2379
+ "strong",
2380
+ {
2381
+ style: {
2382
+ fontSize: "0.84rem",
2383
+ lineHeight: 1.2,
2384
+ whiteSpace: "nowrap",
2385
+ overflow: "hidden",
2386
+ textOverflow: "ellipsis"
2387
+ },
2388
+ children: state.deploymentProgress.label
2389
+ }
2390
+ )
2391
+ ]
2392
+ }
2393
+ ),
2394
+ /* @__PURE__ */ jsxs(
2395
+ "span",
2396
+ {
2397
+ style: {
2398
+ fontSize: "0.72rem",
2399
+ color: "#9fb2ca",
2400
+ fontFamily: '"DM Mono", monospace'
2401
+ },
2402
+ children: [
2403
+ String(
2404
+ Math.round(state.deploymentProgress.progress)
2405
+ ).padStart(3, " "),
2406
+ "%"
2407
+ ]
2408
+ }
2409
+ )
2410
+ ]
2411
+ }
2412
+ ),
2413
+ /* @__PURE__ */ jsx(
2414
+ "div",
2415
+ {
2416
+ style: {
2417
+ marginTop: "0.5rem",
2418
+ width: "100%",
2419
+ height: "0.34rem",
2420
+ borderRadius: "999px",
2421
+ background: "rgba(255,255,255,0.08)",
2422
+ overflow: "hidden"
2423
+ },
2424
+ children: /* @__PURE__ */ jsx(
2425
+ "div",
2426
+ {
2427
+ style: {
2428
+ width: `${Math.max(0, Math.min(100, state.deploymentProgress.progress))}%`,
2429
+ height: "100%",
2430
+ borderRadius: "999px",
2431
+ background: progressToneColor(
2432
+ state.deploymentProgress.status
2433
+ ),
2434
+ transition: "width 180ms ease"
2435
+ }
2436
+ }
2437
+ )
2438
+ }
2439
+ ),
2440
+ /* @__PURE__ */ jsxs(
2441
+ "div",
2442
+ {
2443
+ style: {
2444
+ marginTop: "0.45rem",
2445
+ display: "grid",
2446
+ gap: "0.28rem"
2447
+ },
2448
+ children: [
2449
+ state.deploymentProgress.itemHash ? /* @__PURE__ */ jsxs(
2450
+ "div",
2451
+ {
2452
+ style: {
2453
+ color: "#9fb2ca",
2454
+ fontSize: "0.7rem",
2455
+ fontFamily: '"DM Mono", monospace',
2456
+ whiteSpace: "nowrap",
2457
+ overflow: "hidden",
2458
+ textOverflow: "ellipsis"
2459
+ },
2460
+ children: [
2461
+ "hash ",
2462
+ shortHash(state.deploymentProgress.itemHash, 10, 8)
2463
+ ]
2464
+ }
2465
+ ) : null,
2466
+ state.deploymentProgress.detail ? /* @__PURE__ */ jsx(
2467
+ "div",
2468
+ {
2469
+ style: {
2470
+ color: "#9fb2ca",
2471
+ fontSize: "0.72rem",
2472
+ lineHeight: 1.3,
2473
+ fontFamily: state.deploymentProgress.detail.includes("0x") || state.deploymentProgress.detail.includes("Qm") ? '"DM Mono", monospace' : void 0
2474
+ },
2475
+ children: state.deploymentProgress.detail
2476
+ }
2477
+ ) : null,
2478
+ state.deploymentProgress.error ? /* @__PURE__ */ jsx(
2479
+ "div",
2480
+ {
2481
+ style: {
2482
+ color: "#ffd9d9",
2483
+ fontSize: "0.72rem",
2484
+ lineHeight: 1.3
2485
+ },
2486
+ children: state.deploymentProgress.error
2487
+ }
2488
+ ) : null
2489
+ ]
2490
+ }
2491
+ )
2492
+ ]
2493
+ }
2494
+ ) : null,
2495
+ /* @__PURE__ */ jsxs(
2496
+ "div",
2497
+ {
2498
+ style: { display: "grid", gap: "0.75rem", marginTop: "0.9rem" },
2499
+ children: [
2500
+ /* @__PURE__ */ jsx(
2501
+ "input",
2502
+ {
2503
+ style: fieldStyle,
2504
+ value: state.manifestUrl,
2505
+ onChange: (event) => controller.setManifestUrl(event.currentTarget.value),
2506
+ placeholder: "Manifest URL"
2507
+ }
2508
+ ),
2509
+ /* @__PURE__ */ jsx(
2510
+ "input",
2511
+ {
2512
+ style: fieldStyle,
2513
+ value: state.instanceName,
2514
+ onChange: (event) => controller.setInstanceName(event.currentTarget.value),
2515
+ placeholder: "Instance name"
2516
+ }
2517
+ ),
2518
+ /* @__PURE__ */ jsx(
2519
+ "select",
2520
+ {
2521
+ style: fieldStyle,
2522
+ value: state.pricingSummary.tier?.id ?? state.tierId,
2523
+ onChange: (event) => controller.setTierId(event.currentTarget.value),
2524
+ children: (state.pricingSummary.pricing?.tiers ?? []).map((tier) => /* @__PURE__ */ jsx("option", { value: tier.id, children: tier.id }, tier.id))
2525
+ }
2526
+ ),
2527
+ /* @__PURE__ */ jsx(
2528
+ "textarea",
2529
+ {
2530
+ style: fieldStyle,
2531
+ rows: 3,
2532
+ value: state.sshPublicKey,
2533
+ onChange: (event) => controller.setSshPublicKey(event.currentTarget.value),
2534
+ placeholder: "SSH public key"
2535
+ }
2536
+ ),
2537
+ /* @__PURE__ */ jsxs("details", { children: [
2538
+ /* @__PURE__ */ jsx("summary", { children: "Paste Manifest" }),
2539
+ /* @__PURE__ */ jsx(
2540
+ "textarea",
2541
+ {
2542
+ style: { ...fieldStyle, marginTop: "0.65rem" },
2543
+ rows: 7,
2544
+ value: state.manifestJson,
2545
+ onChange: (event) => controller.setManifestJson(event.currentTarget.value)
2546
+ }
2547
+ )
2548
+ ] })
2549
+ ]
2550
+ }
2551
+ ),
1841
2552
  /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.55rem", marginTop: "1rem" }, children: [
1842
2553
  /* @__PURE__ */ jsxs("div", { children: [
1843
2554
  formatNumber(state.pricingSummary.availableCredits, 0),
@@ -1863,40 +2574,68 @@ function SponsorRelayFab(props) {
1863
2574
  "Latest deployment: ",
1864
2575
  shortHash(state.lastDeploymentHash)
1865
2576
  ] }) : 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
2577
+ state.showInstances ? /* @__PURE__ */ jsx(
2578
+ "div",
2579
+ {
2580
+ style: { marginTop: "1rem", display: "grid", gap: "0.7rem" },
2581
+ children: state.instances.map((entry) => /* @__PURE__ */ jsxs("details", { open: true, children: [
2582
+ /* @__PURE__ */ jsx("summary", { children: (entry.instance.content?.metadata?.name ?? "relay") + " \xB7 " + shortHash(entry.instance.item_hash) }),
2583
+ /* @__PURE__ */ jsxs(
2584
+ "div",
2585
+ {
2586
+ style: {
2587
+ display: "grid",
2588
+ gap: "0.35rem",
2589
+ marginTop: "0.55rem"
2590
+ },
2591
+ children: [
2592
+ /* @__PURE__ */ jsxs("div", { children: [
2593
+ "Status: ",
2594
+ entry.details.messageStatus
2595
+ ] }),
2596
+ /* @__PURE__ */ jsxs("div", { children: [
2597
+ "Host IPv4: ",
2598
+ entry.details.hostIpv4 ?? "-"
2599
+ ] }),
2600
+ /* @__PURE__ */ jsxs("div", { children: [
2601
+ "IPv6: ",
2602
+ entry.details.ipv6 ?? "-"
2603
+ ] }),
2604
+ /* @__PURE__ */ jsxs("div", { children: [
2605
+ "VM IPv4: ",
2606
+ entry.details.vmIpv4 ?? "-"
2607
+ ] }),
2608
+ /* @__PURE__ */ jsxs("div", { children: [
2609
+ "SSH: ",
2610
+ entry.details.sshCommand ?? "-"
2611
+ ] }),
2612
+ /* @__PURE__ */ jsxs("div", { children: [
2613
+ "Ports: ",
2614
+ joinMappedPorts(entry.details.mappedPorts)
2615
+ ] }),
2616
+ /* @__PURE__ */ jsxs("div", { children: [
2617
+ "Submitted:",
2618
+ " ",
2619
+ formatDateTime(
2620
+ entry.instance.reception_time ?? entry.instance.time
2621
+ )
2622
+ ] }),
2623
+ /* @__PURE__ */ jsx(
2624
+ "button",
2625
+ {
2626
+ type: "button",
2627
+ onClick: () => void controller.deleteInstance(
2628
+ entry.instance.item_hash
2629
+ ),
2630
+ children: state.busy.deletingInstanceHash === entry.instance.item_hash ? "Deleting\u2026" : "Delete"
2631
+ }
2632
+ )
2633
+ ]
2634
+ }
2635
+ )
2636
+ ] }, entry.instance.item_hash))
2637
+ }
2638
+ ) : null
1900
2639
  ] })
1901
2640
  ] }) : null
1902
2641
  ] });