@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.
@@ -0,0 +1,796 @@
1
+ import { environment } from "../environment";
2
+ import { deployServiceHelper } from "./deploy-service-helper";
3
+ import { eventHelper } from "../backend-handler";
4
+ import {
5
+ initializeGlobalWebSocketClient,
6
+ getWebSocketStatus,
7
+ makeGlobalWebSocketRequest,
8
+ } from "../websocket-manager";
9
+ import { Link, Notification, Service } from "@hestekumori/aurora-interfaces";
10
+ let pendingLinks = new Map<string, Link[]>();
11
+ /**
12
+ * Function to deploy a service
13
+ * @param data Service object with the data of the service
14
+ */
15
+ export const deployService = async (data: Service, token: string) => {
16
+ try {
17
+ const url = new URL(
18
+ `${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${data.tenant}/service/${data.name}`
19
+ );
20
+ url.searchParams.append("dryrun", "false");
21
+ url.searchParams.append("accept", "true");
22
+ url.searchParams.append("wait", "30000");
23
+ url.searchParams.append("validate", "true");
24
+ if(data.dsl){
25
+ url.searchParams.append("dsl", "true");
26
+ }
27
+ if (data.serviceData) {
28
+ const formData = new FormData();
29
+ formData.append("bundle", data.serviceData);
30
+ formData.append(
31
+ "meta",
32
+ JSON.stringify({
33
+ targetAccount: data.account,
34
+ targetEnvironment: data.environment,
35
+ })
36
+ );
37
+ formData.append("labels", JSON.stringify({ project: data.project }));
38
+ formData.append("comment", " ");
39
+ const response = await fetch(url.toString(), {
40
+ method: "POST",
41
+ body: formData,
42
+ });
43
+ if (!response.ok) {
44
+ throw new Error(`HTTP error! status: ${response.status}`);
45
+ }
46
+
47
+ const jsonResponse = await response.json();
48
+
49
+ const isTimeout = jsonResponse?.events?.some(
50
+ (event: any) => event.content === "_timeout_"
51
+ );
52
+
53
+ if (isTimeout) {
54
+ console.error("Timeout en la petición:", {
55
+ isOk: false,
56
+ code: "TIMEOUT",
57
+ error: "_timeout_",
58
+ });
59
+ }
60
+ } else {
61
+ const formData = await deployServiceHelper(data);
62
+ if (data.download) {
63
+ return;
64
+ }
65
+ const response = await fetch(url.toString(), {
66
+ method: "POST",
67
+ body: formData,
68
+ });
69
+ if (!response.ok) {
70
+ const jsonResponse = await response.json();
71
+ data.error = {
72
+ code: jsonResponse.code,
73
+ message: jsonResponse.content,
74
+ timestamp: new Date().toISOString(),
75
+ };
76
+ eventHelper.service.publish.deploymentError(data);
77
+ throw new Error(`HTTP error! status: ${response.status}`);
78
+ }
79
+
80
+ const jsonResponse = await response.json();
81
+
82
+ const isTimeout = jsonResponse?.events?.some(
83
+ (event: any) => event.content === "_timeout_"
84
+ );
85
+
86
+ if (isTimeout) {
87
+ console.error("Timeout en la petición:", {
88
+ isOk: false,
89
+ code: "TIMEOUT",
90
+ error: "_timeout_",
91
+ });
92
+ } else {
93
+ if (data.links.length > 0) {
94
+ pendingLinks.set(data.name, data.links);
95
+ linkPendingServices(data, token);
96
+ }
97
+ }
98
+ }
99
+ } catch (err) {
100
+ console.error("Error en la petición de despliegue de servicio:", err);
101
+ // data.error = {
102
+ // code: (err as any).code,
103
+ // message: (err as any).content,
104
+ // timestamp: new Date().toISOString(),
105
+ // }
106
+ // eventHelper.service.publish.deploymentError(data);
107
+ }
108
+ };
109
+ export const redeployService = async (data: Service) => {
110
+ try {
111
+ const formData = await deployServiceHelper(data);
112
+ const url = new URL(
113
+ `${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${data.tenant}/service/${data.name}/revision/${data.currentRevision}`
114
+ );
115
+ url.searchParams.append("dryrun", "false");
116
+ url.searchParams.append("accept", "true");
117
+ url.searchParams.append("wait", "30000");
118
+ url.searchParams.append("validate", "true");
119
+
120
+ const response = await fetch(url.toString(), {
121
+ method: "POST",
122
+ body: formData,
123
+ });
124
+
125
+ if (!response.ok) {
126
+ throw new Error(`HTTP error! status: ${response.status}`);
127
+ }
128
+
129
+ const jsonResponse = await response.json();
130
+
131
+ const isTimeout = jsonResponse?.events?.some(
132
+ (event: any) => event.content === "_timeout_"
133
+ );
134
+
135
+ if (isTimeout) {
136
+ console.error("Timeout en la petición:", {
137
+ isOk: false,
138
+ code: "TIMEOUT",
139
+ error: "_timeout_",
140
+ });
141
+ } else {
142
+ eventHelper.service.publish.deployed(data);
143
+ }
144
+ } catch (err) {
145
+ console.error("Error en la petición de redepliegue de servicio:", err);
146
+ }
147
+ };
148
+ export const deleteService = async (data: Service, security: string) => {
149
+ try {
150
+ await initializeGlobalWebSocketClient(security);
151
+ const status = getWebSocketStatus();
152
+
153
+ const deleteBody = {
154
+ tenant: data.tenant,
155
+ service: data.name,
156
+ wait: 0,
157
+ remove_links: true,
158
+ };
159
+
160
+ const response = await makeGlobalWebSocketRequest(
161
+ "service:delete_service",
162
+ deleteBody,
163
+ 30000,
164
+ "DELETE",
165
+ data.name
166
+ );
167
+ return response;
168
+ } catch (err) {
169
+ console.error("Error in service deletion WebSocket request:", err);
170
+ eventHelper.service.publish.deletionError(data);
171
+ throw err;
172
+ }
173
+ };
174
+
175
+ export const restartService = async (data: Service, security: string) => {
176
+ try {
177
+ await initializeGlobalWebSocketClient(security);
178
+ const status = getWebSocketStatus();
179
+
180
+ const restartBody = {
181
+ tenant: data.tenant,
182
+ service: data.name,
183
+ };
184
+
185
+ const response = await makeGlobalWebSocketRequest(
186
+ "service:restart",
187
+ restartBody,
188
+ 30000,
189
+ "RESTART",
190
+ data.name
191
+ );
192
+
193
+ const updatedService: Service = {
194
+ ...data,
195
+ status: {
196
+ code: "PENDING",
197
+ message: "",
198
+ timestamp: "",
199
+ args: [],
200
+ },
201
+ };
202
+ eventHelper.service.publish.restarted(updatedService);
203
+ return response;
204
+ } catch (err) {
205
+ console.error("Error in service restart WebSocket request:", err);
206
+ eventHelper.service.publish.restartError(data);
207
+ throw err;
208
+ }
209
+ };
210
+ const generateLinkBody = (data: Service, link: Link) => {
211
+ const originInClient = data.clientChannels.find(
212
+ (channel) =>
213
+ channel.name === link.originChannel || channel.from === link.originChannel
214
+ );
215
+ const originInServer = data.serverChannels.find(
216
+ (channel) =>
217
+ channel.name === link.originChannel || channel.from === link.originChannel
218
+ );
219
+ const originInDuplex = data.duplexChannels.find(
220
+ (channel) =>
221
+ channel.name === link.originChannel || channel.from === link.originChannel
222
+ );
223
+ const targetInClient = data.clientChannels.find(
224
+ (channel) =>
225
+ channel.name === link.targetChannel || channel.from === link.targetChannel
226
+ );
227
+ const targetInServer = data.serverChannels.find(
228
+ (channel) =>
229
+ channel.name === link.targetChannel || channel.from === link.targetChannel
230
+ );
231
+ const targetInDuplex = data.duplexChannels.find(
232
+ (channel) =>
233
+ channel.name === link.targetChannel || channel.from === link.targetChannel
234
+ );
235
+
236
+ let linkBody;
237
+ if (originInClient) {
238
+ linkBody = {
239
+ client_tenant: data.tenant,
240
+ client_service: data.name,
241
+ client_channel: link.originChannel,
242
+ server_tenant: data.tenant,
243
+ server_service: link.target,
244
+ server_channel: link.targetChannel,
245
+ };
246
+ } else if (targetInClient) {
247
+ linkBody = {
248
+ client_tenant: data.tenant,
249
+ client_service: data.name,
250
+ client_channel: link.targetChannel,
251
+ server_tenant: data.tenant,
252
+ server_service: link.origin,
253
+ server_channel: link.originChannel,
254
+ };
255
+ } else if (originInServer) {
256
+ linkBody = {
257
+ client_tenant: data.tenant,
258
+ client_service: link.target,
259
+ client_channel: link.targetChannel,
260
+ server_tenant: data.tenant,
261
+ server_service: data.name,
262
+ server_channel: link.originChannel,
263
+ };
264
+ } else if (targetInServer) {
265
+ linkBody = {
266
+ client_tenant: data.tenant,
267
+ client_service: link.origin,
268
+ client_channel: link.originChannel,
269
+ server_tenant: data.tenant,
270
+ server_service: data.name,
271
+ server_channel: link.targetChannel,
272
+ };
273
+ } else if (originInDuplex) {
274
+ linkBody = {
275
+ client_tenant: data.tenant,
276
+ client_service: link.target,
277
+ client_channel: link.targetChannel,
278
+ server_tenant: data.tenant,
279
+ server_service: data.name,
280
+ server_channel: link.originChannel,
281
+ };
282
+ } else if (targetInDuplex) {
283
+ linkBody = {
284
+ client_tenant: data.tenant,
285
+ client_service: link.origin,
286
+ client_channel: link.originChannel,
287
+ server_tenant: data.tenant,
288
+ server_service: data.name,
289
+ server_channel: link.targetChannel,
290
+ };
291
+ } else {
292
+ console.warn(
293
+ `No se encontraron canales para el enlace: origin=${link.origin}, target=${link.target}`
294
+ );
295
+ linkBody = {
296
+ client_tenant: data.tenant,
297
+ client_service: link.origin,
298
+ client_channel: link.originChannel,
299
+ server_tenant: data.tenant,
300
+ server_service: link.target,
301
+ server_channel: link.targetChannel,
302
+ };
303
+ }
304
+ return linkBody;
305
+ };
306
+ export const linkPendingServices = async (service: Service, token: string) => {
307
+ const links = pendingLinks.get(service.name);
308
+ if (links) {
309
+ await Promise.all(
310
+ links.map(async (link) => {
311
+ try {
312
+ await initializeGlobalWebSocketClient(token);
313
+ const status = getWebSocketStatus();
314
+ const linkBody = generateLinkBody(service, link);
315
+
316
+ const linkResponse = await makeGlobalWebSocketRequest(
317
+ "link:link_service",
318
+ linkBody,
319
+ 30000,
320
+ "LINK",
321
+ service.name
322
+ );
323
+
324
+ const notification: Notification = {
325
+ type: "success",
326
+ subtype: "service-linked",
327
+ date: Date.now().toString(),
328
+ status: "unread",
329
+ callToAction: false,
330
+ data: {
331
+ service: service.name,
332
+ tenant: service.tenant,
333
+ },
334
+ };
335
+
336
+ eventHelper.notification.publish.creation(notification);
337
+ } catch (linkErr) {
338
+ const notification: Notification = {
339
+ type: "error",
340
+ subtype: "error-service-link",
341
+ date: Date.now().toString(),
342
+ info_content: {
343
+ code: (linkErr as any).error.code,
344
+ message: (linkErr as any).error.content,
345
+ },
346
+ status: "unread",
347
+ callToAction: false,
348
+ data: {
349
+ service: service.name,
350
+ tenant: service.tenant,
351
+ },
352
+ };
353
+ eventHelper.notification.publish.creation(notification);
354
+ }
355
+ })
356
+ );
357
+ }
358
+ };
359
+ export const requestRevisionData = async (service: Service, token: string) => {
360
+ try {
361
+ await initializeGlobalWebSocketClient(token);
362
+ const status = getWebSocketStatus();
363
+
364
+ const revisionBody = {
365
+ tenant: service.tenant,
366
+ service: service.name,
367
+ revision: "latest",
368
+ };
369
+
370
+ const response = await makeGlobalWebSocketRequest(
371
+ "revision:revision_info",
372
+ revisionBody,
373
+ 30000,
374
+ "GET_REVISION",
375
+ service.name,
376
+ "service",
377
+ service
378
+ );
379
+ return response;
380
+ } catch (err) {
381
+ console.error("Error in service deletion WebSocket request:", err);
382
+ throw err;
383
+ }
384
+ };
385
+ export const updateService = async (
386
+ data: Service,
387
+ token: string,
388
+ scale?: number
389
+ ) => {
390
+ try {
391
+ await initializeGlobalWebSocketClient(token);
392
+ // await unlinkServices(data, token);
393
+ // const newLinks: Link[] = data.links.filter((link) => !link.delete);
394
+ // const newLinksToCreate = newLinks.filter((link) => link.delete === false);
395
+ // console.log("DEBUG - newLinks", newLinksToCreate);
396
+ // if (newLinksToCreate.length > 0) {
397
+ // const serviceWithNewLinks: Service = {
398
+ // ...data,
399
+ // links: newLinksToCreate,
400
+ // };
401
+ // pendingLinks.set(data.name, newLinksToCreate);
402
+ // await linkPendingServices(serviceWithNewLinks, token);
403
+ // }
404
+
405
+ const parameterObject: Record<string, any> = {};
406
+ if (data.parameters && data.parameters.length > 0) {
407
+ data.parameters.forEach((param) => {
408
+ const key = param.configKey || param.name;
409
+ const paramType = param.type?.toLowerCase();
410
+ if (paramType === "number" || paramType === "integer") {
411
+ parameterObject[key] = Number(param.value) || 0;
412
+ } else if (paramType === "boolean") {
413
+ parameterObject[key] = param.value === "true";
414
+ } else {
415
+ parameterObject[key] = param.value;
416
+ }
417
+ });
418
+ }
419
+
420
+ const resourceObject: Record<string, any> = {};
421
+ if (data.resources && data.resources.length > 0) {
422
+ data.resources.forEach((resource) => {
423
+ if (resource.type === "volume") {
424
+ resourceObject[resource.name] = {
425
+ volume: {
426
+ kind: "storage",
427
+ size: parseInt(resource.value) || 1,
428
+ unit: "G",
429
+ type: resource.kind,
430
+ },
431
+ };
432
+ } else if (resource.type === "secret") {
433
+ resourceObject[resource.name] = {
434
+ secret: `${data.tenant}/${resource.value}`,
435
+ };
436
+ } else if (resource.type === "domain") {
437
+ resourceObject[resource.name] = {
438
+ domain: `${data.tenant}/${resource.value}`,
439
+ };
440
+ } else if (resource.type === "ca") {
441
+ resourceObject[resource.name] = {
442
+ ca: `${data.tenant}/${resource.value}`,
443
+ };
444
+ } else if (resource.type === "certificate") {
445
+ resourceObject[resource.name] = {
446
+ certificate: `${data.tenant}/${resource.value}`,
447
+ };
448
+ } else if (resource.type === "port") {
449
+ resourceObject[resource.name] = {
450
+ port: `${data.tenant}/${resource.value}`,
451
+ };
452
+ }
453
+ });
454
+ }
455
+
456
+ let previousRevision = 1;
457
+ if (data.currentRevision) {
458
+ previousRevision = parseInt(data.currentRevision.toString(), 10);
459
+ if (isNaN(previousRevision)) {
460
+ console.warn("currentRevision is not a valid number, using 1");
461
+ previousRevision = 1;
462
+ }
463
+ }
464
+ else{
465
+ previousRevision = getLatestRevision(data.revisions) || 1;
466
+ }
467
+
468
+ const scaleConfig: any = {};
469
+ if (data.role && data.role.length > 0) {
470
+ scaleConfig.detail = {};
471
+ data.role.forEach((role) => {
472
+ scaleConfig.detail[role.name] = {
473
+ hsize: role.hsize || scale || data.minReplicas || 1,
474
+ };
475
+ });
476
+ } else {
477
+ scaleConfig.hsize = scale || data.minReplicas || 1;
478
+ }
479
+ const meta = {
480
+ scaling: {
481
+ simple:
482
+ data.role?.reduce((acc, role) => {
483
+ if (role.scalling && role.name) {
484
+ acc[role.name] = {
485
+ scale_up: {
486
+ cpu: Math.min(parseInt(role.scalling.cpu.up) || 0, 100),
487
+ memory: Math.min(parseInt(role.scalling.memory.up) || 0, 100),
488
+ },
489
+ scale_down: {
490
+ cpu: Math.min(parseInt(role.scalling.cpu.down) || 0, 100),
491
+ memory: Math.min(
492
+ parseInt(role.scalling.memory.down) || 0,
493
+ 100
494
+ ),
495
+ },
496
+ hysteresis: parseInt(role.scalling.histeresys) || 0,
497
+ min_replicas: role.scalling.instances.min || 0,
498
+ max_replicas: role.scalling.instances.max || 0,
499
+ };
500
+ }
501
+ return acc;
502
+ }, {} as Record<string, any>) || {},
503
+ },
504
+ };
505
+
506
+ const updateBody = {
507
+ spec: {
508
+ type: "update-config",
509
+ comment: "Service configuration update",
510
+ config: {
511
+ parameter: parameterObject,
512
+ resource: resourceObject,
513
+ resilience: 0,
514
+ scale: scaleConfig,
515
+ },
516
+ meta: meta,
517
+ },
518
+ tenant: data.tenant,
519
+ service: data.name,
520
+ previous: previousRevision,
521
+ };
522
+
523
+ const response = await makeGlobalWebSocketRequest(
524
+ "revision:update_revision",
525
+ updateBody,
526
+ 30000,
527
+ "UPDATE_CONFIG",
528
+ data.name
529
+ );
530
+
531
+ const updatedService: Service = {
532
+ ...data,
533
+ status: {
534
+ code: "PENDING",
535
+ message: "",
536
+ timestamp: "",
537
+ args: [],
538
+ },
539
+ };
540
+ eventHelper.service.publish.updated(updatedService);
541
+
542
+ const updateNotification: Notification = {
543
+ type: "success",
544
+ subtype: "service-updated",
545
+ date: Date.now().toString(),
546
+ status: "unread",
547
+ callToAction: false,
548
+ data: {
549
+ service: data.name,
550
+ tenant: data.tenant,
551
+ },
552
+ };
553
+
554
+ eventHelper.notification.publish.creation(updateNotification);
555
+ return response;
556
+ } catch (err) {
557
+ console.error("Error updating service configuration via WebSocket:", err);
558
+ const notification: Notification = {
559
+ type: "error",
560
+ subtype: "service-update-error",
561
+ date: Date.now().toString(),
562
+ info_content: {
563
+ code: (err as any).error.code,
564
+ message: (err as any).error.content,
565
+ },
566
+ status: "unread",
567
+ callToAction: false,
568
+ data: {
569
+ service: data.name,
570
+ tenant: data.tenant,
571
+ },
572
+ };
573
+ eventHelper.notification.publish.creation(notification);
574
+ eventHelper.service.publish.updateError(data);
575
+ throw err;
576
+ }
577
+ };
578
+ export const unlinkServices = async (service: Service, token: string) => {
579
+ const deleteLinks: Link[] = service.links.filter(
580
+ (link) => link.delete === true
581
+ );
582
+ if (deleteLinks.length > 0) {
583
+ await Promise.all(
584
+ deleteLinks.map(async (link) => {
585
+ try {
586
+ await initializeGlobalWebSocketClient(token);
587
+ const status = getWebSocketStatus();
588
+ const unlinkBody = generateLinkBody(service, link);
589
+
590
+ const unlinkResponse = await makeGlobalWebSocketRequest(
591
+ "link:unlink_service",
592
+ unlinkBody,
593
+ 30000,
594
+ "UNLINK",
595
+ service.name
596
+ );
597
+
598
+ const unlinkNotification: Notification = {
599
+ type: "success",
600
+ subtype: "service-unlinking",
601
+ date: Date.now().toString(),
602
+ status: "unread",
603
+ callToAction: false,
604
+ data: {
605
+ service: service.name,
606
+ tenant: service.tenant,
607
+ },
608
+ };
609
+
610
+ eventHelper.notification.publish.creation(unlinkNotification);
611
+ } catch (unlinkErr) {
612
+ console.error("Error unlinking service via WebSocket:", unlinkErr);
613
+
614
+ const notification: Notification = {
615
+ type: "error",
616
+ subtype: "service-unlink-error",
617
+ date: Date.now().toString(),
618
+ info_content: {
619
+ code: (unlinkErr as any).error.code,
620
+ message: (unlinkErr as any).error.content,
621
+ },
622
+ status: "unread",
623
+ callToAction: false,
624
+ data: {
625
+ service: service.name,
626
+ tenant: service.tenant,
627
+ },
628
+ };
629
+ eventHelper.notification.publish.creation(notification);
630
+ }
631
+ })
632
+ );
633
+ }
634
+ };
635
+ export const updateServiceLinks = async (link: Link, token: string) => {
636
+ const deleteLink = link.delete || false;
637
+ const originClient = link.client === link.originChannel;
638
+ const linkBody = {
639
+ client_tenant: link.tenant,
640
+ client_service: originClient ? link.origin : link.target,
641
+ client_channel: originClient ? link.originChannel : link.targetChannel,
642
+ server_tenant: link.tenant,
643
+ server_service: originClient ? link.target : link.origin,
644
+ server_channel: originClient ? link.targetChannel : link.originChannel,
645
+ };
646
+ if (deleteLink) {
647
+ try {
648
+ await initializeGlobalWebSocketClient(token);
649
+ const status = getWebSocketStatus();
650
+
651
+ const unlinkResponse = await makeGlobalWebSocketRequest(
652
+ "link:unlink_service",
653
+ linkBody,
654
+ 30000,
655
+ "UNLINK",
656
+ link.origin
657
+ );
658
+
659
+ // const unlinkNotification: Notification = {
660
+ // type: "success",
661
+ // subtype: "service-unlinked",
662
+ // date: Date.now().toString(),
663
+ // status: "unread",
664
+ // callToAction: false,
665
+ // data: {
666
+ // service: link.origin,
667
+ // tenant: link.tenant,
668
+ // },
669
+ // };
670
+
671
+ // eventHelper.notification.publish.creation(unlinkNotification);
672
+ } catch (unlinkErr) {
673
+ console.error("Error unlinking service via WebSocket:", unlinkErr);
674
+
675
+ const notification: Notification = {
676
+ type: "error",
677
+ subtype: "service-unlink-error",
678
+ date: Date.now().toString(),
679
+ info_content: {
680
+ code: (unlinkErr as any).error.code,
681
+ message: (unlinkErr as any).error.content,
682
+ },
683
+ status: "unread",
684
+ callToAction: false,
685
+ data: {
686
+ service: link.origin,
687
+ tenant: link.tenant,
688
+ },
689
+ };
690
+ eventHelper.notification.publish.creation(notification);
691
+ }
692
+ } else {
693
+ try {
694
+ await initializeGlobalWebSocketClient(token);
695
+ const status = getWebSocketStatus();
696
+
697
+ const linkResponse = await makeGlobalWebSocketRequest(
698
+ "link:link_service",
699
+ linkBody,
700
+ 30000,
701
+ "LINK",
702
+ link.origin
703
+ );
704
+
705
+ const notification: Notification = {
706
+ type: "success",
707
+ subtype: "service-linked",
708
+ date: Date.now().toString(),
709
+ status: "unread",
710
+ callToAction: false,
711
+ data: {
712
+ service: link.origin,
713
+ tenant: link.tenant,
714
+ },
715
+ };
716
+
717
+ eventHelper.notification.publish.creation(notification);
718
+ } catch (linkErr) {
719
+ const notification: Notification = {
720
+ type: "error",
721
+ subtype: "error-service-link",
722
+ date: Date.now().toString(),
723
+ info_content: {
724
+ code: (linkErr as any).error.code,
725
+ message: (linkErr as any).error.content,
726
+ },
727
+ status: "unread",
728
+ callToAction: false,
729
+ data: {
730
+ service: link.origin,
731
+ tenant: link.tenant,
732
+ },
733
+ };
734
+ eventHelper.notification.publish.creation(notification);
735
+ }
736
+ }
737
+ };
738
+ export const changeRevision = async (data: Service, token: string) => {
739
+ try {
740
+ await initializeGlobalWebSocketClient(token);
741
+ const status = getWebSocketStatus();
742
+ const revisionBody = {
743
+ tenant: data.tenant,
744
+ service: data.name,
745
+ previous: Number(data.lastDeployed),
746
+ spec: {
747
+ type: "rollback",
748
+ labels: {},
749
+ target: Number(data.currentRevision),
750
+ comment: "",
751
+ },
752
+ };
753
+ const revisionResponse = await makeGlobalWebSocketRequest(
754
+ "revision:update_revision",
755
+ revisionBody,
756
+ 30000,
757
+ "UPDATE_REVISION",
758
+ data.name
759
+ );
760
+ const notification: Notification = {
761
+ type: "success",
762
+ subtype: "revision-updated",
763
+ date: Date.now().toString(),
764
+ status: "unread",
765
+ callToAction: false,
766
+ data: {
767
+ service: data,
768
+ },
769
+ };
770
+
771
+ eventHelper.notification.publish.creation(notification);
772
+ } catch (error) {
773
+ const notification: Notification = {
774
+ type: "error",
775
+ subtype: "error-revision-update",
776
+ date: Date.now().toString(),
777
+ info_content: {
778
+ code: (error as any).error.code,
779
+ message: (error as any).error.content,
780
+ },
781
+ status: "unread",
782
+ callToAction: false,
783
+ data: {
784
+ service: data,
785
+ },
786
+ };
787
+ eventHelper.notification.publish.creation(notification);
788
+ }
789
+ };
790
+ function getLatestRevision(revisions: string[]): number | null {
791
+ if (!revisions || revisions.length === 0) {
792
+ return null;
793
+ }
794
+
795
+ return Math.max(...revisions.map(Number));
796
+ }