@le-space/ui 0.1.59 → 0.1.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/react/index.d.ts +4 -0
- package/react/index.js +148 -58
- package/shared/index.d.ts +10 -1
- package/shared/index.js +127 -56
package/package.json
CHANGED
package/react/index.d.ts
CHANGED
|
@@ -296,6 +296,7 @@ declare class SponsorRelayController {
|
|
|
296
296
|
private stopWalletWatch;
|
|
297
297
|
private props;
|
|
298
298
|
private progressEmitter;
|
|
299
|
+
private runtimeCooldownByHash;
|
|
299
300
|
constructor(props?: SponsorRelayProps);
|
|
300
301
|
subscribeToDeploymentProgress(listener: DeploymentProgressListener): () => void;
|
|
301
302
|
subscribe(subscriber: SponsorRelaySubscriber): () => void;
|
|
@@ -303,6 +304,9 @@ declare class SponsorRelayController {
|
|
|
303
304
|
private emit;
|
|
304
305
|
private patch;
|
|
305
306
|
private emitProgress;
|
|
307
|
+
private syncLatestDeploymentProgress;
|
|
308
|
+
private canSkipRuntimeRefresh;
|
|
309
|
+
private noteRuntimeRefreshResult;
|
|
306
310
|
init(): Promise<void>;
|
|
307
311
|
destroy(): void;
|
|
308
312
|
setOpen(open: boolean): void;
|
package/react/index.js
CHANGED
|
@@ -32,6 +32,9 @@ var IDLE_DEPLOYMENT_PROGRESS = {
|
|
|
32
32
|
};
|
|
33
33
|
var REFRESH_INTERVAL_MS = 3e4;
|
|
34
34
|
var RELAY_PING_INTERVAL_MS = 2e4;
|
|
35
|
+
var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
|
|
36
|
+
var STALE_INSTANCE_ALLOCATION_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
37
|
+
var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
35
38
|
|
|
36
39
|
// src/shared/format.ts
|
|
37
40
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -1514,6 +1517,15 @@ async function inspectInstanceRuntime(args) {
|
|
|
1514
1517
|
};
|
|
1515
1518
|
}
|
|
1516
1519
|
}
|
|
1520
|
+
function instanceTimestampMs(instance) {
|
|
1521
|
+
const value = instance.reception_time ?? instance.time;
|
|
1522
|
+
if (!value) return null;
|
|
1523
|
+
if (typeof value === "number") {
|
|
1524
|
+
return value > 0 && value < 1e10 ? value * 1e3 : value;
|
|
1525
|
+
}
|
|
1526
|
+
const parsed = Date.parse(value);
|
|
1527
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
1528
|
+
}
|
|
1517
1529
|
function compatibleCrnsForTier(crns, state) {
|
|
1518
1530
|
if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
|
|
1519
1531
|
return [];
|
|
@@ -1575,6 +1587,9 @@ async function pingPeer(libp2p) {
|
|
|
1575
1587
|
};
|
|
1576
1588
|
}
|
|
1577
1589
|
}
|
|
1590
|
+
function hasUsableRuntime(details) {
|
|
1591
|
+
return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1592
|
+
}
|
|
1578
1593
|
var SponsorRelayController = class {
|
|
1579
1594
|
state;
|
|
1580
1595
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1584,6 +1599,7 @@ var SponsorRelayController = class {
|
|
|
1584
1599
|
stopWalletWatch = null;
|
|
1585
1600
|
props;
|
|
1586
1601
|
progressEmitter = createDeploymentProgressEmitter();
|
|
1602
|
+
runtimeCooldownByHash = /* @__PURE__ */ new Map();
|
|
1587
1603
|
constructor(props = {}) {
|
|
1588
1604
|
this.props = props;
|
|
1589
1605
|
this.state = defaultState(props);
|
|
@@ -1633,6 +1649,86 @@ var SponsorRelayController = class {
|
|
|
1633
1649
|
});
|
|
1634
1650
|
this.progressEmitter.emit(nextEvent);
|
|
1635
1651
|
}
|
|
1652
|
+
syncLatestDeploymentProgress(instances) {
|
|
1653
|
+
const itemHash = this.state.lastDeploymentHash;
|
|
1654
|
+
if (!itemHash) {
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
const latest = instances.find((entry) => entry.instance.item_hash === itemHash);
|
|
1658
|
+
if (!latest) {
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
const status = latest.details.messageStatus;
|
|
1662
|
+
if (status === "rejected") {
|
|
1663
|
+
this.emitProgress({
|
|
1664
|
+
stage: "deployment-rejected",
|
|
1665
|
+
label: "Deployment rejected",
|
|
1666
|
+
progress: 100,
|
|
1667
|
+
status: "error",
|
|
1668
|
+
itemHash,
|
|
1669
|
+
detail: "Aleph rejected the deployment.",
|
|
1670
|
+
error: "Aleph rejected the deployment."
|
|
1671
|
+
});
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
if (status !== "processed") {
|
|
1675
|
+
this.emitProgress({
|
|
1676
|
+
stage: "waiting-for-aleph",
|
|
1677
|
+
label: "Waiting for Aleph",
|
|
1678
|
+
progress: 76,
|
|
1679
|
+
status: "info",
|
|
1680
|
+
itemHash,
|
|
1681
|
+
detail: "Deployment submitted. Waiting for Aleph to process the instance message."
|
|
1682
|
+
});
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
if (!hasUsableRuntime(latest.details)) {
|
|
1686
|
+
this.emitProgress({
|
|
1687
|
+
stage: "deployment-confirmed",
|
|
1688
|
+
label: "Waiting for runtime",
|
|
1689
|
+
progress: 88,
|
|
1690
|
+
status: "warning",
|
|
1691
|
+
itemHash,
|
|
1692
|
+
detail: "Aleph processed the deployment. Waiting for scheduler/runtime allocation details."
|
|
1693
|
+
});
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
this.emitProgress({
|
|
1697
|
+
stage: "completed",
|
|
1698
|
+
label: "Runtime ready",
|
|
1699
|
+
progress: 100,
|
|
1700
|
+
status: "success",
|
|
1701
|
+
itemHash,
|
|
1702
|
+
detail: "Deployment processed and runtime networking is available."
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
canSkipRuntimeRefresh(instance) {
|
|
1706
|
+
if (instance.item_hash === this.state.lastDeploymentHash) {
|
|
1707
|
+
return false;
|
|
1708
|
+
}
|
|
1709
|
+
const cooldownUntil = this.runtimeCooldownByHash.get(instance.item_hash);
|
|
1710
|
+
if (!cooldownUntil) {
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1713
|
+
return cooldownUntil > Date.now();
|
|
1714
|
+
}
|
|
1715
|
+
noteRuntimeRefreshResult(instance, details) {
|
|
1716
|
+
const hasRuntimeData = Boolean(details.crnUrl) || Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1717
|
+
if (hasRuntimeData || details.error || details.messageStatus !== "processed") {
|
|
1718
|
+
this.runtimeCooldownByHash.delete(instance.item_hash);
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
const timestampMs = instanceTimestampMs(instance);
|
|
1722
|
+
const isRecent = timestampMs != null && Date.now() - timestampMs < RECENT_INSTANCE_RUNTIME_GRACE_MS;
|
|
1723
|
+
if (isRecent) {
|
|
1724
|
+
this.runtimeCooldownByHash.delete(instance.item_hash);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
this.runtimeCooldownByHash.set(
|
|
1728
|
+
instance.item_hash,
|
|
1729
|
+
Date.now() + STALE_INSTANCE_ALLOCATION_COOLDOWN_MS
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1636
1732
|
async init() {
|
|
1637
1733
|
this.stopWalletWatch = watchWallet(() => {
|
|
1638
1734
|
void this.refreshWalletDerivedState();
|
|
@@ -1752,25 +1848,11 @@ var SponsorRelayController = class {
|
|
|
1752
1848
|
statusText: "Refreshing relay sponsor data"
|
|
1753
1849
|
});
|
|
1754
1850
|
try {
|
|
1755
|
-
this.emitProgress({
|
|
1756
|
-
stage: "loading-manifest",
|
|
1757
|
-
label: "Loading manifest",
|
|
1758
|
-
progress: 8,
|
|
1759
|
-
status: "info",
|
|
1760
|
-
detail: this.state.manifestUrl
|
|
1761
|
-
});
|
|
1762
1851
|
const manifestState = await resolveManifest({
|
|
1763
1852
|
manifestUrl: this.state.manifestUrl,
|
|
1764
1853
|
manifestJson: this.state.manifestJson
|
|
1765
1854
|
});
|
|
1766
1855
|
const manifest = manifestState.manifest;
|
|
1767
|
-
this.emitProgress({
|
|
1768
|
-
stage: "loading-pricing",
|
|
1769
|
-
label: "Loading pricing and CRNs",
|
|
1770
|
-
progress: 18,
|
|
1771
|
-
status: "info",
|
|
1772
|
-
detail: "Fetching live Aleph pricing and compatible CRNs."
|
|
1773
|
-
});
|
|
1774
1856
|
const [pricingSummary, crns] = await Promise.all([
|
|
1775
1857
|
fetchInstancePricing(this.client.apiHost),
|
|
1776
1858
|
this.client.fetchCrns()
|
|
@@ -1778,39 +1860,43 @@ var SponsorRelayController = class {
|
|
|
1778
1860
|
let balance = this.state.balance;
|
|
1779
1861
|
let instances = [];
|
|
1780
1862
|
if (this.state.wallet.address) {
|
|
1781
|
-
this.emitProgress({
|
|
1782
|
-
stage: "loading-balance",
|
|
1783
|
-
label: "Loading wallet balance",
|
|
1784
|
-
progress: 28,
|
|
1785
|
-
status: "info",
|
|
1786
|
-
detail: this.state.wallet.address
|
|
1787
|
-
});
|
|
1788
1863
|
const [nextBalance, rawInstances] = await Promise.all([
|
|
1789
1864
|
this.client.fetchBalance(this.state.wallet.address),
|
|
1790
1865
|
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
|
|
1791
1866
|
]);
|
|
1792
1867
|
balance = nextBalance;
|
|
1793
1868
|
instances = await Promise.all(
|
|
1794
|
-
rawInstances.map(async (instance) =>
|
|
1795
|
-
instance
|
|
1796
|
-
|
|
1869
|
+
rawInstances.map(async (instance) => {
|
|
1870
|
+
const details = this.canSkipRuntimeRefresh(instance) ? {
|
|
1871
|
+
messageStatus: String(
|
|
1872
|
+
instance.status ?? (instance.confirmed ? "processed" : "pending")
|
|
1873
|
+
).toLowerCase(),
|
|
1874
|
+
allocationSource: null,
|
|
1875
|
+
crnUrl: null,
|
|
1876
|
+
hostIpv4: null,
|
|
1877
|
+
ipv6: null,
|
|
1878
|
+
vmIpv4: null,
|
|
1879
|
+
webUrl: null,
|
|
1880
|
+
sshCommand: null,
|
|
1881
|
+
mappedPorts: [],
|
|
1882
|
+
execution: null,
|
|
1883
|
+
error: null
|
|
1884
|
+
} : await inspectInstanceRuntime({
|
|
1797
1885
|
client: this.client,
|
|
1798
1886
|
instance,
|
|
1799
1887
|
crns
|
|
1800
|
-
})
|
|
1801
|
-
|
|
1888
|
+
});
|
|
1889
|
+
this.noteRuntimeRefreshResult(instance, details);
|
|
1890
|
+
return {
|
|
1891
|
+
instance,
|
|
1892
|
+
details
|
|
1893
|
+
};
|
|
1894
|
+
})
|
|
1802
1895
|
);
|
|
1803
1896
|
}
|
|
1804
1897
|
let rootfsVerified = false;
|
|
1805
1898
|
let rootfsResolution = null;
|
|
1806
1899
|
if (manifestState.valid && manifest) {
|
|
1807
|
-
this.emitProgress({
|
|
1808
|
-
stage: "verifying-rootfs",
|
|
1809
|
-
label: "Verifying rootfs",
|
|
1810
|
-
progress: 38,
|
|
1811
|
-
status: "info",
|
|
1812
|
-
detail: manifest.rootfsItemHash
|
|
1813
|
-
});
|
|
1814
1900
|
rootfsVerified = await verifyRootfsExists(
|
|
1815
1901
|
manifest.rootfsItemHash,
|
|
1816
1902
|
this.client.apiHost
|
|
@@ -1841,26 +1927,13 @@ var SponsorRelayController = class {
|
|
|
1841
1927
|
statusText: "Relay sponsor data ready"
|
|
1842
1928
|
});
|
|
1843
1929
|
this.recomputePricingSummary();
|
|
1844
|
-
this.
|
|
1845
|
-
stage: "completed",
|
|
1846
|
-
label: "Data ready",
|
|
1847
|
-
progress: 100,
|
|
1848
|
-
status: "success",
|
|
1849
|
-
detail: "Manifest, pricing, and wallet state are ready."
|
|
1850
|
-
});
|
|
1930
|
+
this.syncLatestDeploymentProgress(instances);
|
|
1851
1931
|
} catch (error) {
|
|
1852
1932
|
this.patch({
|
|
1853
1933
|
busy: { refreshing: false },
|
|
1854
1934
|
errorText: error instanceof Error ? error.message : String(error),
|
|
1855
1935
|
statusText: "Refresh failed"
|
|
1856
1936
|
});
|
|
1857
|
-
this.emitProgress({
|
|
1858
|
-
stage: "error",
|
|
1859
|
-
label: "Refresh failed",
|
|
1860
|
-
progress: 100,
|
|
1861
|
-
status: "error",
|
|
1862
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1863
|
-
});
|
|
1864
1937
|
}
|
|
1865
1938
|
}
|
|
1866
1939
|
async refreshRelayPing() {
|
|
@@ -1952,15 +2025,6 @@ var SponsorRelayController = class {
|
|
|
1952
2025
|
detail: "Reloading deployments and runtime state."
|
|
1953
2026
|
});
|
|
1954
2027
|
await this.refresh();
|
|
1955
|
-
this.emitProgress({
|
|
1956
|
-
stage: "completed",
|
|
1957
|
-
label: "Deployment completed",
|
|
1958
|
-
progress: 100,
|
|
1959
|
-
status: result.status === "rejected" ? "error" : "success",
|
|
1960
|
-
itemHash: result.itemHash,
|
|
1961
|
-
detail: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : "Deployment submitted and synced back into the panel.",
|
|
1962
|
-
error: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : null
|
|
1963
|
-
});
|
|
1964
2028
|
} catch (error) {
|
|
1965
2029
|
this.patch({
|
|
1966
2030
|
busy: { deploying: false },
|
|
@@ -2132,6 +2196,20 @@ function progressBadgeLabel(stage) {
|
|
|
2132
2196
|
return "DEPLOY";
|
|
2133
2197
|
}
|
|
2134
2198
|
}
|
|
2199
|
+
var COMPLETED_PROGRESS_VISIBLE_MS = 12e3;
|
|
2200
|
+
function isDeploymentProgressVisible(state) {
|
|
2201
|
+
const stage = state.deploymentProgress.stage;
|
|
2202
|
+
if (stage === "idle") {
|
|
2203
|
+
return false;
|
|
2204
|
+
}
|
|
2205
|
+
if (stage === "error") {
|
|
2206
|
+
return true;
|
|
2207
|
+
}
|
|
2208
|
+
if (stage === "completed") {
|
|
2209
|
+
return Date.now() - state.deploymentProgress.timestamp < COMPLETED_PROGRESS_VISIBLE_MS;
|
|
2210
|
+
}
|
|
2211
|
+
return true;
|
|
2212
|
+
}
|
|
2135
2213
|
function launcherIndicator(state) {
|
|
2136
2214
|
if (state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed") {
|
|
2137
2215
|
return {
|
|
@@ -2140,7 +2218,7 @@ function launcherIndicator(state) {
|
|
|
2140
2218
|
tone: state.deploymentProgress.status
|
|
2141
2219
|
};
|
|
2142
2220
|
}
|
|
2143
|
-
if (state.deploymentProgress.stage === "completed") {
|
|
2221
|
+
if (state.deploymentProgress.stage === "completed" && isDeploymentProgressVisible(state)) {
|
|
2144
2222
|
return {
|
|
2145
2223
|
label: "DONE",
|
|
2146
2224
|
detail: state.deploymentProgress.label,
|
|
@@ -2213,6 +2291,17 @@ function SponsorRelayFab(props) {
|
|
|
2213
2291
|
state.deploymentProgress.status,
|
|
2214
2292
|
state.deploymentProgress.timestamp
|
|
2215
2293
|
]);
|
|
2294
|
+
React.useEffect(() => {
|
|
2295
|
+
if (state.deploymentProgress.stage !== "completed") {
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
const timeout = window.setTimeout(() => {
|
|
2299
|
+
setSuccessFlash(false);
|
|
2300
|
+
}, COMPLETED_PROGRESS_VISIBLE_MS);
|
|
2301
|
+
return () => {
|
|
2302
|
+
window.clearTimeout(timeout);
|
|
2303
|
+
};
|
|
2304
|
+
}, [state.deploymentProgress.stage, state.deploymentProgress.timestamp]);
|
|
2216
2305
|
React.useEffect(() => {
|
|
2217
2306
|
if (launcherMode !== "inline" || !state.open) {
|
|
2218
2307
|
return;
|
|
@@ -2258,6 +2347,7 @@ function SponsorRelayFab(props) {
|
|
|
2258
2347
|
};
|
|
2259
2348
|
}, [launcherMode]);
|
|
2260
2349
|
const progressActive = state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed" && state.deploymentProgress.stage !== "error";
|
|
2350
|
+
const deploymentProgressVisible = isDeploymentProgressVisible(state);
|
|
2261
2351
|
const pulseScale = progressActive ? 1.03 : hovered ? 1.015 : 1;
|
|
2262
2352
|
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;
|
|
2263
2353
|
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%)";
|
|
@@ -2437,7 +2527,7 @@ function SponsorRelayFab(props) {
|
|
|
2437
2527
|
),
|
|
2438
2528
|
/* @__PURE__ */ jsx("p", { style: { color: "#9fb2ca" }, children: state.statusText }),
|
|
2439
2529
|
state.errorText ? /* @__PURE__ */ jsx("p", { style: { color: "#ffd9d9" }, children: state.errorText }) : null,
|
|
2440
|
-
|
|
2530
|
+
deploymentProgressVisible ? /* @__PURE__ */ jsxs(
|
|
2441
2531
|
"div",
|
|
2442
2532
|
{
|
|
2443
2533
|
style: {
|
package/shared/index.d.ts
CHANGED
|
@@ -293,6 +293,11 @@ declare const RELAY_PING_IDLE_STATE: RelayPingState;
|
|
|
293
293
|
declare const IDLE_DEPLOYMENT_PROGRESS: DeploymentProgressEvent;
|
|
294
294
|
declare const REFRESH_INTERVAL_MS = 30000;
|
|
295
295
|
declare const RELAY_PING_INTERVAL_MS = 20000;
|
|
296
|
+
declare const INSTANCE_RUNTIME_READY_REFRESH_MS = 30000;
|
|
297
|
+
declare const INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45000;
|
|
298
|
+
declare const INSTANCE_RUNTIME_STALE_REFRESH_MS: number;
|
|
299
|
+
declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
|
|
300
|
+
declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
|
|
296
301
|
|
|
297
302
|
declare function shortHash(value: string | null | undefined, head?: number, tail?: number): string;
|
|
298
303
|
declare function formatNumber(value: number | null | undefined, digits?: number): string;
|
|
@@ -328,6 +333,7 @@ declare class SponsorRelayController {
|
|
|
328
333
|
private stopWalletWatch;
|
|
329
334
|
private props;
|
|
330
335
|
private progressEmitter;
|
|
336
|
+
private runtimeCooldownByHash;
|
|
331
337
|
constructor(props?: SponsorRelayProps);
|
|
332
338
|
subscribeToDeploymentProgress(listener: DeploymentProgressListener): () => void;
|
|
333
339
|
subscribe(subscriber: SponsorRelaySubscriber): () => void;
|
|
@@ -335,6 +341,9 @@ declare class SponsorRelayController {
|
|
|
335
341
|
private emit;
|
|
336
342
|
private patch;
|
|
337
343
|
private emitProgress;
|
|
344
|
+
private syncLatestDeploymentProgress;
|
|
345
|
+
private canSkipRuntimeRefresh;
|
|
346
|
+
private noteRuntimeRefreshResult;
|
|
338
347
|
init(): Promise<void>;
|
|
339
348
|
destroy(): void;
|
|
340
349
|
setOpen(open: boolean): void;
|
|
@@ -362,4 +371,4 @@ interface DeploymentProgressEmitter {
|
|
|
362
371
|
}
|
|
363
372
|
declare function createDeploymentProgressEmitter(): DeploymentProgressEmitter;
|
|
364
373
|
|
|
365
|
-
export { type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, REFRESH_INTERVAL_MS, RELAY_PING_IDLE_STATE, RELAY_PING_INTERVAL_MS, ROOTFS_MISSING_STATE, type RelayPingState, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, getEthereumProvider, joinMappedPorts, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
|
|
374
|
+
export { type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, RELAY_PING_IDLE_STATE, RELAY_PING_INTERVAL_MS, ROOTFS_MISSING_STATE, type RelayPingState, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, getEthereumProvider, joinMappedPorts, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
|
package/shared/index.js
CHANGED
|
@@ -29,6 +29,11 @@ var IDLE_DEPLOYMENT_PROGRESS = {
|
|
|
29
29
|
};
|
|
30
30
|
var REFRESH_INTERVAL_MS = 3e4;
|
|
31
31
|
var RELAY_PING_INTERVAL_MS = 2e4;
|
|
32
|
+
var INSTANCE_RUNTIME_READY_REFRESH_MS = 3e4;
|
|
33
|
+
var INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45e3;
|
|
34
|
+
var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
|
|
35
|
+
var STALE_INSTANCE_ALLOCATION_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
36
|
+
var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
32
37
|
|
|
33
38
|
// src/shared/format.ts
|
|
34
39
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -1511,6 +1516,15 @@ async function inspectInstanceRuntime(args) {
|
|
|
1511
1516
|
};
|
|
1512
1517
|
}
|
|
1513
1518
|
}
|
|
1519
|
+
function instanceTimestampMs(instance) {
|
|
1520
|
+
const value = instance.reception_time ?? instance.time;
|
|
1521
|
+
if (!value) return null;
|
|
1522
|
+
if (typeof value === "number") {
|
|
1523
|
+
return value > 0 && value < 1e10 ? value * 1e3 : value;
|
|
1524
|
+
}
|
|
1525
|
+
const parsed = Date.parse(value);
|
|
1526
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
1527
|
+
}
|
|
1514
1528
|
function compatibleCrnsForTier(crns, state) {
|
|
1515
1529
|
if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
|
|
1516
1530
|
return [];
|
|
@@ -1572,6 +1586,9 @@ async function pingPeer(libp2p) {
|
|
|
1572
1586
|
};
|
|
1573
1587
|
}
|
|
1574
1588
|
}
|
|
1589
|
+
function hasUsableRuntime(details) {
|
|
1590
|
+
return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1591
|
+
}
|
|
1575
1592
|
var SponsorRelayController = class {
|
|
1576
1593
|
state;
|
|
1577
1594
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1581,6 +1598,7 @@ var SponsorRelayController = class {
|
|
|
1581
1598
|
stopWalletWatch = null;
|
|
1582
1599
|
props;
|
|
1583
1600
|
progressEmitter = createDeploymentProgressEmitter();
|
|
1601
|
+
runtimeCooldownByHash = /* @__PURE__ */ new Map();
|
|
1584
1602
|
constructor(props = {}) {
|
|
1585
1603
|
this.props = props;
|
|
1586
1604
|
this.state = defaultState(props);
|
|
@@ -1630,6 +1648,86 @@ var SponsorRelayController = class {
|
|
|
1630
1648
|
});
|
|
1631
1649
|
this.progressEmitter.emit(nextEvent);
|
|
1632
1650
|
}
|
|
1651
|
+
syncLatestDeploymentProgress(instances) {
|
|
1652
|
+
const itemHash = this.state.lastDeploymentHash;
|
|
1653
|
+
if (!itemHash) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
const latest = instances.find((entry) => entry.instance.item_hash === itemHash);
|
|
1657
|
+
if (!latest) {
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
const status = latest.details.messageStatus;
|
|
1661
|
+
if (status === "rejected") {
|
|
1662
|
+
this.emitProgress({
|
|
1663
|
+
stage: "deployment-rejected",
|
|
1664
|
+
label: "Deployment rejected",
|
|
1665
|
+
progress: 100,
|
|
1666
|
+
status: "error",
|
|
1667
|
+
itemHash,
|
|
1668
|
+
detail: "Aleph rejected the deployment.",
|
|
1669
|
+
error: "Aleph rejected the deployment."
|
|
1670
|
+
});
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (status !== "processed") {
|
|
1674
|
+
this.emitProgress({
|
|
1675
|
+
stage: "waiting-for-aleph",
|
|
1676
|
+
label: "Waiting for Aleph",
|
|
1677
|
+
progress: 76,
|
|
1678
|
+
status: "info",
|
|
1679
|
+
itemHash,
|
|
1680
|
+
detail: "Deployment submitted. Waiting for Aleph to process the instance message."
|
|
1681
|
+
});
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
if (!hasUsableRuntime(latest.details)) {
|
|
1685
|
+
this.emitProgress({
|
|
1686
|
+
stage: "deployment-confirmed",
|
|
1687
|
+
label: "Waiting for runtime",
|
|
1688
|
+
progress: 88,
|
|
1689
|
+
status: "warning",
|
|
1690
|
+
itemHash,
|
|
1691
|
+
detail: "Aleph processed the deployment. Waiting for scheduler/runtime allocation details."
|
|
1692
|
+
});
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
this.emitProgress({
|
|
1696
|
+
stage: "completed",
|
|
1697
|
+
label: "Runtime ready",
|
|
1698
|
+
progress: 100,
|
|
1699
|
+
status: "success",
|
|
1700
|
+
itemHash,
|
|
1701
|
+
detail: "Deployment processed and runtime networking is available."
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
canSkipRuntimeRefresh(instance) {
|
|
1705
|
+
if (instance.item_hash === this.state.lastDeploymentHash) {
|
|
1706
|
+
return false;
|
|
1707
|
+
}
|
|
1708
|
+
const cooldownUntil = this.runtimeCooldownByHash.get(instance.item_hash);
|
|
1709
|
+
if (!cooldownUntil) {
|
|
1710
|
+
return false;
|
|
1711
|
+
}
|
|
1712
|
+
return cooldownUntil > Date.now();
|
|
1713
|
+
}
|
|
1714
|
+
noteRuntimeRefreshResult(instance, details) {
|
|
1715
|
+
const hasRuntimeData = Boolean(details.crnUrl) || Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1716
|
+
if (hasRuntimeData || details.error || details.messageStatus !== "processed") {
|
|
1717
|
+
this.runtimeCooldownByHash.delete(instance.item_hash);
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
const timestampMs = instanceTimestampMs(instance);
|
|
1721
|
+
const isRecent = timestampMs != null && Date.now() - timestampMs < RECENT_INSTANCE_RUNTIME_GRACE_MS;
|
|
1722
|
+
if (isRecent) {
|
|
1723
|
+
this.runtimeCooldownByHash.delete(instance.item_hash);
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
this.runtimeCooldownByHash.set(
|
|
1727
|
+
instance.item_hash,
|
|
1728
|
+
Date.now() + STALE_INSTANCE_ALLOCATION_COOLDOWN_MS
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1633
1731
|
async init() {
|
|
1634
1732
|
this.stopWalletWatch = watchWallet(() => {
|
|
1635
1733
|
void this.refreshWalletDerivedState();
|
|
@@ -1749,25 +1847,11 @@ var SponsorRelayController = class {
|
|
|
1749
1847
|
statusText: "Refreshing relay sponsor data"
|
|
1750
1848
|
});
|
|
1751
1849
|
try {
|
|
1752
|
-
this.emitProgress({
|
|
1753
|
-
stage: "loading-manifest",
|
|
1754
|
-
label: "Loading manifest",
|
|
1755
|
-
progress: 8,
|
|
1756
|
-
status: "info",
|
|
1757
|
-
detail: this.state.manifestUrl
|
|
1758
|
-
});
|
|
1759
1850
|
const manifestState = await resolveManifest({
|
|
1760
1851
|
manifestUrl: this.state.manifestUrl,
|
|
1761
1852
|
manifestJson: this.state.manifestJson
|
|
1762
1853
|
});
|
|
1763
1854
|
const manifest = manifestState.manifest;
|
|
1764
|
-
this.emitProgress({
|
|
1765
|
-
stage: "loading-pricing",
|
|
1766
|
-
label: "Loading pricing and CRNs",
|
|
1767
|
-
progress: 18,
|
|
1768
|
-
status: "info",
|
|
1769
|
-
detail: "Fetching live Aleph pricing and compatible CRNs."
|
|
1770
|
-
});
|
|
1771
1855
|
const [pricingSummary, crns] = await Promise.all([
|
|
1772
1856
|
fetchInstancePricing(this.client.apiHost),
|
|
1773
1857
|
this.client.fetchCrns()
|
|
@@ -1775,39 +1859,43 @@ var SponsorRelayController = class {
|
|
|
1775
1859
|
let balance = this.state.balance;
|
|
1776
1860
|
let instances = [];
|
|
1777
1861
|
if (this.state.wallet.address) {
|
|
1778
|
-
this.emitProgress({
|
|
1779
|
-
stage: "loading-balance",
|
|
1780
|
-
label: "Loading wallet balance",
|
|
1781
|
-
progress: 28,
|
|
1782
|
-
status: "info",
|
|
1783
|
-
detail: this.state.wallet.address
|
|
1784
|
-
});
|
|
1785
1862
|
const [nextBalance, rawInstances] = await Promise.all([
|
|
1786
1863
|
this.client.fetchBalance(this.state.wallet.address),
|
|
1787
1864
|
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
|
|
1788
1865
|
]);
|
|
1789
1866
|
balance = nextBalance;
|
|
1790
1867
|
instances = await Promise.all(
|
|
1791
|
-
rawInstances.map(async (instance) =>
|
|
1792
|
-
instance
|
|
1793
|
-
|
|
1868
|
+
rawInstances.map(async (instance) => {
|
|
1869
|
+
const details = this.canSkipRuntimeRefresh(instance) ? {
|
|
1870
|
+
messageStatus: String(
|
|
1871
|
+
instance.status ?? (instance.confirmed ? "processed" : "pending")
|
|
1872
|
+
).toLowerCase(),
|
|
1873
|
+
allocationSource: null,
|
|
1874
|
+
crnUrl: null,
|
|
1875
|
+
hostIpv4: null,
|
|
1876
|
+
ipv6: null,
|
|
1877
|
+
vmIpv4: null,
|
|
1878
|
+
webUrl: null,
|
|
1879
|
+
sshCommand: null,
|
|
1880
|
+
mappedPorts: [],
|
|
1881
|
+
execution: null,
|
|
1882
|
+
error: null
|
|
1883
|
+
} : await inspectInstanceRuntime({
|
|
1794
1884
|
client: this.client,
|
|
1795
1885
|
instance,
|
|
1796
1886
|
crns
|
|
1797
|
-
})
|
|
1798
|
-
|
|
1887
|
+
});
|
|
1888
|
+
this.noteRuntimeRefreshResult(instance, details);
|
|
1889
|
+
return {
|
|
1890
|
+
instance,
|
|
1891
|
+
details
|
|
1892
|
+
};
|
|
1893
|
+
})
|
|
1799
1894
|
);
|
|
1800
1895
|
}
|
|
1801
1896
|
let rootfsVerified = false;
|
|
1802
1897
|
let rootfsResolution = null;
|
|
1803
1898
|
if (manifestState.valid && manifest) {
|
|
1804
|
-
this.emitProgress({
|
|
1805
|
-
stage: "verifying-rootfs",
|
|
1806
|
-
label: "Verifying rootfs",
|
|
1807
|
-
progress: 38,
|
|
1808
|
-
status: "info",
|
|
1809
|
-
detail: manifest.rootfsItemHash
|
|
1810
|
-
});
|
|
1811
1899
|
rootfsVerified = await verifyRootfsExists(
|
|
1812
1900
|
manifest.rootfsItemHash,
|
|
1813
1901
|
this.client.apiHost
|
|
@@ -1838,26 +1926,13 @@ var SponsorRelayController = class {
|
|
|
1838
1926
|
statusText: "Relay sponsor data ready"
|
|
1839
1927
|
});
|
|
1840
1928
|
this.recomputePricingSummary();
|
|
1841
|
-
this.
|
|
1842
|
-
stage: "completed",
|
|
1843
|
-
label: "Data ready",
|
|
1844
|
-
progress: 100,
|
|
1845
|
-
status: "success",
|
|
1846
|
-
detail: "Manifest, pricing, and wallet state are ready."
|
|
1847
|
-
});
|
|
1929
|
+
this.syncLatestDeploymentProgress(instances);
|
|
1848
1930
|
} catch (error) {
|
|
1849
1931
|
this.patch({
|
|
1850
1932
|
busy: { refreshing: false },
|
|
1851
1933
|
errorText: error instanceof Error ? error.message : String(error),
|
|
1852
1934
|
statusText: "Refresh failed"
|
|
1853
1935
|
});
|
|
1854
|
-
this.emitProgress({
|
|
1855
|
-
stage: "error",
|
|
1856
|
-
label: "Refresh failed",
|
|
1857
|
-
progress: 100,
|
|
1858
|
-
status: "error",
|
|
1859
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1860
|
-
});
|
|
1861
1936
|
}
|
|
1862
1937
|
}
|
|
1863
1938
|
async refreshRelayPing() {
|
|
@@ -1949,15 +2024,6 @@ var SponsorRelayController = class {
|
|
|
1949
2024
|
detail: "Reloading deployments and runtime state."
|
|
1950
2025
|
});
|
|
1951
2026
|
await this.refresh();
|
|
1952
|
-
this.emitProgress({
|
|
1953
|
-
stage: "completed",
|
|
1954
|
-
label: "Deployment completed",
|
|
1955
|
-
progress: 100,
|
|
1956
|
-
status: result.status === "rejected" ? "error" : "success",
|
|
1957
|
-
itemHash: result.itemHash,
|
|
1958
|
-
detail: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : "Deployment submitted and synced back into the panel.",
|
|
1959
|
-
error: result.status === "rejected" ? result.rejectionReason ?? "Aleph rejected the deployment." : null
|
|
1960
|
-
});
|
|
1961
2027
|
} catch (error) {
|
|
1962
2028
|
this.patch({
|
|
1963
2029
|
busy: { deploying: false },
|
|
@@ -2047,10 +2113,15 @@ export {
|
|
|
2047
2113
|
DEFAULT_MANIFEST_URL,
|
|
2048
2114
|
DEFAULT_TIER_ID,
|
|
2049
2115
|
IDLE_DEPLOYMENT_PROGRESS,
|
|
2116
|
+
INSTANCE_RUNTIME_PENDING_REFRESH_MS,
|
|
2117
|
+
INSTANCE_RUNTIME_READY_REFRESH_MS,
|
|
2118
|
+
INSTANCE_RUNTIME_STALE_REFRESH_MS,
|
|
2119
|
+
RECENT_INSTANCE_RUNTIME_GRACE_MS,
|
|
2050
2120
|
REFRESH_INTERVAL_MS,
|
|
2051
2121
|
RELAY_PING_IDLE_STATE,
|
|
2052
2122
|
RELAY_PING_INTERVAL_MS,
|
|
2053
2123
|
ROOTFS_MISSING_STATE,
|
|
2124
|
+
STALE_INSTANCE_ALLOCATION_COOLDOWN_MS,
|
|
2054
2125
|
SponsorRelayController,
|
|
2055
2126
|
buildSshCommand,
|
|
2056
2127
|
connectWallet,
|