underpost 2.89.2 → 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/.env.development CHANGED
@@ -22,6 +22,8 @@ ROUTER=changethis
22
22
  CLOUDINARY_CLOUD_NAME=changethis
23
23
  CLOUDINARY_API_KEY=changethis
24
24
  CLOUDINARY_API_SECRET=changethis
25
+ CLOUDINARY_PASSWORD=changethis
26
+ CLOUDINARY_EMAIL=admin@default.net
25
27
  DB_PG_MAAS_NAME=changethis
26
28
  DB_PG_MAAS_PASS=changethis
27
29
  DB_PG_MAAS_USER=changethis
package/.env.production CHANGED
@@ -23,6 +23,8 @@ ROUTER=changethis
23
23
  CLOUDINARY_CLOUD_NAME=changethis
24
24
  CLOUDINARY_API_KEY=changethis
25
25
  CLOUDINARY_API_SECRET=changethis
26
+ CLOUDINARY_PASSWORD=changethis
27
+ CLOUDINARY_EMAIL=admin@default.net
26
28
  DB_PG_MAAS_NAME=changethis
27
29
  DB_PG_MAAS_PASS=changethis
28
30
  DB_PG_MAAS_USER=changethis
package/.env.test CHANGED
@@ -22,6 +22,8 @@ ROUTER=changethis
22
22
  CLOUDINARY_CLOUD_NAME=changethis
23
23
  CLOUDINARY_API_KEY=changethis
24
24
  CLOUDINARY_API_SECRET=changethis
25
+ CLOUDINARY_PASSWORD=changethis
26
+ CLOUDINARY_EMAIL=admin@default.net
25
27
  DB_PG_MAAS_NAME=changethis
26
28
  DB_PG_MAAS_PASS=changethis
27
29
  DB_PG_MAAS_USER=changethis
@@ -22,6 +22,8 @@ jobs:
22
22
  key: ${{ secrets.SSH_PRIV_KEY }}
23
23
  # Remote port (optional)
24
24
  port: ${{ secrets.SSH_PORT }}
25
+ # Optional: increase timeout for long-running commands
26
+ command_timeout: 60m
25
27
  # Optional: if your private key has a passphrase, add:
26
28
  # passphrase: ${{ secrets.SSH_KEY_PASSPHRASE }}
27
29
  # Commands to run on the remote VM
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  <!-- badges -->
20
20
 
