@le-space/ui 0.1.61 → 0.2.0
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 +3 -3
- package/react/index.d.ts +8 -0
- package/react/index.js +974 -28
- package/shared/index.d.ts +21 -5
- package/shared/index.js +960 -26
- package/svelte/SponsorRelayFab.svelte +6 -1
package/shared/index.js
CHANGED
|
@@ -65,6 +65,16 @@ function joinMappedPorts(mappedPorts2) {
|
|
|
65
65
|
if (mappedPorts2.length === 0) return "-";
|
|
66
66
|
return mappedPorts2.map((entry) => `${entry.label}->${entry.hostPort ?? "?"}`).join(" \xB7 ");
|
|
67
67
|
}
|
|
68
|
+
function joinRequiredPortForwards(portForwards) {
|
|
69
|
+
if (!portForwards || portForwards.length === 0) return "-";
|
|
70
|
+
return portForwards.map((entry) => {
|
|
71
|
+
const protocols = [
|
|
72
|
+
entry.tcp === true ? "tcp" : null,
|
|
73
|
+
entry.udp === true ? "udp" : null
|
|
74
|
+
].filter((value) => Boolean(value)).join("/");
|
|
75
|
+
return `${entry.port}/${protocols}`;
|
|
76
|
+
}).join(" \xB7 ");
|
|
77
|
+
}
|
|
68
78
|
|
|
69
79
|
// ../browser/src/http.ts
|
|
70
80
|
async function fetchWithTimeout(input, init = {}, timeoutMs = 15e3) {
|
|
@@ -851,6 +861,7 @@ var DEFAULT_ALEPH_CHANNEL = "TEST";
|
|
|
851
861
|
var DEFAULT_ALEPH_API_HOST2 = "https://api2.aleph.im";
|
|
852
862
|
|
|
853
863
|
// ../core/src/crns.ts
|
|
864
|
+
var DEFAULT_CRN_LIST_URL2 = "https://crns-list.aleph.sh/crns.json";
|
|
854
865
|
function compatibleCrns(crns, excludedHashes = []) {
|
|
855
866
|
const excluded = new Set(excludedHashes.filter(Boolean));
|
|
856
867
|
return [...crns].filter((crn) => {
|
|
@@ -884,8 +895,113 @@ function filterDeployableCrns(crns, options = {}) {
|
|
|
884
895
|
compatibleCrns(crns, options.excludedHashes).filter((crn) => hasCrnCapacity(crn, options.spec))
|
|
885
896
|
);
|
|
886
897
|
}
|
|
898
|
+
async function fetchCrns2(options) {
|
|
899
|
+
const requestUrl = new URL(options.url ?? DEFAULT_CRN_LIST_URL2);
|
|
900
|
+
requestUrl.searchParams.set("filter_inactive", "true");
|
|
901
|
+
const response = await options.fetch(requestUrl.toString(), {
|
|
902
|
+
cache: "no-cache"
|
|
903
|
+
});
|
|
904
|
+
if (!response.ok) {
|
|
905
|
+
throw new Error(`CRN list request failed: ${response.status}`);
|
|
906
|
+
}
|
|
907
|
+
const payload = await response.json();
|
|
908
|
+
return Array.isArray(payload?.crns) ? payload.crns : [];
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// ../core/src/port-forwarding.ts
|
|
912
|
+
var DEFAULT_INSTANCE_PORT_FORWARDS = [
|
|
913
|
+
{ port: 22, tcp: true, udp: false, purpose: "SSH" }
|
|
914
|
+
];
|
|
915
|
+
function normalizeRequestedPort(entry) {
|
|
916
|
+
return {
|
|
917
|
+
port: entry.port,
|
|
918
|
+
tcp: entry.tcp === true,
|
|
919
|
+
udp: entry.udp === true,
|
|
920
|
+
purpose: entry.purpose?.trim() || void 0
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
function normalizePortFlags(value) {
|
|
924
|
+
if (!value || typeof value !== "object") return null;
|
|
925
|
+
const candidate = value;
|
|
926
|
+
return {
|
|
927
|
+
tcp: candidate.tcp === true,
|
|
928
|
+
udp: candidate.udp === true
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
function normalizeExistingPortForwardEntry(entry) {
|
|
932
|
+
if (!entry?.ports || typeof entry.ports !== "object") return {};
|
|
933
|
+
return Object.fromEntries(
|
|
934
|
+
Object.entries(entry.ports).map(([port, flags]) => [port, normalizePortFlags(flags)]).filter((item) => item[1] != null)
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
function requestedPortFlags(portForwards) {
|
|
938
|
+
return Object.fromEntries(
|
|
939
|
+
portForwards.map((entry) => [
|
|
940
|
+
String(entry.port),
|
|
941
|
+
{
|
|
942
|
+
tcp: entry.tcp === true,
|
|
943
|
+
udp: entry.udp === true
|
|
944
|
+
}
|
|
945
|
+
])
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
function mergePortFlagMaps(existing, requested) {
|
|
949
|
+
const merged = /* @__PURE__ */ new Map();
|
|
950
|
+
for (const [port, flags] of Object.entries(existing)) {
|
|
951
|
+
merged.set(port, {
|
|
952
|
+
tcp: flags.tcp === true,
|
|
953
|
+
udp: flags.udp === true
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
for (const [port, flags] of Object.entries(requested)) {
|
|
957
|
+
const current = merged.get(port);
|
|
958
|
+
merged.set(port, {
|
|
959
|
+
tcp: current?.tcp === true || flags.tcp === true,
|
|
960
|
+
udp: current?.udp === true || flags.udp === true
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
return Object.fromEntries([...merged.entries()].sort((left, right) => Number(left[0]) - Number(right[0])));
|
|
964
|
+
}
|
|
965
|
+
function mergeRequiredPortForwards(...groups) {
|
|
966
|
+
const merged = /* @__PURE__ */ new Map();
|
|
967
|
+
for (const group of groups) {
|
|
968
|
+
for (const entry of group ?? []) {
|
|
969
|
+
const normalized = normalizeRequestedPort(entry);
|
|
970
|
+
const current = merged.get(normalized.port);
|
|
971
|
+
merged.set(normalized.port, {
|
|
972
|
+
port: normalized.port,
|
|
973
|
+
tcp: current?.tcp === true || normalized.tcp === true,
|
|
974
|
+
udp: current?.udp === true || normalized.udp === true,
|
|
975
|
+
purpose: current?.purpose ?? normalized.purpose
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return [...merged.values()].sort((left, right) => left.port - right.port);
|
|
980
|
+
}
|
|
981
|
+
function requiredInstancePortForwards(manifest) {
|
|
982
|
+
return mergeRequiredPortForwards(DEFAULT_INSTANCE_PORT_FORWARDS, manifest?.requiredPortForwards);
|
|
983
|
+
}
|
|
984
|
+
async function fetchPortForwardAggregate(address, options) {
|
|
985
|
+
const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
986
|
+
requestUrl.searchParams.set("keys", "port-forwarding");
|
|
987
|
+
const response = await options.fetch(requestUrl.toString(), { cache: "no-cache" });
|
|
988
|
+
if (response.status === 404) return {};
|
|
989
|
+
if (!response.ok) {
|
|
990
|
+
throw new Error(`Port-forward aggregate request failed: ${response.status}`);
|
|
991
|
+
}
|
|
992
|
+
const payload = await response.json();
|
|
993
|
+
const aggregate = payload.data?.["port-forwarding"];
|
|
994
|
+
if (!aggregate || typeof aggregate !== "object" || Array.isArray(aggregate)) return {};
|
|
995
|
+
return aggregate;
|
|
996
|
+
}
|
|
887
997
|
|
|
888
998
|
// ../core/src/aleph-normalizers.ts
|
|
999
|
+
function asString2(value) {
|
|
1000
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
1001
|
+
}
|
|
1002
|
+
function asNumber2(value) {
|
|
1003
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1004
|
+
}
|
|
889
1005
|
function normalizeMessageStatus2(status) {
|
|
890
1006
|
if (typeof status !== "string") return "unknown";
|
|
891
1007
|
const normalized = status.toLowerCase();
|
|
@@ -894,6 +1010,183 @@ function normalizeMessageStatus2(status) {
|
|
|
894
1010
|
}
|
|
895
1011
|
return "unknown";
|
|
896
1012
|
}
|
|
1013
|
+
function normalizeProxyUrl2(value) {
|
|
1014
|
+
const stringValue = asString2(value);
|
|
1015
|
+
if (!stringValue) return null;
|
|
1016
|
+
if (/^https?:\/\//i.test(stringValue)) return stringValue;
|
|
1017
|
+
return `https://${stringValue}`;
|
|
1018
|
+
}
|
|
1019
|
+
function messageTypeFromEnvelope2(payload) {
|
|
1020
|
+
if (!payload) return null;
|
|
1021
|
+
const type = payload.type ?? payload.message?.type ?? (Array.isArray(payload.messages) ? payload.messages[0]?.type : void 0);
|
|
1022
|
+
return typeof type === "string" ? type.toUpperCase() : null;
|
|
1023
|
+
}
|
|
1024
|
+
function extractReferenceHashes2(details) {
|
|
1025
|
+
if (!details || typeof details !== "object" || !("errors" in details)) return [];
|
|
1026
|
+
const errors = details.errors;
|
|
1027
|
+
if (!Array.isArray(errors)) return [];
|
|
1028
|
+
return errors.filter((value) => typeof value === "string");
|
|
1029
|
+
}
|
|
1030
|
+
function describeRejectedDeployment2(payload, references, rootfsRef) {
|
|
1031
|
+
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
1032
|
+
const pendingReferences = references.filter((reference) => reference.status === "pending");
|
|
1033
|
+
const missingReferences = references.filter((reference) => reference.status === "missing");
|
|
1034
|
+
const rootfsReference = references.find((reference) => reference.itemHash === rootfsRef);
|
|
1035
|
+
if (rootfsReference?.status === "pending") {
|
|
1036
|
+
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.`;
|
|
1037
|
+
}
|
|
1038
|
+
if (pendingReferences.length > 0) {
|
|
1039
|
+
return `Aleph rejected this deployment because referenced message(s) are still pending: ${pendingReferences.map((reference) => reference.itemHash).join(", ")}.`;
|
|
1040
|
+
}
|
|
1041
|
+
if (missingReferences.length > 0) {
|
|
1042
|
+
return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
|
|
1043
|
+
}
|
|
1044
|
+
const referencedHashes = extractReferenceHashes2(payload.details);
|
|
1045
|
+
if (referencedHashes.length > 0) {
|
|
1046
|
+
return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
|
|
1047
|
+
}
|
|
1048
|
+
return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
|
|
1049
|
+
}
|
|
1050
|
+
function extractProxyUrl2(item, networking) {
|
|
1051
|
+
const networkingCandidates = [
|
|
1052
|
+
networking?.proxy_url,
|
|
1053
|
+
networking?.proxyUrl,
|
|
1054
|
+
networking?.web_access_url,
|
|
1055
|
+
networking?.webAccessUrl,
|
|
1056
|
+
networking?.proxy_hostname,
|
|
1057
|
+
networking?.proxyHostname,
|
|
1058
|
+
networking?.domain,
|
|
1059
|
+
networking?.hostname
|
|
1060
|
+
];
|
|
1061
|
+
for (const candidate of networkingCandidates) {
|
|
1062
|
+
const normalized = normalizeProxyUrl2(candidate);
|
|
1063
|
+
if (normalized) return normalized;
|
|
1064
|
+
}
|
|
1065
|
+
const webAccessCandidates = [item.web_access, item.webAccess];
|
|
1066
|
+
for (const entry of webAccessCandidates) {
|
|
1067
|
+
const normalized = normalizeProxyUrl2(entry?.url) ?? normalizeProxyUrl2(entry?.proxy_url) ?? normalizeProxyUrl2(entry?.hostname) ?? normalizeProxyUrl2(entry?.domain);
|
|
1068
|
+
if (normalized) return normalized;
|
|
1069
|
+
}
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
function normalizeExecution2(item, crnUrl) {
|
|
1073
|
+
const networking = item.networking ?? null;
|
|
1074
|
+
const mappedPorts2 = networking && "mapped_ports" in networking && networking.mapped_ports && typeof networking.mapped_ports === "object" ? Object.fromEntries(
|
|
1075
|
+
Object.entries(networking.mapped_ports).map(([port, mapping]) => [
|
|
1076
|
+
port,
|
|
1077
|
+
{
|
|
1078
|
+
host: asNumber2(mapping?.host) ?? void 0,
|
|
1079
|
+
tcp: typeof mapping?.tcp === "boolean" ? mapping.tcp : void 0,
|
|
1080
|
+
udp: typeof mapping?.udp === "boolean" ? mapping.udp : void 0
|
|
1081
|
+
}
|
|
1082
|
+
])
|
|
1083
|
+
) : void 0;
|
|
1084
|
+
if (networking && ("host_ipv4" in networking || "ipv6_ip" in networking || "ipv4_network" in networking)) {
|
|
1085
|
+
const v2Item = item;
|
|
1086
|
+
return {
|
|
1087
|
+
crnUrl,
|
|
1088
|
+
version: "v2",
|
|
1089
|
+
running: typeof v2Item.running === "boolean" ? v2Item.running : void 0,
|
|
1090
|
+
networking: {
|
|
1091
|
+
ipv4_network: asString2(networking.ipv4_network),
|
|
1092
|
+
host_ipv4: asString2(networking.host_ipv4),
|
|
1093
|
+
ipv6_network: asString2(networking.ipv6_network),
|
|
1094
|
+
ipv6_ip: asString2(networking.ipv6_ip),
|
|
1095
|
+
ipv4_ip: asString2(networking.ipv4_ip),
|
|
1096
|
+
proxy_url: extractProxyUrl2(v2Item, networking),
|
|
1097
|
+
mapped_ports: mappedPorts2
|
|
1098
|
+
},
|
|
1099
|
+
status: v2Item.status ? {
|
|
1100
|
+
defined_at: asString2(v2Item.status.defined_at),
|
|
1101
|
+
preparing_at: asString2(v2Item.status.preparing_at),
|
|
1102
|
+
prepared_at: asString2(v2Item.status.prepared_at),
|
|
1103
|
+
starting_at: asString2(v2Item.status.starting_at),
|
|
1104
|
+
started_at: asString2(v2Item.status.started_at),
|
|
1105
|
+
stopping_at: asString2(v2Item.status.stopping_at),
|
|
1106
|
+
stopped_at: asString2(v2Item.status.stopped_at)
|
|
1107
|
+
} : null
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
return {
|
|
1111
|
+
crnUrl,
|
|
1112
|
+
version: "v1",
|
|
1113
|
+
networking: {
|
|
1114
|
+
ipv4: networking && "ipv4" in networking ? asString2(networking.ipv4) : null,
|
|
1115
|
+
ipv6: networking && "ipv6" in networking ? asString2(networking.ipv6) : null
|
|
1116
|
+
},
|
|
1117
|
+
status: null
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// ../core/src/deployment-inspection.ts
|
|
1122
|
+
async function fetchMessageEnvelope2(itemHash, options) {
|
|
1123
|
+
const response = await options.fetch(`${options.apiHost ?? DEFAULT_ALEPH_API_HOST2}/api/v0/messages/${itemHash}`, {
|
|
1124
|
+
cache: "no-cache"
|
|
1125
|
+
});
|
|
1126
|
+
if (response.status === 404) return null;
|
|
1127
|
+
if (!response.ok) throw new Error(`Message lookup failed: ${response.status}`);
|
|
1128
|
+
return await response.json();
|
|
1129
|
+
}
|
|
1130
|
+
async function fetchReference2(itemHash, options) {
|
|
1131
|
+
const payload = await fetchMessageEnvelope2(itemHash, options);
|
|
1132
|
+
if (!payload) {
|
|
1133
|
+
return {
|
|
1134
|
+
itemHash,
|
|
1135
|
+
status: "missing",
|
|
1136
|
+
type: null
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
itemHash,
|
|
1141
|
+
status: normalizeMessageStatus2(payload.status),
|
|
1142
|
+
type: messageTypeFromEnvelope2(payload)
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
async function inspectDeploymentResult2(itemHash, options) {
|
|
1146
|
+
const payload = await fetchMessageEnvelope2(itemHash, options);
|
|
1147
|
+
if (!payload) {
|
|
1148
|
+
return {
|
|
1149
|
+
status: "unknown",
|
|
1150
|
+
errorCode: null,
|
|
1151
|
+
details: null,
|
|
1152
|
+
rejectionReason: `Deployment message ${itemHash} was not found on Aleph.`,
|
|
1153
|
+
references: []
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
const relatedHashes = new Set(options.rootfsRef ? [options.rootfsRef] : []);
|
|
1157
|
+
for (const referenceHash of extractReferenceHashes2(payload.details)) {
|
|
1158
|
+
relatedHashes.add(referenceHash);
|
|
1159
|
+
}
|
|
1160
|
+
const references = await Promise.all(
|
|
1161
|
+
Array.from(relatedHashes).map((hash) => fetchReference2(hash, options))
|
|
1162
|
+
);
|
|
1163
|
+
const status = normalizeMessageStatus2(payload.status);
|
|
1164
|
+
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
1165
|
+
const details = payload.details && typeof payload.details === "object" ? payload.details : null;
|
|
1166
|
+
return {
|
|
1167
|
+
status,
|
|
1168
|
+
errorCode,
|
|
1169
|
+
details,
|
|
1170
|
+
rejectionReason: status === "rejected" ? describeRejectedDeployment2(payload, references, options.rootfsRef) : null,
|
|
1171
|
+
references
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
async function waitForDeploymentResult2(itemHash, options) {
|
|
1175
|
+
const attempts = Math.max(1, Number(options.attempts ?? 15));
|
|
1176
|
+
const delayMs = Math.max(0, Number(options.delayMs ?? 2e3));
|
|
1177
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1178
|
+
let lastResult = await inspectDeploymentResult2(itemHash, options);
|
|
1179
|
+
options.onAttempt?.(lastResult, 1, attempts);
|
|
1180
|
+
for (let attempt = 1; attempt < attempts; attempt += 1) {
|
|
1181
|
+
if (lastResult.status === "processed" || lastResult.status === "rejected") {
|
|
1182
|
+
return lastResult;
|
|
1183
|
+
}
|
|
1184
|
+
await sleep(delayMs);
|
|
1185
|
+
lastResult = await inspectDeploymentResult2(itemHash, options);
|
|
1186
|
+
options.onAttempt?.(lastResult, attempt + 1, attempts);
|
|
1187
|
+
}
|
|
1188
|
+
return lastResult;
|
|
1189
|
+
}
|
|
897
1190
|
|
|
898
1191
|
// ../core/src/broadcast.ts
|
|
899
1192
|
function signaturePayload(message) {
|
|
@@ -1056,6 +1349,70 @@ async function forgetAlephMessages(args) {
|
|
|
1056
1349
|
};
|
|
1057
1350
|
}
|
|
1058
1351
|
|
|
1352
|
+
// ../core/src/aggregate-publication.ts
|
|
1353
|
+
function createPortForwardAggregateContent(args) {
|
|
1354
|
+
const existingPorts = normalizeExistingPortForwardEntry(args.existingAggregate?.[args.instanceItemHash]);
|
|
1355
|
+
const mergedPorts = mergePortFlagMaps(existingPorts, requestedPortFlags(args.requestedPorts));
|
|
1356
|
+
return {
|
|
1357
|
+
address: args.sender,
|
|
1358
|
+
key: "port-forwarding",
|
|
1359
|
+
content: {
|
|
1360
|
+
[args.instanceItemHash]: {
|
|
1361
|
+
ports: mergedPorts
|
|
1362
|
+
}
|
|
1363
|
+
},
|
|
1364
|
+
time: args.now ?? Date.now() / 1e3
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
async function createUnsignedAggregateMessage(args) {
|
|
1368
|
+
const itemContent = JSON.stringify(args.content);
|
|
1369
|
+
const itemHash = await args.hasher(itemContent);
|
|
1370
|
+
return {
|
|
1371
|
+
sender: args.sender,
|
|
1372
|
+
chain: "ETH",
|
|
1373
|
+
type: "AGGREGATE",
|
|
1374
|
+
item_hash: itemHash,
|
|
1375
|
+
item_type: "inline",
|
|
1376
|
+
item_content: itemContent,
|
|
1377
|
+
time: args.now ?? Date.now() / 1e3,
|
|
1378
|
+
channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
async function ensureInstancePortForwards(args) {
|
|
1382
|
+
const requestedPorts = requiredInstancePortForwards(args.manifest);
|
|
1383
|
+
const aggregate = await fetchPortForwardAggregate(args.sender, {
|
|
1384
|
+
apiHost: args.apiHost,
|
|
1385
|
+
fetch: args.fetch
|
|
1386
|
+
});
|
|
1387
|
+
const content = createPortForwardAggregateContent({
|
|
1388
|
+
sender: args.sender,
|
|
1389
|
+
instanceItemHash: args.instanceItemHash,
|
|
1390
|
+
requestedPorts,
|
|
1391
|
+
existingAggregate: aggregate
|
|
1392
|
+
});
|
|
1393
|
+
const unsignedMessage = await createUnsignedAggregateMessage({
|
|
1394
|
+
sender: args.sender,
|
|
1395
|
+
content,
|
|
1396
|
+
hasher: args.hasher,
|
|
1397
|
+
channel: args.channel
|
|
1398
|
+
});
|
|
1399
|
+
const message = await signAlephMessage(unsignedMessage, args.signer);
|
|
1400
|
+
const { response, httpStatus } = await broadcastAlephMessage2(message, {
|
|
1401
|
+
apiHost: args.apiHost,
|
|
1402
|
+
sync: args.sync,
|
|
1403
|
+
fetch: args.fetch
|
|
1404
|
+
});
|
|
1405
|
+
const aggregateStatus = normalizeBroadcastStatus(httpStatus, response.message_status);
|
|
1406
|
+
if (aggregateStatus === "rejected") {
|
|
1407
|
+
throw new Error(`Port-forward aggregate was rejected by Aleph: ${JSON.stringify(response.details ?? response)}`);
|
|
1408
|
+
}
|
|
1409
|
+
return {
|
|
1410
|
+
aggregateItemHash: message.item_hash,
|
|
1411
|
+
aggregateStatus,
|
|
1412
|
+
requestedPorts
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1059
1416
|
// ../core/src/instance-deployment.ts
|
|
1060
1417
|
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
1418
|
function toFiniteNumber(value) {
|
|
@@ -1227,6 +1584,252 @@ async function deployInstance(args) {
|
|
|
1227
1584
|
};
|
|
1228
1585
|
}
|
|
1229
1586
|
|
|
1587
|
+
// ../core/src/runtime.ts
|
|
1588
|
+
var DEFAULT_SCHEDULER_ALLOCATION_URL = "https://scheduler.api.aleph.cloud/api/v0/allocation";
|
|
1589
|
+
var DEFAULT_TWO_N_SIX_HASH_URL = "https://api.2n6.me/api/hash";
|
|
1590
|
+
function asString3(value) {
|
|
1591
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1592
|
+
}
|
|
1593
|
+
function asNumber3(value) {
|
|
1594
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1595
|
+
}
|
|
1596
|
+
function findCrnByHash(crns, crnHash) {
|
|
1597
|
+
return crns.find((crn) => crn.hash === crnHash) ?? null;
|
|
1598
|
+
}
|
|
1599
|
+
async function fetchSchedulerAllocation2(itemHash, options) {
|
|
1600
|
+
const response = await options.fetch(
|
|
1601
|
+
`${options.schedulerAllocationUrl ?? DEFAULT_SCHEDULER_ALLOCATION_URL}/${itemHash}`,
|
|
1602
|
+
{ cache: "no-cache" }
|
|
1603
|
+
);
|
|
1604
|
+
if (response.status === 404) return null;
|
|
1605
|
+
if (!response.ok) {
|
|
1606
|
+
throw new Error(`Scheduler allocation request failed: ${response.status}`);
|
|
1607
|
+
}
|
|
1608
|
+
const payload = await response.json();
|
|
1609
|
+
const node = payload?.node;
|
|
1610
|
+
return {
|
|
1611
|
+
source: "scheduler",
|
|
1612
|
+
crnHash: asString3(node?.node_id),
|
|
1613
|
+
crnUrl: asString3(node?.url),
|
|
1614
|
+
node: node ? {
|
|
1615
|
+
node_id: asString3(node.node_id) ?? void 0,
|
|
1616
|
+
url: asString3(node.url) ?? void 0,
|
|
1617
|
+
ipv6: asString3(node.ipv6),
|
|
1618
|
+
supports_ipv6: typeof node.supports_ipv6 === "boolean" ? node.supports_ipv6 : void 0
|
|
1619
|
+
} : null,
|
|
1620
|
+
vmIpv6: asString3(payload?.vm_ipv6),
|
|
1621
|
+
period: payload?.period ? {
|
|
1622
|
+
start_timestamp: asString3(payload.period.start_timestamp) ?? void 0,
|
|
1623
|
+
duration_seconds: asNumber3(payload.period.duration_seconds) ?? void 0
|
|
1624
|
+
} : null
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
async function fetch2n6WebAccessUrl2(itemHash, options) {
|
|
1628
|
+
const response = await options.fetch(
|
|
1629
|
+
`${options.twoN6HashUrl ?? DEFAULT_TWO_N_SIX_HASH_URL}/${itemHash}`,
|
|
1630
|
+
{ cache: "no-cache" }
|
|
1631
|
+
);
|
|
1632
|
+
if (!response.ok) return null;
|
|
1633
|
+
const payload = await response.json();
|
|
1634
|
+
return {
|
|
1635
|
+
url: normalizeProxyUrl2(payload?.url ?? payload?.subdomain),
|
|
1636
|
+
active: typeof payload?.active === "boolean" ? payload.active : null,
|
|
1637
|
+
subdomain: asString3(payload?.subdomain)
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
async function fetchCrnExecutionMap2(crnUrl, options) {
|
|
1641
|
+
const normalizedCrnUrl = String(crnUrl).replace(/\/+$/, "");
|
|
1642
|
+
for (const [version, suffix] of [
|
|
1643
|
+
["v2", "/v2/about/executions/list"],
|
|
1644
|
+
["v1", "/about/executions/list"]
|
|
1645
|
+
]) {
|
|
1646
|
+
const requestUrl = `${normalizedCrnUrl}${suffix}`;
|
|
1647
|
+
const response = await options.fetch(requestUrl, {
|
|
1648
|
+
cache: "no-cache"
|
|
1649
|
+
});
|
|
1650
|
+
if (response.ok) {
|
|
1651
|
+
const payload = await response.json();
|
|
1652
|
+
return {
|
|
1653
|
+
version,
|
|
1654
|
+
payload: payload && typeof payload === "object" ? payload : null,
|
|
1655
|
+
requestUrl
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
if (response.status !== 404) {
|
|
1659
|
+
return {
|
|
1660
|
+
version,
|
|
1661
|
+
payload: null,
|
|
1662
|
+
requestUrl
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
return {
|
|
1667
|
+
version: "v2",
|
|
1668
|
+
payload: null,
|
|
1669
|
+
requestUrl: `${normalizedCrnUrl}/v2/about/executions/list`
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
function describeRuntimeAvailability(runtime) {
|
|
1673
|
+
const execution = runtime.execution ?? null;
|
|
1674
|
+
const mappedPorts2 = runtime.mappedPorts ?? {};
|
|
1675
|
+
const hostIpv4 = runtime.hostIpv4 ?? null;
|
|
1676
|
+
const proxyUrl = runtime.proxyUrl ?? null;
|
|
1677
|
+
const schedulerSource = runtime.allocation?.source ?? null;
|
|
1678
|
+
const webAccessActive = runtime.webAccess?.active ?? null;
|
|
1679
|
+
const mappedPortCount = Object.keys(mappedPorts2).length;
|
|
1680
|
+
if (hostIpv4 && mappedPortCount > 0) {
|
|
1681
|
+
return {
|
|
1682
|
+
state: "ready",
|
|
1683
|
+
reason: null,
|
|
1684
|
+
schedulerSource,
|
|
1685
|
+
executionSeen: Boolean(execution),
|
|
1686
|
+
webAccessActive,
|
|
1687
|
+
mappedPortCount,
|
|
1688
|
+
proxyUrl
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
if (!execution && proxyUrl && webAccessActive === false) {
|
|
1692
|
+
return {
|
|
1693
|
+
state: "proxy-reserved-inactive",
|
|
1694
|
+
reason: "Aleph reserved a proxy URL for the VM, but it is still inactive and the selected CRN has not exposed execution networking yet.",
|
|
1695
|
+
schedulerSource,
|
|
1696
|
+
executionSeen: false,
|
|
1697
|
+
webAccessActive,
|
|
1698
|
+
mappedPortCount,
|
|
1699
|
+
proxyUrl
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
if (!execution && schedulerSource === "manual") {
|
|
1703
|
+
return {
|
|
1704
|
+
state: "crn-execution-missing",
|
|
1705
|
+
reason: "The deployment is pinned to a specific CRN, but that CRN is not exposing this VM in its execution list yet.",
|
|
1706
|
+
schedulerSource,
|
|
1707
|
+
executionSeen: false,
|
|
1708
|
+
webAccessActive,
|
|
1709
|
+
mappedPortCount,
|
|
1710
|
+
proxyUrl
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
if (execution && !hostIpv4) {
|
|
1714
|
+
return {
|
|
1715
|
+
state: "execution-missing-host-ipv4",
|
|
1716
|
+
reason: "The CRN exposed an execution record, but it does not include a public host IPv4 yet.",
|
|
1717
|
+
schedulerSource,
|
|
1718
|
+
executionSeen: true,
|
|
1719
|
+
webAccessActive,
|
|
1720
|
+
mappedPortCount,
|
|
1721
|
+
proxyUrl
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
if (execution && mappedPortCount === 0) {
|
|
1725
|
+
return {
|
|
1726
|
+
state: "execution-missing-port-mappings",
|
|
1727
|
+
reason: "The CRN exposed an execution record, but mapped ports are still empty.",
|
|
1728
|
+
schedulerSource,
|
|
1729
|
+
executionSeen: true,
|
|
1730
|
+
webAccessActive,
|
|
1731
|
+
mappedPortCount,
|
|
1732
|
+
proxyUrl
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
return {
|
|
1736
|
+
state: "runtime-pending",
|
|
1737
|
+
reason: "Aleph has not exposed enough runtime networking details yet.",
|
|
1738
|
+
schedulerSource,
|
|
1739
|
+
executionSeen: Boolean(execution),
|
|
1740
|
+
webAccessActive,
|
|
1741
|
+
mappedPortCount,
|
|
1742
|
+
proxyUrl
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
async function fetchVmRuntime(args) {
|
|
1746
|
+
const crns = args.crns ?? await fetchCrns2({
|
|
1747
|
+
url: args.crnListUrl ?? DEFAULT_CRN_LIST_URL2,
|
|
1748
|
+
fetch: args.fetch
|
|
1749
|
+
});
|
|
1750
|
+
const schedulerAllocation = await fetchSchedulerAllocation2(args.itemHash, {
|
|
1751
|
+
fetch: args.fetch,
|
|
1752
|
+
schedulerAllocationUrl: args.schedulerAllocationUrl
|
|
1753
|
+
}).catch(() => null);
|
|
1754
|
+
const selectedCrn = args.crnHash ? findCrnByHash(crns, args.crnHash) : null;
|
|
1755
|
+
const allocation = schedulerAllocation ?? (selectedCrn ? {
|
|
1756
|
+
source: "manual",
|
|
1757
|
+
crnHash: selectedCrn.hash,
|
|
1758
|
+
crnUrl: selectedCrn.address,
|
|
1759
|
+
node: { url: selectedCrn.address },
|
|
1760
|
+
vmIpv6: null,
|
|
1761
|
+
period: null
|
|
1762
|
+
} : null);
|
|
1763
|
+
const webAccess = await fetch2n6WebAccessUrl2(args.itemHash, {
|
|
1764
|
+
fetch: args.fetch,
|
|
1765
|
+
twoN6HashUrl: args.twoN6HashUrl
|
|
1766
|
+
}).catch(() => null);
|
|
1767
|
+
const webAccessUrl = webAccess?.url ?? null;
|
|
1768
|
+
let execution = null;
|
|
1769
|
+
let executionLookupBlocked = false;
|
|
1770
|
+
if (allocation?.crnUrl) {
|
|
1771
|
+
const executionLookup = await fetchCrnExecutionMap2(allocation.crnUrl, {
|
|
1772
|
+
fetch: args.fetch
|
|
1773
|
+
});
|
|
1774
|
+
const executionPayload = executionLookup.payload?.[args.itemHash];
|
|
1775
|
+
if (executionPayload && typeof executionPayload === "object") {
|
|
1776
|
+
execution = normalizeExecution2(executionPayload, allocation.crnUrl);
|
|
1777
|
+
if (!execution.networking.proxy_url && webAccessUrl) {
|
|
1778
|
+
execution.networking.proxy_url = webAccessUrl;
|
|
1779
|
+
}
|
|
1780
|
+
} else if (executionLookup.payload == null) {
|
|
1781
|
+
executionLookupBlocked = true;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
const hostIpv4 = execution?.networking?.host_ipv4 ?? execution?.networking?.ipv4 ?? null;
|
|
1785
|
+
const ipv6 = execution?.networking?.ipv6_ip ?? execution?.networking?.ipv6 ?? allocation?.vmIpv6 ?? null;
|
|
1786
|
+
const mappedPorts2 = execution?.networking?.mapped_ports ?? {};
|
|
1787
|
+
const sshPort = mappedPorts2?.["22"]?.host ?? null;
|
|
1788
|
+
const proxyUrl = execution?.networking?.proxy_url ?? webAccessUrl ?? null;
|
|
1789
|
+
const diagnostics = describeRuntimeAvailability({
|
|
1790
|
+
allocation,
|
|
1791
|
+
execution,
|
|
1792
|
+
webAccess,
|
|
1793
|
+
hostIpv4,
|
|
1794
|
+
ipv6,
|
|
1795
|
+
proxyUrl,
|
|
1796
|
+
mappedPorts: mappedPorts2
|
|
1797
|
+
});
|
|
1798
|
+
return {
|
|
1799
|
+
allocation,
|
|
1800
|
+
execution,
|
|
1801
|
+
webAccess,
|
|
1802
|
+
webAccessUrl,
|
|
1803
|
+
hostIpv4,
|
|
1804
|
+
ipv6,
|
|
1805
|
+
proxyUrl,
|
|
1806
|
+
mappedPorts: mappedPorts2,
|
|
1807
|
+
diagnostics,
|
|
1808
|
+
sshCommand: hostIpv4 && sshPort ? `ssh root@${hostIpv4} -p ${sshPort}` : ipv6 ? `ssh root@${ipv6}` : null,
|
|
1809
|
+
selectedCrn,
|
|
1810
|
+
executionLookupBlocked
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
async function waitForVmRuntime(args) {
|
|
1814
|
+
const attempts = Math.max(1, Number(args.attempts ?? 20));
|
|
1815
|
+
const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
|
|
1816
|
+
const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1817
|
+
let lastRuntime = await fetchVmRuntime(args);
|
|
1818
|
+
args.onAttempt?.(lastRuntime, 1, attempts);
|
|
1819
|
+
for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
|
|
1820
|
+
if (lastRuntime.hostIpv4 && Object.keys(lastRuntime.mappedPorts ?? {}).length > 0) {
|
|
1821
|
+
return lastRuntime;
|
|
1822
|
+
}
|
|
1823
|
+
await sleep(delayMs);
|
|
1824
|
+
lastRuntime = await fetchVmRuntime(args);
|
|
1825
|
+
args.onAttempt?.(lastRuntime, attempt + 2, attempts);
|
|
1826
|
+
}
|
|
1827
|
+
if (lastRuntime.diagnostics) {
|
|
1828
|
+
lastRuntime.diagnostics.timedOut = true;
|
|
1829
|
+
}
|
|
1830
|
+
return lastRuntime;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1230
1833
|
// ../core/src/guest.ts
|
|
1231
1834
|
async function notifyCrnAllocation2(args) {
|
|
1232
1835
|
const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
|
|
@@ -1381,6 +1984,16 @@ function defaultState(props = {}) {
|
|
|
1381
1984
|
deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
|
|
1382
1985
|
};
|
|
1383
1986
|
}
|
|
1987
|
+
function isDebugEnabled(props) {
|
|
1988
|
+
if (props.debug) {
|
|
1989
|
+
return true;
|
|
1990
|
+
}
|
|
1991
|
+
try {
|
|
1992
|
+
return globalThis.localStorage?.getItem("LE_SPACE_UI_DEBUG") === "1";
|
|
1993
|
+
} catch {
|
|
1994
|
+
return false;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1384
1997
|
async function sha256Hex(payload) {
|
|
1385
1998
|
const digest = await globalThis.crypto.subtle.digest(
|
|
1386
1999
|
"SHA-256",
|
|
@@ -1396,6 +2009,12 @@ function mappedPorts(execution) {
|
|
|
1396
2009
|
})
|
|
1397
2010
|
);
|
|
1398
2011
|
}
|
|
2012
|
+
function runtimeMappedPorts(mappedPortsRecord) {
|
|
2013
|
+
return Object.entries(mappedPortsRecord ?? {}).map(([port, mapping]) => ({
|
|
2014
|
+
label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
|
|
2015
|
+
hostPort: mapping.host ?? null
|
|
2016
|
+
}));
|
|
2017
|
+
}
|
|
1399
2018
|
function rootfsHealth(args) {
|
|
1400
2019
|
if (!args.manifestState.valid || !args.manifestState.manifest) {
|
|
1401
2020
|
return {
|
|
@@ -1466,8 +2085,18 @@ async function inspectInstanceRuntime(args) {
|
|
|
1466
2085
|
execution: null,
|
|
1467
2086
|
error: null
|
|
1468
2087
|
};
|
|
1469
|
-
|
|
1470
|
-
|
|
2088
|
+
const lookup = {
|
|
2089
|
+
allocationFound: false,
|
|
2090
|
+
allocationSource: null,
|
|
2091
|
+
crnUrl: null,
|
|
2092
|
+
webUrl: null,
|
|
2093
|
+
executionPayloadFound: false,
|
|
2094
|
+
executionLookupBlocked: false,
|
|
2095
|
+
executionLookupRequestUrl: null,
|
|
2096
|
+
executionLookupVersion: null
|
|
2097
|
+
};
|
|
2098
|
+
if (details.messageStatus === "rejected" || details.messageStatus === "removing") {
|
|
2099
|
+
return { details, lookup };
|
|
1471
2100
|
}
|
|
1472
2101
|
try {
|
|
1473
2102
|
const allocation = await args.client.fetchSchedulerAllocation(args.instance.item_hash) ?? (() => {
|
|
@@ -1482,26 +2111,35 @@ async function inspectInstanceRuntime(args) {
|
|
|
1482
2111
|
period: null
|
|
1483
2112
|
} : null;
|
|
1484
2113
|
})();
|
|
2114
|
+
lookup.allocationFound = Boolean(allocation);
|
|
2115
|
+
lookup.allocationSource = allocation?.source ?? null;
|
|
1485
2116
|
details.allocationSource = allocation?.source ?? null;
|
|
1486
2117
|
details.crnUrl = allocation?.crnUrl ?? null;
|
|
1487
2118
|
details.ipv6 = allocation?.vmIpv6 ?? null;
|
|
1488
2119
|
details.webUrl = await args.client.fetch2n6WebAccessUrl(
|
|
1489
2120
|
args.instance.item_hash
|
|
1490
2121
|
);
|
|
2122
|
+
lookup.crnUrl = details.crnUrl;
|
|
2123
|
+
lookup.webUrl = details.webUrl;
|
|
1491
2124
|
if (!allocation?.crnUrl) {
|
|
1492
|
-
return details;
|
|
2125
|
+
return { details, lookup };
|
|
1493
2126
|
}
|
|
1494
2127
|
const executionLookup = await args.client.fetchCrnExecutionMap(
|
|
1495
2128
|
allocation.crnUrl
|
|
1496
2129
|
);
|
|
2130
|
+
lookup.executionLookupBlocked = executionLookup.blocked;
|
|
2131
|
+
lookup.executionLookupRequestUrl = executionLookup.requestUrl ?? null;
|
|
2132
|
+
lookup.executionLookupVersion = executionLookup.version ?? null;
|
|
1497
2133
|
const executionPayload = executionLookup.payload?.[args.instance.item_hash];
|
|
2134
|
+
lookup.executionPayloadFound = Boolean(executionPayload);
|
|
1498
2135
|
if (!executionPayload) {
|
|
1499
|
-
return details;
|
|
2136
|
+
return { details, lookup };
|
|
1500
2137
|
}
|
|
1501
2138
|
const execution = normalizeExecution(executionPayload, allocation.crnUrl);
|
|
1502
2139
|
if (!execution.networking.proxy_url && details.webUrl) {
|
|
1503
2140
|
execution.networking.proxy_url = details.webUrl;
|
|
1504
2141
|
}
|
|
2142
|
+
details.messageStatus = "processed";
|
|
1505
2143
|
details.execution = execution;
|
|
1506
2144
|
details.hostIpv4 = execution.networking.host_ipv4 ?? execution.networking.ipv4 ?? null;
|
|
1507
2145
|
details.ipv6 = execution.networking.ipv6_ip ?? execution.networking.ipv6 ?? details.ipv6;
|
|
@@ -1509,11 +2147,14 @@ async function inspectInstanceRuntime(args) {
|
|
|
1509
2147
|
details.webUrl = execution.networking.proxy_url ?? details.webUrl;
|
|
1510
2148
|
details.mappedPorts = mappedPorts(execution);
|
|
1511
2149
|
details.sshCommand = buildSshCommand(details.hostIpv4, details.mappedPorts);
|
|
1512
|
-
return details;
|
|
2150
|
+
return { details, lookup };
|
|
1513
2151
|
} catch (error) {
|
|
1514
2152
|
return {
|
|
1515
|
-
|
|
1516
|
-
|
|
2153
|
+
details: {
|
|
2154
|
+
...details,
|
|
2155
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2156
|
+
},
|
|
2157
|
+
lookup
|
|
1517
2158
|
};
|
|
1518
2159
|
}
|
|
1519
2160
|
}
|
|
@@ -1587,9 +2228,31 @@ async function pingPeer(libp2p) {
|
|
|
1587
2228
|
};
|
|
1588
2229
|
}
|
|
1589
2230
|
}
|
|
2231
|
+
var UI_DEPLOY_WAIT_ATTEMPTS = 60;
|
|
2232
|
+
var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
|
|
2233
|
+
var UI_RUNTIME_WAIT_ATTEMPTS = 40;
|
|
2234
|
+
var UI_RUNTIME_WAIT_DELAY_MS = 5e3;
|
|
1590
2235
|
function hasUsableRuntime(details) {
|
|
1591
2236
|
return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
|
|
1592
2237
|
}
|
|
2238
|
+
function toSharedRootfsManifest(manifest) {
|
|
2239
|
+
if (!manifest) {
|
|
2240
|
+
return null;
|
|
2241
|
+
}
|
|
2242
|
+
return {
|
|
2243
|
+
profile: manifest.profile,
|
|
2244
|
+
version: manifest.version,
|
|
2245
|
+
rootfsInstallStrategy: manifest.rootfsInstallStrategy === "thin" || manifest.rootfsInstallStrategy === "prebaked" ? manifest.rootfsInstallStrategy : void 0,
|
|
2246
|
+
requiresBootstrapNetwork: manifest.requiresBootstrapNetwork,
|
|
2247
|
+
bootstrapSummary: manifest.bootstrapSummary,
|
|
2248
|
+
rootfsItemHash: manifest.rootfsItemHash,
|
|
2249
|
+
rootfsSizeMiB: manifest.rootfsSizeMiB,
|
|
2250
|
+
rootfsSourceSizeBytes: manifest.rootfsSourceSizeBytes,
|
|
2251
|
+
requiredPortForwards: manifest.requiredPortForwards,
|
|
2252
|
+
createdAt: manifest.createdAt,
|
|
2253
|
+
notes: manifest.notes
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
1593
2256
|
var SponsorRelayController = class {
|
|
1594
2257
|
state;
|
|
1595
2258
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1600,9 +2263,13 @@ var SponsorRelayController = class {
|
|
|
1600
2263
|
props;
|
|
1601
2264
|
progressEmitter = createDeploymentProgressEmitter();
|
|
1602
2265
|
runtimeCooldownByHash = /* @__PURE__ */ new Map();
|
|
2266
|
+
runtimeDetailsByHash = /* @__PURE__ */ new Map();
|
|
2267
|
+
debugEnabled;
|
|
2268
|
+
lastRuntimeReadyHash = null;
|
|
1603
2269
|
constructor(props = {}) {
|
|
1604
2270
|
this.props = props;
|
|
1605
2271
|
this.state = defaultState(props);
|
|
2272
|
+
this.debugEnabled = isDebugEnabled(props);
|
|
1606
2273
|
this.client = createAlephBrowserClient({
|
|
1607
2274
|
apiHost: props.apiHost,
|
|
1608
2275
|
crnListUrl: props.crnListUrl,
|
|
@@ -1622,10 +2289,24 @@ var SponsorRelayController = class {
|
|
|
1622
2289
|
getState() {
|
|
1623
2290
|
return this.state;
|
|
1624
2291
|
}
|
|
2292
|
+
updateProps(props) {
|
|
2293
|
+
this.props = { ...this.props, ...props };
|
|
2294
|
+
this.debugEnabled = isDebugEnabled(this.props);
|
|
2295
|
+
}
|
|
1625
2296
|
emit() {
|
|
1626
2297
|
const next = this.state;
|
|
1627
2298
|
this.subscribers.forEach((subscriber) => subscriber(next));
|
|
1628
2299
|
}
|
|
2300
|
+
trace(message, data) {
|
|
2301
|
+
if (!this.debugEnabled) {
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
if (data === void 0) {
|
|
2305
|
+
console.debug("[le-space/ui]", message);
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
console.debug("[le-space/ui]", message, data);
|
|
2309
|
+
}
|
|
1629
2310
|
patch(patch) {
|
|
1630
2311
|
this.state = {
|
|
1631
2312
|
...this.state,
|
|
@@ -1642,6 +2323,14 @@ var SponsorRelayController = class {
|
|
|
1642
2323
|
...event,
|
|
1643
2324
|
timestamp: Date.now()
|
|
1644
2325
|
};
|
|
2326
|
+
this.trace(`progress:${nextEvent.stage}`, {
|
|
2327
|
+
label: nextEvent.label,
|
|
2328
|
+
progress: nextEvent.progress,
|
|
2329
|
+
status: nextEvent.status,
|
|
2330
|
+
itemHash: nextEvent.itemHash ?? null,
|
|
2331
|
+
detail: nextEvent.detail ?? null,
|
|
2332
|
+
error: nextEvent.error ?? null
|
|
2333
|
+
});
|
|
1645
2334
|
this.patch({
|
|
1646
2335
|
deploymentProgress: nextEvent,
|
|
1647
2336
|
statusText: event.error ?? event.detail ?? event.label,
|
|
@@ -1660,6 +2349,9 @@ var SponsorRelayController = class {
|
|
|
1660
2349
|
if (!latest) {
|
|
1661
2350
|
return;
|
|
1662
2351
|
}
|
|
2352
|
+
const currentProgress = this.state.deploymentProgress;
|
|
2353
|
+
const currentProgressIsForLatestHash = currentProgress.itemHash === itemHash || currentProgress.itemHash == null && currentProgress.stage !== "idle" && currentProgress.stage !== "error";
|
|
2354
|
+
const keepTerminalSuccess = this.lastRuntimeReadyHash === itemHash || currentProgressIsForLatestHash && currentProgress.stage === "completed" && currentProgress.status === "success";
|
|
1663
2355
|
const status = latest.details.messageStatus;
|
|
1664
2356
|
if (status === "rejected") {
|
|
1665
2357
|
this.emitProgress({
|
|
@@ -1674,6 +2366,14 @@ var SponsorRelayController = class {
|
|
|
1674
2366
|
return;
|
|
1675
2367
|
}
|
|
1676
2368
|
if (status !== "processed") {
|
|
2369
|
+
if (keepTerminalSuccess) {
|
|
2370
|
+
this.trace("progress:retain-completed", {
|
|
2371
|
+
reason: "latest refresh still reports non-processed status",
|
|
2372
|
+
itemHash,
|
|
2373
|
+
status
|
|
2374
|
+
});
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
1677
2377
|
const submittedAtMs = instanceTimestampMs(latest.instance);
|
|
1678
2378
|
const pendingTooLong = submittedAtMs != null && Date.now() - submittedAtMs >= DEPLOYMENT_PENDING_WARNING_MS;
|
|
1679
2379
|
this.emitProgress({
|
|
@@ -1687,6 +2387,13 @@ var SponsorRelayController = class {
|
|
|
1687
2387
|
return;
|
|
1688
2388
|
}
|
|
1689
2389
|
if (!hasUsableRuntime(latest.details)) {
|
|
2390
|
+
if (keepTerminalSuccess) {
|
|
2391
|
+
this.trace("progress:retain-completed", {
|
|
2392
|
+
reason: "latest refresh lacks runtime details after completed state",
|
|
2393
|
+
itemHash
|
|
2394
|
+
});
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
1690
2397
|
this.emitProgress({
|
|
1691
2398
|
stage: "deployment-confirmed",
|
|
1692
2399
|
label: "Waiting for runtime",
|
|
@@ -1733,8 +2440,42 @@ var SponsorRelayController = class {
|
|
|
1733
2440
|
Date.now() + STALE_INSTANCE_ALLOCATION_COOLDOWN_MS
|
|
1734
2441
|
);
|
|
1735
2442
|
}
|
|
2443
|
+
rememberRuntimeDetails(itemHash, details) {
|
|
2444
|
+
if (!hasUsableRuntime(details)) {
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
this.runtimeDetailsByHash.set(itemHash, {
|
|
2448
|
+
...details,
|
|
2449
|
+
mappedPorts: [...details.mappedPorts]
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
mergeRememberedRuntimeDetails(itemHash, details) {
|
|
2453
|
+
const remembered = this.runtimeDetailsByHash.get(itemHash);
|
|
2454
|
+
if (!remembered) {
|
|
2455
|
+
return details;
|
|
2456
|
+
}
|
|
2457
|
+
if (details.messageStatus === "processed" && hasUsableRuntime(details)) {
|
|
2458
|
+
return details;
|
|
2459
|
+
}
|
|
2460
|
+
this.trace("runtime:retain-known-details", {
|
|
2461
|
+
itemHash,
|
|
2462
|
+
currentStatus: details.messageStatus,
|
|
2463
|
+
rememberedStatus: remembered.messageStatus,
|
|
2464
|
+
currentHasRuntime: hasUsableRuntime(details)
|
|
2465
|
+
});
|
|
2466
|
+
return {
|
|
2467
|
+
...details,
|
|
2468
|
+
...remembered,
|
|
2469
|
+
error: details.error ?? remembered.error
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
1736
2472
|
async init() {
|
|
2473
|
+
this.trace("init:start", {
|
|
2474
|
+
launcherMode: this.props.launcherMode ?? "floating",
|
|
2475
|
+
manifestUrl: this.state.manifestUrl
|
|
2476
|
+
});
|
|
1737
2477
|
this.stopWalletWatch = watchWallet(() => {
|
|
2478
|
+
this.trace("wallet:changed");
|
|
1738
2479
|
void this.refreshWalletDerivedState();
|
|
1739
2480
|
});
|
|
1740
2481
|
await this.refresh();
|
|
@@ -1746,6 +2487,7 @@ var SponsorRelayController = class {
|
|
|
1746
2487
|
}, RELAY_PING_INTERVAL_MS);
|
|
1747
2488
|
await this.refreshRelayPing();
|
|
1748
2489
|
this.patch({ ready: true });
|
|
2490
|
+
this.trace("init:ready");
|
|
1749
2491
|
}
|
|
1750
2492
|
destroy() {
|
|
1751
2493
|
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
|
@@ -1846,6 +2588,11 @@ var SponsorRelayController = class {
|
|
|
1846
2588
|
}
|
|
1847
2589
|
}
|
|
1848
2590
|
async refresh() {
|
|
2591
|
+
this.trace("refresh:start", {
|
|
2592
|
+
wallet: this.state.wallet.address,
|
|
2593
|
+
manifestUrl: this.state.manifestUrl,
|
|
2594
|
+
showInstances: this.state.showInstances
|
|
2595
|
+
});
|
|
1849
2596
|
this.patch({
|
|
1850
2597
|
busy: { refreshing: true },
|
|
1851
2598
|
errorText: null,
|
|
@@ -1871,29 +2618,58 @@ var SponsorRelayController = class {
|
|
|
1871
2618
|
balance = nextBalance;
|
|
1872
2619
|
instances = await Promise.all(
|
|
1873
2620
|
rawInstances.map(async (instance) => {
|
|
1874
|
-
const
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2621
|
+
const inspected = this.canSkipRuntimeRefresh(instance) ? {
|
|
2622
|
+
details: {
|
|
2623
|
+
messageStatus: String(
|
|
2624
|
+
instance.status ?? (instance.confirmed ? "processed" : "pending")
|
|
2625
|
+
).toLowerCase(),
|
|
2626
|
+
allocationSource: null,
|
|
2627
|
+
crnUrl: null,
|
|
2628
|
+
hostIpv4: null,
|
|
2629
|
+
ipv6: null,
|
|
2630
|
+
vmIpv4: null,
|
|
2631
|
+
webUrl: null,
|
|
2632
|
+
sshCommand: null,
|
|
2633
|
+
mappedPorts: [],
|
|
2634
|
+
execution: null,
|
|
2635
|
+
error: null
|
|
2636
|
+
},
|
|
2637
|
+
lookup: {
|
|
2638
|
+
allocationFound: false,
|
|
2639
|
+
allocationSource: null,
|
|
2640
|
+
crnUrl: null,
|
|
2641
|
+
webUrl: null,
|
|
2642
|
+
executionPayloadFound: false,
|
|
2643
|
+
executionLookupBlocked: false,
|
|
2644
|
+
executionLookupRequestUrl: null,
|
|
2645
|
+
executionLookupVersion: null
|
|
2646
|
+
}
|
|
1888
2647
|
} : await inspectInstanceRuntime({
|
|
1889
2648
|
client: this.client,
|
|
1890
2649
|
instance,
|
|
1891
2650
|
crns
|
|
1892
2651
|
});
|
|
1893
|
-
|
|
2652
|
+
const details = inspected.details;
|
|
2653
|
+
const mergedDetails = this.mergeRememberedRuntimeDetails(
|
|
2654
|
+
instance.item_hash,
|
|
2655
|
+
details
|
|
2656
|
+
);
|
|
2657
|
+
if (this.debugEnabled && instance.item_hash === this.state.lastDeploymentHash) {
|
|
2658
|
+
this.trace("runtime:inspect-result", {
|
|
2659
|
+
itemHash: instance.item_hash,
|
|
2660
|
+
messageStatus: details.messageStatus,
|
|
2661
|
+
hostIpv4: details.hostIpv4,
|
|
2662
|
+
vmIpv4: details.vmIpv4,
|
|
2663
|
+
mappedPorts: details.mappedPorts,
|
|
2664
|
+
error: details.error,
|
|
2665
|
+
lookup: inspected.lookup
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
this.rememberRuntimeDetails(instance.item_hash, mergedDetails);
|
|
2669
|
+
this.noteRuntimeRefreshResult(instance, mergedDetails);
|
|
1894
2670
|
return {
|
|
1895
2671
|
instance,
|
|
1896
|
-
details
|
|
2672
|
+
details: mergedDetails
|
|
1897
2673
|
};
|
|
1898
2674
|
})
|
|
1899
2675
|
);
|
|
@@ -1930,9 +2706,15 @@ var SponsorRelayController = class {
|
|
|
1930
2706
|
busy: { refreshing: false },
|
|
1931
2707
|
statusText: "Relay sponsor data ready"
|
|
1932
2708
|
});
|
|
2709
|
+
this.trace("refresh:success", {
|
|
2710
|
+
manifestValid: manifestState.valid,
|
|
2711
|
+
rootfsVerified,
|
|
2712
|
+
instances: instances.length
|
|
2713
|
+
});
|
|
1933
2714
|
this.recomputePricingSummary();
|
|
1934
2715
|
this.syncLatestDeploymentProgress(instances);
|
|
1935
2716
|
} catch (error) {
|
|
2717
|
+
this.trace("refresh:error", error);
|
|
1936
2718
|
this.patch({
|
|
1937
2719
|
busy: { refreshing: false },
|
|
1938
2720
|
errorText: error instanceof Error ? error.message : String(error),
|
|
@@ -1943,8 +2725,17 @@ var SponsorRelayController = class {
|
|
|
1943
2725
|
async refreshRelayPing() {
|
|
1944
2726
|
const relayPing = await pingPeer(this.props.libp2p);
|
|
1945
2727
|
this.patch({ relayPing });
|
|
2728
|
+
this.trace("libp2p:relay-ping", relayPing);
|
|
1946
2729
|
}
|
|
1947
2730
|
async deploy() {
|
|
2731
|
+
this.trace("deploy:start", {
|
|
2732
|
+
wallet: this.state.wallet.address,
|
|
2733
|
+
instanceName: this.state.instanceName,
|
|
2734
|
+
tierId: this.state.tierId,
|
|
2735
|
+
selectedCrn: this.state.selectedCrn?.name ?? null,
|
|
2736
|
+
manifestRootfsItemHash: this.state.manifest?.rootfsItemHash ?? null,
|
|
2737
|
+
requiredPortForwards: this.state.manifest?.requiredPortForwards ?? []
|
|
2738
|
+
});
|
|
1948
2739
|
if (!this.state.wallet.address) {
|
|
1949
2740
|
this.patch({ errorText: "Connect MetaMask before deploying." });
|
|
1950
2741
|
return;
|
|
@@ -2006,11 +2797,88 @@ var SponsorRelayController = class {
|
|
|
2006
2797
|
}
|
|
2007
2798
|
});
|
|
2008
2799
|
this.patch({
|
|
2009
|
-
busy: { deploying: false },
|
|
2010
2800
|
statusText: `Deployment submitted: ${result.itemHash}`,
|
|
2011
2801
|
lastDeploymentHash: result.itemHash
|
|
2012
2802
|
});
|
|
2013
|
-
|
|
2803
|
+
this.trace("deploy:broadcasted", result);
|
|
2804
|
+
this.lastRuntimeReadyHash = null;
|
|
2805
|
+
const inspection = await waitForDeploymentResult2(result.itemHash, {
|
|
2806
|
+
rootfsRef: this.state.manifest.rootfsItemHash,
|
|
2807
|
+
apiHost: this.client.apiHost,
|
|
2808
|
+
fetch: (url, init) => fetch(url, init),
|
|
2809
|
+
attempts: UI_DEPLOY_WAIT_ATTEMPTS,
|
|
2810
|
+
delayMs: UI_DEPLOY_WAIT_DELAY_MS,
|
|
2811
|
+
onAttempt: (inspectionResult) => {
|
|
2812
|
+
if (inspectionResult.status === "processed") {
|
|
2813
|
+
this.emitProgress({
|
|
2814
|
+
stage: "deployment-confirmed",
|
|
2815
|
+
label: "Deployment accepted by Aleph",
|
|
2816
|
+
progress: 82,
|
|
2817
|
+
status: "success",
|
|
2818
|
+
itemHash: result.itemHash,
|
|
2819
|
+
detail: "Aleph processed the instance message."
|
|
2820
|
+
});
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
if (inspectionResult.status === "rejected") {
|
|
2824
|
+
this.emitProgress({
|
|
2825
|
+
stage: "deployment-rejected",
|
|
2826
|
+
label: "Deployment rejected",
|
|
2827
|
+
progress: 100,
|
|
2828
|
+
status: "error",
|
|
2829
|
+
itemHash: result.itemHash,
|
|
2830
|
+
detail: inspectionResult.rejectionReason ?? "Aleph rejected the deployment.",
|
|
2831
|
+
error: inspectionResult.rejectionReason ?? "Aleph rejected the deployment."
|
|
2832
|
+
});
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
this.emitProgress({
|
|
2836
|
+
stage: "waiting-for-aleph",
|
|
2837
|
+
label: "Waiting for Aleph",
|
|
2838
|
+
progress: 76,
|
|
2839
|
+
status: "info",
|
|
2840
|
+
itemHash: result.itemHash,
|
|
2841
|
+
detail: "Deployment submitted. Waiting for Aleph to process the instance message."
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
});
|
|
2845
|
+
if (inspection.status !== "processed") {
|
|
2846
|
+
throw new Error(
|
|
2847
|
+
inspection.rejectionReason ?? `Deployment ${result.itemHash} stayed ${inspection.status} on Aleph.`
|
|
2848
|
+
);
|
|
2849
|
+
}
|
|
2850
|
+
this.trace("deploy:aleph-processed", inspection);
|
|
2851
|
+
if ((this.state.manifest.requiredPortForwards?.length ?? 0) > 0) {
|
|
2852
|
+
this.emitProgress({
|
|
2853
|
+
stage: "refreshing-instances",
|
|
2854
|
+
label: "Publishing port forwards",
|
|
2855
|
+
progress: 86,
|
|
2856
|
+
status: "info",
|
|
2857
|
+
itemHash: result.itemHash,
|
|
2858
|
+
detail: "Publishing the required Aleph port-forward aggregate from the manifest."
|
|
2859
|
+
});
|
|
2860
|
+
await ensureInstancePortForwards({
|
|
2861
|
+
sender: this.state.wallet.address,
|
|
2862
|
+
instanceItemHash: result.itemHash,
|
|
2863
|
+
manifest: toSharedRootfsManifest(this.state.manifest),
|
|
2864
|
+
signer: personalSign,
|
|
2865
|
+
hasher: sha256Hex,
|
|
2866
|
+
fetch: (url, init) => fetch(url, init),
|
|
2867
|
+
apiHost: this.client.apiHost,
|
|
2868
|
+
sync: true
|
|
2869
|
+
});
|
|
2870
|
+
this.trace("deploy:port-forwards-published", {
|
|
2871
|
+
itemHash: result.itemHash,
|
|
2872
|
+
requiredPortForwards: this.state.manifest.requiredPortForwards
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
if (this.state.selectedCrn?.address) {
|
|
2876
|
+
this.trace("deploy:notifying-crn", {
|
|
2877
|
+
itemHash: result.itemHash,
|
|
2878
|
+
crnName: this.state.selectedCrn.name,
|
|
2879
|
+
crnHash: this.state.selectedCrn.hash,
|
|
2880
|
+
crnUrl: this.state.selectedCrn.address
|
|
2881
|
+
});
|
|
2014
2882
|
await notifyCrnAllocationWithRetry({
|
|
2015
2883
|
crnUrl: this.state.selectedCrn.address,
|
|
2016
2884
|
itemHash: result.itemHash,
|
|
@@ -2020,16 +2888,78 @@ var SponsorRelayController = class {
|
|
|
2020
2888
|
}
|
|
2021
2889
|
});
|
|
2022
2890
|
}
|
|
2891
|
+
this.emitProgress({
|
|
2892
|
+
stage: "deployment-confirmed",
|
|
2893
|
+
label: "Waiting for runtime",
|
|
2894
|
+
progress: 90,
|
|
2895
|
+
status: "warning",
|
|
2896
|
+
itemHash: result.itemHash,
|
|
2897
|
+
detail: "Aleph accepted the deployment. Waiting for runtime networking and mapped ports."
|
|
2898
|
+
});
|
|
2899
|
+
const runtime = await waitForVmRuntime({
|
|
2900
|
+
itemHash: result.itemHash,
|
|
2901
|
+
fetch: (url, init) => fetch(url, init),
|
|
2902
|
+
crnHash: this.state.selectedCrn?.hash,
|
|
2903
|
+
crns: this.state.crns,
|
|
2904
|
+
crnListUrl: this.props.crnListUrl,
|
|
2905
|
+
attempts: UI_RUNTIME_WAIT_ATTEMPTS,
|
|
2906
|
+
delayMs: UI_RUNTIME_WAIT_DELAY_MS,
|
|
2907
|
+
onAttempt: (runtime2) => {
|
|
2908
|
+
if (runtime2.hostIpv4 && Object.keys(runtime2.mappedPorts ?? {}).length > 0) {
|
|
2909
|
+
this.emitProgress({
|
|
2910
|
+
stage: "completed",
|
|
2911
|
+
label: "Runtime ready",
|
|
2912
|
+
progress: 100,
|
|
2913
|
+
status: "success",
|
|
2914
|
+
itemHash: result.itemHash,
|
|
2915
|
+
detail: "Runtime networking and mapped ports are now available."
|
|
2916
|
+
});
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
this.emitProgress({
|
|
2920
|
+
stage: "deployment-confirmed",
|
|
2921
|
+
label: "Waiting for runtime",
|
|
2922
|
+
progress: 90,
|
|
2923
|
+
status: "warning",
|
|
2924
|
+
itemHash: result.itemHash,
|
|
2925
|
+
detail: runtime2.diagnostics?.reason ?? "Waiting for CRN runtime networking and mapped ports."
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
if (!runtime.hostIpv4 || Object.keys(runtime.mappedPorts ?? {}).length === 0) {
|
|
2930
|
+
throw new Error(
|
|
2931
|
+
runtime.diagnostics?.reason ?? "Deployment was processed, but runtime networking never exposed mapped ports."
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
this.trace("deploy:runtime-ready", runtime);
|
|
2935
|
+
this.lastRuntimeReadyHash = result.itemHash;
|
|
2936
|
+
this.rememberRuntimeDetails(result.itemHash, {
|
|
2937
|
+
messageStatus: "processed",
|
|
2938
|
+
allocationSource: runtime.allocation?.source ?? null,
|
|
2939
|
+
crnUrl: runtime.allocation?.crnUrl ?? runtime.execution?.crnUrl ?? null,
|
|
2940
|
+
hostIpv4: runtime.hostIpv4 ?? null,
|
|
2941
|
+
ipv6: runtime.ipv6 ?? null,
|
|
2942
|
+
vmIpv4: runtime.execution?.networking?.ipv4_ip ?? null,
|
|
2943
|
+
webUrl: runtime.webAccessUrl ?? runtime.proxyUrl ?? runtime.execution?.networking?.proxy_url ?? null,
|
|
2944
|
+
sshCommand: runtime.sshCommand ?? null,
|
|
2945
|
+
mappedPorts: runtime.execution != null ? mappedPorts(runtime.execution) : runtimeMappedPorts(runtime.mappedPorts),
|
|
2946
|
+
execution: runtime.execution ?? null,
|
|
2947
|
+
error: null
|
|
2948
|
+
});
|
|
2949
|
+
this.patch({
|
|
2950
|
+
busy: { deploying: false }
|
|
2951
|
+
});
|
|
2023
2952
|
this.emitProgress({
|
|
2024
2953
|
stage: "refreshing-instances",
|
|
2025
2954
|
label: "Refreshing instances",
|
|
2026
|
-
progress:
|
|
2955
|
+
progress: 96,
|
|
2027
2956
|
status: "info",
|
|
2028
2957
|
itemHash: result.itemHash,
|
|
2029
2958
|
detail: "Reloading deployments and runtime state."
|
|
2030
2959
|
});
|
|
2031
2960
|
await this.refresh();
|
|
2032
2961
|
} catch (error) {
|
|
2962
|
+
this.trace("deploy:error", error);
|
|
2033
2963
|
this.patch({
|
|
2034
2964
|
busy: { deploying: false },
|
|
2035
2965
|
errorText: error instanceof Error ? error.message : String(error),
|
|
@@ -2045,6 +2975,7 @@ var SponsorRelayController = class {
|
|
|
2045
2975
|
}
|
|
2046
2976
|
}
|
|
2047
2977
|
async deleteInstance(instanceHash) {
|
|
2978
|
+
this.trace("delete:start", { instanceHash });
|
|
2048
2979
|
if (!this.state.wallet.address) {
|
|
2049
2980
|
this.patch({ errorText: "Connect MetaMask before deleting instances." });
|
|
2050
2981
|
return;
|
|
@@ -2092,6 +3023,7 @@ var SponsorRelayController = class {
|
|
|
2092
3023
|
detail: "Reloading current deployments."
|
|
2093
3024
|
});
|
|
2094
3025
|
await this.refresh();
|
|
3026
|
+
this.trace("delete:submitted", { instanceHash });
|
|
2095
3027
|
this.emitProgress({
|
|
2096
3028
|
stage: "completed",
|
|
2097
3029
|
label: "Delete completed",
|
|
@@ -2102,6 +3034,7 @@ var SponsorRelayController = class {
|
|
|
2102
3034
|
error: null
|
|
2103
3035
|
});
|
|
2104
3036
|
} catch (error) {
|
|
3037
|
+
this.trace("delete:error", error);
|
|
2105
3038
|
this.patch({
|
|
2106
3039
|
busy: { deletingInstanceHash: null },
|
|
2107
3040
|
errorText: error instanceof Error ? error.message : String(error),
|
|
@@ -2137,6 +3070,7 @@ export {
|
|
|
2137
3070
|
formatNumber,
|
|
2138
3071
|
getEthereumProvider,
|
|
2139
3072
|
joinMappedPorts,
|
|
3073
|
+
joinRequiredPortForwards,
|
|
2140
3074
|
personalSign,
|
|
2141
3075
|
resolveManifestSource,
|
|
2142
3076
|
shortHash,
|