@le-space/ui 0.1.59 → 0.1.61
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 +153 -58
- package/shared/index.d.ts +11 -1
- package/shared/index.js +133 -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,10 @@ 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;
|
|
38
|
+
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
35
39
|
|
|
36
40
|
// src/shared/format.ts
|
|
37
41
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -1514,6 +1518,15 @@ async function inspectInstanceRuntime(args) {
|
|
|
1514
1518
|
};
|
|
1515
1519
|
}
|
|
1516
1520
|
}
|
|
1521
|
+
function instanceTimestampMs(instance) {
|
|
1522
|
+
const value = instance.reception_time ?? instance.time;
|
|
1523
|
+
if (!value) return null;
|
|
1524
|
+
if (typeof value === "number") {
|
|
1525
|
+
return value > 0 && value < 1e10 ? value * 1e3 : value;
|
|
1526
|
+
}
|
|
1527
|
+
const parsed = Date.parse(value);
|
|
1528
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
1529
|
+
}
|
|
1517
1530
|
function compatibleCrnsForTier(crns, state) {
|
|
1518
1531
|
if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
|
|
1519
1532
|
return [];
|
|
@@ -1575,6 +1588,9 @@ async function pingPeer(libp2p) {
|
|
|
1575
1588
|
};
|
|
1576
1589
|
}
|
|
1577
1590
|
}
|
|
1591
|
+
function hasUsableRuntime(details) {
|
|
1592
|
+
return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1593
|
+
}
|
|
1578
1594
|
var SponsorRelayController = class {
|
|
1579
1595
|
state;
|
|
1580
1596
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1584,6 +1600,7 @@ var SponsorRelayController = class {
|
|
|
1584
1600
|
stopWalletWatch = null;
|
|
1585
1601
|
props;
|
|
1586
1602
|
progressEmitter = createDeploymentProgressEmitter();
|
|
1603
|
+
runtimeCooldownByHash = /* @__PURE__ */ new Map();
|
|
1587
1604
|
constructor(props = {}) {
|
|
1588
1605
|
this.props = props;
|
|
1589
1606
|
this.state = defaultState(props);
|
|
@@ -1633,6 +1650,90 @@ var SponsorRelayController = class {
|
|
|
1633
1650
|
});
|
|
1634
1651
|
this.progressEmitter.emit(nextEvent);
|
|
1635
1652
|
}
|
|
1653
|
+
syncLatestDeploymentProgress(instances) {
|
|
1654
|
+
const itemHash = this.state.lastDeploymentHash;
|
|
1655
|
+
if (!itemHash) {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
const latest = instances.find(
|
|
1659
|
+
(entry) => entry.instance.item_hash === itemHash
|
|
1660
|
+
);
|
|
1661
|
+
if (!latest) {
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
const status = latest.details.messageStatus;
|
|
1665
|
+
if (status === "rejected") {
|
|
1666
|
+
this.emitProgress({
|
|
1667
|
+
stage: "deployment-rejected",
|
|
1668
|
+
label: "Deployment rejected",
|
|
1669
|
+
progress: 100,
|
|
1670
|
+
status: "error",
|
|
1671
|
+
itemHash,
|
|
1672
|
+
detail: "Aleph rejected the deployment.",
|
|
1673
|
+
error: "Aleph rejected the deployment."
|
|
1674
|
+
});
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
if (status !== "processed") {
|
|
1678
|
+
const submittedAtMs = instanceTimestampMs(latest.instance);
|
|
1679
|
+
const pendingTooLong = submittedAtMs != null && Date.now() - submittedAtMs >= DEPLOYMENT_PENDING_WARNING_MS;
|
|
1680
|
+
this.emitProgress({
|
|
1681
|
+
stage: "waiting-for-aleph",
|
|
1682
|
+
label: pendingTooLong ? "Aleph processing delayed" : "Waiting for Aleph",
|
|
1683
|
+
progress: 76,
|
|
1684
|
+
status: pendingTooLong ? "warning" : "info",
|
|
1685
|
+
itemHash,
|
|
1686
|
+
detail: pendingTooLong ? "The instance is still pending on Aleph after several minutes. Retry, inspect the Aleph message, or delete and redeploy." : "Deployment submitted. Waiting for Aleph to process the instance message."
|
|
1687
|
+
});
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (!hasUsableRuntime(latest.details)) {
|
|
1691
|
+
this.emitProgress({
|
|
1692
|
+
stage: "deployment-confirmed",
|
|
1693
|
+
label: "Waiting for runtime",
|
|
1694
|
+
progress: 88,
|
|
1695
|
+
status: "warning",
|
|
1696
|
+
itemHash,
|
|
1697
|
+
detail: "Aleph processed the deployment. Waiting for scheduler/runtime allocation details."
|
|
1698
|
+
});
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
this.emitProgress({
|
|
1702
|
+
stage: "completed",
|
|
1703
|
+
label: "Runtime ready",
|
|
1704
|
+
progress: 100,
|
|
1705
|
+
status: "success",
|
|
1706
|
+
itemHash,
|
|
1707
|
+
detail: "Deployment processed and runtime networking is available."
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
canSkipRuntimeRefresh(instance) {
|
|
1711
|
+
if (instance.item_hash === this.state.lastDeploymentHash) {
|
|
1712
|
+
return false;
|
|
1713
|
+
}
|
|
1714
|
+
const cooldownUntil = this.runtimeCooldownByHash.get(instance.item_hash);
|
|
1715
|
+
if (!cooldownUntil) {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
return cooldownUntil > Date.now();
|
|
1719
|
+
}
|
|
1720
|
+
noteRuntimeRefreshResult(instance, details) {
|
|
1721
|
+
const hasRuntimeData = Boolean(details.crnUrl) || Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1722
|
+
if (hasRuntimeData || details.error || details.messageStatus !== "processed") {
|
|
1723
|
+
this.runtimeCooldownByHash.delete(instance.item_hash);
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
const timestampMs = instanceTimestampMs(instance);
|
|
1727
|
+
const isRecent = timestampMs != null && Date.now() - timestampMs < RECENT_INSTANCE_RUNTIME_GRACE_MS;
|
|
1728
|
+
if (isRecent) {
|
|
1729
|
+
this.runtimeCooldownByHash.delete(instance.item_hash);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
this.runtimeCooldownByHash.set(
|
|
1733
|
+
instance.item_hash,
|
|
1734
|
+
Date.now() + STALE_INSTANCE_ALLOCATION_COOLDOWN_MS
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1636
1737
|
async init() {
|
|
1637
1738
|
this.stopWalletWatch = watchWallet(() => {
|
|
1638
1739
|
void this.refreshWalletDerivedState();
|
|
@@ -1752,25 +1853,11 @@ var SponsorRelayController = class {
|
|
|
1752
1853
|
statusText: "Refreshing relay sponsor data"
|
|
1753
1854
|
});
|
|
1754
1855
|
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
1856
|
const manifestState = await resolveManifest({
|
|
1763
1857
|
manifestUrl: this.state.manifestUrl,
|
|
1764
1858
|
manifestJson: this.state.manifestJson
|
|
1765
1859
|
});
|
|
1766
1860
|
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
1861
|
const [pricingSummary, crns] = await Promise.all([
|
|
1775
1862
|
fetchInstancePricing(this.client.apiHost),
|
|
1776
1863
|
this.client.fetchCrns()
|
|
@@ -1778,39 +1865,43 @@ var SponsorRelayController = class {
|
|
|
1778
1865
|
let balance = this.state.balance;
|
|
1779
1866
|
let instances = [];
|
|
1780
1867
|
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
1868
|
const [nextBalance, rawInstances] = await Promise.all([
|
|
1789
1869
|
this.client.fetchBalance(this.state.wallet.address),
|
|
1790
1870
|
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
|
|
1791
1871
|
]);
|
|
1792
1872
|
balance = nextBalance;
|
|
1793
1873
|
instances = await Promise.all(
|
|
1794
|
-
rawInstances.map(async (instance) =>
|
|
1795
|
-
instance
|
|
1796
|
-
|
|
1874
|
+
rawInstances.map(async (instance) => {
|
|
1875
|
+
const details = this.canSkipRuntimeRefresh(instance) ? {
|
|
1876
|
+
messageStatus: String(
|
|
1877
|
+
instance.status ?? (instance.confirmed ? "processed" : "pending")
|
|
1878
|
+
).toLowerCase(),
|
|
1879
|
+
allocationSource: null,
|
|
1880
|
+
crnUrl: null,
|
|
1881
|
+
hostIpv4: null,
|
|
1882
|
+
ipv6: null,
|
|
1883
|
+
vmIpv4: null,
|
|
1884
|
+
webUrl: null,
|
|
1885
|
+
sshCommand: null,
|
|
1886
|
+
mappedPorts: [],
|
|
1887
|
+
execution: null,
|
|
1888
|
+
error: null
|
|
1889
|
+
} : await inspectInstanceRuntime({
|
|
1797
1890
|
client: this.client,
|
|
1798
1891
|
instance,
|
|
1799
1892
|
crns
|
|
1800
|
-
})
|
|
1801
|
-
|
|
1893
|
+
});
|
|
1894
|
+
this.noteRuntimeRefreshResult(instance, details);
|
|
1895
|
+
return {
|
|
1896
|
+
instance,
|
|
1897
|
+
details
|
|
1898
|
+
};
|
|
1899
|
+
})
|
|
1802
1900
|
);
|
|
1803
1901
|
}
|
|
1804
1902
|
let rootfsVerified = false;
|
|
1805
1903
|
let rootfsResolution = null;
|
|
1806
1904
|
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
1905
|
rootfsVerified = await verifyRootfsExists(
|
|
1815
1906
|
manifest.rootfsItemHash,
|
|
1816
1907
|
this.client.apiHost
|
|
@@ -1841,26 +1932,13 @@ var SponsorRelayController = class {
|
|
|
1841
1932
|
statusText: "Relay sponsor data ready"
|
|
1842
1933
|
});
|
|
1843
1934
|
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
|
-
});
|
|
1935
|
+
this.syncLatestDeploymentProgress(instances);
|
|
1851
1936
|
} catch (error) {
|
|
1852
1937
|
this.patch({
|
|
1853
1938
|
busy: { refreshing: false },
|
|
1854
1939
|
errorText: error instanceof Error ? error.message : String(error),
|
|
1855
1940
|
statusText: "Refresh failed"
|
|
1856
1941
|
});
|
|
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
1942
|
}
|
|
1865
1943
|
}
|
|
1866
1944
|
async refreshRelayPing() {
|
|
@@ -1952,15 +2030,6 @@ var SponsorRelayController = class {
|
|
|
1952
2030
|
detail: "Reloading deployments and runtime state."
|
|
1953
2031
|
});
|
|
1954
2032
|
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
2033
|
} catch (error) {
|
|
1965
2034
|
this.patch({
|
|
1966
2035
|
busy: { deploying: false },
|
|
@@ -2132,6 +2201,20 @@ function progressBadgeLabel(stage) {
|
|
|
2132
2201
|
return "DEPLOY";
|
|
2133
2202
|
}
|
|
2134
2203
|
}
|
|
2204
|
+
var COMPLETED_PROGRESS_VISIBLE_MS = 12e3;
|
|
2205
|
+
function isDeploymentProgressVisible(state) {
|
|
2206
|
+
const stage = state.deploymentProgress.stage;
|
|
2207
|
+
if (stage === "idle") {
|
|
2208
|
+
return false;
|
|
2209
|
+
}
|
|
2210
|
+
if (stage === "error") {
|
|
2211
|
+
return true;
|
|
2212
|
+
}
|
|
2213
|
+
if (stage === "completed") {
|
|
2214
|
+
return Date.now() - state.deploymentProgress.timestamp < COMPLETED_PROGRESS_VISIBLE_MS;
|
|
2215
|
+
}
|
|
2216
|
+
return true;
|
|
2217
|
+
}
|
|
2135
2218
|
function launcherIndicator(state) {
|
|
2136
2219
|
if (state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed") {
|
|
2137
2220
|
return {
|
|
@@ -2140,7 +2223,7 @@ function launcherIndicator(state) {
|
|
|
2140
2223
|
tone: state.deploymentProgress.status
|
|
2141
2224
|
};
|
|
2142
2225
|
}
|
|
2143
|
-
if (state.deploymentProgress.stage === "completed") {
|
|
2226
|
+
if (state.deploymentProgress.stage === "completed" && isDeploymentProgressVisible(state)) {
|
|
2144
2227
|
return {
|
|
2145
2228
|
label: "DONE",
|
|
2146
2229
|
detail: state.deploymentProgress.label,
|
|
@@ -2213,6 +2296,17 @@ function SponsorRelayFab(props) {
|
|
|
2213
2296
|
state.deploymentProgress.status,
|
|
2214
2297
|
state.deploymentProgress.timestamp
|
|
2215
2298
|
]);
|
|
2299
|
+
React.useEffect(() => {
|
|
2300
|
+
if (state.deploymentProgress.stage !== "completed") {
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
const timeout = window.setTimeout(() => {
|
|
2304
|
+
setSuccessFlash(false);
|
|
2305
|
+
}, COMPLETED_PROGRESS_VISIBLE_MS);
|
|
2306
|
+
return () => {
|
|
2307
|
+
window.clearTimeout(timeout);
|
|
2308
|
+
};
|
|
2309
|
+
}, [state.deploymentProgress.stage, state.deploymentProgress.timestamp]);
|
|
2216
2310
|
React.useEffect(() => {
|
|
2217
2311
|
if (launcherMode !== "inline" || !state.open) {
|
|
2218
2312
|
return;
|
|
@@ -2258,6 +2352,7 @@ function SponsorRelayFab(props) {
|
|
|
2258
2352
|
};
|
|
2259
2353
|
}, [launcherMode]);
|
|
2260
2354
|
const progressActive = state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed" && state.deploymentProgress.stage !== "error";
|
|
2355
|
+
const deploymentProgressVisible = isDeploymentProgressVisible(state);
|
|
2261
2356
|
const pulseScale = progressActive ? 1.03 : hovered ? 1.015 : 1;
|
|
2262
2357
|
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
2358
|
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 +2532,7 @@ function SponsorRelayFab(props) {
|
|
|
2437
2532
|
),
|
|
2438
2533
|
/* @__PURE__ */ jsx("p", { style: { color: "#9fb2ca" }, children: state.statusText }),
|
|
2439
2534
|
state.errorText ? /* @__PURE__ */ jsx("p", { style: { color: "#ffd9d9" }, children: state.errorText }) : null,
|
|
2440
|
-
|
|
2535
|
+
deploymentProgressVisible ? /* @__PURE__ */ jsxs(
|
|
2441
2536
|
"div",
|
|
2442
2537
|
{
|
|
2443
2538
|
style: {
|
package/shared/index.d.ts
CHANGED
|
@@ -293,6 +293,12 @@ 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;
|
|
301
|
+
declare const DEPLOYMENT_PENDING_WARNING_MS: number;
|
|
296
302
|
|
|
297
303
|
declare function shortHash(value: string | null | undefined, head?: number, tail?: number): string;
|
|
298
304
|
declare function formatNumber(value: number | null | undefined, digits?: number): string;
|
|
@@ -328,6 +334,7 @@ declare class SponsorRelayController {
|
|
|
328
334
|
private stopWalletWatch;
|
|
329
335
|
private props;
|
|
330
336
|
private progressEmitter;
|
|
337
|
+
private runtimeCooldownByHash;
|
|
331
338
|
constructor(props?: SponsorRelayProps);
|
|
332
339
|
subscribeToDeploymentProgress(listener: DeploymentProgressListener): () => void;
|
|
333
340
|
subscribe(subscriber: SponsorRelaySubscriber): () => void;
|
|
@@ -335,6 +342,9 @@ declare class SponsorRelayController {
|
|
|
335
342
|
private emit;
|
|
336
343
|
private patch;
|
|
337
344
|
private emitProgress;
|
|
345
|
+
private syncLatestDeploymentProgress;
|
|
346
|
+
private canSkipRuntimeRefresh;
|
|
347
|
+
private noteRuntimeRefreshResult;
|
|
338
348
|
init(): Promise<void>;
|
|
339
349
|
destroy(): void;
|
|
340
350
|
setOpen(open: boolean): void;
|
|
@@ -362,4 +372,4 @@ interface DeploymentProgressEmitter {
|
|
|
362
372
|
}
|
|
363
373
|
declare function createDeploymentProgressEmitter(): DeploymentProgressEmitter;
|
|
364
374
|
|
|
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 };
|
|
375
|
+
export { type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, 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,12 @@ 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;
|
|
37
|
+
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
32
38
|
|
|
33
39
|
// src/shared/format.ts
|
|
34
40
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -1511,6 +1517,15 @@ async function inspectInstanceRuntime(args) {
|
|
|
1511
1517
|
};
|
|
1512
1518
|
}
|
|
1513
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
|
+
}
|
|
1514
1529
|
function compatibleCrnsForTier(crns, state) {
|
|
1515
1530
|
if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
|
|
1516
1531
|
return [];
|
|
@@ -1572,6 +1587,9 @@ async function pingPeer(libp2p) {
|
|
|
1572
1587
|
};
|
|
1573
1588
|
}
|
|
1574
1589
|
}
|
|
1590
|
+
function hasUsableRuntime(details) {
|
|
1591
|
+
return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1592
|
+
}
|
|
1575
1593
|
var SponsorRelayController = class {
|
|
1576
1594
|
state;
|
|
1577
1595
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1581,6 +1599,7 @@ var SponsorRelayController = class {
|
|
|
1581
1599
|
stopWalletWatch = null;
|
|
1582
1600
|
props;
|
|
1583
1601
|
progressEmitter = createDeploymentProgressEmitter();
|
|
1602
|
+
runtimeCooldownByHash = /* @__PURE__ */ new Map();
|
|
1584
1603
|
constructor(props = {}) {
|
|
1585
1604
|
this.props = props;
|
|
1586
1605
|
this.state = defaultState(props);
|
|
@@ -1630,6 +1649,90 @@ var SponsorRelayController = class {
|
|
|
1630
1649
|
});
|
|
1631
1650
|
this.progressEmitter.emit(nextEvent);
|
|
1632
1651
|
}
|
|
1652
|
+
syncLatestDeploymentProgress(instances) {
|
|
1653
|
+
const itemHash = this.state.lastDeploymentHash;
|
|
1654
|
+
if (!itemHash) {
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
const latest = instances.find(
|
|
1658
|
+
(entry) => entry.instance.item_hash === itemHash
|
|
1659
|
+
);
|
|
1660
|
+
if (!latest) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
const status = latest.details.messageStatus;
|
|
1664
|
+
if (status === "rejected") {
|
|
1665
|
+
this.emitProgress({
|
|
1666
|
+
stage: "deployment-rejected",
|
|
1667
|
+
label: "Deployment rejected",
|
|
1668
|
+
progress: 100,
|
|
1669
|
+
status: "error",
|
|
1670
|
+
itemHash,
|
|
1671
|
+
detail: "Aleph rejected the deployment.",
|
|
1672
|
+
error: "Aleph rejected the deployment."
|
|
1673
|
+
});
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
if (status !== "processed") {
|
|
1677
|
+
const submittedAtMs = instanceTimestampMs(latest.instance);
|
|
1678
|
+
const pendingTooLong = submittedAtMs != null && Date.now() - submittedAtMs >= DEPLOYMENT_PENDING_WARNING_MS;
|
|
1679
|
+
this.emitProgress({
|
|
1680
|
+
stage: "waiting-for-aleph",
|
|
1681
|
+
label: pendingTooLong ? "Aleph processing delayed" : "Waiting for Aleph",
|
|
1682
|
+
progress: 76,
|
|
1683
|
+
status: pendingTooLong ? "warning" : "info",
|
|
1684
|
+
itemHash,
|
|
1685
|
+
detail: pendingTooLong ? "The instance is still pending on Aleph after several minutes. Retry, inspect the Aleph message, or delete and redeploy." : "Deployment submitted. Waiting for Aleph to process the instance message."
|
|
1686
|
+
});
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if (!hasUsableRuntime(latest.details)) {
|
|
1690
|
+
this.emitProgress({
|
|
1691
|
+
stage: "deployment-confirmed",
|
|
1692
|
+
label: "Waiting for runtime",
|
|
1693
|
+
progress: 88,
|
|
1694
|
+
status: "warning",
|
|
1695
|
+
itemHash,
|
|
1696
|
+
detail: "Aleph processed the deployment. Waiting for scheduler/runtime allocation details."
|
|
1697
|
+
});
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
this.emitProgress({
|
|
1701
|
+
stage: "completed",
|
|
1702
|
+
label: "Runtime ready",
|
|
1703
|
+
progress: 100,
|
|
1704
|
+
status: "success",
|
|
1705
|
+
itemHash,
|
|
1706
|
+
detail: "Deployment processed and runtime networking is available."
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
canSkipRuntimeRefresh(instance) {
|
|
1710
|
+
if (instance.item_hash === this.state.lastDeploymentHash) {
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1713
|
+
const cooldownUntil = this.runtimeCooldownByHash.get(instance.item_hash);
|
|
1714
|
+
if (!cooldownUntil) {
|
|
1715
|
+
return false;
|
|
1716
|
+
}
|
|
1717
|
+
return cooldownUntil > Date.now();
|
|
1718
|
+
}
|
|
1719
|
+
noteRuntimeRefreshResult(instance, details) {
|
|
1720
|
+
const hasRuntimeData = Boolean(details.crnUrl) || Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1721
|
+
if (hasRuntimeData || details.error || details.messageStatus !== "processed") {
|
|
1722
|
+
this.runtimeCooldownByHash.delete(instance.item_hash);
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
const timestampMs = instanceTimestampMs(instance);
|
|
1726
|
+
const isRecent = timestampMs != null && Date.now() - timestampMs < RECENT_INSTANCE_RUNTIME_GRACE_MS;
|
|
1727
|
+
if (isRecent) {
|
|
1728
|
+
this.runtimeCooldownByHash.delete(instance.item_hash);
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
this.runtimeCooldownByHash.set(
|
|
1732
|
+
instance.item_hash,
|
|
1733
|
+
Date.now() + STALE_INSTANCE_ALLOCATION_COOLDOWN_MS
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1633
1736
|
async init() {
|
|
1634
1737
|
this.stopWalletWatch = watchWallet(() => {
|
|
1635
1738
|
void this.refreshWalletDerivedState();
|
|
@@ -1749,25 +1852,11 @@ var SponsorRelayController = class {
|
|
|
1749
1852
|
statusText: "Refreshing relay sponsor data"
|
|
1750
1853
|
});
|
|
1751
1854
|
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
1855
|
const manifestState = await resolveManifest({
|
|
1760
1856
|
manifestUrl: this.state.manifestUrl,
|
|
1761
1857
|
manifestJson: this.state.manifestJson
|
|
1762
1858
|
});
|
|
1763
1859
|
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
1860
|
const [pricingSummary, crns] = await Promise.all([
|
|
1772
1861
|
fetchInstancePricing(this.client.apiHost),
|
|
1773
1862
|
this.client.fetchCrns()
|
|
@@ -1775,39 +1864,43 @@ var SponsorRelayController = class {
|
|
|
1775
1864
|
let balance = this.state.balance;
|
|
1776
1865
|
let instances = [];
|
|
1777
1866
|
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
1867
|
const [nextBalance, rawInstances] = await Promise.all([
|
|
1786
1868
|
this.client.fetchBalance(this.state.wallet.address),
|
|
1787
1869
|
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([])
|
|
1788
1870
|
]);
|
|
1789
1871
|
balance = nextBalance;
|
|
1790
1872
|
instances = await Promise.all(
|
|
1791
|
-
rawInstances.map(async (instance) =>
|
|
1792
|
-
instance
|
|
1793
|
-
|
|
1873
|
+
rawInstances.map(async (instance) => {
|
|
1874
|
+
const details = this.canSkipRuntimeRefresh(instance) ? {
|
|
1875
|
+
messageStatus: String(
|
|
1876
|
+
instance.status ?? (instance.confirmed ? "processed" : "pending")
|
|
1877
|
+
).toLowerCase(),
|
|
1878
|
+
allocationSource: null,
|
|
1879
|
+
crnUrl: null,
|
|
1880
|
+
hostIpv4: null,
|
|
1881
|
+
ipv6: null,
|
|
1882
|
+
vmIpv4: null,
|
|
1883
|
+
webUrl: null,
|
|
1884
|
+
sshCommand: null,
|
|
1885
|
+
mappedPorts: [],
|
|
1886
|
+
execution: null,
|
|
1887
|
+
error: null
|
|
1888
|
+
} : await inspectInstanceRuntime({
|
|
1794
1889
|
client: this.client,
|
|
1795
1890
|
instance,
|
|
1796
1891
|
crns
|
|
1797
|
-
})
|
|
1798
|
-
|
|
1892
|
+
});
|
|
1893
|
+
this.noteRuntimeRefreshResult(instance, details);
|
|
1894
|
+
return {
|
|
1895
|
+
instance,
|
|
1896
|
+
details
|
|
1897
|
+
};
|
|
1898
|
+
})
|
|
1799
1899
|
);
|
|
1800
1900
|
}
|
|
1801
1901
|
let rootfsVerified = false;
|
|
1802
1902
|
let rootfsResolution = null;
|
|
1803
1903
|
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
1904
|
rootfsVerified = await verifyRootfsExists(
|
|
1812
1905
|
manifest.rootfsItemHash,
|
|
1813
1906
|
this.client.apiHost
|
|
@@ -1838,26 +1931,13 @@ var SponsorRelayController = class {
|
|
|
1838
1931
|
statusText: "Relay sponsor data ready"
|
|
1839
1932
|
});
|
|
1840
1933
|
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
|
-
});
|
|
1934
|
+
this.syncLatestDeploymentProgress(instances);
|
|
1848
1935
|
} catch (error) {
|
|
1849
1936
|
this.patch({
|
|
1850
1937
|
busy: { refreshing: false },
|
|
1851
1938
|
errorText: error instanceof Error ? error.message : String(error),
|
|
1852
1939
|
statusText: "Refresh failed"
|
|
1853
1940
|
});
|
|
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
1941
|
}
|
|
1862
1942
|
}
|
|
1863
1943
|
async refreshRelayPing() {
|
|
@@ -1949,15 +2029,6 @@ var SponsorRelayController = class {
|
|
|
1949
2029
|
detail: "Reloading deployments and runtime state."
|
|
1950
2030
|
});
|
|
1951
2031
|
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
2032
|
} catch (error) {
|
|
1962
2033
|
this.patch({
|
|
1963
2034
|
busy: { deploying: false },
|
|
@@ -2046,11 +2117,17 @@ export {
|
|
|
2046
2117
|
DEFAULT_INSTANCE_NAME,
|
|
2047
2118
|
DEFAULT_MANIFEST_URL,
|
|
2048
2119
|
DEFAULT_TIER_ID,
|
|
2120
|
+
DEPLOYMENT_PENDING_WARNING_MS,
|
|
2049
2121
|
IDLE_DEPLOYMENT_PROGRESS,
|
|
2122
|
+
INSTANCE_RUNTIME_PENDING_REFRESH_MS,
|
|
2123
|
+
INSTANCE_RUNTIME_READY_REFRESH_MS,
|
|
2124
|
+
INSTANCE_RUNTIME_STALE_REFRESH_MS,
|
|
2125
|
+
RECENT_INSTANCE_RUNTIME_GRACE_MS,
|
|
2050
2126
|
REFRESH_INTERVAL_MS,
|
|
2051
2127
|
RELAY_PING_IDLE_STATE,
|
|
2052
2128
|
RELAY_PING_INTERVAL_MS,
|
|
2053
2129
|
ROOTFS_MISSING_STATE,
|
|
2130
|
+
STALE_INSTANCE_ALLOCATION_COOLDOWN_MS,
|
|
2054
2131
|
SponsorRelayController,
|
|
2055
2132
|
buildSshCommand,
|
|
2056
2133
|
connectWallet,
|