@le-space/ui 0.1.60 → 0.1.62
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.js +755 -8
- package/shared/index.d.ts +14 -5
- package/shared/index.js +749 -7
- package/svelte/SponsorRelayFab.svelte +6 -1
package/react/index.js
CHANGED
|
@@ -35,6 +35,7 @@ var RELAY_PING_INTERVAL_MS = 2e4;
|
|
|
35
35
|
var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
|
|
36
36
|
var STALE_INSTANCE_ALLOCATION_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
37
37
|
var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
38
|
+
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
38
39
|
|
|
39
40
|
// src/shared/format.ts
|
|
40
41
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -65,6 +66,16 @@ function joinMappedPorts(mappedPorts2) {
|
|
|
65
66
|
if (mappedPorts2.length === 0) return "-";
|
|
66
67
|
return mappedPorts2.map((entry) => `${entry.label}->${entry.hostPort ?? "?"}`).join(" \xB7 ");
|
|
67
68
|
}
|
|
69
|
+
function joinRequiredPortForwards(portForwards) {
|
|
70
|
+
if (!portForwards || portForwards.length === 0) return "-";
|
|
71
|
+
return portForwards.map((entry) => {
|
|
72
|
+
const protocols = [
|
|
73
|
+
entry.tcp === true ? "tcp" : null,
|
|
74
|
+
entry.udp === true ? "udp" : null
|
|
75
|
+
].filter((value) => Boolean(value)).join("/");
|
|
76
|
+
return `${entry.port}/${protocols}`;
|
|
77
|
+
}).join(" \xB7 ");
|
|
78
|
+
}
|
|
68
79
|
|
|
69
80
|
// ../browser/src/http.ts
|
|
70
81
|
async function fetchWithTimeout(input, init = {}, timeoutMs = 15e3) {
|
|
@@ -851,6 +862,7 @@ var DEFAULT_ALEPH_CHANNEL = "TEST";
|
|
|
851
862
|
var DEFAULT_ALEPH_API_HOST2 = "https://api2.aleph.im";
|
|
852
863
|
|
|
853
864
|
// ../core/src/crns.ts
|
|
865
|
+
var DEFAULT_CRN_LIST_URL2 = "https://crns-list.aleph.sh/crns.json";
|
|
854
866
|
function compatibleCrns(crns, excludedHashes = []) {
|
|
855
867
|
const excluded = new Set(excludedHashes.filter(Boolean));
|
|
856
868
|
return [...crns].filter((crn) => {
|
|
@@ -884,8 +896,113 @@ function filterDeployableCrns(crns, options = {}) {
|
|
|
884
896
|
compatibleCrns(crns, options.excludedHashes).filter((crn) => hasCrnCapacity(crn, options.spec))
|
|
885
897
|
);
|
|
886
898
|
}
|
|
899
|
+
async function fetchCrns2(options) {
|
|
900
|
+
const requestUrl = new URL(options.url ?? DEFAULT_CRN_LIST_URL2);
|
|
901
|
+
requestUrl.searchParams.set("filter_inactive", "true");
|
|
902
|
+
const response = await options.fetch(requestUrl.toString(), {
|
|
903
|
+
cache: "no-cache"
|
|
904
|
+
});
|
|
905
|
+
if (!response.ok) {
|
|
906
|
+
throw new Error(`CRN list request failed: ${response.status}`);
|
|
907
|
+
}
|
|
908
|
+
const payload = await response.json();
|
|
909
|
+
return Array.isArray(payload?.crns) ? payload.crns : [];
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// ../core/src/port-forwarding.ts
|
|
913
|
+
var DEFAULT_INSTANCE_PORT_FORWARDS = [
|
|
914
|
+
{ port: 22, tcp: true, udp: false, purpose: "SSH" }
|
|
915
|
+
];
|
|
916
|
+
function normalizeRequestedPort(entry) {
|
|
917
|
+
return {
|
|
918
|
+
port: entry.port,
|
|
919
|
+
tcp: entry.tcp === true,
|
|
920
|
+
udp: entry.udp === true,
|
|
921
|
+
purpose: entry.purpose?.trim() || void 0
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function normalizePortFlags(value) {
|
|
925
|
+
if (!value || typeof value !== "object") return null;
|
|
926
|
+
const candidate = value;
|
|
927
|
+
return {
|
|
928
|
+
tcp: candidate.tcp === true,
|
|
929
|
+
udp: candidate.udp === true
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
function normalizeExistingPortForwardEntry(entry) {
|
|
933
|
+
if (!entry?.ports || typeof entry.ports !== "object") return {};
|
|
934
|
+
return Object.fromEntries(
|
|
935
|
+
Object.entries(entry.ports).map(([port, flags]) => [port, normalizePortFlags(flags)]).filter((item) => item[1] != null)
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
function requestedPortFlags(portForwards) {
|
|
939
|
+
return Object.fromEntries(
|
|
940
|
+
portForwards.map((entry) => [
|
|
941
|
+
String(entry.port),
|
|
942
|
+
{
|
|
943
|
+
tcp: entry.tcp === true,
|
|
944
|
+
udp: entry.udp === true
|
|
945
|
+
}
|
|
946
|
+
])
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
function mergePortFlagMaps(existing, requested) {
|
|
950
|
+
const merged = /* @__PURE__ */ new Map();
|
|
951
|
+
for (const [port, flags] of Object.entries(existing)) {
|
|
952
|
+
merged.set(port, {
|
|
953
|
+
tcp: flags.tcp === true,
|
|
954
|
+
udp: flags.udp === true
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
for (const [port, flags] of Object.entries(requested)) {
|
|
958
|
+
const current = merged.get(port);
|
|
959
|
+
merged.set(port, {
|
|
960
|
+
tcp: current?.tcp === true || flags.tcp === true,
|
|
961
|
+
udp: current?.udp === true || flags.udp === true
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return Object.fromEntries([...merged.entries()].sort((left, right) => Number(left[0]) - Number(right[0])));
|
|
965
|
+
}
|
|
966
|
+
function mergeRequiredPortForwards(...groups) {
|
|
967
|
+
const merged = /* @__PURE__ */ new Map();
|
|
968
|
+
for (const group of groups) {
|
|
969
|
+
for (const entry of group ?? []) {
|
|
970
|
+
const normalized = normalizeRequestedPort(entry);
|
|
971
|
+
const current = merged.get(normalized.port);
|
|
972
|
+
merged.set(normalized.port, {
|
|
973
|
+
port: normalized.port,
|
|
974
|
+
tcp: current?.tcp === true || normalized.tcp === true,
|
|
975
|
+
udp: current?.udp === true || normalized.udp === true,
|
|
976
|
+
purpose: current?.purpose ?? normalized.purpose
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return [...merged.values()].sort((left, right) => left.port - right.port);
|
|
981
|
+
}
|
|
982
|
+
function requiredInstancePortForwards(manifest) {
|
|
983
|
+
return mergeRequiredPortForwards(DEFAULT_INSTANCE_PORT_FORWARDS, manifest?.requiredPortForwards);
|
|
984
|
+
}
|
|
985
|
+
async function fetchPortForwardAggregate(address, options) {
|
|
986
|
+
const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
987
|
+
requestUrl.searchParams.set("keys", "port-forwarding");
|
|
988
|
+
const response = await options.fetch(requestUrl.toString(), { cache: "no-cache" });
|
|
989
|
+
if (response.status === 404) return {};
|
|
990
|
+
if (!response.ok) {
|
|
991
|
+
throw new Error(`Port-forward aggregate request failed: ${response.status}`);
|
|
992
|
+
}
|
|
993
|
+
const payload = await response.json();
|
|
994
|
+
const aggregate = payload.data?.["port-forwarding"];
|
|
995
|
+
if (!aggregate || typeof aggregate !== "object" || Array.isArray(aggregate)) return {};
|
|
996
|
+
return aggregate;
|
|
997
|
+
}
|
|
887
998
|
|
|
888
999
|
// ../core/src/aleph-normalizers.ts
|
|
1000
|
+
function asString2(value) {
|
|
1001
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
1002
|
+
}
|
|
1003
|
+
function asNumber2(value) {
|
|
1004
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1005
|
+
}
|
|
889
1006
|
function normalizeMessageStatus2(status) {
|
|
890
1007
|
if (typeof status !== "string") return "unknown";
|
|
891
1008
|
const normalized = status.toLowerCase();
|
|
@@ -894,6 +1011,183 @@ function normalizeMessageStatus2(status) {
|
|
|
894
1011
|
}
|
|
895
1012
|
return "unknown";
|
|
896
1013
|
}
|
|
1014
|
+
function normalizeProxyUrl2(value) {
|
|
1015
|
+
const stringValue = asString2(value);
|
|
1016
|
+
if (!stringValue) return null;
|
|
1017
|
+
if (/^https?:\/\//i.test(stringValue)) return stringValue;
|
|
1018
|
+
return `https://${stringValue}`;
|
|
1019
|
+
}
|
|
1020
|
+
function messageTypeFromEnvelope2(payload) {
|
|
1021
|
+
if (!payload) return null;
|
|
1022
|
+
const type = payload.type ?? payload.message?.type ?? (Array.isArray(payload.messages) ? payload.messages[0]?.type : void 0);
|
|
1023
|
+
return typeof type === "string" ? type.toUpperCase() : null;
|
|
1024
|
+
}
|
|
1025
|
+
function extractReferenceHashes2(details) {
|
|
1026
|
+
if (!details || typeof details !== "object" || !("errors" in details)) return [];
|
|
1027
|
+
const errors = details.errors;
|
|
1028
|
+
if (!Array.isArray(errors)) return [];
|
|
1029
|
+
return errors.filter((value) => typeof value === "string");
|
|
1030
|
+
}
|
|
1031
|
+
function describeRejectedDeployment2(payload, references, rootfsRef) {
|
|
1032
|
+
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
1033
|
+
const pendingReferences = references.filter((reference) => reference.status === "pending");
|
|
1034
|
+
const missingReferences = references.filter((reference) => reference.status === "missing");
|
|
1035
|
+
const rootfsReference = references.find((reference) => reference.itemHash === rootfsRef);
|
|
1036
|
+
if (rootfsReference?.status === "pending") {
|
|
1037
|
+
return `Aleph rejected this deployment because the referenced rootfs STORE message ${rootfsReference.itemHash} is still pending and cannot yet be used by an instance. Wait for that STORE message to process, then deploy again.`;
|
|
1038
|
+
}
|
|
1039
|
+
if (pendingReferences.length > 0) {
|
|
1040
|
+
return `Aleph rejected this deployment because referenced message(s) are still pending: ${pendingReferences.map((reference) => reference.itemHash).join(", ")}.`;
|
|
1041
|
+
}
|
|
1042
|
+
if (missingReferences.length > 0) {
|
|
1043
|
+
return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
|
|
1044
|
+
}
|
|
1045
|
+
const referencedHashes = extractReferenceHashes2(payload.details);
|
|
1046
|
+
if (referencedHashes.length > 0) {
|
|
1047
|
+
return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
|
|
1048
|
+
}
|
|
1049
|
+
return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
|
|
1050
|
+
}
|
|
1051
|
+
function extractProxyUrl2(item, networking) {
|
|
1052
|
+
const networkingCandidates = [
|
|
1053
|
+
networking?.proxy_url,
|
|
1054
|
+
networking?.proxyUrl,
|
|
1055
|
+
networking?.web_access_url,
|
|
1056
|
+
networking?.webAccessUrl,
|
|
1057
|
+
networking?.proxy_hostname,
|
|
1058
|
+
networking?.proxyHostname,
|
|
1059
|
+
networking?.domain,
|
|
1060
|
+
networking?.hostname
|
|
1061
|
+
];
|
|
1062
|
+
for (const candidate of networkingCandidates) {
|
|
1063
|
+
const normalized = normalizeProxyUrl2(candidate);
|
|
1064
|
+
if (normalized) return normalized;
|
|
1065
|
+
}
|
|
1066
|
+
const webAccessCandidates = [item.web_access, item.webAccess];
|
|
1067
|
+
for (const entry of webAccessCandidates) {
|
|
1068
|
+
const normalized = normalizeProxyUrl2(entry?.url) ?? normalizeProxyUrl2(entry?.proxy_url) ?? normalizeProxyUrl2(entry?.hostname) ?? normalizeProxyUrl2(entry?.domain);
|
|
1069
|
+
if (normalized) return normalized;
|
|
1070
|
+
}
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
function normalizeExecution2(item, crnUrl) {
|
|
1074
|
+
const networking = item.networking ?? null;
|
|
1075
|
+
const mappedPorts2 = networking && "mapped_ports" in networking && networking.mapped_ports && typeof networking.mapped_ports === "object" ? Object.fromEntries(
|
|
1076
|
+
Object.entries(networking.mapped_ports).map(([port, mapping]) => [
|
|
1077
|
+
port,
|
|
1078
|
+
{
|
|
1079
|
+
host: asNumber2(mapping?.host) ?? void 0,
|
|
1080
|
+
tcp: typeof mapping?.tcp === "boolean" ? mapping.tcp : void 0,
|
|
1081
|
+
udp: typeof mapping?.udp === "boolean" ? mapping.udp : void 0
|
|
1082
|
+
}
|
|
1083
|
+
])
|
|
1084
|
+
) : void 0;
|
|
1085
|
+
if (networking && ("host_ipv4" in networking || "ipv6_ip" in networking || "ipv4_network" in networking)) {
|
|
1086
|
+
const v2Item = item;
|
|
1087
|
+
return {
|
|
1088
|
+
crnUrl,
|
|
1089
|
+
version: "v2",
|
|
1090
|
+
running: typeof v2Item.running === "boolean" ? v2Item.running : void 0,
|
|
1091
|
+
networking: {
|
|
1092
|
+
ipv4_network: asString2(networking.ipv4_network),
|
|
1093
|
+
host_ipv4: asString2(networking.host_ipv4),
|
|
1094
|
+
ipv6_network: asString2(networking.ipv6_network),
|
|
1095
|
+
ipv6_ip: asString2(networking.ipv6_ip),
|
|
1096
|
+
ipv4_ip: asString2(networking.ipv4_ip),
|
|
1097
|
+
proxy_url: extractProxyUrl2(v2Item, networking),
|
|
1098
|
+
mapped_ports: mappedPorts2
|
|
1099
|
+
},
|
|
1100
|
+
status: v2Item.status ? {
|
|
1101
|
+
defined_at: asString2(v2Item.status.defined_at),
|
|
1102
|
+
preparing_at: asString2(v2Item.status.preparing_at),
|
|
1103
|
+
prepared_at: asString2(v2Item.status.prepared_at),
|
|
1104
|
+
starting_at: asString2(v2Item.status.starting_at),
|
|
1105
|
+
started_at: asString2(v2Item.status.started_at),
|
|
1106
|
+
stopping_at: asString2(v2Item.status.stopping_at),
|
|
1107
|
+
stopped_at: asString2(v2Item.status.stopped_at)
|
|
1108
|
+
} : null
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
crnUrl,
|
|
1113
|
+
version: "v1",
|
|
1114
|
+
networking: {
|
|
1115
|
+
ipv4: networking && "ipv4" in networking ? asString2(networking.ipv4) : null,
|
|
1116
|
+
ipv6: networking && "ipv6" in networking ? asString2(networking.ipv6) : null
|
|
1117
|
+
},
|
|
1118
|
+
status: null
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// ../core/src/deployment-inspection.ts
|
|
1123
|
+
async function fetchMessageEnvelope2(itemHash, options) {
|
|
1124
|
+
const response = await options.fetch(`${options.apiHost ?? DEFAULT_ALEPH_API_HOST2}/api/v0/messages/${itemHash}`, {
|
|
1125
|
+
cache: "no-cache"
|
|
1126
|
+
});
|
|
1127
|
+
if (response.status === 404) return null;
|
|
1128
|
+
if (!response.ok) throw new Error(`Message lookup failed: ${response.status}`);
|
|
1129
|
+
return await response.json();
|
|
1130
|
+
}
|
|
1131
|
+
async function fetchReference2(itemHash, options) {
|
|
1132
|
+
const payload = await fetchMessageEnvelope2(itemHash, options);
|
|
1133
|
+
if (!payload) {
|
|
1134
|
+
return {
|
|
1135
|
+
itemHash,
|
|
1136
|
+
status: "missing",
|
|
1137
|
+
type: null
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
itemHash,
|
|
1142
|
+
status: normalizeMessageStatus2(payload.status),
|
|
1143
|
+
type: messageTypeFromEnvelope2(payload)
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
async function inspectDeploymentResult2(itemHash, options) {
|
|
1147
|
+
const payload = await fetchMessageEnvelope2(itemHash, options);
|
|
1148
|
+
if (!payload) {
|
|
1149
|
+
return {
|
|
1150
|
+
status: "unknown",
|
|
1151
|
+
errorCode: null,
|
|
1152
|
+
details: null,
|
|
1153
|
+
rejectionReason: `Deployment message ${itemHash} was not found on Aleph.`,
|
|
1154
|
+
references: []
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
const relatedHashes = new Set(options.rootfsRef ? [options.rootfsRef] : []);
|
|
1158
|
+
for (const referenceHash of extractReferenceHashes2(payload.details)) {
|
|
1159
|
+
relatedHashes.add(referenceHash);
|
|
1160
|
+
}
|
|
1161
|
+
const references = await Promise.all(
|
|
1162
|
+
Array.from(relatedHashes).map((hash) => fetchReference2(hash, options))
|
|
1163
|
+
);
|
|
1164
|
+
const status = normalizeMessageStatus2(payload.status);
|
|
1165
|
+
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
1166
|
+
const details = payload.details && typeof payload.details === "object" ? payload.details : null;
|
|
1167
|
+
return {
|
|
1168
|
+
status,
|
|
1169
|
+
errorCode,
|
|
1170
|
+
details,
|
|
1171
|
+
rejectionReason: status === "rejected" ? describeRejectedDeployment2(payload, references, options.rootfsRef) : null,
|
|
1172
|
+
references
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
async function waitForDeploymentResult2(itemHash, options) {
|
|
1176
|
+
const attempts = Math.max(1, Number(options.attempts ?? 15));
|
|
1177
|
+
const delayMs = Math.max(0, Number(options.delayMs ?? 2e3));
|
|
1178
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1179
|
+
let lastResult = await inspectDeploymentResult2(itemHash, options);
|
|
1180
|
+
options.onAttempt?.(lastResult, 1, attempts);
|
|
1181
|
+
for (let attempt = 1; attempt < attempts; attempt += 1) {
|
|
1182
|
+
if (lastResult.status === "processed" || lastResult.status === "rejected") {
|
|
1183
|
+
return lastResult;
|
|
1184
|
+
}
|
|
1185
|
+
await sleep(delayMs);
|
|
1186
|
+
lastResult = await inspectDeploymentResult2(itemHash, options);
|
|
1187
|
+
options.onAttempt?.(lastResult, attempt + 1, attempts);
|
|
1188
|
+
}
|
|
1189
|
+
return lastResult;
|
|
1190
|
+
}
|
|
897
1191
|
|
|
898
1192
|
// ../core/src/broadcast.ts
|
|
899
1193
|
function signaturePayload(message) {
|
|
@@ -1056,6 +1350,70 @@ async function forgetAlephMessages(args) {
|
|
|
1056
1350
|
};
|
|
1057
1351
|
}
|
|
1058
1352
|
|
|
1353
|
+
// ../core/src/aggregate-publication.ts
|
|
1354
|
+
function createPortForwardAggregateContent(args) {
|
|
1355
|
+
const existingPorts = normalizeExistingPortForwardEntry(args.existingAggregate?.[args.instanceItemHash]);
|
|
1356
|
+
const mergedPorts = mergePortFlagMaps(existingPorts, requestedPortFlags(args.requestedPorts));
|
|
1357
|
+
return {
|
|
1358
|
+
address: args.sender,
|
|
1359
|
+
key: "port-forwarding",
|
|
1360
|
+
content: {
|
|
1361
|
+
[args.instanceItemHash]: {
|
|
1362
|
+
ports: mergedPorts
|
|
1363
|
+
}
|
|
1364
|
+
},
|
|
1365
|
+
time: args.now ?? Date.now() / 1e3
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
async function createUnsignedAggregateMessage(args) {
|
|
1369
|
+
const itemContent = JSON.stringify(args.content);
|
|
1370
|
+
const itemHash = await args.hasher(itemContent);
|
|
1371
|
+
return {
|
|
1372
|
+
sender: args.sender,
|
|
1373
|
+
chain: "ETH",
|
|
1374
|
+
type: "AGGREGATE",
|
|
1375
|
+
item_hash: itemHash,
|
|
1376
|
+
item_type: "inline",
|
|
1377
|
+
item_content: itemContent,
|
|
1378
|
+
time: args.now ?? Date.now() / 1e3,
|
|
1379
|
+
channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
async function ensureInstancePortForwards(args) {
|
|
1383
|
+
const requestedPorts = requiredInstancePortForwards(args.manifest);
|
|
1384
|
+
const aggregate = await fetchPortForwardAggregate(args.sender, {
|
|
1385
|
+
apiHost: args.apiHost,
|
|
1386
|
+
fetch: args.fetch
|
|
1387
|
+
});
|
|
1388
|
+
const content = createPortForwardAggregateContent({
|
|
1389
|
+
sender: args.sender,
|
|
1390
|
+
instanceItemHash: args.instanceItemHash,
|
|
1391
|
+
requestedPorts,
|
|
1392
|
+
existingAggregate: aggregate
|
|
1393
|
+
});
|
|
1394
|
+
const unsignedMessage = await createUnsignedAggregateMessage({
|
|
1395
|
+
sender: args.sender,
|
|
1396
|
+
content,
|
|
1397
|
+
hasher: args.hasher,
|
|
1398
|
+
channel: args.channel
|
|
1399
|
+
});
|
|
1400
|
+
const message = await signAlephMessage(unsignedMessage, args.signer);
|
|
1401
|
+
const { response, httpStatus } = await broadcastAlephMessage2(message, {
|
|
1402
|
+
apiHost: args.apiHost,
|
|
1403
|
+
sync: args.sync,
|
|
1404
|
+
fetch: args.fetch
|
|
1405
|
+
});
|
|
1406
|
+
const aggregateStatus = normalizeBroadcastStatus(httpStatus, response.message_status);
|
|
1407
|
+
if (aggregateStatus === "rejected") {
|
|
1408
|
+
throw new Error(`Port-forward aggregate was rejected by Aleph: ${JSON.stringify(response.details ?? response)}`);
|
|
1409
|
+
}
|
|
1410
|
+
return {
|
|
1411
|
+
aggregateItemHash: message.item_hash,
|
|
1412
|
+
aggregateStatus,
|
|
1413
|
+
requestedPorts
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1059
1417
|
// ../core/src/instance-deployment.ts
|
|
1060
1418
|
var SSH_PUBLIC_KEY_PATTERN = /^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ssh-ed25519@openssh\.com|sk-ecdsa-sha2-nistp256@openssh\.com)\s+[A-Za-z0-9+/]+={0,3}(?:\s+.+)?$/;
|
|
1061
1419
|
function toFiniteNumber(value) {
|
|
@@ -1227,6 +1585,252 @@ async function deployInstance(args) {
|
|
|
1227
1585
|
};
|
|
1228
1586
|
}
|
|
1229
1587
|
|
|
1588
|
+
// ../core/src/runtime.ts
|
|
1589
|
+
var DEFAULT_SCHEDULER_ALLOCATION_URL = "https://scheduler.api.aleph.cloud/api/v0/allocation";
|
|
1590
|
+
var DEFAULT_TWO_N_SIX_HASH_URL = "https://api.2n6.me/api/hash";
|
|
1591
|
+
function asString3(value) {
|
|
1592
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1593
|
+
}
|
|
1594
|
+
function asNumber3(value) {
|
|
1595
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1596
|
+
}
|
|
1597
|
+
function findCrnByHash(crns, crnHash) {
|
|
1598
|
+
return crns.find((crn) => crn.hash === crnHash) ?? null;
|
|
1599
|
+
}
|
|
1600
|
+
async function fetchSchedulerAllocation2(itemHash, options) {
|
|
1601
|
+
const response = await options.fetch(
|
|
1602
|
+
`${options.schedulerAllocationUrl ?? DEFAULT_SCHEDULER_ALLOCATION_URL}/${itemHash}`,
|
|
1603
|
+
{ cache: "no-cache" }
|
|
1604
|
+
);
|
|
1605
|
+
if (response.status === 404) return null;
|
|
1606
|
+
if (!response.ok) {
|
|
1607
|
+
throw new Error(`Scheduler allocation request failed: ${response.status}`);
|
|
1608
|
+
}
|
|
1609
|
+
const payload = await response.json();
|
|
1610
|
+
const node = payload?.node;
|
|
1611
|
+
return {
|
|
1612
|
+
source: "scheduler",
|
|
1613
|
+
crnHash: asString3(node?.node_id),
|
|
1614
|
+
crnUrl: asString3(node?.url),
|
|
1615
|
+
node: node ? {
|
|
1616
|
+
node_id: asString3(node.node_id) ?? void 0,
|
|
1617
|
+
url: asString3(node.url) ?? void 0,
|
|
1618
|
+
ipv6: asString3(node.ipv6),
|
|
1619
|
+
supports_ipv6: typeof node.supports_ipv6 === "boolean" ? node.supports_ipv6 : void 0
|
|
1620
|
+
} : null,
|
|
1621
|
+
vmIpv6: asString3(payload?.vm_ipv6),
|
|
1622
|
+
period: payload?.period ? {
|
|
1623
|
+
start_timestamp: asString3(payload.period.start_timestamp) ?? void 0,
|
|
1624
|
+
duration_seconds: asNumber3(payload.period.duration_seconds) ?? void 0
|
|
1625
|
+
} : null
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
async function fetch2n6WebAccessUrl2(itemHash, options) {
|
|
1629
|
+
const response = await options.fetch(
|
|
1630
|
+
`${options.twoN6HashUrl ?? DEFAULT_TWO_N_SIX_HASH_URL}/${itemHash}`,
|
|
1631
|
+
{ cache: "no-cache" }
|
|
1632
|
+
);
|
|
1633
|
+
if (!response.ok) return null;
|
|
1634
|
+
const payload = await response.json();
|
|
1635
|
+
return {
|
|
1636
|
+
url: normalizeProxyUrl2(payload?.url ?? payload?.subdomain),
|
|
1637
|
+
active: typeof payload?.active === "boolean" ? payload.active : null,
|
|
1638
|
+
subdomain: asString3(payload?.subdomain)
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
async function fetchCrnExecutionMap2(crnUrl, options) {
|
|
1642
|
+
const normalizedCrnUrl = String(crnUrl).replace(/\/+$/, "");
|
|
1643
|
+
for (const [version, suffix] of [
|
|
1644
|
+
["v2", "/v2/about/executions/list"],
|
|
1645
|
+
["v1", "/about/executions/list"]
|
|
1646
|
+
]) {
|
|
1647
|
+
const requestUrl = `${normalizedCrnUrl}${suffix}`;
|
|
1648
|
+
const response = await options.fetch(requestUrl, {
|
|
1649
|
+
cache: "no-cache"
|
|
1650
|
+
});
|
|
1651
|
+
if (response.ok) {
|
|
1652
|
+
const payload = await response.json();
|
|
1653
|
+
return {
|
|
1654
|
+
version,
|
|
1655
|
+
payload: payload && typeof payload === "object" ? payload : null,
|
|
1656
|
+
requestUrl
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
if (response.status !== 404) {
|
|
1660
|
+
return {
|
|
1661
|
+
version,
|
|
1662
|
+
payload: null,
|
|
1663
|
+
requestUrl
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return {
|
|
1668
|
+
version: "v2",
|
|
1669
|
+
payload: null,
|
|
1670
|
+
requestUrl: `${normalizedCrnUrl}/v2/about/executions/list`
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
function describeRuntimeAvailability(runtime) {
|
|
1674
|
+
const execution = runtime.execution ?? null;
|
|
1675
|
+
const mappedPorts2 = runtime.mappedPorts ?? {};
|
|
1676
|
+
const hostIpv4 = runtime.hostIpv4 ?? null;
|
|
1677
|
+
const proxyUrl = runtime.proxyUrl ?? null;
|
|
1678
|
+
const schedulerSource = runtime.allocation?.source ?? null;
|
|
1679
|
+
const webAccessActive = runtime.webAccess?.active ?? null;
|
|
1680
|
+
const mappedPortCount = Object.keys(mappedPorts2).length;
|
|
1681
|
+
if (hostIpv4 && mappedPortCount > 0) {
|
|
1682
|
+
return {
|
|
1683
|
+
state: "ready",
|
|
1684
|
+
reason: null,
|
|
1685
|
+
schedulerSource,
|
|
1686
|
+
executionSeen: Boolean(execution),
|
|
1687
|
+
webAccessActive,
|
|
1688
|
+
mappedPortCount,
|
|
1689
|
+
proxyUrl
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
if (!execution && proxyUrl && webAccessActive === false) {
|
|
1693
|
+
return {
|
|
1694
|
+
state: "proxy-reserved-inactive",
|
|
1695
|
+
reason: "Aleph reserved a proxy URL for the VM, but it is still inactive and the selected CRN has not exposed execution networking yet.",
|
|
1696
|
+
schedulerSource,
|
|
1697
|
+
executionSeen: false,
|
|
1698
|
+
webAccessActive,
|
|
1699
|
+
mappedPortCount,
|
|
1700
|
+
proxyUrl
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
if (!execution && schedulerSource === "manual") {
|
|
1704
|
+
return {
|
|
1705
|
+
state: "crn-execution-missing",
|
|
1706
|
+
reason: "The deployment is pinned to a specific CRN, but that CRN is not exposing this VM in its execution list yet.",
|
|
1707
|
+
schedulerSource,
|
|
1708
|
+
executionSeen: false,
|
|
1709
|
+
webAccessActive,
|
|
1710
|
+
mappedPortCount,
|
|
1711
|
+
proxyUrl
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
if (execution && !hostIpv4) {
|
|
1715
|
+
return {
|
|
1716
|
+
state: "execution-missing-host-ipv4",
|
|
1717
|
+
reason: "The CRN exposed an execution record, but it does not include a public host IPv4 yet.",
|
|
1718
|
+
schedulerSource,
|
|
1719
|
+
executionSeen: true,
|
|
1720
|
+
webAccessActive,
|
|
1721
|
+
mappedPortCount,
|
|
1722
|
+
proxyUrl
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
if (execution && mappedPortCount === 0) {
|
|
1726
|
+
return {
|
|
1727
|
+
state: "execution-missing-port-mappings",
|
|
1728
|
+
reason: "The CRN exposed an execution record, but mapped ports are still empty.",
|
|
1729
|
+
schedulerSource,
|
|
1730
|
+
executionSeen: true,
|
|
1731
|
+
webAccessActive,
|
|
1732
|
+
mappedPortCount,
|
|
1733
|
+
proxyUrl
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
return {
|
|
1737
|
+
state: "runtime-pending",
|
|
1738
|
+
reason: "Aleph has not exposed enough runtime networking details yet.",
|
|
1739
|
+
schedulerSource,
|
|
1740
|
+
executionSeen: Boolean(execution),
|
|
1741
|
+
webAccessActive,
|
|
1742
|
+
mappedPortCount,
|
|
1743
|
+
proxyUrl
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
async function fetchVmRuntime(args) {
|
|
1747
|
+
const crns = args.crns ?? await fetchCrns2({
|
|
1748
|
+
url: args.crnListUrl ?? DEFAULT_CRN_LIST_URL2,
|
|
1749
|
+
fetch: args.fetch
|
|
1750
|
+
});
|
|
1751
|
+
const schedulerAllocation = await fetchSchedulerAllocation2(args.itemHash, {
|
|
1752
|
+
fetch: args.fetch,
|
|
1753
|
+
schedulerAllocationUrl: args.schedulerAllocationUrl
|
|
1754
|
+
}).catch(() => null);
|
|
1755
|
+
const selectedCrn = args.crnHash ? findCrnByHash(crns, args.crnHash) : null;
|
|
1756
|
+
const allocation = schedulerAllocation ?? (selectedCrn ? {
|
|
1757
|
+
source: "manual",
|
|
1758
|
+
crnHash: selectedCrn.hash,
|
|
1759
|
+
crnUrl: selectedCrn.address,
|
|
1760
|
+
node: { url: selectedCrn.address },
|
|
1761
|
+
vmIpv6: null,
|
|
1762
|
+
period: null
|
|
1763
|
+
} : null);
|
|
1764
|
+
const webAccess = await fetch2n6WebAccessUrl2(args.itemHash, {
|
|
1765
|
+
fetch: args.fetch,
|
|
1766
|
+
twoN6HashUrl: args.twoN6HashUrl
|
|
1767
|
+
}).catch(() => null);
|
|
1768
|
+
const webAccessUrl = webAccess?.url ?? null;
|
|
1769
|
+
let execution = null;
|
|
1770
|
+
let executionLookupBlocked = false;
|
|
1771
|
+
if (allocation?.crnUrl) {
|
|
1772
|
+
const executionLookup = await fetchCrnExecutionMap2(allocation.crnUrl, {
|
|
1773
|
+
fetch: args.fetch
|
|
1774
|
+
});
|
|
1775
|
+
const executionPayload = executionLookup.payload?.[args.itemHash];
|
|
1776
|
+
if (executionPayload && typeof executionPayload === "object") {
|
|
1777
|
+
execution = normalizeExecution2(executionPayload, allocation.crnUrl);
|
|
1778
|
+
if (!execution.networking.proxy_url && webAccessUrl) {
|
|
1779
|
+
execution.networking.proxy_url = webAccessUrl;
|
|
1780
|
+
}
|
|
1781
|
+
} else if (executionLookup.payload == null) {
|
|
1782
|
+
executionLookupBlocked = true;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
const hostIpv4 = execution?.networking?.host_ipv4 ?? execution?.networking?.ipv4 ?? null;
|
|
1786
|
+
const ipv6 = execution?.networking?.ipv6_ip ?? execution?.networking?.ipv6 ?? allocation?.vmIpv6 ?? null;
|
|
1787
|
+
const mappedPorts2 = execution?.networking?.mapped_ports ?? {};
|
|
1788
|
+
const sshPort = mappedPorts2?.["22"]?.host ?? null;
|
|
1789
|
+
const proxyUrl = execution?.networking?.proxy_url ?? webAccessUrl ?? null;
|
|
1790
|
+
const diagnostics = describeRuntimeAvailability({
|
|
1791
|
+
allocation,
|
|
1792
|
+
execution,
|
|
1793
|
+
webAccess,
|
|
1794
|
+
hostIpv4,
|
|
1795
|
+
ipv6,
|
|
1796
|
+
proxyUrl,
|
|
1797
|
+
mappedPorts: mappedPorts2
|
|
1798
|
+
});
|
|
1799
|
+
return {
|
|
1800
|
+
allocation,
|
|
1801
|
+
execution,
|
|
1802
|
+
webAccess,
|
|
1803
|
+
webAccessUrl,
|
|
1804
|
+
hostIpv4,
|
|
1805
|
+
ipv6,
|
|
1806
|
+
proxyUrl,
|
|
1807
|
+
mappedPorts: mappedPorts2,
|
|
1808
|
+
diagnostics,
|
|
1809
|
+
sshCommand: hostIpv4 && sshPort ? `ssh root@${hostIpv4} -p ${sshPort}` : ipv6 ? `ssh root@${ipv6}` : null,
|
|
1810
|
+
selectedCrn,
|
|
1811
|
+
executionLookupBlocked
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
async function waitForVmRuntime(args) {
|
|
1815
|
+
const attempts = Math.max(1, Number(args.attempts ?? 20));
|
|
1816
|
+
const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
|
|
1817
|
+
const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1818
|
+
let lastRuntime = await fetchVmRuntime(args);
|
|
1819
|
+
args.onAttempt?.(lastRuntime, 1, attempts);
|
|
1820
|
+
for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
|
|
1821
|
+
if (lastRuntime.hostIpv4 && Object.keys(lastRuntime.mappedPorts ?? {}).length > 0) {
|
|
1822
|
+
return lastRuntime;
|
|
1823
|
+
}
|
|
1824
|
+
await sleep(delayMs);
|
|
1825
|
+
lastRuntime = await fetchVmRuntime(args);
|
|
1826
|
+
args.onAttempt?.(lastRuntime, attempt + 2, attempts);
|
|
1827
|
+
}
|
|
1828
|
+
if (lastRuntime.diagnostics) {
|
|
1829
|
+
lastRuntime.diagnostics.timedOut = true;
|
|
1830
|
+
}
|
|
1831
|
+
return lastRuntime;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1230
1834
|
// ../core/src/guest.ts
|
|
1231
1835
|
async function notifyCrnAllocation2(args) {
|
|
1232
1836
|
const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
|
|
@@ -1587,9 +2191,31 @@ async function pingPeer(libp2p) {
|
|
|
1587
2191
|
};
|
|
1588
2192
|
}
|
|
1589
2193
|
}
|
|
2194
|
+
var UI_DEPLOY_WAIT_ATTEMPTS = 60;
|
|
2195
|
+
var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
|
|
2196
|
+
var UI_RUNTIME_WAIT_ATTEMPTS = 40;
|
|
2197
|
+
var UI_RUNTIME_WAIT_DELAY_MS = 5e3;
|
|
1590
2198
|
function hasUsableRuntime(details) {
|
|
1591
2199
|
return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1592
2200
|
}
|
|
2201
|
+
function toSharedRootfsManifest(manifest) {
|
|
2202
|
+
if (!manifest) {
|
|
2203
|
+
return null;
|
|
2204
|
+
}
|
|
2205
|
+
return {
|
|
2206
|
+
profile: manifest.profile,
|
|
2207
|
+
version: manifest.version,
|
|
2208
|
+
rootfsInstallStrategy: manifest.rootfsInstallStrategy === "thin" || manifest.rootfsInstallStrategy === "prebaked" ? manifest.rootfsInstallStrategy : void 0,
|
|
2209
|
+
requiresBootstrapNetwork: manifest.requiresBootstrapNetwork,
|
|
2210
|
+
bootstrapSummary: manifest.bootstrapSummary,
|
|
2211
|
+
rootfsItemHash: manifest.rootfsItemHash,
|
|
2212
|
+
rootfsSizeMiB: manifest.rootfsSizeMiB,
|
|
2213
|
+
rootfsSourceSizeBytes: manifest.rootfsSourceSizeBytes,
|
|
2214
|
+
requiredPortForwards: manifest.requiredPortForwards,
|
|
2215
|
+
createdAt: manifest.createdAt,
|
|
2216
|
+
notes: manifest.notes
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
1593
2219
|
var SponsorRelayController = class {
|
|
1594
2220
|
state;
|
|
1595
2221
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1654,7 +2280,9 @@ var SponsorRelayController = class {
|
|
|
1654
2280
|
if (!itemHash) {
|
|
1655
2281
|
return;
|
|
1656
2282
|
}
|
|
1657
|
-
const latest = instances.find(
|
|
2283
|
+
const latest = instances.find(
|
|
2284
|
+
(entry) => entry.instance.item_hash === itemHash
|
|
2285
|
+
);
|
|
1658
2286
|
if (!latest) {
|
|
1659
2287
|
return;
|
|
1660
2288
|
}
|
|
@@ -1672,13 +2300,15 @@ var SponsorRelayController = class {
|
|
|
1672
2300
|
return;
|
|
1673
2301
|
}
|
|
1674
2302
|
if (status !== "processed") {
|
|
2303
|
+
const submittedAtMs = instanceTimestampMs(latest.instance);
|
|
2304
|
+
const pendingTooLong = submittedAtMs != null && Date.now() - submittedAtMs >= DEPLOYMENT_PENDING_WARNING_MS;
|
|
1675
2305
|
this.emitProgress({
|
|
1676
2306
|
stage: "waiting-for-aleph",
|
|
1677
|
-
label: "Waiting for Aleph",
|
|
2307
|
+
label: pendingTooLong ? "Aleph processing delayed" : "Waiting for Aleph",
|
|
1678
2308
|
progress: 76,
|
|
1679
|
-
status: "info",
|
|
2309
|
+
status: pendingTooLong ? "warning" : "info",
|
|
1680
2310
|
itemHash,
|
|
1681
|
-
detail: "Deployment submitted. Waiting for Aleph to process the instance message."
|
|
2311
|
+
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."
|
|
1682
2312
|
});
|
|
1683
2313
|
return;
|
|
1684
2314
|
}
|
|
@@ -2002,11 +2632,75 @@ var SponsorRelayController = class {
|
|
|
2002
2632
|
}
|
|
2003
2633
|
});
|
|
2004
2634
|
this.patch({
|
|
2005
|
-
busy: { deploying: false },
|
|
2006
2635
|
statusText: `Deployment submitted: ${result.itemHash}`,
|
|
2007
2636
|
lastDeploymentHash: result.itemHash
|
|
2008
2637
|
});
|
|
2009
|
-
|
|
2638
|
+
const inspection = await waitForDeploymentResult2(result.itemHash, {
|
|
2639
|
+
rootfsRef: this.state.manifest.rootfsItemHash,
|
|
2640
|
+
apiHost: this.client.apiHost,
|
|
2641
|
+
fetch: (url, init) => fetch(url, init),
|
|
2642
|
+
attempts: UI_DEPLOY_WAIT_ATTEMPTS,
|
|
2643
|
+
delayMs: UI_DEPLOY_WAIT_DELAY_MS,
|
|
2644
|
+
onAttempt: (inspectionResult) => {
|
|
2645
|
+
if (inspectionResult.status === "processed") {
|
|
2646
|
+
this.emitProgress({
|
|
2647
|
+
stage: "deployment-confirmed",
|
|
2648
|
+
label: "Deployment accepted by Aleph",
|
|
2649
|
+
progress: 82,
|
|
2650
|
+
status: "success",
|
|
2651
|
+
itemHash: result.itemHash,
|
|
2652
|
+
detail: "Aleph processed the instance message."
|
|
2653
|
+
});
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
if (inspectionResult.status === "rejected") {
|
|
2657
|
+
this.emitProgress({
|
|
2658
|
+
stage: "deployment-rejected",
|
|
2659
|
+
label: "Deployment rejected",
|
|
2660
|
+
progress: 100,
|
|
2661
|
+
status: "error",
|
|
2662
|
+
itemHash: result.itemHash,
|
|
2663
|
+
detail: inspectionResult.rejectionReason ?? "Aleph rejected the deployment.",
|
|
2664
|
+
error: inspectionResult.rejectionReason ?? "Aleph rejected the deployment."
|
|
2665
|
+
});
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
this.emitProgress({
|
|
2669
|
+
stage: "waiting-for-aleph",
|
|
2670
|
+
label: "Waiting for Aleph",
|
|
2671
|
+
progress: 76,
|
|
2672
|
+
status: "info",
|
|
2673
|
+
itemHash: result.itemHash,
|
|
2674
|
+
detail: "Deployment submitted. Waiting for Aleph to process the instance message."
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
});
|
|
2678
|
+
if (inspection.status !== "processed") {
|
|
2679
|
+
throw new Error(
|
|
2680
|
+
inspection.rejectionReason ?? `Deployment ${result.itemHash} stayed ${inspection.status} on Aleph.`
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
if ((this.state.manifest.requiredPortForwards?.length ?? 0) > 0) {
|
|
2684
|
+
this.emitProgress({
|
|
2685
|
+
stage: "refreshing-instances",
|
|
2686
|
+
label: "Publishing port forwards",
|
|
2687
|
+
progress: 86,
|
|
2688
|
+
status: "info",
|
|
2689
|
+
itemHash: result.itemHash,
|
|
2690
|
+
detail: "Publishing the required Aleph port-forward aggregate from the manifest."
|
|
2691
|
+
});
|
|
2692
|
+
await ensureInstancePortForwards({
|
|
2693
|
+
sender: this.state.wallet.address,
|
|
2694
|
+
instanceItemHash: result.itemHash,
|
|
2695
|
+
manifest: toSharedRootfsManifest(this.state.manifest),
|
|
2696
|
+
signer: personalSign,
|
|
2697
|
+
hasher: sha256Hex,
|
|
2698
|
+
fetch: (url, init) => fetch(url, init),
|
|
2699
|
+
apiHost: this.client.apiHost,
|
|
2700
|
+
sync: true
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
if (this.state.selectedCrn?.address) {
|
|
2010
2704
|
await notifyCrnAllocationWithRetry({
|
|
2011
2705
|
crnUrl: this.state.selectedCrn.address,
|
|
2012
2706
|
itemHash: result.itemHash,
|
|
@@ -2016,10 +2710,56 @@ var SponsorRelayController = class {
|
|
|
2016
2710
|
}
|
|
2017
2711
|
});
|
|
2018
2712
|
}
|
|
2713
|
+
this.emitProgress({
|
|
2714
|
+
stage: "deployment-confirmed",
|
|
2715
|
+
label: "Waiting for runtime",
|
|
2716
|
+
progress: 90,
|
|
2717
|
+
status: "warning",
|
|
2718
|
+
itemHash: result.itemHash,
|
|
2719
|
+
detail: "Aleph accepted the deployment. Waiting for runtime networking and mapped ports."
|
|
2720
|
+
});
|
|
2721
|
+
const runtime = await waitForVmRuntime({
|
|
2722
|
+
itemHash: result.itemHash,
|
|
2723
|
+
fetch: (url, init) => fetch(url, init),
|
|
2724
|
+
crnHash: this.state.selectedCrn?.hash,
|
|
2725
|
+
crns: this.state.crns,
|
|
2726
|
+
crnListUrl: this.props.crnListUrl,
|
|
2727
|
+
attempts: UI_RUNTIME_WAIT_ATTEMPTS,
|
|
2728
|
+
delayMs: UI_RUNTIME_WAIT_DELAY_MS,
|
|
2729
|
+
onAttempt: (runtime2) => {
|
|
2730
|
+
if (runtime2.hostIpv4 && Object.keys(runtime2.mappedPorts ?? {}).length > 0) {
|
|
2731
|
+
this.emitProgress({
|
|
2732
|
+
stage: "completed",
|
|
2733
|
+
label: "Runtime ready",
|
|
2734
|
+
progress: 100,
|
|
2735
|
+
status: "success",
|
|
2736
|
+
itemHash: result.itemHash,
|
|
2737
|
+
detail: "Runtime networking and mapped ports are now available."
|
|
2738
|
+
});
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
this.emitProgress({
|
|
2742
|
+
stage: "deployment-confirmed",
|
|
2743
|
+
label: "Waiting for runtime",
|
|
2744
|
+
progress: 90,
|
|
2745
|
+
status: "warning",
|
|
2746
|
+
itemHash: result.itemHash,
|
|
2747
|
+
detail: runtime2.diagnostics?.reason ?? "Waiting for CRN runtime networking and mapped ports."
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
if (!runtime.hostIpv4 || Object.keys(runtime.mappedPorts ?? {}).length === 0) {
|
|
2752
|
+
throw new Error(
|
|
2753
|
+
runtime.diagnostics?.reason ?? "Deployment was processed, but runtime networking never exposed mapped ports."
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
this.patch({
|
|
2757
|
+
busy: { deploying: false }
|
|
2758
|
+
});
|
|
2019
2759
|
this.emitProgress({
|
|
2020
2760
|
stage: "refreshing-instances",
|
|
2021
2761
|
label: "Refreshing instances",
|
|
2022
|
-
progress:
|
|
2762
|
+
progress: 96,
|
|
2023
2763
|
status: "info",
|
|
2024
2764
|
itemHash: result.itemHash,
|
|
2025
2765
|
detail: "Reloading deployments and runtime state."
|
|
@@ -2765,7 +3505,14 @@ function SponsorRelayFab(props) {
|
|
|
2765
3505
|
" credits required"
|
|
2766
3506
|
] }),
|
|
2767
3507
|
/* @__PURE__ */ jsx("div", { children: state.rootfsHealth.label }),
|
|
2768
|
-
/* @__PURE__ */ jsx("div", { children: state.selectedCrn?.name ?? shortHash(state.selectedCrn?.hash) })
|
|
3508
|
+
/* @__PURE__ */ jsx("div", { children: state.selectedCrn?.name ?? shortHash(state.selectedCrn?.hash) }),
|
|
3509
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3510
|
+
"Ports",
|
|
3511
|
+
" ",
|
|
3512
|
+
joinRequiredPortForwards(
|
|
3513
|
+
state.manifest?.requiredPortForwards ?? []
|
|
3514
|
+
)
|
|
3515
|
+
] })
|
|
2769
3516
|
] }),
|
|
2770
3517
|
/* @__PURE__ */ jsx(
|
|
2771
3518
|
"button",
|