underpost 2.89.1 → 2.89.21

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/src/cli/run.js CHANGED
@@ -42,6 +42,9 @@ class UnderpostRun {
42
42
  * @type {Object}
43
43
  * @property {boolean} dev - Whether to run in development mode.
44
44
  * @property {string} podName - The name of the pod to run.
45
+ * @property {string} nodeName - The name of the node to run.
46
+ * @property {number} port - Custom port to use.
47
+ * @property {boolean} etcHosts - Whether to modify /etc/hosts.
45
48
  * @property {string} volumeHostPath - The host path for the volume.
46
49
  * @property {string} volumeMountPath - The mount path for the volume.
47
50
  * @property {string} imageName - The name of the image to run.
@@ -57,14 +60,22 @@ class UnderpostRun {
57
60
  * @property {string} tty - The TTY option for the container.
58
61
  * @property {string} stdin - The stdin option for the container.
59
62
  * @property {string} restartPolicy - The restart policy for the container.
63
+ * @property {string} runtimeClassName - The runtime class name for the container.
64
+ * @property {string} imagePullPolicy - The image pull policy for the container.
65
+ * @property {string} apiVersion - The API version for the container.
66
+ * @property {string} claimName - The claim name for the volume.
67
+ * @property {string} kind - The kind of resource to create.
60
68
  * @property {boolean} terminal - Whether to open a terminal.
61
69
  * @property {number} devProxyPortOffset - The port offset for the development proxy.
62
70
  * @property {string} confServerPath - The configuration server path.
71
+ * @property {string} underpostRoot - The root path of the Underpost installation.
63
72
  * @memberof UnderpostRun
64
73
  */
65
74
  static DEFAULT_OPTION = {
66
75
  dev: false,
67
76
  podName: '',
77
+ nodeName: '',
78
+ port: 0,
68
79
  volumeHostPath: '',
69
80
  volumeMountPath: '',
70
81
  imageName: '',
@@ -80,9 +91,15 @@ class UnderpostRun {
80
91
  tty: '',
81
92
  stdin: '',
82
93
  restartPolicy: '',
94
+ runtimeClassName: '',
95
+ imagePullPolicy: '',
96
+ apiVersion: '',
97
+ claimName: '',
98
+ kind: '',
83
99
  terminal: false,
84
100
  devProxyPortOffset: 0,
85
101
  confServerPath: '',
102
+ underpostRoot: '',
86
103
  };
87
104
  /**
88
105
  * @static
@@ -216,13 +233,66 @@ class UnderpostRun {
216
233
  );
217
234
  shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --valkey --pull-image`);
218
235
  }
219
- shellExec(`${baseCommand} deploy --expose mongo`, { async: true });
220
- shellExec(`${baseCommand} deploy --expose valkey`, { async: true });
236
+ shellExec(`${baseCommand} deploy --expose --disable-update-underpost-config mongo`, { async: true });
237
+ shellExec(`${baseCommand} deploy --expose --disable-update-underpost-config valkey`, { async: true });
221
238
  {
222
239
  const hostListenResult = UnderpostDeploy.API.etcHostFactory(mongoHosts);
223
240
  logger.info(hostListenResult.renderHosts);
224
241
  }
225
242
  },
243
+
244
+ /**
245
+ * @method metadata
246
+ * @description Generates metadata for the specified path after exposing the development cluster.
247
+ * @param {string} path - The input value, identifier, or path for the operation.
248
+ * @param {Object} options - The default underpost runner options for customizing workflow
249
+ * @memberof UnderpostRun
250
+ */
251
+ metadata: async (path, options = UnderpostRun.DEFAULT_OPTION) => {
252
+ const ports = '6379,27017';
253
+ shellExec(`node bin run kill '${ports}'`);
254
+ shellExec(`node bin run dev-cluster --dev expose`, { async: true });
255
+ console.log('Loading fordward services...');
256
+ await timer(5000);
257
+ shellExec(`node bin metadata --generate ${path}`);
258
+ shellExec(`node bin run kill '${ports}'`);
259
+ },
260
+
261
+ /**
262
+ * @method svc-ls
263
+ * @description Lists systemd services and installed packages, optionally filtering by the provided path.
264
+ * @param {string} path - The input value, identifier, or path for the operation (used as the optional filter for services and packages).
265
+ * @param {Object} options - The default underpost runner options for customizing workflow
266
+ * @memberof UnderpostRun
267
+ */
268
+ 'svc-ls': (path, options = UnderpostRun.DEFAULT_OPTION) => {
269
+ const log = shellExec(`systemctl list-units --type=service${path ? ` | grep ${path}` : ''}`, {
270
+ silent: true,
271
+ stdout: true,
272
+ });
273
+ console.log(path ? log.replaceAll(path, path.red) : log);
274
+ const log0 = shellExec(`sudo dnf list installed${path ? ` | grep ${path}` : ''}`, {
275
+ silent: true,
276
+ stdout: true,
277
+ });
278
+ console.log(path ? log0.replaceAll(path, path.red) : log0);
279
+ },
280
+
281
+ /**
282
+ * @method svc-rm
283
+ * @description Removes a systemd service by stopping it, disabling it, uninstalling the package, and deleting related files.
284
+ * @param {string} path - The input value, identifier, or path for the operation (used as the service name).
285
+ * @param {Object} options - The default underpost runner options for customizing workflow
286
+ * @memberof UnderpostRun
287
+ */
288
+ 'svc-rm': (path, options = UnderpostRun.DEFAULT_OPTION) => {
289
+ shellExec(`sudo systemctl stop ${path}`);
290
+ shellExec(`sudo systemctl disable --now ${path}`);
291
+ shellExec(`sudo dnf remove -y ${path}*`);
292
+ shellExec(`sudo rm -f /usr/lib/systemd/system/${path}.service`);
293
+ shellExec(`sudo rm -f /etc/yum.repos.d/${path}*.repo`);
294
+ },
295
+
226
296
  /**
227
297
  * @method ssh-cluster-info
228
298
  * @description Executes the `ssh-cluster-info.sh` script to display cluster connection information.
@@ -498,7 +568,12 @@ class UnderpostRun {
498
568
  * @memberof UnderpostRun
499
569
  */
500
570
  'ls-images': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
501
- console.table(UnderpostDeploy.API.getCurrentLoadedImages('kind-worker', false));
571
+ console.table(
572
+ UnderpostDeploy.API.getCurrentLoadedImages(
573
+ options.nodeName ? options.nodeName : 'kind-worker',
574
+ path === 'spec' ? true : false,
575
+ ),
576
+ );
502
577
  },
503
578
 
504
579
  /**
@@ -515,39 +590,51 @@ class UnderpostRun {
515
590
  },
516
591
 
517
592
  /**
518
- * @method dev-container
519
- * @description Runs a development container pod named `underpost-dev-container` with specified volume mounts and opens a terminal to follow its logs.
520
- * @param {string} path - The input value, identifier, or path for the operation (used as an optional command to run inside the container).
593
+ * @method dd-container
594
+ * @description Deploys a development or debug container tasks jobs, setting up necessary volumes and images, and running specified commands within the container.
595
+ * @param {string} path - The input value, identifier, or path for the operation (used as the command to run inside the container).
521
596
  * @param {Object} options - The default underpost runner options for customizing workflow
522
597
  * @memberof UnderpostRun
523
598
  */
524
- 'dev-container': async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
525
- options.dev = true;
599
+ 'dd-container': async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
526
600
  const baseCommand = options.dev ? 'node bin' : 'underpost';
527
601
  const baseClusterCommand = options.dev ? ' --dev' : '';
528
- const currentImage = UnderpostDeploy.API.getCurrentLoadedImages('kind-worker', false).find((o) =>
529
- o.IMAGE.match('underpost'),
530
- );
602
+ const currentImage = UnderpostDeploy.API.getCurrentLoadedImages(
603
+ options.nodeName ? options.nodeName : 'kind-worker',
604
+ false,
605
+ ).find((o) => o.IMAGE.match('underpost'));
531
606
  const podName = `underpost-dev-container`;
532
- if (!UnderpostDeploy.API.existsContainerFile({ podName: 'kind-worker', path: '/home/dd/engine' })) {
607
+ if (!options.nodeName) {
608
+ shellExec(`docker exec -i kind-worker bash -c "rm -rf /home/dd"`);
533
609
  shellExec(`docker exec -i kind-worker bash -c "mkdir -p /home/dd"`);
534
610
  shellExec(`docker cp /home/dd/engine kind-worker:/home/dd/engine`);
535
611
  shellExec(`docker exec -i kind-worker bash -c "chown -R 1000:1000 /home/dd || true; chmod -R 755 /home/dd"`);
612
+ } else {
613
+ shellExec(`kubectl apply -f ${options.underpostRoot}/manifests/pv-pvc-dd.yaml`);
536
614
  }
537
- if (!currentImage) shellExec(`${baseCommand} dockerfile-pull-base-images${baseClusterCommand} --kind-load`);
615
+ if (!currentImage)
616
+ shellExec(
617
+ `${baseCommand} dockerfile-pull-base-images${baseClusterCommand} ${options.dev ? '--kind-load' : '--kubeadm-load'}`,
618
+ );
538
619
  // shellExec(`kubectl delete pod ${podName} --ignore-not-found`);
539
620
  await UnderpostRun.RUNNERS['deploy-job']('', {
540
- dev: true,
621
+ dev: options.dev,
541
622
  podName,
542
- imageName: currentImage ? currentImage.image : `localhost/rockylinux9-underpost:${Underpost.version}`,
543
- volumeHostPath: '/home/dd',
623
+ imageName: currentImage
624
+ ? currentImage.image
625
+ ? currentImage.image
626
+ : currentImage.IMAGE
627
+ ? `${currentImage.IMAGE}:${currentImage.TAG}`
628
+ : `localhost/rockylinux9-underpost:${Underpost.version}`
629
+ : `localhost/rockylinux9-underpost:${Underpost.version}`,
544
630
  volumeMountPath: '/home/dd',
631
+ ...(options.dev ? { volumeHostPath: '/home/dd' } : { claimName: 'pvc-dd' }),
545
632
  on: {
546
633
  init: async () => {
547
634
  // openTerminal(`kubectl logs -f ${podName}`);
548
635
  },
549
636
  },
550
- args: [daemonProcess(path ? path : `cd /home/dd/engine && npm run test`)],
637
+ args: [daemonProcess(path ? path : `cd /home/dd/engine && npm install && npm run test`)],
551
638
  });
552
639
  },
553
640
 
@@ -883,52 +970,84 @@ class UnderpostRun {
883
970
  service: async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
884
971
  const env = options.dev ? 'development' : 'production';
885
972
  const baseCommand = options.dev ? 'node bin' : 'underpost';
886
- // const baseClusterCommand = options.dev ? ' --dev' : '';
973
+ const baseClusterCommand = options.dev ? ' --dev' : '';
887
974
  shellCd(`/home/dd/engine`);
888
975
  let [deployId, serviceId, host, _path, replicas, image, node] = path.split(',');
976
+ // const confClient = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.client.json`, 'utf8'));
977
+ const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
978
+ // const confSSR = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.ssr.json`, 'utf8'));
979
+ // const packageData = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/package.json`, 'utf8'));
889
980
  const services = fs.existsSync(`./engine-private/deploy/${deployId}/conf.services.json`)
890
981
  ? JSON.parse(fs.readFileSync(`./engine-private/deploy/${deployId}/conf.services.json`, 'utf8'))
891
982
  : [];
983
+ let serviceData = services.findIndex((s) => s.serviceId === serviceId);
984
+ const payload = {
985
+ serviceId,
986
+ path: _path,
987
+ port: options.port,
988
+ host,
989
+ };
990
+ let podToMonitor;
991
+ if (!payload.port)
992
+ switch (serviceId) {
993
+ case 'mongo-express-service': {
994
+ payload.port = 8081;
995
+ break;
996
+ }
997
+ case 'grafana': {
998
+ payload.port = 3000;
999
+ // payload.pathRewritePolicy = [
1000
+ // {
1001
+ // prefix: '/grafana',
1002
+ // replacement: '/',
1003
+ // },
1004
+ // ];
1005
+ break;
1006
+ }
1007
+ }
1008
+ if (serviceData == -1) {
1009
+ services.push(payload);
1010
+ } else {
1011
+ services[serviceData] = payload;
1012
+ }
1013
+ fs.writeFileSync(
1014
+ `./engine-private/conf/${deployId}/conf.services.json`,
1015
+ JSON.stringify(services, null, 4),
1016
+ 'utf8',
1017
+ );
892
1018
  switch (serviceId) {
893
1019
  case 'mongo-express-service': {
894
- let serviceData = services.findIndex((s) => s.serviceId === serviceId);
895
- const payload = {
896
- serviceId,
897
- path: _path,
898
- port: 8081,
899
- host,
900
- };
901
- if (serviceData == -1) {
902
- services.push(payload);
903
- } else {
904
- services[serviceData] = payload;
905
- }
906
- fs.writeFileSync(
907
- `./engine-private/conf/${deployId}/conf.services.json`,
908
- JSON.stringify(services, null, 4),
909
- 'utf8',
910
- );
911
1020
  shellExec(`kubectl delete svc mongo-express-service --ignore-not-found`);
912
1021
  shellExec(`kubectl delete deployment mongo-express --ignore-not-found`);
913
1022
  shellExec(`kubectl apply -f manifests/deployment/mongo-express/deployment.yaml`);
914
-
915
- const success = await UnderpostTest.API.statusMonitor('mongo-express');
916
-
917
- if (success) {
918
- const versions = UnderpostDeploy.API.getCurrentTraffic(deployId) || 'blue';
919
- if (!node) node = os.hostname();
920
- shellExec(
921
- `${baseCommand} deploy --kubeadm --build-manifest --sync --info-router --replicas ${
922
- replicas ? replicas : 1
923
- } --node ${node}${image ? ` --image ${image}` : ''}${versions ? ` --versions ${versions}` : ''} dd ${env}`,
924
- );
925
- shellExec(
926
- `${baseCommand} deploy --kubeadm --disable-update-deployment ${deployId} ${env} --versions ${versions}`,
927
- );
928
- } else logger.error('Mongo Express deployment failed');
1023
+ podToMonitor = 'mongo-express';
1024
+ break;
1025
+ }
1026
+ case 'grafana': {
1027
+ shellExec(
1028
+ `node bin cluster${baseClusterCommand} --grafana --hosts '${host}' --prom '${Object.keys(confServer)}'`,
1029
+ );
1030
+ podToMonitor = 'grafana';
929
1031
  break;
930
1032
  }
931
1033
  }
1034
+ const success = await UnderpostTest.API.statusMonitor(podToMonitor);
1035
+ if (success) {
1036
+ const versions = UnderpostDeploy.API.getCurrentTraffic(deployId) || 'blue';
1037
+ if (!node) node = os.hostname();
1038
+ shellExec(
1039
+ `${baseCommand} deploy${options.dev ? '' : ' --kubeadm'}${options.devProxyPortOffset ? ' --disable-deployment-proxy' : ''} --build-manifest --sync --info-router --replicas ${
1040
+ replicas ? replicas : 1
1041
+ } --node ${node}${image ? ` --image ${image}` : ''}${versions ? ` --versions ${versions}` : ''} dd ${env}`,
1042
+ );
1043
+ shellExec(
1044
+ `${baseCommand} deploy${options.dev ? '' : ' --kubeadm'}${options.devProxyPortOffset ? ' --disable-deployment-proxy' : ''} --disable-update-deployment ${deployId} ${env} --versions ${versions}`,
1045
+ );
1046
+ } else logger.error('Mongo Express deployment failed');
1047
+ if (options.etcHosts === true) {
1048
+ const hostListenResult = UnderpostDeploy.API.etcHostFactory([host]);
1049
+ logger.info(hostListenResult.renderHosts);
1050
+ }
932
1051
  },
933
1052
 
934
1053
  /**
@@ -1058,28 +1177,34 @@ class UnderpostRun {
1058
1177
  'deploy-job': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
1059
1178
  const podName = options.podName || 'deploy-job';
1060
1179
  const volumeName = `${podName}-volume`;
1061
- const args = (options.args ? options.args : path ? [`python ${path}`] : []).filter((c) => c.trim());
1180
+ if (typeof options.args === 'string') options.args = options.args.split(',');
1181
+ const args = (options.args ? options.args : path ? [path] : [`python ${path}`]).filter((c) => c.trim());
1062
1182
  const imageName = options.imageName || 'nvcr.io/nvidia/tensorflow:24.04-tf2-py3';
1063
1183
  const containerName = options.containerName || `${podName}-container`;
1064
1184
  const gpuEnable = imageName.match('nvidia');
1065
- const runtimeClassName = gpuEnable ? 'nvidia' : '';
1185
+ const runtimeClassName = options.runtimeClassName ? options.runtimeClassName : gpuEnable ? 'nvidia' : '';
1066
1186
  const namespace = options.namespace || 'default';
1067
1187
  const volumeMountPath = options.volumeMountPath || path;
1068
1188
  const volumeHostPath = options.volumeHostPath || path;
1069
- const enableVolumeMount = volumeHostPath && volumeMountPath;
1189
+ const claimName = options.claimName || '';
1190
+ const enableVolumeMount = volumeMountPath && (volumeHostPath || claimName);
1070
1191
  const tty = options.tty ? 'true' : 'false';
1071
1192
  const stdin = options.stdin ? 'true' : 'false';
1072
1193
  const restartPolicy = options.restartPolicy || 'Never';
1073
-
1194
+ const kind = options.kind || 'Pod';
1195
+ const imagePullPolicy = options.imagePullPolicy || 'IfNotPresent';
1196
+ const apiVersion = options.apiVersion || 'v1';
1074
1197
  if (options.volumeType === 'dev') options.volumeType = 'FileOrCreate';
1075
1198
  const volumeType =
1076
- options.volumeType || (enableVolumeMount && fs.statSync(volumeHostPath).isDirectory()) ? 'Directory' : 'File';
1199
+ options.volumeType || (enableVolumeMount && volumeHostPath && fs.statSync(volumeHostPath).isDirectory())
1200
+ ? 'Directory'
1201
+ : 'File';
1077
1202
 
1078
1203
  const envs = UnderpostRootEnv.API.list();
1079
1204
 
1080
1205
  const cmd = `kubectl apply -f - <<EOF
1081
- apiVersion: v1
1082
- kind: Pod
1206
+ apiVersion: ${apiVersion}
1207
+ kind: ${kind}
1083
1208
  metadata:
1084
1209
  name: ${podName}
1085
1210
  namespace: ${namespace}
@@ -1091,7 +1216,7 @@ ${runtimeClassName ? ` runtimeClassName: ${runtimeClassName}` : ''}
1091
1216
  containers:
1092
1217
  - name: ${containerName}
1093
1218
  image: ${imageName}
1094
- imagePullPolicy: IfNotPresent
1219
+ imagePullPolicy: ${imagePullPolicy}
1095
1220
  tty: ${tty}
1096
1221
  stdin: ${stdin}
1097
1222
  command: ${JSON.stringify(options.command ? options.command : ['/bin/bash', '-c'])}
@@ -1117,15 +1242,7 @@ ${Object.keys(envs)
1117
1242
  .join('\n')}`}
1118
1243
  ${
1119
1244
  enableVolumeMount
1120
- ? `
1121
- volumeMounts:
1122
- - name: ${volumeName}
1123
- mountPath: ${volumeMountPath}
1124
- volumes:
1125
- - name: ${volumeName}
1126
- hostPath:
1127
- path: ${volumeHostPath}
1128
- type: ${volumeType}`
1245
+ ? UnderpostDeploy.API.volumeFactory([{ volumeMountPath, volumeName, volumeHostPath, volumeType, claimName }]).render
1129
1246
  : ''
1130
1247
  }
1131
1248
  EOF`;
@@ -1156,7 +1273,7 @@ EOF`;
1156
1273
  const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
1157
1274
  if (options.command) options.command = options.command.split(',');
1158
1275
  if (options.args) options.args = options.args.split(',');
1159
- options.underpostRoot = underpostRoot;
1276
+ if (!options.underpostRoot) options.underpostRoot = underpostRoot;
1160
1277
  options.npmRoot = npmRoot;
1161
1278
  logger.info('callback', { path, options });
1162
1279
  if (!(runner in UnderpostRun.RUNNERS)) throw new Error(`Runner not found: ${runner}`);
@@ -940,12 +940,13 @@ const ObjectLayerEngineModal = {
940
940
  const queryParams = getQueryParams();
941
941
  queryParams.page = 1;
942
942
  setQueryParams(queryParams);
943
- const managerComponent = DefaultManagement.Tokens['modal-object-layer-engine-management'];
943
+ const modalId = 'modal-object-layer-engine-management';
944
+ const managerComponent = DefaultManagement.Tokens[modalId];
944
945
  if (managerComponent) {
945
946
  managerComponent.page = 1;
946
947
  if (!managerComponent.readyRowDataEvent) managerComponent.readyRowDataEvent = {};
947
948
  let readyLoad = false;
948
- const gridId = 'object-layer-engine-management-grid-modal-object-layer-engine-management';
949
+ const gridId = `object-layer-engine-management-grid-${modalId}`;
949
950
  managerComponent.readyRowDataEvent['object-layer-engine-management'] = async () => {
950
951
  if (readyLoad) {
951
952
  AgGrid.grids[gridId].setGridOption('getRowClass', null);
@@ -961,7 +962,7 @@ const ObjectLayerEngineModal = {
961
962
  };
962
963
  }
963
964
 
964
- const _s = s(`.management-table-btn-reload-modal-object-layer-engine-management`);
965
+ const _s = s(`.management-table-btn-reload-${modalId}`);
965
966
  if (_s) _s.click();
966
967
 
967
968
  s(`.main-btn-object-layer-engine-management`).click();
@@ -1,11 +1,12 @@
1
1
  import { loggerFactory } from './Logger.js';
2
- import { getProxyPath, listenQueryPathInstance } from './Router.js';
2
+ import { getProxyPath, listenQueryPathInstance, setPath, setQueryParams } from './Router.js';
3
3
  import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
4
4
  import { NotificationManager } from './NotificationManager.js';
5
5
  import { htmls, s } from './VanillaJs.js';
6
6
  import { BtnIcon } from './BtnIcon.js';
7
7
  import { darkTheme, ThemeEvents } from './Css.js';
8
- import { ObjectLayerCyberiaPortal } from '../cyberia-portal/ObjectLayerCyberiaPortal.js';
8
+ import { ObjectLayerManagement } from '../../services/object-layer/object-layer.management.js';
9
+ import { ObjectLayerEngineModal } from './ObjectLayerEngineModal.js';
9
10
 
10
11
  const logger = loggerFactory(import.meta);
11
12
 
@@ -66,7 +67,7 @@ const ObjectLayerEngineViewer = {
66
67
  if (cid) {
67
68
  await this.loadObjectLayer(cid);
68
69
  } else {
69
- this.renderEmpty();
70
+ this.renderEmpty({ Elements });
70
71
  }
71
72
  },
72
73
  },
@@ -103,9 +104,15 @@ const ObjectLayerEngineViewer = {
103
104
  `;
104
105
  },
105
106
 
106
- renderEmpty: async function () {
107
+ renderEmpty: async function ({ Elements }) {
107
108
  const id = 'object-layer-engine-viewer';
108
- htmls(`#${id}`, await ObjectLayerCyberiaPortal.Render());
109
+ htmls(
110
+ `#${id}`,
111
+ await ObjectLayerManagement.RenderTable({
112
+ Elements,
113
+ idModal: 'modal-object-layer-engine-viewer',
114
+ }),
115
+ );
109
116
  },
110
117
 
111
118
  loadObjectLayer: async function (objectLayerId) {
@@ -398,6 +405,14 @@ const ObjectLayerEngineViewer = {
398
405
  transform: none;
399
406
  }
400
407
 
408
+ .edit-btn {
409
+ background: ${darkTheme ? '#4a9eff' : '#2196F3'};
410
+ }
411
+
412
+ .edit-btn:hover {
413
+ background: ${darkTheme ? '#3a8eff' : '#1186f2'};
414
+ }
415
+
401
416
  @media (max-width: 768px) {
402
417
  .gif-display-area {
403
418
  max-height: 500px;
@@ -440,7 +455,8 @@ const ObjectLayerEngineViewer = {
440
455
  .item-data-value-label {
441
456
  font-size: 20px;
442
457
  font-weight: 700;
443
- color: ${darkTheme ? '#4a9eff' : '#2196F3'};
458
+ color: ${darkTheme ? '#aaa' : '#666'};
459
+ text-align: center;
444
460
  }
445
461
  .item-stat-entry {
446
462
  display: flex;
@@ -493,6 +509,32 @@ const ObjectLayerEngineViewer = {
493
509
  </div>
494
510
  </div>
495
511
 
512
+ <!-- Stats Data Section -->
513
+ <div class="control-group" style="margin-bottom: 20px;">
514
+ <h4><i class="fa-solid fa-chart-bar"></i> Stats Data</h4>
515
+ <div
516
+ style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; padding: 10px 0;"
517
+ >
518
+ ${Object.keys(stats).length > 0
519
+ ? Object.entries(stats)
520
+ .map(([statKey, statValue]) => {
521
+ const statInfo = ObjectLayerEngineModal.statDescriptions[statKey];
522
+ if (!statInfo) return '';
523
+ return html`
524
+ <div class="item-stat-entry">
525
+ <div style="display: flex; align-items: center; gap: 8px;">
526
+ <i class="${statInfo.icon}" id="stat-icon-${statKey}-${id}"></i>
527
+ <span class="item-data-key-label">${statInfo.title}</span>
528
+ </div>
529
+ <span class="item-data-value-label">${statValue}</span>
530
+ </div>
531
+ `;
532
+ })
533
+ .join('')
534
+ : html`<div class="no-data-container">No stats data available</div>`}
535
+ </div>
536
+ </div>
537
+
496
538
  <div class="gif-display-area">
497
539
  <div class="gif-canvas-container" id="gif-canvas-container">
498
540
  <div style="text-align: center; color: ${darkTheme ? '#aaa' : '#666'};">
@@ -587,30 +629,17 @@ const ObjectLayerEngineViewer = {
587
629
  </div>
588
630
  </div>
589
631
  </div>
590
- <!-- Stats Data Section -->
591
- <div class="control-group" style="margin-bottom: 20px;">
592
- <h4><i class="fa-solid fa-chart-bar"></i> Stats Data</h4>
593
- <div
594
- style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; padding: 10px 0;"
595
- >
596
- ${Object.keys(stats).length > 0
597
- ? Object.entries(stats)
598
- .map(
599
- ([statKey, statValue]) => html`
600
- <div class="item-stat-entry">
601
- <span class="item-data-key-label"> ${statKey} </span>
602
- <span style="item-data-value-label"> ${statValue} </span>
603
- </div>
604
- `,
605
- )
606
- .join('')
607
- : html`<div class="no-data-container">No stats data available</div>`}
608
- </div>
632
+
633
+ <div style="display: flex; gap: 10px; margin-top: 20px;">
634
+ <button class="download-btn" id="download-gif-btn" style="width: 100%;">
635
+ <i class="fa-solid fa-download"></i>
636
+ <span>Download GIF</span>
637
+ </button>
638
+ <button class="download-btn edit-btn" id="edit-object-layer-btn" style="width: 100%;">
639
+ <i class="fa-solid fa-edit"></i>
640
+ <span>Edit</span>
641
+ </button>
609
642
  </div>
610
- <button class="download-btn" id="download-gif-btn">
611
- <i class="fa-solid fa-download"></i>
612
- <span>Download GIF</span>
613
- </button>
614
643
  </div>
615
644
  `,
616
645
  );
@@ -658,6 +687,13 @@ const ObjectLayerEngineViewer = {
658
687
  });
659
688
  }
660
689
 
690
+ const editBtn = s('#edit-object-layer-btn');
691
+ if (editBtn) {
692
+ editBtn.addEventListener('click', () => {
693
+ this.toEngine();
694
+ });
695
+ }
696
+
661
697
  // Back button
662
698
  setTimeout(() => {
663
699
  const backBtn = s('[data-id="btn-back"]');
@@ -1046,14 +1082,30 @@ const ObjectLayerEngineViewer = {
1046
1082
  });
1047
1083
  },
1048
1084
 
1049
- Reload: async function () {
1085
+ toEngine: function () {
1086
+ const { objectLayer } = this.Data;
1087
+ if (!objectLayer || !objectLayer._id) return;
1088
+
1089
+ // Navigate to editor route first
1090
+ setPath(`${getProxyPath()}object-layer-engine`);
1091
+ // Then add query param without replacing history
1092
+ setQueryParams({ cid: objectLayer._id }, { replace: true });
1093
+
1094
+ if (s(`.modal-object-layer-engine`)) {
1095
+ ObjectLayerEngineModal.Reload();
1096
+ } else {
1097
+ s(`.main-btn-object-layer-engine`)?.click();
1098
+ }
1099
+ },
1100
+
1101
+ Reload: async function ({ Elements }) {
1050
1102
  const queryParams = new URLSearchParams(window.location.search);
1051
1103
  const cid = queryParams.get('cid');
1052
1104
 
1053
1105
  if (cid) {
1054
1106
  await this.loadObjectLayer(cid);
1055
1107
  } else {
1056
- this.renderEmpty();
1108
+ this.renderEmpty({ Elements });
1057
1109
  }
1058
1110
  },
1059
1111
  };
package/src/index.js CHANGED
@@ -35,7 +35,7 @@ class Underpost {
35
35
  * @type {String}
36
36
  * @memberof Underpost
37
37
  */
38
- static version = 'v2.89.1';
38
+ static version = 'v2.89.21';
39
39
  /**
40
40
  * Repository cli API
41
41
  * @static
@@ -132,7 +132,8 @@ class UnderpostStartUp {
132
132
  const buildBasePath = `/home/dd`;
133
133
  const repoName = `engine-${deployId.split('-')[1]}`;
134
134
  shellExec(`cd ${buildBasePath} && underpost clone ${process.env.GITHUB_USERNAME}/${repoName}`);
135
- shellExec(`cd ${buildBasePath} && sudo mv ./${repoName} ./engine`);
135
+ shellExec(`cd ${buildBasePath} && sudo cp -a ./${repoName}/* ./engine`);
136
+ shellExec(`cd ${buildBasePath} && sudo rm -rf ./${repoName}`);
136
137
  shellExec(`cd ${buildBasePath}/engine && underpost clone ${process.env.GITHUB_USERNAME}/${repoName}-private`);
137
138
  shellExec(`cd ${buildBasePath}/engine && sudo mv ./${repoName}-private ./engine-private`);
138
139
  shellCd(`${buildBasePath}/engine`);