@kumori/aurora-backend-handler 1.0.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/.gitlab-ci.yml +57 -0
- package/README.md +352 -0
- package/api/account-api-service.ts +1092 -0
- package/api/deploy-service-helper.ts +1611 -0
- package/api/environment-api-service.ts +542 -0
- package/api/marketplace-api-service.ts +1031 -0
- package/api/organizations-api-service.ts +0 -0
- package/api/planProvider-api-service.ts +24 -0
- package/api/reporting-api-service.ts +35 -0
- package/api/resources-api-service.ts +821 -0
- package/api/service-api-service.ts +796 -0
- package/api/tenant-api-service.ts +1260 -0
- package/api/user-api-service.ts +1161 -0
- package/backend-handler.ts +1127 -0
- package/environment.ts +7 -0
- package/event-helper.ts +577 -0
- package/event-names.ts +152 -0
- package/helpers/account-helper.ts +331 -0
- package/helpers/environment-helper.ts +289 -0
- package/helpers/link-helper.ts +114 -0
- package/helpers/plan-helper.ts +104 -0
- package/helpers/registry-helper.ts +134 -0
- package/helpers/resource-helper.ts +387 -0
- package/helpers/revision-helper.ts +899 -0
- package/helpers/service-helper.ts +627 -0
- package/helpers/tenant-helper.ts +191 -0
- package/helpers/token-helper.ts +107 -0
- package/helpers/user-helper.ts +140 -0
- package/jest.config.ts +40 -0
- package/jest.setup.js +4 -0
- package/package.json +50 -0
- package/test/backend-handler.test.ts +792 -0
- package/test/deploy-service-helper.test.ts +518 -0
- package/test/event-helper.test.ts +152 -0
- package/tsconfig.json +26 -0
- package/utils/utils.ts +78 -0
- package/websocket-manager.ts +1833 -0
|
@@ -0,0 +1,1031 @@
|
|
|
1
|
+
import { Link, MarketplaceItem, MarketplaceService, Notification, Service } from "@hestekumori/aurora-interfaces";
|
|
2
|
+
import { eventHelper } from "../backend-handler";
|
|
3
|
+
import { environment } from "../environment";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getWebSocketStatus,
|
|
7
|
+
initializeGlobalWebSocketClient,
|
|
8
|
+
makeGlobalWebSocketRequest,
|
|
9
|
+
} from "../websocket-manager";
|
|
10
|
+
import { deployServiceHelper } from "./deploy-service-helper";
|
|
11
|
+
|
|
12
|
+
interface DeploymentParameter {
|
|
13
|
+
name: string;
|
|
14
|
+
value: string | number | boolean;
|
|
15
|
+
type: "string" | "number" | "boolean";
|
|
16
|
+
}
|
|
17
|
+
interface DeploymentResourceSpec {
|
|
18
|
+
name: string;
|
|
19
|
+
resource: string;
|
|
20
|
+
}
|
|
21
|
+
interface DeploymentVolatileVolumeSpec {
|
|
22
|
+
name: string;
|
|
23
|
+
volume: {
|
|
24
|
+
kind: string;
|
|
25
|
+
size: number;
|
|
26
|
+
unit: "G";
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
interface Secret<VARIANT> {
|
|
30
|
+
secret: VARIANT;
|
|
31
|
+
}
|
|
32
|
+
interface CA<VARIANT> {
|
|
33
|
+
ca: VARIANT;
|
|
34
|
+
}
|
|
35
|
+
interface Volume<VARIANT> {
|
|
36
|
+
volume: VARIANT;
|
|
37
|
+
}
|
|
38
|
+
interface Domain<VARIANT> {
|
|
39
|
+
domain: VARIANT;
|
|
40
|
+
}
|
|
41
|
+
interface Port<VARIANT> {
|
|
42
|
+
port: VARIANT;
|
|
43
|
+
}
|
|
44
|
+
interface Certificate<VARIANT> {
|
|
45
|
+
certificate: VARIANT;
|
|
46
|
+
}
|
|
47
|
+
type DeploymentResource =
|
|
48
|
+
| CA<DeploymentResourceSpec>
|
|
49
|
+
| Secret<DeploymentResourceSpec>
|
|
50
|
+
| Volume<DeploymentResourceSpec>
|
|
51
|
+
| Volume<DeploymentVolatileVolumeSpec>
|
|
52
|
+
| Domain<DeploymentResourceSpec>
|
|
53
|
+
| Port<DeploymentResourceSpec>
|
|
54
|
+
| Certificate<DeploymentResourceSpec>;
|
|
55
|
+
let pendingLinks = new Map<string, Link[]>();
|
|
56
|
+
/**
|
|
57
|
+
* Function to deploy a marketplace item
|
|
58
|
+
* @param item A MarketplaceService object containing the item to deploy
|
|
59
|
+
*/
|
|
60
|
+
export const deployMarketplaceItem = async (item: MarketplaceService) => {
|
|
61
|
+
const complexDeployment = item.deploymentData.serverChannels.find(
|
|
62
|
+
(channel) => channel.isPublic
|
|
63
|
+
);
|
|
64
|
+
if (complexDeployment !== undefined) {
|
|
65
|
+
try {
|
|
66
|
+
const formData = await deployServiceHelper(item.deploymentData, item);
|
|
67
|
+
|
|
68
|
+
const url = new URL(
|
|
69
|
+
`${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${item.deploymentData.tenant}/service/${item.deploymentData.name}`
|
|
70
|
+
);
|
|
71
|
+
url.searchParams.append("dryrun", "false");
|
|
72
|
+
url.searchParams.append("accept", "true");
|
|
73
|
+
url.searchParams.append("wait", "30000");
|
|
74
|
+
url.searchParams.append("validate", "true");
|
|
75
|
+
url.searchParams.append("dsl", "true");
|
|
76
|
+
|
|
77
|
+
const response = await fetch(url.toString(), {
|
|
78
|
+
method: "POST",
|
|
79
|
+
body: formData,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const jsonResponse = await response.json();
|
|
87
|
+
|
|
88
|
+
const isTimeout = jsonResponse?.events?.some(
|
|
89
|
+
(event: any) => event.content === "_timeout_"
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (isTimeout) {
|
|
93
|
+
console.error("Timeout en la petición:", {
|
|
94
|
+
isOk: false,
|
|
95
|
+
code: "TIMEOUT",
|
|
96
|
+
error: "_timeout_",
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
if (item.deploymentData.links.length > 0) {
|
|
100
|
+
pendingLinks.set(item.deploymentData.name, item.deploymentData.links);
|
|
101
|
+
linkPendingServices(item.deploymentData, "");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.error("Error en la petición de despliegue de servicio:", err);
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
const deploymentConfigParameters: DeploymentParameter[] = item.parameterList
|
|
109
|
+
.filter(
|
|
110
|
+
(resource) =>
|
|
111
|
+
resource.type === "string" ||
|
|
112
|
+
resource.type === "integer" ||
|
|
113
|
+
resource.type === "number" ||
|
|
114
|
+
resource.type === "bool" ||
|
|
115
|
+
resource.type === "boolean" ||
|
|
116
|
+
resource.type === "fileContent" ||
|
|
117
|
+
resource.type === "array"
|
|
118
|
+
)
|
|
119
|
+
.map((resource) => {
|
|
120
|
+
if (resource.type === "string") {
|
|
121
|
+
return {
|
|
122
|
+
name: resource.name || "",
|
|
123
|
+
value: (resource as any).value as string,
|
|
124
|
+
type: "string",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (resource.type === "fileContent") {
|
|
128
|
+
return {
|
|
129
|
+
name: resource.name || "",
|
|
130
|
+
value: (resource as any).value as string,
|
|
131
|
+
type: "string",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (resource.type === "bool" || resource.type === "boolean") {
|
|
135
|
+
return {
|
|
136
|
+
name: resource.name || "",
|
|
137
|
+
value:
|
|
138
|
+
(resource as any).value === "true" ||
|
|
139
|
+
(resource as any).value === true,
|
|
140
|
+
type: "boolean",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (resource.type === "number" || resource.type === "integer") {
|
|
144
|
+
return {
|
|
145
|
+
name: resource.name || "",
|
|
146
|
+
value: Number((resource as any).value),
|
|
147
|
+
type: "number",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (resource.type === "array") {
|
|
151
|
+
return {
|
|
152
|
+
name: resource.name || "",
|
|
153
|
+
value: (resource as any).value || [],
|
|
154
|
+
type: "string",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
name: resource.name || "",
|
|
159
|
+
value: (resource as any).value as string,
|
|
160
|
+
type: "string",
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const deploymentData: Service = item.deploymentData as unknown as Service;
|
|
165
|
+
const url = new URL(
|
|
166
|
+
`${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${item.deploymentData.tenant}/service/${deploymentData.name}`
|
|
167
|
+
);
|
|
168
|
+
url.searchParams.append("dsl", "true");
|
|
169
|
+
const parametersObj = deploymentConfigParameters.reduce((acc, param) => {
|
|
170
|
+
const paramMetadata = item.parameterList.find(
|
|
171
|
+
(p) => p.name === param.name
|
|
172
|
+
);
|
|
173
|
+
if (paramMetadata && (paramMetadata as any).parent === "accesspolicies") {
|
|
174
|
+
if (!acc.accesspolicies) {
|
|
175
|
+
acc.accesspolicies = {};
|
|
176
|
+
}
|
|
177
|
+
acc.accesspolicies[param.name] = param.value;
|
|
178
|
+
} else {
|
|
179
|
+
acc[param.name] = param.value;
|
|
180
|
+
}
|
|
181
|
+
return acc;
|
|
182
|
+
}, {} as { [key: string]: any });
|
|
183
|
+
|
|
184
|
+
const resourcesObj = item.resourceList.reduce((acc, resource: any) => {
|
|
185
|
+
if (resource.type === "secret") {
|
|
186
|
+
acc[resource.name] = {
|
|
187
|
+
secret: resource.value as string,
|
|
188
|
+
} as unknown as DeploymentResource;
|
|
189
|
+
} else if (resource.type === "volume") {
|
|
190
|
+
acc[resource.name] = {
|
|
191
|
+
volume: {
|
|
192
|
+
kind: "storage",
|
|
193
|
+
type: resource.kind,
|
|
194
|
+
size: Number(resource.value || 1),
|
|
195
|
+
unit: "G",
|
|
196
|
+
},
|
|
197
|
+
} as unknown as DeploymentResource;
|
|
198
|
+
} else if (resource.type === "domain") {
|
|
199
|
+
acc[resource.name] = {
|
|
200
|
+
domain: resource.value,
|
|
201
|
+
} as unknown as DeploymentResource;
|
|
202
|
+
} else if (resource.type === "certificate") {
|
|
203
|
+
acc[resource.name] = {
|
|
204
|
+
certificate: resource.value,
|
|
205
|
+
} as unknown as DeploymentResource;
|
|
206
|
+
} else if (resource.type === "ca") {
|
|
207
|
+
acc[resource.name] = {
|
|
208
|
+
ca: resource.value,
|
|
209
|
+
} as unknown as DeploymentResource;
|
|
210
|
+
}
|
|
211
|
+
else if (resource.type === "port") {
|
|
212
|
+
acc[resource.name] = {
|
|
213
|
+
port: resource.value,
|
|
214
|
+
} as unknown as DeploymentResource;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return acc;
|
|
218
|
+
}, {} as { [key: string]: DeploymentResource });
|
|
219
|
+
|
|
220
|
+
const isInbound = item.module === "builtins/inbound";
|
|
221
|
+
|
|
222
|
+
if (isInbound) {
|
|
223
|
+
const hasCertificate = item.resourceList.some(
|
|
224
|
+
(resource) =>
|
|
225
|
+
resource.type === "certificate" ||
|
|
226
|
+
resource.name.toLowerCase().includes("cert") ||
|
|
227
|
+
resource.name === "servercert"
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const hasDomain = item.resourceList.some(
|
|
231
|
+
(resource) =>
|
|
232
|
+
resource.type === "domain" ||
|
|
233
|
+
resource.name.toLowerCase().includes("domain") ||
|
|
234
|
+
resource.name === "serverdomain"
|
|
235
|
+
);
|
|
236
|
+
const hasPort = item.resourceList.some(
|
|
237
|
+
(resource) =>
|
|
238
|
+
resource.type === "port" ||
|
|
239
|
+
resource.name.toLowerCase().includes("port")
|
|
240
|
+
);
|
|
241
|
+
let correctType = "tcp";
|
|
242
|
+
let requiredParameters: { [key: string]: any } = {};
|
|
243
|
+
|
|
244
|
+
if (hasCertificate && hasDomain) {
|
|
245
|
+
correctType = "https";
|
|
246
|
+
requiredParameters = {
|
|
247
|
+
type: "https",
|
|
248
|
+
clientcert: false,
|
|
249
|
+
websocket: false,
|
|
250
|
+
remoteaddressheader: parametersObj.remoteaddressheader || "",
|
|
251
|
+
cleanxforwardedfor: false,
|
|
252
|
+
};
|
|
253
|
+
} else if (hasPort && !hasDomain && !hasCertificate) {
|
|
254
|
+
correctType = "tcp";
|
|
255
|
+
requiredParameters = {
|
|
256
|
+
type: "tcp",
|
|
257
|
+
};
|
|
258
|
+
} else {
|
|
259
|
+
console.warn(
|
|
260
|
+
"Inbound configuration doesn't match any valid schema variant:",
|
|
261
|
+
{
|
|
262
|
+
hasCertificate,
|
|
263
|
+
hasDomain,
|
|
264
|
+
hasPort,
|
|
265
|
+
resources: item.resourceList.map((r) => ({
|
|
266
|
+
name: r.name,
|
|
267
|
+
type: r.type,
|
|
268
|
+
})),
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
Object.assign(parametersObj, requiredParameters);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const serviceName = item.serviceName.startsWith("./")
|
|
276
|
+
? item.serviceName.split("./")[1]
|
|
277
|
+
: item.serviceName.startsWith(".")
|
|
278
|
+
? item.serviceName.split(".")[1]
|
|
279
|
+
: item.serviceName;
|
|
280
|
+
|
|
281
|
+
const scale =
|
|
282
|
+
item.type === "component" || item.serviceName === ""
|
|
283
|
+
? { hsize: 1 }
|
|
284
|
+
: item.schema?.name === "builtins/inbound"
|
|
285
|
+
? { detail: {} }
|
|
286
|
+
: item.type === "service" && item.roles && Array.isArray(item.roles)
|
|
287
|
+
? {
|
|
288
|
+
detail: item.roles.reduce(
|
|
289
|
+
(acc, role) => ({
|
|
290
|
+
...acc,
|
|
291
|
+
[role]: { hsize: 1 },
|
|
292
|
+
}),
|
|
293
|
+
{}
|
|
294
|
+
),
|
|
295
|
+
}
|
|
296
|
+
: { detail: { [`${item.artifactName}`]: { hsize: 1 } } };
|
|
297
|
+
|
|
298
|
+
const scaling: {
|
|
299
|
+
simple: {
|
|
300
|
+
[key: string]: {
|
|
301
|
+
scale_up: { cpu: number; memory: number };
|
|
302
|
+
scale_down: { cpu: number; memory: number };
|
|
303
|
+
hysteresis: number;
|
|
304
|
+
min_replicas: number;
|
|
305
|
+
max_replicas: number;
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
} = {
|
|
309
|
+
simple: {},
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
scaling.simple[item.serviceName] = {
|
|
313
|
+
scale_up: {
|
|
314
|
+
cpu: parseInt(item.scaling?.cpu.up || "") || 0,
|
|
315
|
+
memory: parseInt(item.scaling?.memory.up || "") || 0,
|
|
316
|
+
},
|
|
317
|
+
scale_down: {
|
|
318
|
+
cpu: parseInt(item.scaling?.cpu.down || "") || 0,
|
|
319
|
+
memory: parseInt(item.scaling?.memory.down || "") || 0,
|
|
320
|
+
},
|
|
321
|
+
hysteresis: parseInt(item.scaling?.histeresys || "") || 0,
|
|
322
|
+
min_replicas: item.scaling?.instances.min || 0,
|
|
323
|
+
max_replicas: item.scaling?.instances.max || 0,
|
|
324
|
+
};
|
|
325
|
+
const itemBody = {
|
|
326
|
+
type: "deployment",
|
|
327
|
+
deployment: {
|
|
328
|
+
name: deploymentData.name,
|
|
329
|
+
up: null,
|
|
330
|
+
meta: {},
|
|
331
|
+
config: {
|
|
332
|
+
parameter: parametersObj,
|
|
333
|
+
resource: resourcesObj,
|
|
334
|
+
resilience: 0,
|
|
335
|
+
scale: scale,
|
|
336
|
+
},
|
|
337
|
+
artifact: {
|
|
338
|
+
ref: {
|
|
339
|
+
version: [
|
|
340
|
+
Number(item.version.split(".")[0]),
|
|
341
|
+
Number(item.version.split(".")[1]),
|
|
342
|
+
Number(item.version.split(".")[2]),
|
|
343
|
+
],
|
|
344
|
+
kind: item.type,
|
|
345
|
+
domain: item.domain,
|
|
346
|
+
module: item.module,
|
|
347
|
+
name: item.artifact,
|
|
348
|
+
package: serviceName,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
comment: " ",
|
|
353
|
+
meta: {
|
|
354
|
+
targetAccount: deploymentData.account,
|
|
355
|
+
targetEnvironment: deploymentData.environment,
|
|
356
|
+
scaling: scaling,
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
const response = await fetch(url.toString(), {
|
|
360
|
+
method: "POST",
|
|
361
|
+
headers: {
|
|
362
|
+
"Content-Type": "application/json",
|
|
363
|
+
},
|
|
364
|
+
body: JSON.stringify(itemBody),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
if (!response.ok) {
|
|
368
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const jsonResponse = await response.json();
|
|
372
|
+
|
|
373
|
+
const isTimeout = jsonResponse?.events?.some(
|
|
374
|
+
(event: any) => event.content === "_timeout_"
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
if (isTimeout) {
|
|
378
|
+
console.error("Timeout en la petición:", {
|
|
379
|
+
isOk: false,
|
|
380
|
+
code: "TIMEOUT",
|
|
381
|
+
error: "_timeout_",
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (deploymentData.links && deploymentData.links.length > 0) {
|
|
386
|
+
pendingLinks.set(deploymentData.name, deploymentData.links);
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
await linkPendingServices(deploymentData, "");
|
|
390
|
+
} catch (linkError) {
|
|
391
|
+
console.error("⭐ Error en linkPendingServices:", linkError);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
export const getMarketplaceItems = async (
|
|
398
|
+
tenants: string[],
|
|
399
|
+
security: string
|
|
400
|
+
) => {
|
|
401
|
+
await initializeGlobalWebSocketClient(security);
|
|
402
|
+
const promises = tenants.map(async (tenant) => {
|
|
403
|
+
try {
|
|
404
|
+
const response = await makeGlobalWebSocketRequest(
|
|
405
|
+
"marketplace:search_marketplace",
|
|
406
|
+
{ tenant },
|
|
407
|
+
30000,
|
|
408
|
+
"GET",
|
|
409
|
+
tenant
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const tenantItems: MarketplaceItem[] = [];
|
|
413
|
+
|
|
414
|
+
const itemPromises = Object.entries(response.data.items).map(
|
|
415
|
+
async ([key, value]) => {
|
|
416
|
+
let cpu = 0;
|
|
417
|
+
let memory = 0;
|
|
418
|
+
|
|
419
|
+
(value as any).tags.forEach((tag: string) => {
|
|
420
|
+
if (tag.includes("cpu")) {
|
|
421
|
+
cpu = Number(tag.split("::")[1].split("vCPU")[0]);
|
|
422
|
+
} else if (tag.includes("memory")) {
|
|
423
|
+
const value = tag.split("::")[1]?.trim();
|
|
424
|
+
if (value?.includes("GB")) {
|
|
425
|
+
const memStr = value.split("GB")[0].trim();
|
|
426
|
+
memory = Number(memStr);
|
|
427
|
+
} else if (value?.includes("MB")) {
|
|
428
|
+
const memStr = value.split("MB")[0].trim();
|
|
429
|
+
memory = Number(memStr) / 1024;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
const item: MarketplaceItem = {
|
|
434
|
+
tenant: tenant.toString(),
|
|
435
|
+
name: (value as any).name,
|
|
436
|
+
logo: "https://gitlab.com/uploads/-/system/group/avatar/86481133/Axebow-Logo-2.jpeg?width=96",
|
|
437
|
+
description: (value as any).name,
|
|
438
|
+
version: (value as any).version,
|
|
439
|
+
requirements: { cpu, memory },
|
|
440
|
+
status: "",
|
|
441
|
+
instances: [],
|
|
442
|
+
links: [],
|
|
443
|
+
resources: [],
|
|
444
|
+
domain: (value as any).domain,
|
|
445
|
+
type: (value as any).artifactTypes[0],
|
|
446
|
+
artifact: (value as any).artifact,
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
const schemaData = await getMarketplaceSchema(
|
|
451
|
+
tenant,
|
|
452
|
+
item,
|
|
453
|
+
security
|
|
454
|
+
);
|
|
455
|
+
item.schema = schemaData;
|
|
456
|
+
} catch (schemaError) {
|
|
457
|
+
console.error(
|
|
458
|
+
`Error cargando schema para ${item.name}:`,
|
|
459
|
+
schemaError
|
|
460
|
+
);
|
|
461
|
+
item.schema = undefined;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return item;
|
|
465
|
+
}
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const processedItems = await Promise.all(itemPromises);
|
|
469
|
+
tenantItems.push(...processedItems);
|
|
470
|
+
return tenantItems;
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error(`Error al obtener items para el tenant ${tenant}:`, error);
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const itemsArrays = await Promise.all(promises);
|
|
478
|
+
const items = itemsArrays.flat();
|
|
479
|
+
return { items };
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
export const getMarketplaceSchema = async (
|
|
483
|
+
tenant: string,
|
|
484
|
+
marketplaceItem: MarketplaceItem,
|
|
485
|
+
security: string
|
|
486
|
+
): Promise<
|
|
487
|
+
| {
|
|
488
|
+
resources: {
|
|
489
|
+
name: string;
|
|
490
|
+
type: string;
|
|
491
|
+
required: boolean;
|
|
492
|
+
protocol?: string;
|
|
493
|
+
defaultValue?: string;
|
|
494
|
+
}[];
|
|
495
|
+
parameters: {
|
|
496
|
+
name: string;
|
|
497
|
+
type: string;
|
|
498
|
+
required: boolean;
|
|
499
|
+
protocol?: string;
|
|
500
|
+
pattern?: string;
|
|
501
|
+
parent?: string;
|
|
502
|
+
defaultValue?: string | number;
|
|
503
|
+
}[];
|
|
504
|
+
channels: {
|
|
505
|
+
name: string;
|
|
506
|
+
type: "client" | "server" | "duplex";
|
|
507
|
+
resource?: { type: "port" | "domain"; value: string };
|
|
508
|
+
protocol?: string;
|
|
509
|
+
}[];
|
|
510
|
+
name?: string;
|
|
511
|
+
roleName?: string;
|
|
512
|
+
roles?: string[];
|
|
513
|
+
hasVariants?: boolean;
|
|
514
|
+
variants?: any[];
|
|
515
|
+
}
|
|
516
|
+
| undefined
|
|
517
|
+
> => {
|
|
518
|
+
await initializeGlobalWebSocketClient(security);
|
|
519
|
+
try {
|
|
520
|
+
const requestPayload = {
|
|
521
|
+
tenant,
|
|
522
|
+
module: marketplaceItem.name,
|
|
523
|
+
version: marketplaceItem.version,
|
|
524
|
+
domain: marketplaceItem.domain,
|
|
525
|
+
artifact: marketplaceItem.artifact,
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const response = await makeGlobalWebSocketRequest(
|
|
529
|
+
"marketplace:artifact_schema",
|
|
530
|
+
requestPayload,
|
|
531
|
+
30000,
|
|
532
|
+
"GET",
|
|
533
|
+
tenant
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
const nameKeys = Object.keys(response.data);
|
|
537
|
+
const schemaData = response.data[nameKeys[0]];
|
|
538
|
+
|
|
539
|
+
let roleName = "";
|
|
540
|
+
let roles: string[] = [];
|
|
541
|
+
if (nameKeys[0] !== ".") {
|
|
542
|
+
roles = schemaData.roles || [];
|
|
543
|
+
roleName = roles.join(", ");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const schemas = schemaData.schemas || [];
|
|
547
|
+
if (schemas.length > 0 && schemas[0].oneOf) {
|
|
548
|
+
const schemaOptions = schemas[0].oneOf;
|
|
549
|
+
const processedSchemas: any[] = [];
|
|
550
|
+
|
|
551
|
+
for (const schemaRef of schemaOptions) {
|
|
552
|
+
const refKey = schemaRef.$ref?.replace("#/$defs/", "");
|
|
553
|
+
const actualSchema = schemas[0].$defs?.[refKey];
|
|
554
|
+
|
|
555
|
+
if (actualSchema) {
|
|
556
|
+
const schemaResult = processSchema(actualSchema, refKey);
|
|
557
|
+
if (schemaResult) {
|
|
558
|
+
processedSchemas.push({
|
|
559
|
+
name: nameKeys[0],
|
|
560
|
+
roleName,
|
|
561
|
+
roles,
|
|
562
|
+
variant: refKey,
|
|
563
|
+
...schemaResult,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
parameters: processedSchemas[0]?.parameters || [],
|
|
570
|
+
resources: processedSchemas[0]?.resources || [],
|
|
571
|
+
channels: processedSchemas[0]?.channels || [],
|
|
572
|
+
name: nameKeys[0],
|
|
573
|
+
roleName,
|
|
574
|
+
roles,
|
|
575
|
+
hasVariants: true,
|
|
576
|
+
variants: processedSchemas,
|
|
577
|
+
} as any;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const singleSchema = schemas[0];
|
|
581
|
+
if (singleSchema) {
|
|
582
|
+
const schemaResult = processSchema(singleSchema, "default");
|
|
583
|
+
return {
|
|
584
|
+
parameters: schemaResult?.parameters || [],
|
|
585
|
+
resources: schemaResult?.resources || [],
|
|
586
|
+
channels: schemaResult?.channels || [],
|
|
587
|
+
name: nameKeys[0],
|
|
588
|
+
roleName,
|
|
589
|
+
roles,
|
|
590
|
+
hasVariants: false,
|
|
591
|
+
} as any;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
parameters: [],
|
|
596
|
+
resources: [],
|
|
597
|
+
channels: [],
|
|
598
|
+
name: nameKeys[0],
|
|
599
|
+
roleName,
|
|
600
|
+
roles,
|
|
601
|
+
hasVariants: false,
|
|
602
|
+
} as any;
|
|
603
|
+
} catch (error) {
|
|
604
|
+
console.error("Error obteniendo el schema del item:", error);
|
|
605
|
+
throw error;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
function processSchema(schema: any, schemaType: string) {
|
|
610
|
+
let resources: {
|
|
611
|
+
name: string;
|
|
612
|
+
type: string;
|
|
613
|
+
required: boolean;
|
|
614
|
+
defaultValue?: string;
|
|
615
|
+
}[] = [];
|
|
616
|
+
let parameters: {
|
|
617
|
+
name: string;
|
|
618
|
+
type: string;
|
|
619
|
+
required: boolean;
|
|
620
|
+
defaultValue?: string | number;
|
|
621
|
+
pattern?: string;
|
|
622
|
+
parent?: string;
|
|
623
|
+
}[] = [];
|
|
624
|
+
let channels: {
|
|
625
|
+
name: string;
|
|
626
|
+
type: "client" | "server" | "duplex";
|
|
627
|
+
resource?: {
|
|
628
|
+
type: "port" | "domain";
|
|
629
|
+
value: string;
|
|
630
|
+
};
|
|
631
|
+
}[] = [];
|
|
632
|
+
|
|
633
|
+
const configProps = schema.properties?.config?.properties;
|
|
634
|
+
|
|
635
|
+
if (configProps) {
|
|
636
|
+
Object.entries(configProps).forEach(([key, value]) => {
|
|
637
|
+
const param = value as any;
|
|
638
|
+
parameters.push({
|
|
639
|
+
name: key,
|
|
640
|
+
type: param.type || "string",
|
|
641
|
+
required: schema.properties?.config?.required?.includes(key) || false,
|
|
642
|
+
defaultValue:
|
|
643
|
+
param.default !== undefined
|
|
644
|
+
? param.default
|
|
645
|
+
: param.enum
|
|
646
|
+
? param.enum[0]
|
|
647
|
+
: param.const
|
|
648
|
+
? param.const
|
|
649
|
+
: undefined,
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const resourceProps = schema.properties?.resource;
|
|
655
|
+
if (resourceProps?.properties) {
|
|
656
|
+
Object.entries(resourceProps.properties).forEach(([key, value]) => {
|
|
657
|
+
if (
|
|
658
|
+
key.toLowerCase() === "data" ||
|
|
659
|
+
key.toLowerCase() === "jetstreamdata" ||
|
|
660
|
+
key.toLowerCase() === "shared"
|
|
661
|
+
) {
|
|
662
|
+
resources.push({
|
|
663
|
+
name: key,
|
|
664
|
+
type: "volume",
|
|
665
|
+
required: resourceProps.required?.includes(key) || false,
|
|
666
|
+
defaultValue: undefined,
|
|
667
|
+
});
|
|
668
|
+
} else {
|
|
669
|
+
const resourceValue = value as any;
|
|
670
|
+
let resourceType = "";
|
|
671
|
+
let defaultValue: string | undefined = undefined;
|
|
672
|
+
|
|
673
|
+
if (resourceValue.properties?.["$kdsl"]?.const?.NamedType?.Name) {
|
|
674
|
+
const typeName =
|
|
675
|
+
resourceValue.properties["$kdsl"].const.NamedType.Name;
|
|
676
|
+
resourceType = typeName.toLowerCase();
|
|
677
|
+
} else {
|
|
678
|
+
resourceType = Object.keys(resourceValue.properties || {})[0];
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (resourceValue.properties) {
|
|
682
|
+
const innerResource =
|
|
683
|
+
resourceValue.properties[resourceType] ||
|
|
684
|
+
resourceValue.properties.inner;
|
|
685
|
+
if (innerResource) {
|
|
686
|
+
if (innerResource.default !== undefined) {
|
|
687
|
+
defaultValue = String(innerResource.default);
|
|
688
|
+
} else if (innerResource.enum && innerResource.enum.length > 0) {
|
|
689
|
+
defaultValue = String(innerResource.enum[0]);
|
|
690
|
+
} else if (resourceType === "volume" && innerResource.oneOf) {
|
|
691
|
+
const firstOption = innerResource.oneOf[0];
|
|
692
|
+
if (firstOption?.properties?.volume?.properties) {
|
|
693
|
+
const sizeDefault =
|
|
694
|
+
firstOption.properties.volume.properties.size?.default;
|
|
695
|
+
const unitDefault =
|
|
696
|
+
firstOption.properties.volume.properties.unit?.enum?.[0];
|
|
697
|
+
if (sizeDefault !== undefined && unitDefault) {
|
|
698
|
+
defaultValue = `${sizeDefault}${unitDefault}`;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
resources.push({
|
|
706
|
+
name: key,
|
|
707
|
+
type: resourceType || "string",
|
|
708
|
+
required: resourceProps.required?.includes(key) || false,
|
|
709
|
+
defaultValue: defaultValue,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const srvProps = schema.properties?.srv?.properties;
|
|
716
|
+
if (srvProps) {
|
|
717
|
+
if (srvProps.client?.properties) {
|
|
718
|
+
Object.entries(srvProps.client.properties).forEach(
|
|
719
|
+
([channelName, channelData]) => {
|
|
720
|
+
const channelInfo = channelData as any;
|
|
721
|
+
let resource: { type: "port" | "domain"; value: string } | undefined;
|
|
722
|
+
|
|
723
|
+
if (channelInfo.properties) {
|
|
724
|
+
const port =
|
|
725
|
+
channelInfo.properties.port?.const ||
|
|
726
|
+
channelInfo.properties.port?.enum?.[0];
|
|
727
|
+
const protocol =
|
|
728
|
+
channelInfo.properties.protocol?.const ||
|
|
729
|
+
channelInfo.properties.protocol?.enum?.[0];
|
|
730
|
+
|
|
731
|
+
if (port && protocol) {
|
|
732
|
+
resource = {
|
|
733
|
+
type: "port",
|
|
734
|
+
value: `${protocol}:${port}`,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
channels.push({
|
|
740
|
+
name: channelName,
|
|
741
|
+
type: "client",
|
|
742
|
+
resource,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
if (srvProps.server?.properties) {
|
|
748
|
+
Object.entries(srvProps.server.properties).forEach(
|
|
749
|
+
([channelName, channelData]) => {
|
|
750
|
+
const channelInfo = channelData as any;
|
|
751
|
+
let resource: { type: "port" | "domain"; value: string } | undefined;
|
|
752
|
+
|
|
753
|
+
if (channelInfo.properties) {
|
|
754
|
+
const port =
|
|
755
|
+
channelInfo.properties.port?.const ||
|
|
756
|
+
channelInfo.properties.port?.enum?.[0];
|
|
757
|
+
const protocol =
|
|
758
|
+
channelInfo.properties.protocol?.const ||
|
|
759
|
+
channelInfo.properties.protocol?.enum?.[0];
|
|
760
|
+
|
|
761
|
+
if (port && protocol) {
|
|
762
|
+
resource = {
|
|
763
|
+
type: "port",
|
|
764
|
+
value: `${protocol}:${port}`,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
channels.push({
|
|
770
|
+
name: channelName,
|
|
771
|
+
type: "server",
|
|
772
|
+
resource,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
if (srvProps.duplex?.properties) {
|
|
778
|
+
Object.entries(srvProps.duplex.properties).forEach(
|
|
779
|
+
([channelName, channelData]) => {
|
|
780
|
+
const channelInfo = channelData as any;
|
|
781
|
+
let resource: { type: "port" | "domain"; value: string } | undefined;
|
|
782
|
+
|
|
783
|
+
if (channelInfo.properties) {
|
|
784
|
+
const port =
|
|
785
|
+
channelInfo.properties.port?.const ||
|
|
786
|
+
channelInfo.properties.port?.enum?.[0];
|
|
787
|
+
const protocol =
|
|
788
|
+
channelInfo.properties.protocol?.const ||
|
|
789
|
+
channelInfo.properties.protocol?.enum?.[0];
|
|
790
|
+
|
|
791
|
+
if (port && protocol) {
|
|
792
|
+
resource = {
|
|
793
|
+
type: "port",
|
|
794
|
+
value: `${protocol}:${port}`,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
channels.push({
|
|
800
|
+
name: channelName,
|
|
801
|
+
type: "duplex",
|
|
802
|
+
resource,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
return {
|
|
810
|
+
parameters,
|
|
811
|
+
resources,
|
|
812
|
+
channels,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
const generateLinkBody = (data: Service, link: Link) => {
|
|
816
|
+
let linkBody;
|
|
817
|
+
|
|
818
|
+
if (link.origin === data.name) {
|
|
819
|
+
const originInClient = data.clientChannels.find(
|
|
820
|
+
(channel) =>
|
|
821
|
+
channel.name === link.originChannel ||
|
|
822
|
+
channel.from === link.originChannel
|
|
823
|
+
);
|
|
824
|
+
const originInServer = data.serverChannels.find(
|
|
825
|
+
(channel) =>
|
|
826
|
+
channel.name === link.originChannel ||
|
|
827
|
+
channel.from === link.originChannel
|
|
828
|
+
);
|
|
829
|
+
const originInDuplex = data.duplexChannels.find(
|
|
830
|
+
(channel) =>
|
|
831
|
+
channel.name === link.originChannel ||
|
|
832
|
+
channel.from === link.originChannel
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
if (originInClient) {
|
|
836
|
+
linkBody = {
|
|
837
|
+
client_tenant: data.tenant,
|
|
838
|
+
client_service: data.name,
|
|
839
|
+
client_channel: link.originChannel,
|
|
840
|
+
server_tenant: data.tenant,
|
|
841
|
+
server_service: link.target,
|
|
842
|
+
server_channel: link.targetChannel,
|
|
843
|
+
};
|
|
844
|
+
} else if (originInServer || originInDuplex) {
|
|
845
|
+
linkBody = {
|
|
846
|
+
client_tenant: data.tenant,
|
|
847
|
+
client_service: link.target,
|
|
848
|
+
client_channel: link.targetChannel,
|
|
849
|
+
server_tenant: data.tenant,
|
|
850
|
+
server_service: data.name,
|
|
851
|
+
server_channel: link.originChannel,
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
} else if (link.target === data.name) {
|
|
855
|
+
const targetInClient = data.clientChannels.find(
|
|
856
|
+
(channel) =>
|
|
857
|
+
channel.name === link.targetChannel ||
|
|
858
|
+
channel.from === link.targetChannel
|
|
859
|
+
);
|
|
860
|
+
const targetInServer = data.serverChannels.find(
|
|
861
|
+
(channel) =>
|
|
862
|
+
channel.name === link.targetChannel ||
|
|
863
|
+
channel.from === link.targetChannel
|
|
864
|
+
);
|
|
865
|
+
const targetInDuplex = data.duplexChannels.find(
|
|
866
|
+
(channel) =>
|
|
867
|
+
channel.name === link.targetChannel ||
|
|
868
|
+
channel.from === link.targetChannel
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
if (targetInClient) {
|
|
872
|
+
linkBody = {
|
|
873
|
+
client_tenant: data.tenant,
|
|
874
|
+
client_service: data.name,
|
|
875
|
+
client_channel: link.targetChannel,
|
|
876
|
+
server_tenant: data.tenant,
|
|
877
|
+
server_service: link.origin,
|
|
878
|
+
server_channel: link.originChannel,
|
|
879
|
+
};
|
|
880
|
+
} else if (targetInServer || targetInDuplex) {
|
|
881
|
+
linkBody = {
|
|
882
|
+
client_tenant: data.tenant,
|
|
883
|
+
client_service: link.origin,
|
|
884
|
+
client_channel: link.originChannel,
|
|
885
|
+
server_tenant: data.tenant,
|
|
886
|
+
server_service: data.name,
|
|
887
|
+
server_channel: link.targetChannel,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
console.warn(
|
|
892
|
+
`Servicio actual no involucrado en el enlace: current=${data.name}, origin=${link.origin}, target=${link.target}`
|
|
893
|
+
);
|
|
894
|
+
linkBody = {
|
|
895
|
+
client_tenant: data.tenant,
|
|
896
|
+
client_service: link.origin,
|
|
897
|
+
client_channel: link.originChannel,
|
|
898
|
+
server_tenant: data.tenant,
|
|
899
|
+
server_service: link.target,
|
|
900
|
+
server_channel: link.targetChannel,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
return linkBody;
|
|
904
|
+
};
|
|
905
|
+
const linkPendingServices = async (service: Service, token: string) => {
|
|
906
|
+
const links = pendingLinks.get(service.name);
|
|
907
|
+
|
|
908
|
+
if (links) {
|
|
909
|
+
await Promise.all(
|
|
910
|
+
service.links.map(async (link, index) => {
|
|
911
|
+
try {
|
|
912
|
+
await initializeGlobalWebSocketClient(token);
|
|
913
|
+
const status = getWebSocketStatus();
|
|
914
|
+
const linkBody = generateLinkBody(service, link);
|
|
915
|
+
|
|
916
|
+
const linkResponse = await makeGlobalWebSocketRequest(
|
|
917
|
+
"link:link_service",
|
|
918
|
+
linkBody,
|
|
919
|
+
30000,
|
|
920
|
+
"LINK",
|
|
921
|
+
service.name
|
|
922
|
+
);
|
|
923
|
+
|
|
924
|
+
const notification: Notification = {
|
|
925
|
+
type: "success",
|
|
926
|
+
subtype: "service-linked",
|
|
927
|
+
date: Date.now().toString(),
|
|
928
|
+
status: "unread",
|
|
929
|
+
callToAction: false,
|
|
930
|
+
data: {
|
|
931
|
+
service: service.name,
|
|
932
|
+
tenant: service.tenant,
|
|
933
|
+
},
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
eventHelper.notification.publish.creation(notification);
|
|
937
|
+
} catch (linkErr) {
|
|
938
|
+
const notification: Notification = {
|
|
939
|
+
type: "error",
|
|
940
|
+
subtype: "error-service-link",
|
|
941
|
+
date: Date.now().toString(),
|
|
942
|
+
info_content: {
|
|
943
|
+
code: (linkErr as any).error.code,
|
|
944
|
+
message: (linkErr as any).error.content,
|
|
945
|
+
},
|
|
946
|
+
status: "unread",
|
|
947
|
+
callToAction: false,
|
|
948
|
+
data: {
|
|
949
|
+
service: service.name,
|
|
950
|
+
tenant: service.tenant,
|
|
951
|
+
},
|
|
952
|
+
};
|
|
953
|
+
eventHelper.notification.publish.creation(notification);
|
|
954
|
+
}
|
|
955
|
+
})
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
/**
|
|
960
|
+
* Function to load marketplace items for a specific tenant
|
|
961
|
+
* @param tenant Tenant ID
|
|
962
|
+
* @param security Authorization token
|
|
963
|
+
*/
|
|
964
|
+
export const loadMarketplaceItemsForTenant = async (
|
|
965
|
+
tenant: string,
|
|
966
|
+
security: string
|
|
967
|
+
) => {
|
|
968
|
+
try {
|
|
969
|
+
await initializeGlobalWebSocketClient(security);
|
|
970
|
+
|
|
971
|
+
const response = await makeGlobalWebSocketRequest(
|
|
972
|
+
"marketplace:search_marketplace",
|
|
973
|
+
{ tenant },
|
|
974
|
+
30000,
|
|
975
|
+
"GET",
|
|
976
|
+
tenant
|
|
977
|
+
);
|
|
978
|
+
const items = response.data?.items || [];
|
|
979
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
980
|
+
return [];
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const itemPromises = items.map(async (item: any) => {
|
|
984
|
+
const marketplaceItem: MarketplaceItem = {
|
|
985
|
+
tenant: tenant,
|
|
986
|
+
name: item.module,
|
|
987
|
+
logo: "https://gitlab.com/uploads/-/system/group/avatar/86481133/Axebow-Logo-2.jpeg?width=96",
|
|
988
|
+
description: item.description,
|
|
989
|
+
version: item.version,
|
|
990
|
+
requirements: {
|
|
991
|
+
cpu: item.requirements?.cpu || 0,
|
|
992
|
+
memory: item.requirements?.memory || 0,
|
|
993
|
+
},
|
|
994
|
+
status: "",
|
|
995
|
+
instances: [],
|
|
996
|
+
links: [],
|
|
997
|
+
resources: [],
|
|
998
|
+
domain: item.domain,
|
|
999
|
+
type: item.artifactTypes?.[0],
|
|
1000
|
+
artifact: item.artifact,
|
|
1001
|
+
package: item.package,
|
|
1002
|
+
categories: item.categories || [],
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
try {
|
|
1006
|
+
const schemaData = await getMarketplaceSchema(
|
|
1007
|
+
tenant,
|
|
1008
|
+
marketplaceItem,
|
|
1009
|
+
security
|
|
1010
|
+
);
|
|
1011
|
+
marketplaceItem.schema = schemaData;
|
|
1012
|
+
} catch (schemaError) {
|
|
1013
|
+
console.error(
|
|
1014
|
+
`Error loading schema for ${marketplaceItem.name}:`,
|
|
1015
|
+
schemaError
|
|
1016
|
+
);
|
|
1017
|
+
marketplaceItem.schema = undefined;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
return marketplaceItem;
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
return await Promise.all(itemPromises);
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
console.error(
|
|
1026
|
+
`Error loading marketplace items for tenant ${tenant}:`,
|
|
1027
|
+
error
|
|
1028
|
+
);
|
|
1029
|
+
return [];
|
|
1030
|
+
}
|
|
1031
|
+
};
|