underpost 3.0.0 → 3.0.2
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 +1 -2
- package/.env.test +1 -2
- package/.github/workflows/gitlab.ci.yml +20 -0
- package/CHANGELOG.md +105 -1
- package/CLI-HELP.md +2 -4
- package/README.md +2 -2
- package/bin/build.js +6 -3
- package/bin/deploy.js +18 -26
- package/bin/file.js +31 -2
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/manifests/ipfs/configmap.yaml +7 -0
- package/package.json +5 -1
- package/src/api/file/file.controller.js +3 -13
- package/src/api/file/file.ref.json +0 -21
- package/src/api/user/user.router.js +0 -47
- package/src/cli/cluster.js +30 -38
- package/src/cli/index.js +0 -1
- package/src/cli/lxd.js +1 -1
- package/src/cli/run.js +17 -2
- package/src/client/components/core/Docs.js +92 -6
- package/src/client/components/core/LoadingAnimation.js +2 -3
- package/src/client/components/core/Modal.js +1 -1
- package/src/client/components/core/VanillaJs.js +36 -0
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +4 -3
- package/src/server/auth.js +18 -18
- package/src/server/client-build-docs.js +152 -34
- package/src/server/ipfs-client.js +433 -0
- package/bin/ssl.js +0 -63
package/src/cli/cluster.js
CHANGED
|
@@ -37,7 +37,6 @@ class UnderpostCluster {
|
|
|
37
37
|
* @param {boolean} [options.postgresql=false] - Deploy PostgreSQL.
|
|
38
38
|
* @param {boolean} [options.valkey=false] - Deploy Valkey.
|
|
39
39
|
* @param {boolean} [options.ipfs=false] - Deploy ipfs-cluster statefulset.
|
|
40
|
-
* @param {boolean} [options.full=false] - Deploy a full set of common components.
|
|
41
40
|
* @param {boolean} [options.info=false] - Display extensive Kubernetes cluster information.
|
|
42
41
|
* @param {boolean} [options.certManager=false] - Deploy Cert-Manager for certificate management.
|
|
43
42
|
* @param {boolean} [options.listPods=false] - List Kubernetes pods.
|
|
@@ -75,7 +74,6 @@ class UnderpostCluster {
|
|
|
75
74
|
postgresql: false,
|
|
76
75
|
valkey: false,
|
|
77
76
|
ipfs: false,
|
|
78
|
-
full: false,
|
|
79
77
|
info: false,
|
|
80
78
|
certManager: false,
|
|
81
79
|
listPods: false,
|
|
@@ -102,26 +100,22 @@ class UnderpostCluster {
|
|
|
102
100
|
replicas: '',
|
|
103
101
|
},
|
|
104
102
|
) {
|
|
105
|
-
|
|
106
|
-
if (options.initHost === true) return Underpost.cluster.initHost();
|
|
103
|
+
if (options.initHost) return Underpost.cluster.initHost();
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
if (options.uninstallHost === true) return Underpost.cluster.uninstallHost();
|
|
105
|
+
if (options.uninstallHost) return Underpost.cluster.uninstallHost();
|
|
110
106
|
|
|
111
|
-
|
|
112
|
-
if (options.config === true) return Underpost.cluster.config();
|
|
107
|
+
if (options.config) return Underpost.cluster.config();
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
if (options.chown === true) return Underpost.cluster.chown();
|
|
109
|
+
if (options.chown) return Underpost.cluster.chown();
|
|
116
110
|
|
|
117
111
|
const npmRoot = getNpmRootPath();
|
|
118
|
-
const underpostRoot = options
|
|
112
|
+
const underpostRoot = options.dev ? '.' : `${npmRoot}/underpost`;
|
|
119
113
|
|
|
120
|
-
if (options.listPods
|
|
114
|
+
if (options.listPods) return console.table(Underpost.deploy.get(podName ?? undefined));
|
|
121
115
|
// Set default namespace if not specified
|
|
122
116
|
if (!options.namespace) options.namespace = 'default';
|
|
123
117
|
|
|
124
|
-
if (options.nsUse
|
|
118
|
+
if (options.nsUse) {
|
|
125
119
|
// Verify if namespace exists, create if not
|
|
126
120
|
const namespaceExists = shellExec(`kubectl get namespace ${options.nsUse} --ignore-not-found -o name`, {
|
|
127
121
|
stdout: true,
|
|
@@ -142,8 +136,8 @@ class UnderpostCluster {
|
|
|
142
136
|
}
|
|
143
137
|
|
|
144
138
|
// Reset Kubernetes cluster components (Kind/Kubeadm/K3s) and container runtimes
|
|
145
|
-
if (options.reset
|
|
146
|
-
const clusterType = options.k3s
|
|
139
|
+
if (options.reset) {
|
|
140
|
+
const clusterType = options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind';
|
|
147
141
|
return await Underpost.cluster.safeReset({
|
|
148
142
|
underpostRoot,
|
|
149
143
|
removeVolumeHostPaths: options.removeVolumeHostPaths,
|
|
@@ -160,7 +154,7 @@ class UnderpostCluster {
|
|
|
160
154
|
// --- Kubeadm/Kind/K3s Cluster Initialization ---
|
|
161
155
|
if (!alreadyKubeadmCluster && !alreadyKindCluster && !alreadyK3sCluster) {
|
|
162
156
|
Underpost.cluster.config();
|
|
163
|
-
if (options.k3s
|
|
157
|
+
if (options.k3s) {
|
|
164
158
|
logger.info('Initializing K3s control plane...');
|
|
165
159
|
// Install K3s
|
|
166
160
|
logger.info('Installing K3s...');
|
|
@@ -172,7 +166,7 @@ class UnderpostCluster {
|
|
|
172
166
|
logger.info('Waiting for K3s to be ready...');
|
|
173
167
|
shellExec(`sudo systemctl is-active --wait k3s || sudo systemctl wait --for=active k3s.service`);
|
|
174
168
|
logger.info('K3s service is active.');
|
|
175
|
-
} else if (options.kubeadm
|
|
169
|
+
} else if (options.kubeadm) {
|
|
176
170
|
logger.info('Initializing Kubeadm control plane...');
|
|
177
171
|
// Set default values if not provided
|
|
178
172
|
const podNetworkCidr = options.podNetworkCidr || '192.168.0.0/16';
|
|
@@ -208,7 +202,7 @@ class UnderpostCluster {
|
|
|
208
202
|
logger.info('Initializing Kind cluster...');
|
|
209
203
|
shellExec(
|
|
210
204
|
`cd ${underpostRoot}/manifests && kind create cluster --config kind-config${
|
|
211
|
-
options
|
|
205
|
+
options.dev ? '-dev' : ''
|
|
212
206
|
}.yaml`,
|
|
213
207
|
);
|
|
214
208
|
Underpost.cluster.chown('kind'); // Pass 'kind' to chown
|
|
@@ -218,14 +212,12 @@ class UnderpostCluster {
|
|
|
218
212
|
// --- Optional Component Deployments (Databases, Ingress, Cert-Manager) ---
|
|
219
213
|
// These deployments happen after the base cluster is up.
|
|
220
214
|
|
|
221
|
-
if (options.
|
|
215
|
+
if (options.dedicatedGpu) {
|
|
222
216
|
shellExec(`node ${underpostRoot}/bin/deploy nvidia-gpu-operator`);
|
|
223
|
-
shellExec(
|
|
224
|
-
`node ${underpostRoot}/bin/deploy kubeflow-spark-operator${options.kubeadm === true ? ' kubeadm' : ''}`,
|
|
225
|
-
);
|
|
217
|
+
shellExec(`node ${underpostRoot}/bin/deploy kubeflow-spark-operator${options.kubeadm ? ' kubeadm' : ''}`);
|
|
226
218
|
}
|
|
227
219
|
|
|
228
|
-
if (options.grafana
|
|
220
|
+
if (options.grafana) {
|
|
229
221
|
shellExec(`kubectl delete deployment grafana -n ${options.namespace} --ignore-not-found`);
|
|
230
222
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/grafana -n ${options.namespace}`);
|
|
231
223
|
const yaml = `${fs
|
|
@@ -238,7 +230,7 @@ EOF
|
|
|
238
230
|
`);
|
|
239
231
|
}
|
|
240
232
|
|
|
241
|
-
if (options.prom
|
|
233
|
+
if (options.prom) {
|
|
242
234
|
shellExec(`kubectl delete deployment prometheus --ignore-not-found`);
|
|
243
235
|
shellExec(`kubectl delete configmap prometheus-config --ignore-not-found`);
|
|
244
236
|
shellExec(`kubectl delete service prometheus --ignore-not-found`);
|
|
@@ -257,8 +249,8 @@ EOF
|
|
|
257
249
|
`);
|
|
258
250
|
}
|
|
259
251
|
|
|
260
|
-
if (options.
|
|
261
|
-
if (options.pullImage
|
|
252
|
+
if (options.valkey) {
|
|
253
|
+
if (options.pullImage) Underpost.cluster.pullImage('valkey/valkey:latest', options);
|
|
262
254
|
shellExec(`kubectl delete statefulset valkey-service -n ${options.namespace} --ignore-not-found`);
|
|
263
255
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey -n ${options.namespace}`);
|
|
264
256
|
await Underpost.test.statusMonitor('valkey-service', 'Running', 'pods', 1000, 60 * 10);
|
|
@@ -266,17 +258,17 @@ EOF
|
|
|
266
258
|
if (options.ipfs) {
|
|
267
259
|
await Underpost.ipfs.deploy(options, underpostRoot);
|
|
268
260
|
}
|
|
269
|
-
if (options.
|
|
261
|
+
if (options.mariadb) {
|
|
270
262
|
shellExec(
|
|
271
263
|
`sudo kubectl create secret generic mariadb-secret --from-file=username=/home/dd/engine/engine-private/mariadb-username --from-file=password=/home/dd/engine/engine-private/mariadb-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
272
264
|
);
|
|
273
265
|
shellExec(`kubectl delete statefulset mariadb-statefulset -n ${options.namespace} --ignore-not-found`);
|
|
274
266
|
|
|
275
|
-
if (options.pullImage
|
|
267
|
+
if (options.pullImage) Underpost.cluster.pullImage('mariadb:latest', options);
|
|
276
268
|
shellExec(`kubectl apply -f ${underpostRoot}/manifests/mariadb/storage-class.yaml -n ${options.namespace}`);
|
|
277
269
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb -n ${options.namespace}`);
|
|
278
270
|
}
|
|
279
|
-
if (options.
|
|
271
|
+
if (options.mysql) {
|
|
280
272
|
shellExec(
|
|
281
273
|
`sudo kubectl create secret generic mysql-secret --from-file=username=/home/dd/engine/engine-private/mysql-username --from-file=password=/home/dd/engine/engine-private/mysql-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
282
274
|
);
|
|
@@ -285,15 +277,15 @@ EOF
|
|
|
285
277
|
shellExec(`sudo chown -R $(whoami):$(whoami) /mnt/data`);
|
|
286
278
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mysql -n ${options.namespace}`);
|
|
287
279
|
}
|
|
288
|
-
if (options.
|
|
289
|
-
if (options.pullImage
|
|
280
|
+
if (options.postgresql) {
|
|
281
|
+
if (options.pullImage) Underpost.cluster.pullImage('postgres:latest', options);
|
|
290
282
|
shellExec(
|
|
291
283
|
`sudo kubectl create secret generic postgres-secret --from-file=password=/home/dd/engine/engine-private/postgresql-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
292
284
|
);
|
|
293
285
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/postgresql -n ${options.namespace}`);
|
|
294
286
|
}
|
|
295
|
-
if (options.mongodb4
|
|
296
|
-
if (options.pullImage
|
|
287
|
+
if (options.mongodb4) {
|
|
288
|
+
if (options.pullImage) Underpost.cluster.pullImage('mongo:4.4', options);
|
|
297
289
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4 -n ${options.namespace}`);
|
|
298
290
|
|
|
299
291
|
const deploymentName = 'mongodb-deployment';
|
|
@@ -314,8 +306,8 @@ EOF
|
|
|
314
306
|
--eval 'rs.initiate(${JSON.stringify(mongoConfig)})'`,
|
|
315
307
|
);
|
|
316
308
|
}
|
|
317
|
-
} else if (options.
|
|
318
|
-
if (options.pullImage
|
|
309
|
+
} else if (options.mongodb) {
|
|
310
|
+
if (options.pullImage) Underpost.cluster.pullImage('mongo:latest', options);
|
|
319
311
|
shellExec(
|
|
320
312
|
`sudo kubectl create secret generic mongodb-keyfile --from-file=/home/dd/engine/engine-private/mongodb-keyfile --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
321
313
|
);
|
|
@@ -344,11 +336,11 @@ EOF
|
|
|
344
336
|
}
|
|
345
337
|
}
|
|
346
338
|
|
|
347
|
-
if (options.
|
|
339
|
+
if (options.contour) {
|
|
348
340
|
shellExec(
|
|
349
341
|
`kubectl apply -f https://cdn.jsdelivr.net/gh/projectcontour/contour@release-1.33/examples/render/contour.yaml`,
|
|
350
342
|
);
|
|
351
|
-
if (options.kubeadm
|
|
343
|
+
if (options.kubeadm) {
|
|
352
344
|
// Envoy service might need NodePort for kubeadm
|
|
353
345
|
shellExec(
|
|
354
346
|
`sudo kubectl apply -f ${underpostRoot}/manifests/envoy-service-nodeport.yaml -n ${options.namespace}`,
|
|
@@ -358,7 +350,7 @@ EOF
|
|
|
358
350
|
// so a specific NodePort service might not be needed or can be configured differently.
|
|
359
351
|
}
|
|
360
352
|
|
|
361
|
-
if (options.
|
|
353
|
+
if (options.certManager) {
|
|
362
354
|
if (!Underpost.deploy.get('cert-manager').find((p) => p.STATUS === 'Running')) {
|
|
363
355
|
shellExec(`helm repo add jetstack https://charts.jetstack.io --force-update`);
|
|
364
356
|
shellExec(
|
package/src/cli/index.js
CHANGED
|
@@ -218,7 +218,6 @@ program
|
|
|
218
218
|
.option('--contour', 'Initializes the cluster with Project Contour base HTTPProxy and Envoy.')
|
|
219
219
|
.option('--cert-manager', "Initializes the cluster with a Let's Encrypt production ClusterIssuer.")
|
|
220
220
|
.option('--dedicated-gpu', 'Initializes the cluster with dedicated GPU base resources and environment settings.')
|
|
221
|
-
.option('--full', 'Initializes the cluster with all available statefulsets and services.')
|
|
222
221
|
.option(
|
|
223
222
|
'--ns-use <ns-name>',
|
|
224
223
|
"Switches the current Kubernetes context to the specified namespace (creates if it doesn't exist).",
|
package/src/cli/lxd.js
CHANGED
|
@@ -344,7 +344,7 @@ ipv6.address=none`);
|
|
|
344
344
|
}
|
|
345
345
|
case 'dev-reset': {
|
|
346
346
|
shellExec(
|
|
347
|
-
`lxc exec ${vmName} -- bash -lc 'cd /home/dd/engine && node bin cluster --dev --reset && node bin cluster --dev --k3s'`,
|
|
347
|
+
`lxc exec ${vmName} -- bash -lc 'cd /home/dd/engine && node bin cluster --dev --reset --k3s && node bin cluster --dev --k3s'`,
|
|
348
348
|
);
|
|
349
349
|
break;
|
|
350
350
|
}
|
package/src/cli/run.js
CHANGED
|
@@ -217,6 +217,20 @@ class UnderpostRun {
|
|
|
217
217
|
}
|
|
218
218
|
},
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* @method ipfs-expose
|
|
222
|
+
* @description Exposes IPFS Cluster services on specified ports for local access.
|
|
223
|
+
* @type {Function}
|
|
224
|
+
* @memberof UnderpostRun
|
|
225
|
+
*/
|
|
226
|
+
'ipfs-expose': (path, options = DEFAULT_OPTION) => {
|
|
227
|
+
const ports = [5001, 9094, 8080];
|
|
228
|
+
for (const port of ports)
|
|
229
|
+
shellExec(`node bin deploy --expose ipfs-cluster --expose-port ${port} --disable-update-underpost-config`, {
|
|
230
|
+
async: true,
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
|
|
220
234
|
/**
|
|
221
235
|
* @method metadata
|
|
222
236
|
* @description Generates metadata for the specified path after exposing the development cluster.
|
|
@@ -1148,7 +1162,7 @@ EOF
|
|
|
1148
1162
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1149
1163
|
const clusterType = options.k3s ? 'k3s' : 'kubeadm';
|
|
1150
1164
|
shellCd(`/home/dd/engine`);
|
|
1151
|
-
shellExec(`${baseCommand} cluster${baseClusterCommand} --reset`);
|
|
1165
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --reset --${clusterType}`);
|
|
1152
1166
|
await timer(5000);
|
|
1153
1167
|
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType}`);
|
|
1154
1168
|
await timer(5000);
|
|
@@ -1779,8 +1793,9 @@ EOF
|
|
|
1779
1793
|
* @memberof UnderpostRun
|
|
1780
1794
|
*/
|
|
1781
1795
|
'gpu-env': (path, options = DEFAULT_OPTION) => {
|
|
1796
|
+
const clusterType = 'kubeadm';
|
|
1782
1797
|
shellExec(
|
|
1783
|
-
`node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu
|
|
1798
|
+
`node bin cluster --dev --reset --${clusterType} && node bin cluster --dev --dedicated-gpu --${clusterType} && kubectl get pods --all-namespaces -o wide -w`,
|
|
1784
1799
|
);
|
|
1785
1800
|
},
|
|
1786
1801
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Badge } from './Badge.js';
|
|
2
2
|
import { BtnIcon } from './BtnIcon.js';
|
|
3
|
-
import { Css, renderCssAttr, simpleIconsRender, ThemeEvents, Themes } from './Css.js';
|
|
3
|
+
import { Css, darkTheme, renderCssAttr, simpleIconsRender, ThemeEvents, Themes } from './Css.js';
|
|
4
4
|
import { buildBadgeToolTipMenuOption, Modal, renderViewTitle } from './Modal.js';
|
|
5
5
|
import { listenQueryPathInstance, setQueryPath, closeModalRouteChangeEvent, getProxyPath } from './Router.js';
|
|
6
|
-
import { htmls, s } from './VanillaJs.js';
|
|
6
|
+
import { htmls, s, sIframe } from './VanillaJs.js';
|
|
7
7
|
|
|
8
8
|
// https://mintlify.com/docs/quickstart
|
|
9
9
|
|
|
@@ -39,6 +39,7 @@ const Docs = {
|
|
|
39
39
|
RouterInstance: Modal.Data['modal-docs'].options.RouterInstance,
|
|
40
40
|
});
|
|
41
41
|
const iframeEl = s(`.iframe-${ModalId}`);
|
|
42
|
+
let swaggerThemeEventKey = null;
|
|
42
43
|
if (iframeEl) {
|
|
43
44
|
iframeEl.addEventListener('load', () => {
|
|
44
45
|
try {
|
|
@@ -51,7 +52,95 @@ const Docs = {
|
|
|
51
52
|
// cross-origin or security restriction — safe to ignore
|
|
52
53
|
}
|
|
53
54
|
window.scrollTo(0, 0);
|
|
55
|
+
// Bind Shift+K inside the iframe to focus the parent SearchBox (mirrors app-wide shortcut)
|
|
56
|
+
try {
|
|
57
|
+
const iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow?.document;
|
|
58
|
+
if (iframeDoc) {
|
|
59
|
+
iframeDoc.addEventListener('keydown', (e) => {
|
|
60
|
+
if (e.shiftKey && e.key.toLowerCase() === 'k') {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
if (s(`.top-bar-search-box`)) {
|
|
64
|
+
if (s(`.main-body-btn-ui-close`) && s(`.main-body-btn-ui-close`).classList.contains('hide')) {
|
|
65
|
+
s(`.main-body-btn-ui-open`).click();
|
|
66
|
+
}
|
|
67
|
+
s(`.top-bar-search-box`).blur();
|
|
68
|
+
s(`.top-bar-search-box`).focus();
|
|
69
|
+
s(`.top-bar-search-box`).select();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
// cross-origin or security restriction — safe to ignore
|
|
76
|
+
}
|
|
54
77
|
});
|
|
78
|
+
|
|
79
|
+
if (type === 'src') {
|
|
80
|
+
swaggerThemeEventKey = `jsdocs-iframe-${ModalId}`;
|
|
81
|
+
|
|
82
|
+
const applyJsDocsTheme = (isDark) => {
|
|
83
|
+
try {
|
|
84
|
+
const iframeWin = iframeEl.contentWindow;
|
|
85
|
+
if (!iframeWin) return;
|
|
86
|
+
const theme = isDark ? 'dark' : 'light';
|
|
87
|
+
if (typeof iframeWin.updateTheme === 'function') {
|
|
88
|
+
// clean-jsdoc-theme exposes updateTheme() globally
|
|
89
|
+
iframeWin.updateTheme(theme);
|
|
90
|
+
} else {
|
|
91
|
+
// Fallback: replicate localUpdateTheme manually
|
|
92
|
+
const iframeDoc = iframeEl.contentDocument || iframeWin.document;
|
|
93
|
+
if (!iframeDoc || !iframeDoc.body) return;
|
|
94
|
+
iframeDoc.body.setAttribute('data-theme', theme);
|
|
95
|
+
iframeDoc.body.classList.remove('dark', 'light');
|
|
96
|
+
iframeDoc.body.classList.add(theme);
|
|
97
|
+
const iconID = isDark ? '#light-theme-icon' : '#dark-theme-icon';
|
|
98
|
+
const svgUses = sIframe(iframeEl, '.theme-svg-use') ? iframeDoc.querySelectorAll('.theme-svg-use') : [];
|
|
99
|
+
svgUses.forEach((svg) => svg.setAttribute('xlink:href', iconID));
|
|
100
|
+
iframeWin.localStorage?.setItem('theme', theme);
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
// cross-origin or security restriction — safe to ignore
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Apply current theme as soon as the iframe content is ready
|
|
108
|
+
iframeEl.addEventListener('load', () => applyJsDocsTheme(darkTheme));
|
|
109
|
+
|
|
110
|
+
// Keep in sync whenever the parent page theme changes
|
|
111
|
+
ThemeEvents[swaggerThemeEventKey] = () => {
|
|
112
|
+
if (s(`.iframe-${ModalId}`)) applyJsDocsTheme(darkTheme);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (type === 'api') {
|
|
117
|
+
swaggerThemeEventKey = `swagger-iframe-${ModalId}`;
|
|
118
|
+
|
|
119
|
+
const applySwaggerTheme = (isDark) => {
|
|
120
|
+
try {
|
|
121
|
+
const iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow?.document;
|
|
122
|
+
if (!iframeDoc || !iframeDoc.body) return;
|
|
123
|
+
if (isDark) {
|
|
124
|
+
iframeDoc.body.classList.add('swagger-dark');
|
|
125
|
+
} else {
|
|
126
|
+
iframeDoc.body.classList.remove('swagger-dark');
|
|
127
|
+
}
|
|
128
|
+
iframeEl.contentWindow?.localStorage?.setItem('swagger-theme', isDark ? 'dark' : 'light');
|
|
129
|
+
const toggleBtn = sIframe(iframeEl, '#swagger-theme-toggle');
|
|
130
|
+
if (toggleBtn) toggleBtn.textContent = isDark ? '\u2600\uFE0F Light Mode' : '\uD83C\uDF19 Dark Mode';
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// cross-origin or security restriction — safe to ignore
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Apply current theme as soon as the iframe content is ready
|
|
137
|
+
iframeEl.addEventListener('load', () => applySwaggerTheme(darkTheme));
|
|
138
|
+
|
|
139
|
+
// Keep in sync whenever the parent page theme changes
|
|
140
|
+
ThemeEvents[swaggerThemeEventKey] = () => {
|
|
141
|
+
if (s(`.iframe-${ModalId}`)) applySwaggerTheme(darkTheme);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
55
144
|
}
|
|
56
145
|
Modal.Data[ModalId].onObserverListener[ModalId] = () => {
|
|
57
146
|
if (s(`.iframe-${ModalId}`)) {
|
|
@@ -67,6 +156,7 @@ const Docs = {
|
|
|
67
156
|
};
|
|
68
157
|
Modal.Data[ModalId].onObserverListener[ModalId]();
|
|
69
158
|
Modal.Data[ModalId].onCloseListener[ModalId] = () => {
|
|
159
|
+
if (swaggerThemeEventKey) delete ThemeEvents[swaggerThemeEventKey];
|
|
70
160
|
closeModalRouteChangeEvent({ closedId: ModalId });
|
|
71
161
|
};
|
|
72
162
|
},
|
|
@@ -342,10 +432,6 @@ const Docs = {
|
|
|
342
432
|
<div class="docs-landing">
|
|
343
433
|
<div class="docs-header">
|
|
344
434
|
<h1>Documentation</h1>
|
|
345
|
-
<p>
|
|
346
|
-
Find everything you need to build amazing applications with our platform. Get started with our guides, API
|
|
347
|
-
reference, and examples.
|
|
348
|
-
</p>
|
|
349
435
|
<!--
|
|
350
436
|
<div class="search-bar">
|
|
351
437
|
<i class="fas fa-search"></i>
|
|
@@ -27,8 +27,8 @@ const LoadingAnimation = {
|
|
|
27
27
|
? subThemeManager.darkColor
|
|
28
28
|
: `#66e400`
|
|
29
29
|
: subThemeManager.lightColor
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
? subThemeManager.lightColor
|
|
31
|
+
: `#157e00`};"
|
|
32
32
|
></div>
|
|
33
33
|
`,
|
|
34
34
|
);
|
|
@@ -131,7 +131,6 @@ const LoadingAnimation = {
|
|
|
131
131
|
if (!backgroundContainer) backgroundContainer = '.ssr-background';
|
|
132
132
|
if (s(backgroundContainer)) {
|
|
133
133
|
s(backgroundContainer).style.display = 'none';
|
|
134
|
-
if (s(`.main-body-btn-container`)) s(`.main-body-btn-container`).classList.remove('hide');
|
|
135
134
|
if (callBack) callBack();
|
|
136
135
|
}
|
|
137
136
|
},
|
|
@@ -322,7 +322,7 @@ const Modal = {
|
|
|
322
322
|
'body',
|
|
323
323
|
html`
|
|
324
324
|
<div
|
|
325
|
-
class="abs main-body-btn-container
|
|
325
|
+
class="abs main-body-btn-container"
|
|
326
326
|
style="top: ${options.heightTopBar + 50}px; z-index: 9; ${true ||
|
|
327
327
|
(options.mode && options.mode.match('right'))
|
|
328
328
|
? 'right'
|
|
@@ -418,12 +418,48 @@ function hexToRgbA(hex) {
|
|
|
418
418
|
|
|
419
419
|
const htmlStrSanitize = (str) => (str ? str.replace(/<\/?[^>]+(>|$)/g, '').trim() : '');
|
|
420
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Query selector inside an iframe. Allows obtaining a single element that is
|
|
423
|
+
* inside an iframe in order to execute events on it.
|
|
424
|
+
* Note: the iframe must be same-origin for this to work.
|
|
425
|
+
*
|
|
426
|
+
* @param {string|Element} iframeEl The CSS selector string or the iframe Element itself.
|
|
427
|
+
* @param {string} el The query selector for the element inside the iframe.
|
|
428
|
+
* @returns {Element|null} The matched element inside the iframe, or null if not found.
|
|
429
|
+
* @memberof VanillaJS
|
|
430
|
+
*/
|
|
431
|
+
const sIframe = (iframeEl, el) => {
|
|
432
|
+
const iframe = typeof iframeEl === 'string' ? s(iframeEl) : iframeEl;
|
|
433
|
+
if (!iframe) return null;
|
|
434
|
+
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
435
|
+
return iframeDoc ? iframeDoc.querySelector(el) : null;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Query selector all inside an iframe. Allows obtaining all elements matching a
|
|
440
|
+
* selector that are inside an iframe in order to execute events on them.
|
|
441
|
+
* Note: the iframe must be same-origin for this to work.
|
|
442
|
+
*
|
|
443
|
+
* @param {string|Element} iframeEl The CSS selector string or the iframe Element itself.
|
|
444
|
+
* @param {string} el The query selector for the elements inside the iframe.
|
|
445
|
+
* @returns {NodeList|null} A NodeList of matched elements inside the iframe, or null if not found.
|
|
446
|
+
* @memberof VanillaJS
|
|
447
|
+
*/
|
|
448
|
+
const saIframe = (iframeEl, el) => {
|
|
449
|
+
const iframe = typeof iframeEl === 'string' ? s(iframeEl) : iframeEl;
|
|
450
|
+
if (!iframe) return null;
|
|
451
|
+
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
452
|
+
return iframeDoc ? iframeDoc.querySelectorAll(el) : null;
|
|
453
|
+
};
|
|
454
|
+
|
|
421
455
|
export {
|
|
422
456
|
s,
|
|
423
457
|
htmls,
|
|
424
458
|
append,
|
|
425
459
|
prepend,
|
|
426
460
|
sa,
|
|
461
|
+
sIframe,
|
|
462
|
+
saIframe,
|
|
427
463
|
copyData,
|
|
428
464
|
pasteData,
|
|
429
465
|
preHTML,
|
package/src/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { createPeerServer } from '../../server/peer.js';
|
|
|
20
20
|
import { createValkeyConnection } from '../../server/valkey.js';
|
|
21
21
|
import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
|
|
22
22
|
import { ssrMiddlewareFactory } from '../../server/ssr.js';
|
|
23
|
+
import { buildSwaggerUiOptions } from '../../server/client-build-docs.js';
|
|
23
24
|
|
|
24
25
|
import { shellExec } from '../../server/process.js';
|
|
25
26
|
import { devProxyHostFactory, isDevProxyContext, isTlsDevProxy } from '../../server/conf.js';
|
|
@@ -98,7 +99,7 @@ class ExpressService {
|
|
|
98
99
|
|
|
99
100
|
if (origins && isDevProxyContext())
|
|
100
101
|
origins.push(devProxyHostFactory({ host, includeHttp: true, tls: isTlsDevProxy() }));
|
|
101
|
-
app.set('trust proxy',
|
|
102
|
+
app.set('trust proxy', 1);
|
|
102
103
|
|
|
103
104
|
app.use((req, res, next) => {
|
|
104
105
|
res.on('finish', () => {
|
|
@@ -167,8 +168,8 @@ class ExpressService {
|
|
|
167
168
|
// Swagger UI setup
|
|
168
169
|
if (fs.existsSync(swaggerJsonPath)) {
|
|
169
170
|
const swaggerDoc = JSON.parse(fs.readFileSync(swaggerJsonPath, 'utf8'));
|
|
170
|
-
|
|
171
|
-
app.use(swaggerPath, swaggerUi.serve, swaggerUi.setup(swaggerDoc));
|
|
171
|
+
const swaggerUiOptions = await buildSwaggerUiOptions();
|
|
172
|
+
app.use(swaggerPath, swaggerUi.serve, swaggerUi.setup(swaggerDoc, swaggerUiOptions));
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
// Security and CORS
|
package/src/server/auth.js
CHANGED
|
@@ -647,24 +647,24 @@ function applySecurity(app, opts = {}) {
|
|
|
647
647
|
}),
|
|
648
648
|
);
|
|
649
649
|
logger.info('Cors origin', origin);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
650
|
+
if (!process.env.DISABLE_API_RATE_LIMIT) {
|
|
651
|
+
// Rate limiting + slow down
|
|
652
|
+
const limiter = rateLimit({
|
|
653
|
+
windowMs: rate.windowMs,
|
|
654
|
+
max: rate.max,
|
|
655
|
+
standardHeaders: true,
|
|
656
|
+
legacyHeaders: false,
|
|
657
|
+
message: { error: 'Too many requests, please try again later.' },
|
|
658
|
+
});
|
|
659
|
+
app.use(limiter);
|
|
660
|
+
|
|
661
|
+
const speedLimiter = slowDown({
|
|
662
|
+
windowMs: slowdown.windowMs,
|
|
663
|
+
delayAfter: slowdown.delayAfter,
|
|
664
|
+
delayMs: () => slowdown.delayMs,
|
|
665
|
+
});
|
|
666
|
+
app.use(speedLimiter);
|
|
667
|
+
}
|
|
668
668
|
// Cookie parsing
|
|
669
669
|
app.use(cookieParser(process.env.JWT_SECRET));
|
|
670
670
|
}
|