21
- [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/2.89.2)](https://socket.dev/npm/package/underpost/overview/2.89.2) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
21
+ [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/2.89.21)](https://socket.dev/npm/package/underpost/overview/2.89.21) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
22
22
 
23
23
  <!-- end-badges -->
24
24
 
@@ -66,7 +66,7 @@ Run dev client server
66
66
  npm run dev
67
67
  ```
68
68
  <!-- -->
69
- ## underpost ci/cd cli v2.89.2
69
+ ## underpost ci/cd cli v2.89.21
70
70
 
71
71
  ### Usage: `underpost [options] [command]`
72
72
  ```
@@ -106,4 +106,3 @@ Commands:
106
106
  ```
107
107
 
108
108
  <a target="_top" href="https://github.com/underpostnet/pwa-microservices-template/blob/master/cli.md">See complete CLI Docs here.</a>
109
-
package/cli.md CHANGED
@@ -1,4 +1,4 @@
1
- ## underpost ci/cd cli v2.89.2
1
+ ## underpost ci/cd cli v2.89.21
2
2
 
3
3
  ### Usage: `underpost [options] [command]`
4
4
  ```
@@ -146,6 +146,9 @@ Options:
146
146
  --deploy-id <deploy-id> Sets the deployment configuration ID for the commit
147
147
  context.
148
148
  --cached Commit staged changes only or context.
149
+ --hashes <hashes> Comma-separated list of specific file hashes of
150
+ commits.
151
+ --extension <extension> specific file extensions of commits.
149
152
  -h, --help display help for command
150
153
 
151
154
  ```
@@ -228,58 +231,66 @@ Options:
228
231
  Manages Kubernetes clusters, defaulting to Kind cluster initialization.
229
232
 
230
233
  Arguments:
231
- pod-name Optional: Filters information by a specific pod name.
234
+ pod-name Optional: Filters information by a specific pod
235
+ name.
232
236
 
233
237
  Options:
234
- --reset Deletes all clusters and prunes all related data and
235
- caches.
236
- --mariadb Initializes the cluster with a MariaDB statefulset.
237
- --mysql Initializes the cluster with a MySQL statefulset.
238
- --mongodb Initializes the cluster with a MongoDB statefulset.
239
- --mongo-db-host <host> Set custom mongo db host
240
- --postgresql Initializes the cluster with a PostgreSQL
241
- statefulset.
242
- --mongodb4 Initializes the cluster with a MongoDB 4.4 service.
243
- --valkey Initializes the cluster with a Valkey service.
244
- --contour Initializes the cluster with Project Contour base
245
- HTTPProxy and Envoy.
246
- --cert-manager Initializes the cluster with a Let's Encrypt
247
- production ClusterIssuer.
248
- --dedicated-gpu Initializes the cluster with dedicated GPU base
249
- resources and environment settings.
250
- --info Retrieves information about all deployed Kubernetes
251
- objects.
252
- --full Initializes the cluster with all available
253
- statefulsets and services.
254
- --ns-use <ns-name> Switches the current Kubernetes context to the
255
- specified namespace.
256
- --kubeadm Initializes the cluster using kubeadm for control
257
- plane management.
258
- --grafana Initializes the cluster with a Grafana deployment.
259
- --prom [hosts] Initializes the cluster with a Prometheus Operator
260
- deployment and monitor scrap for specified hosts.
261
- --dev Initializes a development-specific cluster
262
- configuration.
263
- --list-pods Displays detailed information about all pods.
264
- --info-capacity Displays the current total machine capacity
265
- information.
266
- --info-capacity-pod Displays the current machine capacity information per
267
- pod.
268
- --pull-image Sets an optional associated image to pull during
269
- initialization.
270
- --init-host Installs necessary Kubernetes node CLI tools (e.g.,
271
- kind, kubeadm, docker, podman, helm).
272
- --uninstall-host Uninstalls all host components installed by
273
- init-host.
274
- --config Sets the base Kubernetes node configuration.
275
- --worker Sets the context for a worker node.
276
- --chown Sets the appropriate ownership for Kubernetes
277
- kubeconfig files.
278
- --k3s Initializes the cluster using K3s (Lightweight
279
- Kubernetes).
280
- --hosts <hosts> A comma-separated list of cluster hostnames or IP
281
- addresses.
282
- -h, --help display help for command
238
+ --reset Deletes all clusters and prunes all related data
239
+ and caches.
240
+ --mariadb Initializes the cluster with a MariaDB
241
+ statefulset.
242
+ --mysql Initializes the cluster with a MySQL statefulset.
243
+ --mongodb Initializes the cluster with a MongoDB
244
+ statefulset.
245
+ --mongo-db-host <host> Set custom mongo db host
246
+ --postgresql Initializes the cluster with a PostgreSQL
247
+ statefulset.
248
+ --mongodb4 Initializes the cluster with a MongoDB 4.4
249
+ service.
250
+ --valkey Initializes the cluster with a Valkey service.
251
+ --contour Initializes the cluster with Project Contour base
252
+ HTTPProxy and Envoy.
253
+ --cert-manager Initializes the cluster with a Let's Encrypt
254
+ production ClusterIssuer.
255
+ --dedicated-gpu Initializes the cluster with dedicated GPU base
256
+ resources and environment settings.
257
+ --info Retrieves information about all deployed
258
+ Kubernetes objects.
259
+ --full Initializes the cluster with all available
260
+ statefulsets and services.
261
+ --ns-use <ns-name> Switches the current Kubernetes context to the
262
+ specified namespace.
263
+ --kubeadm Initializes the cluster using kubeadm for control
264
+ plane management.
265
+ --grafana Initializes the cluster with a Grafana
266
+ deployment.
267
+ --prom [hosts] Initializes the cluster with a Prometheus
268
+ Operator deployment and monitor scrap for
269
+ specified hosts.
270
+ --dev Initializes a development-specific cluster
271
+ configuration.
272
+ --list-pods Displays detailed information about all pods.
273
+ --info-capacity Displays the current total machine capacity
274
+ information.
275
+ --info-capacity-pod Displays the current machine capacity information
276
+ per pod.
277
+ --pull-image Sets an optional associated image to pull during
278
+ initialization.
279
+ --init-host Installs necessary Kubernetes node CLI tools
280
+ (e.g., kind, kubeadm, docker, podman, helm).
281
+ --uninstall-host Uninstalls all host components installed by
282
+ init-host.
283
+ --config Sets the base Kubernetes node configuration.
284
+ --worker Sets the context for a worker node.
285
+ --chown Sets the appropriate ownership for Kubernetes
286
+ kubeconfig files.
287
+ --k3s Initializes the cluster using K3s (Lightweight
288
+ Kubernetes).
289
+ --hosts <hosts> A comma-separated list of cluster hostnames or IP
290
+ addresses.
291
+ --remove-volume-host-paths Removes specified volume host paths after
292
+ execution.
293
+ -h, --help display help for command
283
294
 
284
295
  ```
285
296
 
@@ -327,6 +338,8 @@ Options:
327
338
  --disable-update-deployment Disables updates to deployments.
328
339
  --disable-update-proxy Disables updates to proxies.
329
340
  --disable-deployment-proxy Disables proxies of deployments.
341
+ --disable-update-volume Disables updates to volume mounts during
342
+ deployment.
330
343
  --status Retrieves current network traffic data
331
344
  from resource deployments and the host
332
345
  machine network configuration.
@@ -628,7 +641,7 @@ Options:
628
641
  Runs a script from the specified path.
629
642
 
630
643
  Arguments:
631
- runner-id The runner ID to run. Options: spark-template, rmi, kill, secret, underpost-config, gpu-env, tf-gpu-test, dev-cluster, metadata, svc-ls, svc-rm, ssh-cluster-info, dev-hosts-expose, dev-hosts-restore, cluster-build, template-deploy, template-deploy-image, clean, pull, release-deploy, ssh-deploy, ide, sync, tz, cron, ls-deployments, ls-images, host-update, dev-container, monitor, db-client, git-conf, promote, metrics, cluster, deploy, dev, service, sh, log, release-cmt, sync-replica, tf-vae-test, deploy-job.
644
+ runner-id The runner ID to run. Options: spark-template, rmi, kill, secret, underpost-config, gpu-env, tf-gpu-test, dev-cluster, metadata, svc-ls, svc-rm, ssh-cluster-info, dev-hosts-expose, dev-hosts-restore, cluster-build, template-deploy, template-deploy-image, clean, pull, release-deploy, ssh-deploy, ide, sync, tz, cron, ls-deployments, ls-images, host-update, dd-container, monitor, db-client, git-conf, promote, metrics, cluster, deploy, dev, service, sh, log, release-cmt, sync-replica, tf-vae-test, deploy-job.
632
645
  path The absolute or relative directory path where the script is located.
633
646
 
634
647
  Options:
@@ -647,6 +660,14 @@ Options:
647
660
  --image-name <image-name> Optional: Specifies the image name for test execution.
648
661
  --container-name <container-name> Optional: Specifies the container name for test execution.
649
662
  --namespace <namespace> Optional: Specifies the namespace for test execution.
663
+ --tty Enables TTY for the container in deploy-job.
664
+ --stdin Keeps STDIN open for the container in deploy-job.
665
+ --restart-policy <policy> Sets the restart policy for the job in deploy-job.
666
+ --runtime-class-name <name> Sets the runtime class name for the job in deploy-job.
667
+ --image-pull-policy <policy> Sets the image pull policy for the job in deploy-job.
668
+ --api-version <version> Sets the API version for the job manifest in deploy-job.
669
+ --claim-name <name> Optional: Specifies the claim name for volume mounting in deploy-job.
670
+ --kind <kind-type> Specifies the kind of Kubernetes resource (e.g., Job, Deployment) for deploy-job.
650
671
  --kubeadm Flag to indicate Kubeadm cluster type context
651
672
  --k3s Flag to indicate K3s cluster type context
652
673
  --force Forces operation, overriding any warnings or conflicts.
@@ -655,6 +676,7 @@ Options:
655
676
  --terminal Enables terminal mode for interactive script execution.
656
677
  --dev-proxy-port-offset <port-offset> Sets a custom port offset for development proxy.
657
678
  --conf-server-path <conf-server-path> Sets a custom configuration server path.
679
+ --underpost-root <underpost-root> Sets a custom Underpost root path.
658
680
  -h, --help display help for command
659
681
 
660
682
  ```
@@ -741,4 +763,3 @@ Options:
741
763
  -h, --help display help for command
742
764
 
743
765
  ```
744
-
@@ -17,7 +17,7 @@ spec:
17
17
  spec:
18
18
  containers:
19
19
  - name: dd-default-development-blue
20
- image: localhost/rockylinux9-underpost:v2.89.2
20
+ image: localhost/rockylinux9-underpost:v2.89.21
21
21
  # resources:
22
22
  # requests:
23
23
  # memory: "124Ki"
@@ -100,7 +100,7 @@ spec:
100
100
  spec:
101
101
  containers:
102
102
  - name: dd-default-development-green
103
- image: localhost/rockylinux9-underpost:v2.89.2
103
+ image: localhost/rockylinux9-underpost:v2.89.21
104
104
  # resources:
105
105
  # requests:
106
106
  # memory: "124Ki"
@@ -17,7 +17,7 @@ spec:
17
17
  spec:
18
18
  containers:
19
19
  - name: dd-test-development-blue
20
- image: localhost/rockylinux9-underpost:v2.89.2
20
+ image: localhost/rockylinux9-underpost:v2.89.21
21
21
  # resources:
22
22
  # requests:
23
23
  # memory: "96294Ki"
@@ -33,13 +33,17 @@ spec:
33
33
  npm install -g underpost &&
34
34
  underpost secret underpost --create-from-file /etc/config/.env.development &&
35
35
  underpost start --build --run dd-test development
36
+
36
37
  volumeMounts:
37
38
  - name: config-volume
38
39
  mountPath: /etc/config
40
+
39
41
  volumes:
40
42
  - name: config-volume
41
43
  configMap:
42
44
  name: underpost-config
45
+
46
+
43
47
  ---
44
48
  apiVersion: v1
45
49
  kind: Service
@@ -104,7 +108,7 @@ spec:
104
108
  spec:
105
109
  containers:
106
110
  - name: dd-test-development-green
107
- image: localhost/rockylinux9-underpost:v2.89.2
111
+ image: localhost/rockylinux9-underpost:v2.89.21
108
112
  # resources:
109
113
  # requests:
110
114
  # memory: "96294Ki"
@@ -120,13 +124,17 @@ spec:
120
124
  npm install -g underpost &&
121
125
  underpost secret underpost --create-from-file /etc/config/.env.development &&
122
126
  underpost start --build --run dd-test development
127
+
123
128
  volumeMounts:
124
129
  - name: config-volume
125
130
  mountPath: /etc/config
131
+
126
132
  volumes:
127
133
  - name: config-volume
128
134
  configMap:
129
135
  name: underpost-config
136
+
137
+
130
138
  ---
131
139
  apiVersion: v1
132
140
  kind: Service
@@ -0,0 +1,34 @@
1
+ apiVersion: v1
2
+ kind: PersistentVolume
3
+ metadata:
4
+ name: pv-dd
5
+ spec:
6
+ capacity:
7
+ storage: 5Gi
8
+ accessModes:
9
+ - ReadWriteOnce
10
+ persistentVolumeReclaimPolicy: Retain
11
+ storageClassName: manual
12
+ hostPath:
13
+ path: /home/dd
14
+ type: DirectoryOrCreate
15
+ nodeAffinity:
16
+ required:
17
+ nodeSelectorTerms:
18
+ - matchExpressions:
19
+ - key: kubernetes.io/hostname
20
+ operator: In
21
+ values:
22
+ - localhost
23
+ ---
24
+ apiVersion: v1
25
+ kind: PersistentVolumeClaim
26
+ metadata:
27
+ name: pvc-dd
28
+ spec:
29
+ accessModes:
30
+ - ReadWriteOnce
31
+ storageClassName: manual
32
+ resources:
33
+ requests:
34
+ storage: 1Gi
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "underpost",
5
- "version": "2.89.2",
5
+ "version": "2.89.21",
6
6
  "description": "pwa api rest template",
7
7
  "scripts": {
8
8
  "start": "env-cmd -f .env.production node --max-old-space-size=8192 src/server",
@@ -58,6 +58,7 @@ class UnderpostCluster {
58
58
  * @param {boolean} [options.config=false] - Apply general host configuration (SELinux, containerd, sysctl, firewalld).
59
59
  * @param {boolean} [options.worker=false] - Configure as a worker node (for Kubeadm or K3s join).
60
60
  * @param {boolean} [options.chown=false] - Set up kubectl configuration for the current user.
61
+ * @param {boolean} [options.removeVolumeHostPaths=false] - Remove data from host paths used by Persistent Volumes.
61
62
  * @param {string} [options.hosts] - Set custom hosts entries.
62
63
  * @memberof UnderpostCluster
63
64
  */
@@ -91,6 +92,7 @@ class UnderpostCluster {
91
92
  config: false,
92
93
  worker: false,
93
94
  chown: false,
95
+ removeVolumeHostPaths: false,
94
96
  hosts: '',
95
97
  },
96
98
  ) {
@@ -153,7 +155,11 @@ class UnderpostCluster {
153
155
  }
154
156
 
155
157
  // Reset Kubernetes cluster components (Kind/Kubeadm/K3s) and container runtimes
156
- if (options.reset === true) return await UnderpostCluster.API.safeReset({ underpostRoot });
158
+ if (options.reset === true)
159
+ return await UnderpostCluster.API.safeReset({
160
+ underpostRoot,
161
+ removeVolumeHostPaths: options.removeVolumeHostPaths,
162
+ });
157
163
 
158
164
  // Check if a cluster (Kind, Kubeadm, or K3s) is already initialized
159
165
  const alreadyKubeadmCluster = UnderpostDeploy.API.get('calico-kube-controllers')[0];
@@ -585,9 +591,10 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
585
591
  * in coredns) by restoring SELinux security contexts and safely cleaning up cluster artifacts.
586
592
  * @param {object} [options] - Configuration options for the reset.
587
593
  * @param {string} [options.underpostRoot] - The root path of the underpost project.
594
+ * @param {boolean} [options.removeVolumeHostPaths=false] - Whether to remove data from host paths used by Persistent Volumes.
588
595
  * @memberof UnderpostCluster
589
596
  */
590
- async safeReset(options = { underpostRoot: '.' }) {
597
+ async safeReset(options = { underpostRoot: '.', removeVolumeHostPaths: false }) {
591
598
  logger.info('Starting a safe and comprehensive reset of Kubernetes and container environments...');
592
599
 
593
600
  try {
@@ -614,25 +621,30 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
614
621
  // Phase 1: Clean up Persistent Volumes with hostPath
615
622
  // This targets data created by Kubernetes Persistent Volumes that use hostPath.
616
623
  logger.info('Phase 1/7: Cleaning Kubernetes hostPath volumes...');
617
- try {
618
- const pvListJson = shellExec(`kubectl get pv -o json || echo '{"items":[]}'`, { stdout: true, silent: true });
619
- const pvList = JSON.parse(pvListJson);
620
-
621
- if (pvList.items && pvList.items.length > 0) {
622
- for (const pv of pvList.items) {
623
- // Check if the PV uses hostPath and delete its contents
624
- if (pv.spec.hostPath && pv.spec.hostPath.path) {
625
- const hostPath = pv.spec.hostPath.path;
626
- logger.info(`Removing data from host path for PV '${pv.metadata.name}': ${hostPath}`);
627
- shellExec(`sudo rm -rf ${hostPath}/* || true`);
624
+ if (options.removeVolumeHostPaths)
625
+ try {
626
+ const pvListJson = shellExec(`kubectl get pv -o json || echo '{"items":[]}'`, {
627
+ stdout: true,
628
+ silent: true,
629
+ });
630
+ const pvList = JSON.parse(pvListJson);
631
+
632
+ if (pvList.items && pvList.items.length > 0) {
633
+ for (const pv of pvList.items) {
634
+ // Check if the PV uses hostPath and delete its contents
635
+ if (pv.spec.hostPath && pv.spec.hostPath.path) {
636
+ const hostPath = pv.spec.hostPath.path;
637
+ logger.info(`Removing data from host path for PV '${pv.metadata.name}': ${hostPath}`);
638
+ shellExec(`sudo rm -rf ${hostPath}/* || true`);
639
+ }
628
640
  }
641
+ } else {
642
+ logger.info('No Persistent Volumes found with hostPath to clean up.');
629
643
  }
630
- } else {
631
- logger.info('No Persistent Volumes found with hostPath to clean up.');
644
+ } catch (error) {
645
+ logger.error('Failed to clean up Persistent Volumes:', error);
632
646
  }
633
- } catch (error) {
634
- logger.error('Failed to clean up Persistent Volumes:', error);
635
- }
647
+ else logger.info(' -> Skipping hostPath volume cleanup as per configuration.');
636
648
  // Phase 2: Restore SELinux and stop services
637
649
  // This is critical for fixing the 'permission denied' error you experienced.
638
650
  // Enable SELinux permissive mode and restore file contexts.
package/src/cli/deploy.js CHANGED
@@ -134,6 +134,17 @@ class UnderpostDeploy {
134
134
  */
135
135
  deploymentYamlPartsFactory({ deployId, env, suffix, resources, replicas, image }) {
136
136
  const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
137
+ let volumes = [
138
+ {
139
+ volumeMountPath: '/etc/config',
140
+ volumeName: 'config-volume',
141
+ configMap: 'underpost-config',
142
+ },
143
+ ];
144
+ const confVolume = fs.existsSync(`./engine-private/conf/${deployId}/conf.volume.json`)
145
+ ? JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.volume.json`, 'utf8'))
146
+ : [];
147
+ volumes = volumes.concat(confVolume);
137
148
  return `apiVersion: apps/v1
138
149
  kind: Deployment
139
150
  metadata:
@@ -168,13 +179,10 @@ spec:
168
179
  npm install -g underpost &&
169
180
  underpost secret underpost --create-from-file /etc/config/.env.${env} &&
170
181
  underpost start --build --run ${deployId} ${env}
171
- volumeMounts:
172
- - name: config-volume
173
- mountPath: /etc/config
174
- volumes:
175
- - name: config-volume
176
- configMap:
177
- name: underpost-config
182
+ ${UnderpostDeploy.API.volumeFactory(volumes)
183
+ .render.split(`\n`)
184
+ .map((l) => ' ' + l)
185
+ .join(`\n`)}
178
186
  ---
179
187
  apiVersion: v1
180
188
  kind: Service
@@ -364,6 +372,7 @@ spec:
364
372
  * @param {boolean} options.disableUpdateDeployment - Whether to disable deployment updates.
365
373
  * @param {boolean} options.disableUpdateProxy - Whether to disable proxy updates.
366
374
  * @param {boolean} options.disableDeploymentProxy - Whether to disable deployment proxy.
375
+ * @param {boolean} options.disableUpdateVolume - Whether to disable volume updates.
367
376
  * @param {boolean} options.status - Whether to display deployment status.
368
377
  * @param {boolean} options.etcHosts - Whether to display the /etc/hosts file.
369
378
  * @param {boolean} options.disableUpdateUnderpostConfig - Whether to disable Underpost config updates.
@@ -391,6 +400,7 @@ spec:
391
400
  disableUpdateDeployment: false,
392
401
  disableUpdateProxy: false,
393
402
  disableDeploymentProxy: false,
403
+ disableUpdateVolume: false,
394
404
  status: false,
395
405
  etcHosts: false,
396
406
  disableUpdateUnderpostConfig: false,
@@ -520,6 +530,26 @@ EOF`);
520
530
  }
521
531
 
522
532
  const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
533
+ const confVolume = fs.existsSync(`./engine-private/conf/${deployId}/conf.volume.json`)
534
+ ? JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.volume.json`, 'utf8'))
535
+ : [];
536
+
537
+ if (!options.disableUpdateVolume) {
538
+ for (const volume of confVolume) {
539
+ if (options.remove) {
540
+ shellExec(`kubectl delete pvc ${volume.claimName}`);
541
+ shellExec(`kubectl delete pv ${volume.claimName.replace('pvc-', 'pv-')}`);
542
+ continue;
543
+ }
544
+ shellExec(`kubectl apply -f - <<EOF
545
+ ${UnderpostDeploy.API.persistentVolumeFactory({
546
+ hostPath: volume.volumeMountPath,
547
+ pvcId: volume.claimName,
548
+ })}
549
+ EOF
550
+ `);
551
+ }
552
+ }
523
553
 
524
554
  for (const host of Object.keys(confServer)) {
525
555
  if (!options.disableUpdateProxy) {
@@ -702,6 +732,78 @@ EOF`);
702
732
  );
703
733
  shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
704
734
  },
735
+
736
+ /**
737
+ * Creates volume mounts and volumes for a deployment.
738
+ * @param {Array<volume>} volumes - List of volume configurations.
739
+ * @param {string} volume.volumeName - Name of the volume.
740
+ * @param {string} volume.volumeMountPath - Mount path of the volume in the container.
741
+ * @param {string} volume.volumeHostPath - Host path of the volume.
742
+ * @param {string} volume.volumeType - Type of the volume (e.g. 'Directory').
743
+ * @param {string|null} volume.claimName - Name of the persistent volume claim (if applicable).
744
+ * @param {string|null} volume.configMap - Name of the config map (if applicable).
745
+ * @returns {object} - Object containing the rendered volume mounts and volumes.
746
+ * @memberof UnderpostDeploy
747
+ */
748
+ volumeFactory(
749
+ volumes = [
750
+ {
751
+ volumeName: 'volume-name',
752
+ volumeMountPath: '/path/in/container',
753
+ volumeHostPath: '/path/on/host',
754
+ volumeType: 'Directory',
755
+ claimName: null,
756
+ configMap: null,
757
+ },
758
+ ],
759
+ ) {
760
+ let _volumeMounts = `
761
+ volumeMounts:`;
762
+ let _volumes = `
763
+ volumes:`;
764
+ volumes.map((volumeData) => {
765
+ const { volumeName, volumeMountPath, volumeHostPath, volumeType, claimName, configMap } = volumeData;
766
+ _volumeMounts += `
767
+ - name: ${volumeName}
768
+ mountPath: ${volumeMountPath}
769
+ `;
770
+
771
+ _volumes += `
772
+ - name: ${volumeName}
773
+ ${
774
+ configMap
775
+ ? ` configMap:
776
+ name: ${configMap}`
777
+ : claimName
778
+ ? ` persistentVolumeClaim:
779
+ claimName: ${claimName}`
780
+ : ` hostPath:
781
+ path: ${volumeHostPath}
782
+ type: ${volumeType}
783
+ `
784
+ }
785
+
786
+ `;
787
+ });
788
+ return { render: _volumeMounts + _volumes };
789
+ },
790
+
791
+ /**
792
+ * Creates a persistent volume and persistent volume claim for a deployment.
793
+ * @param {object} options - Options for the persistent volume and claim creation.
794
+ * @param {string} options.hostPath - Host path for the persistent volume.
795
+ * @param {string} options.pvcId - Persistent volume claim ID.
796
+ * @returns {string} - YAML configuration for the persistent volume and claim.
797
+ * @memberof UnderpostDeploy
798
+ */
799
+ persistentVolumeFactory({ hostPath, pvcId }) {
800
+ return fs
801
+ .readFileSync(`./manifests/pv-pvc-dd.yaml`, 'utf8')
802
+ .replace('/home/dd', hostPath)
803
+ .replace('pv-dd', pvcId.replace('pvc-', 'pv-'))
804
+ .replace('pvc-dd', pvcId);
805
+ },
806
+
705
807
  /**
706
808
  * Creates a hosts file for a deployment.
707
809
  * @param {Array<string>} hosts - List of hosts to be added to the hosts file.
package/src/cli/index.js CHANGED
@@ -78,6 +78,8 @@ program
78
78
  .option('--msg <msg>', 'Sets a custom commit message.')
79
79
  .option('--deploy-id <deploy-id>', 'Sets the deployment configuration ID for the commit context.')
80
80
  .option('--cached', 'Commit staged changes only or context.')
81
+ .option('--hashes <hashes>', 'Comma-separated list of specific file hashes of commits.')
82
+ .option('--extension <extension>', 'specific file extensions of commits.')
81
83
  .description('Manages commits to a GitHub repository, supporting various commit types and options.')
82
84
  .action(Underpost.repo.commit);
83
85
 
@@ -163,6 +165,7 @@ program
163
165
  .option('--chown', 'Sets the appropriate ownership for Kubernetes kubeconfig files.')
164
166
  .option('--k3s', 'Initializes the cluster using K3s (Lightweight Kubernetes).')
165
167
  .option('--hosts <hosts>', 'A comma-separated list of cluster hostnames or IP addresses.')
168
+ .option('--remove-volume-host-paths', 'Removes specified volume host paths after execution.')
166
169
  .action(Underpost.cluster.init)
167
170
  .description('Manages Kubernetes clusters, defaulting to Kind cluster initialization.');
168
171
 
@@ -193,6 +196,7 @@ program
193
196
  .option('--disable-update-deployment', 'Disables updates to deployments.')
194
197
  .option('--disable-update-proxy', 'Disables updates to proxies.')
195
198
  .option('--disable-deployment-proxy', 'Disables proxies of deployments.')
199
+ .option('--disable-update-volume', 'Disables updates to volume mounts during deployment.')
196
200
  .option(
197
201
  '--status',
198
202
  'Retrieves current network traffic data from resource deployments and the host machine network configuration.',
@@ -392,6 +396,14 @@ program
392
396
  .option('--image-name <image-name>', 'Optional: Specifies the image name for test execution.')
393
397
  .option('--container-name <container-name>', 'Optional: Specifies the container name for test execution.')
394
398
  .option('--namespace <namespace>', 'Optional: Specifies the namespace for test execution.')
399
+ .option('--tty', 'Enables TTY for the container in deploy-job.')
400
+ .option('--stdin', 'Keeps STDIN open for the container in deploy-job.')
401
+ .option('--restart-policy <policy>', 'Sets the restart policy for the job in deploy-job.')
402
+ .option('--runtime-class-name <name>', 'Sets the runtime class name for the job in deploy-job.')
403
+ .option('--image-pull-policy <policy>', 'Sets the image pull policy for the job in deploy-job.')
404
+ .option('--api-version <version>', 'Sets the API version for the job manifest in deploy-job.')
405
+ .option('--claim-name <name>', 'Optional: Specifies the claim name for volume mounting in deploy-job.')
406
+ .option('--kind <kind-type>', 'Specifies the kind of Kubernetes resource (e.g., Job, Deployment) for deploy-job.')
395
407
  .option('--kubeadm', 'Flag to indicate Kubeadm cluster type context')
396
408
  .option('--k3s', 'Flag to indicate K3s cluster type context')
397
409
  .option('--force', 'Forces operation, overriding any warnings or conflicts.')
@@ -400,6 +412,7 @@ program
400
412
  .option('--terminal', 'Enables terminal mode for interactive script execution.')
401
413
  .option('--dev-proxy-port-offset <port-offset>', 'Sets a custom port offset for development proxy.')
402
414
  .option('--conf-server-path <conf-server-path>', 'Sets a custom configuration server path.')
415
+ .option('--underpost-root <underpost-root>', 'Sets a custom Underpost root path.')
403
416
  .description('Runs a script from the specified path.')
404
417
  .action(UnderpostRun.API.callback);
405
418
 
@@ -89,6 +89,8 @@ class UnderpostRepository {
89
89
  * @param {boolean} [options.lastMsg=0] - If greater than 0, copies or show the last last single n commit message to clipboard.
90
90
  * @param {string} [options.msg=''] - If provided, outputs this message instead of committing.
91
91
  * @param {string} [options.deployId=''] - An optional deploy ID to include in the commit message.
92
+ * @param {string} [options.hashes=''] - If provided with diff option, shows the diff between two hashes.
93
+ * @param {string} [options.extension=''] - If provided with diff option, filters the diff by this file extension.
92
94
  * @memberof UnderpostRepository
93
95
  */
94
96
  commit(
@@ -107,9 +109,19 @@ class UnderpostRepository {
107
109
  log: 0,
108
110
  msg: '',
109
111
  deployId: '',
112
+ hashes: '',
113
+ extension: '',
110
114
  },
111
115
  ) {
112
116
  if (!repoPath) repoPath = '.';
117
+ if (options.diff && options.hashes) {
118
+ const hashes = options.hashes.split(',');
119
+ const cmd = `git --no-pager diff ${hashes[0]} ${hashes[1] ? hashes[1] : 'HEAD'}${options.extension ? ` -- '*.${options.extension}'` : ''}`;
120
+ if (options.copy) {
121
+ pbcopy(cmd);
122
+ } else console.log(cmd);
123
+ return;
124
+ }
113
125
  if (options.msg) {
114
126
  options.msg = options.msg.replaceAll('"', '').replaceAll(`'`, '').replaceAll('`', '');
115
127
  let key = Object.keys(commitData).find((k) => k && options.msg.toLocaleLowerCase().slice(0, 16).match(k));
package/src/cli/run.js CHANGED
@@ -60,9 +60,15 @@ class UnderpostRun {
60
60
  * @property {string} tty - The TTY option for the container.
61
61
  * @property {string} stdin - The stdin option for the container.
62
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.
63
68
  * @property {boolean} terminal - Whether to open a terminal.
64
69
  * @property {number} devProxyPortOffset - The port offset for the development proxy.
65
70
  * @property {string} confServerPath - The configuration server path.
71
+ * @property {string} underpostRoot - The root path of the Underpost installation.
66
72
  * @memberof UnderpostRun
67
73
  */
68
74
  static DEFAULT_OPTION = {
@@ -85,9 +91,15 @@ class UnderpostRun {
85
91
  tty: '',
86
92
  stdin: '',
87
93
  restartPolicy: '',
94
+ runtimeClassName: '',
95
+ imagePullPolicy: '',
96
+ apiVersion: '',
97
+ claimName: '',
98
+ kind: '',
88
99
  terminal: false,
89
100
  devProxyPortOffset: 0,
90
101
  confServerPath: '',
102
+ underpostRoot: '',
91
103
  };
92
104
  /**
93
105
  * @static
@@ -578,39 +590,51 @@ class UnderpostRun {
578
590
  },
579
591
 
580
592
  /**
581
- * @method dev-container
582
- * @description Runs a development container pod named `underpost-dev-container` with specified volume mounts and opens a terminal to follow its logs.
583
- * @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).
584
596
  * @param {Object} options - The default underpost runner options for customizing workflow
585
597
  * @memberof UnderpostRun
586
598
  */
587
- 'dev-container': async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
588
- options.dev = true;
599
+ 'dd-container': async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
589
600
  const baseCommand = options.dev ? 'node bin' : 'underpost';
590
601
  const baseClusterCommand = options.dev ? ' --dev' : '';
591
- const currentImage = UnderpostDeploy.API.getCurrentLoadedImages('kind-worker', false).find((o) =>
592
- o.IMAGE.match('underpost'),
593
- );
602
+ const currentImage = UnderpostDeploy.API.getCurrentLoadedImages(
603
+ options.nodeName ? options.nodeName : 'kind-worker',
604
+ false,
605
+ ).find((o) => o.IMAGE.match('underpost'));
594
606
  const podName = `underpost-dev-container`;
595
- 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"`);
596
609
  shellExec(`docker exec -i kind-worker bash -c "mkdir -p /home/dd"`);
597
610
  shellExec(`docker cp /home/dd/engine kind-worker:/home/dd/engine`);
598
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`);
599
614
  }
600
- 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
+ );
601
619
  // shellExec(`kubectl delete pod ${podName} --ignore-not-found`);
602
620
  await UnderpostRun.RUNNERS['deploy-job']('', {
603
- dev: true,
621
+ dev: options.dev,
604
622
  podName,
605
- imageName: currentImage ? currentImage.image : `localhost/rockylinux9-underpost:${Underpost.version}`,
606
- 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}`,
607
630
  volumeMountPath: '/home/dd',
631
+ ...(options.dev ? { volumeHostPath: '/home/dd' } : { claimName: 'pvc-dd' }),
608
632
  on: {
609
633
  init: async () => {
610
634
  // openTerminal(`kubectl logs -f ${podName}`);
611
635
  },
612
636
  },
613
- 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`)],
614
638
  });
615
639
  },
616
640
 
@@ -1153,28 +1177,34 @@ class UnderpostRun {
1153
1177
  'deploy-job': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
1154
1178
  const podName = options.podName || 'deploy-job';
1155
1179
  const volumeName = `${podName}-volume`;
1156
- 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());
1157
1182
  const imageName = options.imageName || 'nvcr.io/nvidia/tensorflow:24.04-tf2-py3';
1158
1183
  const containerName = options.containerName || `${podName}-container`;
1159
1184
  const gpuEnable = imageName.match('nvidia');
1160
- const runtimeClassName = gpuEnable ? 'nvidia' : '';
1185
+ const runtimeClassName = options.runtimeClassName ? options.runtimeClassName : gpuEnable ? 'nvidia' : '';
1161
1186
  const namespace = options.namespace || 'default';
1162
1187
  const volumeMountPath = options.volumeMountPath || path;
1163
1188
  const volumeHostPath = options.volumeHostPath || path;
1164
- const enableVolumeMount = volumeHostPath && volumeMountPath;
1189
+ const claimName = options.claimName || '';
1190
+ const enableVolumeMount = volumeMountPath && (volumeHostPath || claimName);
1165
1191
  const tty = options.tty ? 'true' : 'false';
1166
1192
  const stdin = options.stdin ? 'true' : 'false';
1167
1193
  const restartPolicy = options.restartPolicy || 'Never';
1168
-
1194
+ const kind = options.kind || 'Pod';
1195
+ const imagePullPolicy = options.imagePullPolicy || 'IfNotPresent';
1196
+ const apiVersion = options.apiVersion || 'v1';
1169
1197
  if (options.volumeType === 'dev') options.volumeType = 'FileOrCreate';
1170
1198
  const volumeType =
1171
- options.volumeType || (enableVolumeMount && fs.statSync(volumeHostPath).isDirectory()) ? 'Directory' : 'File';
1199
+ options.volumeType || (enableVolumeMount && volumeHostPath && fs.statSync(volumeHostPath).isDirectory())
1200
+ ? 'Directory'
1201
+ : 'File';
1172
1202
 
1173
1203
  const envs = UnderpostRootEnv.API.list();
1174
1204
 
1175
1205
  const cmd = `kubectl apply -f - <<EOF
1176
- apiVersion: v1
1177
- kind: Pod
1206
+ apiVersion: ${apiVersion}
1207
+ kind: ${kind}
1178
1208
  metadata:
1179
1209
  name: ${podName}
1180
1210
  namespace: ${namespace}
@@ -1186,7 +1216,7 @@ ${runtimeClassName ? ` runtimeClassName: ${runtimeClassName}` : ''}
1186
1216
  containers:
1187
1217
  - name: ${containerName}
1188
1218
  image: ${imageName}
1189
- imagePullPolicy: IfNotPresent
1219
+ imagePullPolicy: ${imagePullPolicy}
1190
1220
  tty: ${tty}
1191
1221
  stdin: ${stdin}
1192
1222
  command: ${JSON.stringify(options.command ? options.command : ['/bin/bash', '-c'])}
@@ -1212,15 +1242,7 @@ ${Object.keys(envs)
1212
1242
  .join('\n')}`}
1213
1243
  ${
1214
1244
  enableVolumeMount
1215
- ? `
1216
- volumeMounts:
1217
- - name: ${volumeName}
1218
- mountPath: ${volumeMountPath}
1219
- volumes:
1220
- - name: ${volumeName}
1221
- hostPath:
1222
- path: ${volumeHostPath}
1223
- type: ${volumeType}`
1245
+ ? UnderpostDeploy.API.volumeFactory([{ volumeMountPath, volumeName, volumeHostPath, volumeType, claimName }]).render
1224
1246
  : ''
1225
1247
  }
1226
1248
  EOF`;
@@ -1251,7 +1273,7 @@ EOF`;
1251
1273
  const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
1252
1274
  if (options.command) options.command = options.command.split(',');
1253
1275
  if (options.args) options.args = options.args.split(',');
1254
- options.underpostRoot = underpostRoot;
1276
+ if (!options.underpostRoot) options.underpostRoot = underpostRoot;
1255
1277
  options.npmRoot = npmRoot;
1256
1278
  logger.info('callback', { path, options });
1257
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.2';
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`);