@underpostnet/underpost 3.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{.env.production → .env.example} +20 -2
- package/.github/workflows/ghpkg.ci.yml +1 -1
- package/.github/workflows/gitlab.ci.yml +1 -1
- package/.github/workflows/npmpkg.ci.yml +22 -7
- package/.github/workflows/publish.ci.yml +5 -5
- package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -3
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +3 -2
- package/.vscode/extensions.json +9 -8
- package/.vscode/settings.json +3 -2
- package/CHANGELOG.md +468 -290
- package/CLI-HELP.md +72 -52
- package/README.md +2 -2
- package/bin/build.js +4 -2
- package/bin/deploy.js +150 -208
- package/bin/file.js +2 -1
- package/bin/vs.js +3 -3
- package/conf.js +30 -13
- 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 +52 -52
- package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
- package/manifests/pv-pvc-dd.yaml +1 -1
- package/package.json +53 -44
- package/scripts/k3s-node-setup.sh +1 -1
- package/src/api/document/document.service.js +1 -1
- package/src/api/file/file.controller.js +3 -1
- package/src/api/file/file.service.js +28 -5
- package/src/api/user/user.router.js +10 -5
- package/src/api/user/user.service.js +7 -7
- package/src/cli/baremetal.js +6 -10
- package/src/cli/cloud-init.js +0 -3
- package/src/cli/db.js +54 -71
- package/src/cli/deploy.js +64 -12
- package/src/cli/env.js +4 -4
- package/src/cli/fs.js +0 -2
- package/src/cli/image.js +0 -3
- package/src/cli/index.js +33 -13
- package/src/cli/monitor.js +5 -6
- package/src/cli/repository.js +322 -35
- package/src/cli/run.js +148 -71
- package/src/cli/secrets.js +0 -3
- package/src/cli/ssh.js +1 -1
- package/src/client/components/core/AgGrid.js +20 -5
- package/src/client/components/core/Content.js +22 -3
- package/src/client/components/core/Docs.js +21 -4
- package/src/client/components/core/FileExplorer.js +71 -4
- package/src/client/components/core/Input.js +1 -1
- package/src/client/components/core/Modal.js +22 -6
- package/src/client/components/core/PublicProfile.js +3 -3
- package/src/client/components/core/Router.js +34 -1
- package/src/client/components/core/Worker.js +1 -1
- package/src/client/public/default/sitemap +3 -3
- package/src/client/public/test/sitemap +3 -3
- package/src/client.build.js +0 -3
- package/src/client.dev.js +0 -3
- package/src/db/DataBaseProvider.js +17 -2
- package/src/db/mariadb/MariaDB.js +14 -9
- package/src/db/mongo/MongooseDB.js +17 -1
- package/src/index.js +1 -1
- package/src/proxy.js +0 -3
- package/src/runtime/express/Express.js +7 -1
- package/src/runtime/lampp/Lampp.js +6 -13
- package/src/server/auth.js +6 -9
- package/src/server/backup.js +2 -3
- package/src/server/client-build-docs.js +178 -3
- package/src/server/client-build-live.js +9 -18
- package/src/server/client-build.js +175 -38
- package/src/server/client-dev-server.js +14 -13
- package/src/server/conf.js +357 -149
- package/src/server/cron.js +2 -1
- package/src/server/dns.js +28 -12
- package/src/server/downloader.js +0 -2
- package/src/server/logger.js +27 -9
- package/src/server/peer.js +0 -2
- package/src/server/process.js +1 -50
- package/src/server/proxy.js +4 -8
- package/src/server/runtime.js +5 -8
- package/src/server/ssr.js +0 -3
- package/src/server/start.js +5 -5
- package/src/server/tls.js +0 -2
- package/src/server.js +0 -4
- package/.env.development +0 -43
- package/.env.test +0 -43
|
@@ -544,10 +544,13 @@ const FileExplorer = {
|
|
|
544
544
|
this.eGui = document.createElement('div');
|
|
545
545
|
const isPublic = params.data.isPublic;
|
|
546
546
|
const toggleId = `toggle-public-${params.data._id}`;
|
|
547
|
+
const hasGenericFile = !!params.data.hasGenericFile;
|
|
548
|
+
const hasMdFile = !!params.data.hasMdFile;
|
|
549
|
+
|
|
547
550
|
this.eGui.innerHTML = html`
|
|
548
551
|
<div class="fl">
|
|
549
552
|
${await BtnIcon.Render({
|
|
550
|
-
class: `in fll management-table-btn-mini btn-file-download-${params.data._id}`,
|
|
553
|
+
class: `in fll management-table-btn-mini btn-file-download-${params.data._id}${!hasGenericFile ? ' btn-disabled' : ''}`,
|
|
551
554
|
label: html` <i class="fas fa-download"></i>`,
|
|
552
555
|
type: 'button',
|
|
553
556
|
})}
|
|
@@ -562,10 +565,15 @@ const FileExplorer = {
|
|
|
562
565
|
type: 'button',
|
|
563
566
|
})}
|
|
564
567
|
${await BtnIcon.Render({
|
|
565
|
-
class: `in fll management-table-btn-mini btn-file-copy-content-link-${params.data._id}`,
|
|
568
|
+
class: `in fll management-table-btn-mini btn-file-copy-content-link-${params.data._id}${!hasGenericFile ? ' btn-disabled' : ''}`,
|
|
566
569
|
label: html`<i class="fas fa-copy"></i>`,
|
|
567
570
|
type: 'button',
|
|
568
571
|
})}
|
|
572
|
+
${await BtnIcon.Render({
|
|
573
|
+
class: `in fll management-table-btn-mini btn-file-copy-md-link-${params.data._id}${!hasMdFile ? ' btn-disabled' : ''}`,
|
|
574
|
+
label: html`<i class="fas fa-file-code"></i>`,
|
|
575
|
+
type: 'button',
|
|
576
|
+
})}
|
|
569
577
|
${await BtnIcon.Render({
|
|
570
578
|
class: `in fll management-table-btn-mini btn-file-edit-${params.data._id}`,
|
|
571
579
|
label: html`<i class="fas fa-edit"></i>`,
|
|
@@ -591,11 +599,46 @@ const FileExplorer = {
|
|
|
591
599
|
? getApiBaseUrl({ id: originObj.fileId._id, endpoint: 'file/blob' })
|
|
592
600
|
: undefined;
|
|
593
601
|
|
|
602
|
+
const mdBlobUri =
|
|
603
|
+
originObj && originObj.mdFileId
|
|
604
|
+
? getApiBaseUrl({ id: originObj.mdFileId._id, endpoint: 'file/blob' })
|
|
605
|
+
: undefined;
|
|
606
|
+
|
|
594
607
|
if (!originObj) {
|
|
595
608
|
s(`.btn-file-view-${params.data._id}`).classList.add('hide');
|
|
596
609
|
s(`.btn-file-copy-content-link-${params.data._id}`).classList.add('hide');
|
|
597
610
|
}
|
|
598
611
|
|
|
612
|
+
// Disable download button if no generic file
|
|
613
|
+
if (!hasGenericFile) {
|
|
614
|
+
const dlBtn = s(`.btn-file-download-${params.data._id}`);
|
|
615
|
+
if (dlBtn) {
|
|
616
|
+
dlBtn.style.opacity = '0.4';
|
|
617
|
+
dlBtn.style.cursor = 'not-allowed';
|
|
618
|
+
dlBtn.style.pointerEvents = 'none';
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Disable copy generic file link button if no generic file
|
|
623
|
+
if (!hasGenericFile) {
|
|
624
|
+
const copyBtn = s(`.btn-file-copy-content-link-${params.data._id}`);
|
|
625
|
+
if (copyBtn) {
|
|
626
|
+
copyBtn.style.opacity = '0.4';
|
|
627
|
+
copyBtn.style.cursor = 'not-allowed';
|
|
628
|
+
copyBtn.style.pointerEvents = 'none';
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Disable copy md file link button if no md file
|
|
633
|
+
if (!hasMdFile) {
|
|
634
|
+
const mdCopyBtn = s(`.btn-file-copy-md-link-${params.data._id}`);
|
|
635
|
+
if (mdCopyBtn) {
|
|
636
|
+
mdCopyBtn.style.opacity = '0.4';
|
|
637
|
+
mdCopyBtn.style.cursor = 'not-allowed';
|
|
638
|
+
mdCopyBtn.style.pointerEvents = 'none';
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
599
642
|
EventsUI.onClick(`.btn-file-view-${params.data._id}`, async (e) => {
|
|
600
643
|
e.preventDefault();
|
|
601
644
|
if (location.href !== url) {
|
|
@@ -606,6 +649,7 @@ const FileExplorer = {
|
|
|
606
649
|
|
|
607
650
|
EventsUI.onClick(`.btn-file-copy-content-link-${params.data._id}`, async (e) => {
|
|
608
651
|
e.preventDefault();
|
|
652
|
+
if (!hasGenericFile || !blobUri) return;
|
|
609
653
|
await copyData(blobUri);
|
|
610
654
|
NotificationManager.Push({
|
|
611
655
|
html: Translate.Render('success-copy-data'),
|
|
@@ -613,10 +657,21 @@ const FileExplorer = {
|
|
|
613
657
|
});
|
|
614
658
|
});
|
|
615
659
|
|
|
660
|
+
EventsUI.onClick(`.btn-file-copy-md-link-${params.data._id}`, async (e) => {
|
|
661
|
+
e.preventDefault();
|
|
662
|
+
if (!hasMdFile || !mdBlobUri) return;
|
|
663
|
+
await copyData(mdBlobUri);
|
|
664
|
+
NotificationManager.Push({
|
|
665
|
+
html: Translate.Render('success-copy-data'),
|
|
666
|
+
status: 'success',
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
616
670
|
EventsUI.onClick(`.btn-file-download-${params.data._id}`, async (e) => {
|
|
617
671
|
e.preventDefault();
|
|
672
|
+
if (!hasGenericFile) return;
|
|
618
673
|
try {
|
|
619
|
-
// Use FileService with blob/ prefix for
|
|
674
|
+
// Use FileService with blob/ prefix for blob fetching
|
|
620
675
|
const { data: blobArray, status } = await FileService.get({ id: `blob/${params.data.fileId}` });
|
|
621
676
|
if (status === 'success' && blobArray && blobArray[0]) {
|
|
622
677
|
downloadFile(blobArray[0], params.data.name);
|
|
@@ -721,6 +776,12 @@ const FileExplorer = {
|
|
|
721
776
|
documentInstance[docIndex].isPublic = data.isPublic;
|
|
722
777
|
}
|
|
723
778
|
|
|
779
|
+
// Refresh the isPublic column cell in the grid
|
|
780
|
+
const rowNode = AgGrid.grids[gridFileId].getRowNode(params.node.id);
|
|
781
|
+
if (rowNode) {
|
|
782
|
+
rowNode.setDataValue('isPublic', data.isPublic);
|
|
783
|
+
}
|
|
784
|
+
|
|
724
785
|
// Update button icon
|
|
725
786
|
const btnElement = s(`.${toggleId}`);
|
|
726
787
|
if (btnElement) {
|
|
@@ -1347,7 +1408,13 @@ const FileExplorer = {
|
|
|
1347
1408
|
{ field: 'name', flex: 2, headerName: 'Title', cellRenderer: LoadFileNameRenderer },
|
|
1348
1409
|
{ field: 'mdFileName', flex: 1, headerName: 'MD File Name' },
|
|
1349
1410
|
{ field: 'fileName', flex: 1, headerName: 'Generic File Name' },
|
|
1350
|
-
{
|
|
1411
|
+
{
|
|
1412
|
+
field: 'isPublic',
|
|
1413
|
+
headerName: 'Public',
|
|
1414
|
+
width: 90,
|
|
1415
|
+
cellDataType: 'boolean',
|
|
1416
|
+
},
|
|
1417
|
+
{ headerName: '', width: 180, cellRenderer: LoadFileActionsRenderer },
|
|
1351
1418
|
],
|
|
1352
1419
|
},
|
|
1353
1420
|
})}
|
|
@@ -101,7 +101,7 @@ const getFileFromFileData = (fileData) => {
|
|
|
101
101
|
/**
|
|
102
102
|
* Fetch file content from blob endpoint and create File object.
|
|
103
103
|
* Used for metadata-only format files during edit mode.
|
|
104
|
-
* Uses FileService with blob/ prefix for
|
|
104
|
+
* Uses FileService with blob/ prefix for blob fetching.
|
|
105
105
|
*
|
|
106
106
|
* @async
|
|
107
107
|
* @function getFileFromBlobEndpoint
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
coreUI,
|
|
24
24
|
sanitizeRoute,
|
|
25
25
|
getQueryParams,
|
|
26
|
+
setRouterReady,
|
|
26
27
|
} from './Router.js';
|
|
27
28
|
import { NotificationManager } from './NotificationManager.js';
|
|
28
29
|
import { EventsUI } from './EventsUI.js';
|
|
@@ -1401,6 +1402,7 @@ const Modal = {
|
|
|
1401
1402
|
}
|
|
1402
1403
|
});
|
|
1403
1404
|
setTimeout(window.onresize);
|
|
1405
|
+
setRouterReady();
|
|
1404
1406
|
});
|
|
1405
1407
|
})();
|
|
1406
1408
|
break;
|
|
@@ -2422,20 +2424,34 @@ const Modal = {
|
|
|
2422
2424
|
},
|
|
2423
2425
|
};
|
|
2424
2426
|
|
|
2425
|
-
const renderMenuLabel = ({ img, text, icon }) => {
|
|
2426
|
-
if (!img) return html`<span class="inl menu-btn-icon">${icon}</span> ${text}`;
|
|
2427
|
-
|
|
2427
|
+
const renderMenuLabel = ({ img, src, text, icon }) => {
|
|
2428
|
+
if (!img && !src) return html`<span class="inl menu-btn-icon">${icon}</span> ${text}`;
|
|
2429
|
+
const imgSrc = src ? src : `${getProxyPath()}assets/ui-icons/${img}`;
|
|
2430
|
+
return html`<img class="abs center img-btn-square-menu" src="${imgSrc}" />
|
|
2428
2431
|
<div class="abs center main-btn-menu-text">${text}</div>`;
|
|
2429
2432
|
};
|
|
2430
2433
|
|
|
2431
2434
|
const renderViewTitle = (
|
|
2432
|
-
options = {
|
|
2435
|
+
options = {
|
|
2436
|
+
icon: '',
|
|
2437
|
+
img: '',
|
|
2438
|
+
text: '',
|
|
2439
|
+
assetFolder: '',
|
|
2440
|
+
'ui-icon': '',
|
|
2441
|
+
imgClass: '',
|
|
2442
|
+
textClass: '',
|
|
2443
|
+
dim,
|
|
2444
|
+
top,
|
|
2445
|
+
topText: '',
|
|
2446
|
+
},
|
|
2433
2447
|
) => {
|
|
2434
2448
|
if (options.dim === undefined) options.dim = 30;
|
|
2435
2449
|
const { img, text, icon, dim, top } = options;
|
|
2436
2450
|
if (!img && !options['ui-icon']) return html`<span class="view-title-icon">${icon}</span> ${text}`;
|
|
2451
|
+
const imgClass = options.imgClass || 'abs img-btn-square-view-title';
|
|
2452
|
+
const textClass = options.textClass || 'in text-btn-square-view-title';
|
|
2437
2453
|
return html`<img
|
|
2438
|
-
class="
|
|
2454
|
+
class="${imgClass}"
|
|
2439
2455
|
style="${renderCssAttr({
|
|
2440
2456
|
style: {
|
|
2441
2457
|
width: `${dim}px`,
|
|
@@ -2448,7 +2464,7 @@ const renderViewTitle = (
|
|
|
2448
2464
|
: img}"
|
|
2449
2465
|
/>
|
|
2450
2466
|
<div
|
|
2451
|
-
class="
|
|
2467
|
+
class="${textClass}"
|
|
2452
2468
|
style="${renderCssAttr({
|
|
2453
2469
|
style: {
|
|
2454
2470
|
// 'padding-left': `${20 + dim}px`,
|
|
@@ -196,7 +196,7 @@ const PublicProfile = {
|
|
|
196
196
|
user: { _id: userId, username },
|
|
197
197
|
} = options;
|
|
198
198
|
const idModal = options.idModal || getId();
|
|
199
|
-
const profileId = `public-profile-${
|
|
199
|
+
const profileId = `public-profile-${username}`;
|
|
200
200
|
const waveAnimationId = `${profileId}-wave`;
|
|
201
201
|
const profileImageClass = `${profileId}-image`;
|
|
202
202
|
const profileContainerId = `${profileId}-container`;
|
|
@@ -673,7 +673,7 @@ const PublicProfile = {
|
|
|
673
673
|
"
|
|
674
674
|
>
|
|
675
675
|
<div
|
|
676
|
-
class="${profileId}-image-container"
|
|
676
|
+
class="${profileId}-image-container public-profile-image-container"
|
|
677
677
|
style="
|
|
678
678
|
position: relative;
|
|
679
679
|
width: 160px;
|
|
@@ -688,7 +688,7 @@ const PublicProfile = {
|
|
|
688
688
|
"
|
|
689
689
|
>
|
|
690
690
|
<img
|
|
691
|
-
class="${profileImageClass}"
|
|
691
|
+
class="${profileImageClass} public-profile-image"
|
|
692
692
|
style="
|
|
693
693
|
width: 100%;
|
|
694
694
|
height: 100%;
|
|
@@ -39,6 +39,36 @@ const coreUI = ['modal-menu', 'main-body', 'main-body-top', 'bottom-bar', 'board
|
|
|
39
39
|
*/
|
|
40
40
|
const closeModalRouteChangeEvents = {};
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Deferred promise that resolves once the full UI (including deferred slide-menu DOM
|
|
44
|
+
* setup in Modal) is ready. Any code that depends on the complete DOM — route handlers,
|
|
45
|
+
* session callbacks, panel updates, etc. — can simply `await RouterReady` instead of
|
|
46
|
+
* scattering individual null-checks across every microfrontend.
|
|
47
|
+
*
|
|
48
|
+
* Resolved by calling `setRouterReady()`, which should happen exactly once at the end
|
|
49
|
+
* of Modal's deferred slide-menu `setTimeout` block.
|
|
50
|
+
* @type {Promise<void>}
|
|
51
|
+
* @memberof PwaRouter
|
|
52
|
+
*/
|
|
53
|
+
let _routerReadyResolve;
|
|
54
|
+
const RouterReady = new Promise((resolve) => {
|
|
55
|
+
_routerReadyResolve = resolve;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Signals that the deferred UI setup is complete and the router (and any other
|
|
60
|
+
* awaiter of `RouterReady`) may safely access the full DOM.
|
|
61
|
+
* This must be called exactly once – typically at the end of Modal's deferred
|
|
62
|
+
* slide-menu `setTimeout` block.
|
|
63
|
+
* @memberof PwaRouter
|
|
64
|
+
*/
|
|
65
|
+
const setRouterReady = () => {
|
|
66
|
+
if (_routerReadyResolve) {
|
|
67
|
+
_routerReadyResolve();
|
|
68
|
+
_routerReadyResolve = undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
42
72
|
/**
|
|
43
73
|
* Determines the base path for the application, often used for routing within a sub-directory.
|
|
44
74
|
* It checks the current URL's pathname and `window.Routes` to return the appropriate proxy path.
|
|
@@ -209,7 +239,8 @@ const Router = function (options = { Routes: () => {}, e: new PopStateEvent() })
|
|
|
209
239
|
* @param {object} RouterInstance - The router instance configuration, including the `Routes` function.
|
|
210
240
|
* @memberof PwaRouter
|
|
211
241
|
*/
|
|
212
|
-
const LoadRouter = function (RouterInstance) {
|
|
242
|
+
const LoadRouter = async function (RouterInstance) {
|
|
243
|
+
await RouterReady;
|
|
213
244
|
Router(RouterInstance);
|
|
214
245
|
window.onpopstate = (e) => {
|
|
215
246
|
Router({ ...RouterInstance, e });
|
|
@@ -466,4 +497,6 @@ export {
|
|
|
466
497
|
sanitizeRoute,
|
|
467
498
|
queryParamsChangeListeners,
|
|
468
499
|
listenQueryParamsChange,
|
|
500
|
+
setRouterReady,
|
|
501
|
+
RouterReady,
|
|
469
502
|
};
|
|
@@ -135,7 +135,7 @@ class PwaWorker {
|
|
|
135
135
|
const isInstall = await this.status();
|
|
136
136
|
if (!isInstall) await this.install();
|
|
137
137
|
await render();
|
|
138
|
-
LoadRouter(this.RouterInstance);
|
|
138
|
+
await LoadRouter(this.RouterInstance);
|
|
139
139
|
LoadingAnimation.removeSplashScreen();
|
|
140
140
|
if (this.devMode()) {
|
|
141
141
|
// const delayLiveReload = 1250;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<xsl:stylesheet version="
|
|
2
|
+
<xsl:stylesheet version="1.0"
|
|
3
3
|
xmlns:html="http://www.w3.org/TR/REC-html40"
|
|
4
4
|
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
|
5
5
|
xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
<div id="content">
|
|
68
68
|
<h1>XML Sitemap</h1>
|
|
69
69
|
<p class="desc"> This is a sitemap generated by <a
|
|
70
|
-
href="
|
|
70
|
+
href="{{web-url}}">{{web-url}}</a>
|
|
71
71
|
</p>
|
|
72
72
|
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) > 0">
|
|
73
73
|
<table id="sitemap" cellpadding="3">
|
|
@@ -145,4 +145,4 @@
|
|
|
145
145
|
</html>
|
|
146
146
|
|
|
147
147
|
</xsl:template>
|
|
148
|
-
</xsl:stylesheet>
|
|
148
|
+
</xsl:stylesheet>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<xsl:stylesheet version="
|
|
2
|
+
<xsl:stylesheet version="1.0"
|
|
3
3
|
xmlns:html="http://www.w3.org/TR/REC-html40"
|
|
4
4
|
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
|
5
5
|
xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
<div id="content">
|
|
68
68
|
<h1>XML Sitemap</h1>
|
|
69
69
|
<p class="desc"> This is a sitemap generated by <a
|
|
70
|
-
href="
|
|
70
|
+
href="{{web-url}}">{{web-url}}</a>
|
|
71
71
|
</p>
|
|
72
72
|
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) > 0">
|
|
73
73
|
<table id="sitemap" cellpadding="3">
|
|
@@ -145,4 +145,4 @@
|
|
|
145
145
|
</html>
|
|
146
146
|
|
|
147
147
|
</xsl:template>
|
|
148
|
-
</xsl:stylesheet>
|
|
148
|
+
</xsl:stylesheet>
|
package/src/client.build.js
CHANGED
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
// https://nodejs.org/api
|
|
4
4
|
// https://expressjs.com/en/4x/api.html
|
|
5
5
|
|
|
6
|
-
import dotenv from 'dotenv';
|
|
7
6
|
import { loggerFactory } from './server/logger.js';
|
|
8
7
|
import { Config } from './server/conf.js';
|
|
9
8
|
import { ProcessController } from './server/process.js';
|
|
10
9
|
import { clientLiveBuild } from './server/client-build-live.js';
|
|
11
10
|
|
|
12
|
-
dotenv.config();
|
|
13
|
-
|
|
14
11
|
await Config.build();
|
|
15
12
|
|
|
16
13
|
const logger = loggerFactory(import.meta);
|
package/src/client.dev.js
CHANGED
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
// https://nodejs.org/api
|
|
4
4
|
// https://expressjs.com/en/4x/api.html
|
|
5
5
|
|
|
6
|
-
import dotenv from 'dotenv';
|
|
7
6
|
import { loggerFactory } from './server/logger.js';
|
|
8
7
|
import { ProcessController } from './server/process.js';
|
|
9
8
|
import { Config, buildClientStaticConf } from './server/conf.js';
|
|
10
9
|
import { createClientDevServer } from './server/client-dev-server.js';
|
|
11
10
|
|
|
12
|
-
dotenv.config();
|
|
13
|
-
|
|
14
11
|
const logger = loggerFactory(import.meta);
|
|
15
12
|
|
|
16
13
|
await logger.setUpInfo();
|
|
@@ -13,7 +13,7 @@ const logger = loggerFactory(import.meta);
|
|
|
13
13
|
* @class
|
|
14
14
|
* @alias DataBaseProviderService
|
|
15
15
|
* @memberof DataBaseProviderService
|
|
16
|
-
* @classdesc
|
|
16
|
+
* @classdesc Service for loading, managing, and accessing multiple database connections
|
|
17
17
|
* based on application configuration (host, path, provider type).
|
|
18
18
|
*/
|
|
19
19
|
class DataBaseProviderService {
|
|
@@ -81,7 +81,22 @@ class DataBaseProviderService {
|
|
|
81
81
|
}
|
|
82
82
|
return this.#instance[key][db.provider];
|
|
83
83
|
} catch (error) {
|
|
84
|
-
|
|
84
|
+
// Sanitize options to prevent credential exposure in logs
|
|
85
|
+
const safeOptions = {
|
|
86
|
+
apis: options.apis,
|
|
87
|
+
host: options.host,
|
|
88
|
+
path: options.path,
|
|
89
|
+
db: options.db
|
|
90
|
+
? {
|
|
91
|
+
provider: options.db.provider,
|
|
92
|
+
name: options.db.name ? '***' : undefined,
|
|
93
|
+
host: options.db.host ? '***' : undefined,
|
|
94
|
+
user: options.db.user ? '***' : undefined,
|
|
95
|
+
password: options.db.password ? '***' : undefined,
|
|
96
|
+
}
|
|
97
|
+
: {},
|
|
98
|
+
};
|
|
99
|
+
logger.error(error.message, { safeOptions });
|
|
85
100
|
return undefined;
|
|
86
101
|
}
|
|
87
102
|
}
|
|
@@ -16,6 +16,11 @@ const logger = loggerFactory(import.meta);
|
|
|
16
16
|
* @memberof MariaDBService
|
|
17
17
|
* @classdesc Provides a simplified interface for executing queries against a MariaDB/MySQL database
|
|
18
18
|
* using a connection pool, ensuring connection management (acquisition and release).
|
|
19
|
+
*
|
|
20
|
+
* Connection credentials are resolved in the following order:
|
|
21
|
+
* 1. Explicit values passed in the `options` parameter.
|
|
22
|
+
* 2. Environment variables (`MARIADB_HOST`, `MARIADB_PORT`, `MARIADB_USER`, `MARIADB_PASSWORD`).
|
|
23
|
+
* 3. Safe built-in defaults (`127.0.0.1`, `3306`, `root`, empty password).
|
|
19
24
|
*/
|
|
20
25
|
class MariaDBService {
|
|
21
26
|
/**
|
|
@@ -23,20 +28,20 @@ class MariaDBService {
|
|
|
23
28
|
*
|
|
24
29
|
* @async
|
|
25
30
|
* @param {object} options - The database connection and query options.
|
|
26
|
-
* @param {string} [options.host
|
|
27
|
-
* @param {number} [options.port
|
|
28
|
-
* @param {string} [options.user
|
|
29
|
-
* @param {string} [options.password
|
|
31
|
+
* @param {string} [options.host] - The database host. Falls back to `process.env.MARIADB_HOST` then `'127.0.0.1'`.
|
|
32
|
+
* @param {number} [options.port] - The database port. Falls back to `process.env.MARIADB_PORT` then `3306`.
|
|
33
|
+
* @param {string} [options.user] - The database user. Falls back to `process.env.MARIADB_USER` then `'root'`.
|
|
34
|
+
* @param {string} [options.password] - The database password. Falls back to `process.env.MARIADB_PASSWORD` then `''`.
|
|
30
35
|
* @param {string} options.query - The SQL query string to execute.
|
|
31
36
|
* @returns {Promise<any>} The result of the database query.
|
|
32
37
|
*/
|
|
33
38
|
async query(options) {
|
|
34
39
|
const { host, port, user, password, query } = options;
|
|
35
40
|
const pool = createPool({
|
|
36
|
-
host: 'host' in options ? host : '127.0.0.1',
|
|
37
|
-
port: 'port' in options ? port : 3306,
|
|
38
|
-
user: 'user' in options ? user : 'root',
|
|
39
|
-
password: 'password' in options ? password : '',
|
|
41
|
+
host: 'host' in options ? host : process.env.MARIADB_HOST || '127.0.0.1',
|
|
42
|
+
port: 'port' in options ? port : parseInt(process.env.MARIADB_PORT, 10) || 3306,
|
|
43
|
+
user: 'user' in options ? user : process.env.MARIADB_USER || 'root',
|
|
44
|
+
password: 'password' in options ? password : process.env.MARIADB_PASSWORD || '',
|
|
40
45
|
});
|
|
41
46
|
let conn, result;
|
|
42
47
|
try {
|
|
@@ -45,7 +50,7 @@ class MariaDBService {
|
|
|
45
50
|
logger.info('query');
|
|
46
51
|
console.log(result);
|
|
47
52
|
} catch (error) {
|
|
48
|
-
logger.error(
|
|
53
|
+
logger.error('MariaDB query failed', { error: error.message });
|
|
49
54
|
} finally {
|
|
50
55
|
if (conn) conn.release(); // release to pool
|
|
51
56
|
await pool.end();
|
|
@@ -16,17 +16,33 @@ const logger = loggerFactory(import.meta);
|
|
|
16
16
|
* @memberof MongooseDBService
|
|
17
17
|
* @classdesc Manages the Mongoose connection lifecycle and dynamic loading of database models
|
|
18
18
|
* based on API configuration.
|
|
19
|
+
*
|
|
20
|
+
* Connection parameters are resolved in the following order:
|
|
21
|
+
* 1. Explicit values passed as arguments to {@link MongooseDBService#connect}.
|
|
22
|
+
* 2. Environment variables (`DB_HOST` for host, `DB_NAME` for database name).
|
|
23
|
+
* 3. No built-in defaults — both `host` and `name` are required from the caller or environment.
|
|
19
24
|
*/
|
|
20
25
|
class MongooseDBService {
|
|
21
26
|
/**
|
|
22
27
|
* Establishes a Mongoose connection to the specified MongoDB instance.
|
|
23
28
|
*
|
|
24
29
|
* @async
|
|
25
|
-
* @param {string} host - The MongoDB host (e.g., 'mongodb://localhost:27017').
|
|
30
|
+
* @param {string} host - The MongoDB host URI (e.g., `'mongodb://localhost:27017'`).
|
|
31
|
+
* Falls back to `process.env.DB_HOST` when not provided.
|
|
26
32
|
* @param {string} name - The database name.
|
|
33
|
+
* Falls back to `process.env.DB_NAME` when not provided.
|
|
27
34
|
* @returns {Promise<mongoose.Connection>} A promise that resolves to the established Mongoose connection object.
|
|
35
|
+
* @throws {Error} If neither the argument nor the corresponding environment variable supplies a value.
|
|
28
36
|
*/
|
|
29
37
|
async connect(host, name) {
|
|
38
|
+
host = host || process.env.DB_HOST;
|
|
39
|
+
name = name || process.env.DB_NAME;
|
|
40
|
+
|
|
41
|
+
if (!host || !name) {
|
|
42
|
+
const missing = [!host && 'host (DB_HOST)', !name && 'name (DB_NAME)'].filter(Boolean).join(', ');
|
|
43
|
+
throw new Error(`MongooseDBService.connect: missing required parameter(s): ${missing}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
const uri = `${host}/${name}`;
|
|
31
47
|
// logger.info('MongooseDB connect', { host, name, uri });
|
|
32
48
|
return await mongoose
|
package/src/index.js
CHANGED
package/src/proxy.js
CHANGED
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
// https://nodejs.org/api
|
|
4
4
|
// https://expressjs.com/en/4x/api.html
|
|
5
5
|
|
|
6
|
-
import dotenv from 'dotenv';
|
|
7
6
|
import { loggerFactory } from './server/logger.js';
|
|
8
7
|
import { buildProxy } from './server/proxy.js';
|
|
9
8
|
import { ProcessController } from './server/process.js';
|
|
10
9
|
import { Config } from './server/conf.js';
|
|
11
10
|
|
|
12
|
-
dotenv.config();
|
|
13
|
-
|
|
14
11
|
await Config.build(process.argv[2], process.argv[3], process.argv[4]);
|
|
15
12
|
|
|
16
13
|
const logger = loggerFactory(import.meta);
|
|
@@ -249,7 +249,13 @@ class ExpressService {
|
|
|
249
249
|
for (const [_, ssrMiddleware] of Object.entries(ssr)) app.use(ssrMiddleware);
|
|
250
250
|
|
|
251
251
|
// Start listening on the main port
|
|
252
|
-
|
|
252
|
+
// When behind a dev proxy (isDevProxyContext), the proxy handles TLS termination,
|
|
253
|
+
// so backend servers should listen on plain HTTP to be reachable via http://localhost:PORT
|
|
254
|
+
if (
|
|
255
|
+
(useLocalSsl || process.argv.find((arg) => arg === 'tls')) &&
|
|
256
|
+
process.env.NODE_ENV === 'development' &&
|
|
257
|
+
!isDevProxyContext()
|
|
258
|
+
) {
|
|
253
259
|
if (!Underpost.tls.validateSecureContext()) shellExec(`node bin/deploy tls`);
|
|
254
260
|
const { ServerSSL } = await Underpost.tls.createSslServer(app);
|
|
255
261
|
await Underpost.start.listenPortController(ServerSSL, port, runningData);
|
|
@@ -21,11 +21,11 @@ const logger = loggerFactory(import.meta);
|
|
|
21
21
|
class LamppService {
|
|
22
22
|
/**
|
|
23
23
|
* @method
|
|
24
|
-
* @type {string
|
|
24
|
+
* @type {string}
|
|
25
25
|
* @description Stores the accumulated Apache virtual host configuration (router definition).
|
|
26
26
|
* @memberof LamppService
|
|
27
27
|
*/
|
|
28
|
-
router;
|
|
28
|
+
router = '';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* @public
|
|
@@ -42,7 +42,7 @@ class LamppService {
|
|
|
42
42
|
* @memberof LamppService
|
|
43
43
|
*/
|
|
44
44
|
constructor() {
|
|
45
|
-
this.router =
|
|
45
|
+
this.router = '';
|
|
46
46
|
this.ports = [];
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -125,7 +125,6 @@ class LamppService {
|
|
|
125
125
|
|
|
126
126
|
// 6. Start the service
|
|
127
127
|
cmd = `sudo /opt/lampp/lampp start`;
|
|
128
|
-
if (this.router) fs.writeFileSync(`./tmp/lampp-router.conf`, this.router, 'utf-8');
|
|
129
128
|
shellExec(cmd);
|
|
130
129
|
}
|
|
131
130
|
|
|
@@ -139,13 +138,7 @@ class LamppService {
|
|
|
139
138
|
* @memberof LamppService
|
|
140
139
|
*/
|
|
141
140
|
appendRouter(render) {
|
|
142
|
-
if (!this.router)
|
|
143
|
-
if (fs.existsSync(`./tmp/lampp-router.conf`)) {
|
|
144
|
-
this.router = fs.readFileSync(`./tmp/lampp-router.conf`, 'utf-8');
|
|
145
|
-
return this.router + render;
|
|
146
|
-
}
|
|
147
|
-
return (this.router = render);
|
|
148
|
-
}
|
|
141
|
+
if (!this.router) return (this.router = render);
|
|
149
142
|
return (this.router += render);
|
|
150
143
|
}
|
|
151
144
|
|
|
@@ -154,10 +147,10 @@ class LamppService {
|
|
|
154
147
|
*
|
|
155
148
|
* @memberof LamppService
|
|
156
149
|
* @returns {void}
|
|
150
|
+
* @method removeRouter
|
|
157
151
|
*/
|
|
158
152
|
removeRouter() {
|
|
159
|
-
this.router =
|
|
160
|
-
if (fs.existsSync(`./tmp/lampp-router.conf`)) fs.rmSync(`./tmp/lampp-router.conf`);
|
|
153
|
+
this.router = '';
|
|
161
154
|
}
|
|
162
155
|
|
|
163
156
|
/**
|
package/src/server/auth.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @namespace Auth
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import dotenv from 'dotenv';
|
|
8
7
|
import jwt from 'jsonwebtoken';
|
|
9
8
|
import { loggerFactory } from './logger.js';
|
|
10
9
|
import crypto from 'crypto';
|
|
@@ -19,7 +18,6 @@ import cookieParser from 'cookie-parser';
|
|
|
19
18
|
import { DataBaseProvider } from '../db/DataBaseProvider.js';
|
|
20
19
|
import { isDevProxyContext } from './conf.js';
|
|
21
20
|
|
|
22
|
-
dotenv.config();
|
|
23
21
|
const logger = loggerFactory(import.meta);
|
|
24
22
|
|
|
25
23
|
// Promisified crypto functions
|
|
@@ -349,12 +347,7 @@ const cookieOptionsFactory = (req, host) => {
|
|
|
349
347
|
secure,
|
|
350
348
|
sameSite,
|
|
351
349
|
path: '/',
|
|
352
|
-
domain:
|
|
353
|
-
process.env.NODE_ENV === 'production' ||
|
|
354
|
-
isDevProxyContext() ||
|
|
355
|
-
(req.headers.host && req.headers.host.toLocaleLowerCase().match(host))
|
|
356
|
-
? host
|
|
357
|
-
: 'localhost',
|
|
350
|
+
domain: process.env.NODE_ENV === 'production' || isDevProxyContext() ? host : 'localhost',
|
|
358
351
|
maxAge,
|
|
359
352
|
};
|
|
360
353
|
|
|
@@ -389,7 +382,11 @@ async function createSessionAndUserToken(user, User, req, res, options = { host:
|
|
|
389
382
|
};
|
|
390
383
|
|
|
391
384
|
// push session
|
|
392
|
-
const updatedUser = await User.findByIdAndUpdate(
|
|
385
|
+
const updatedUser = await User.findByIdAndUpdate(
|
|
386
|
+
user._id,
|
|
387
|
+
{ $push: { activeSessions: newSession } },
|
|
388
|
+
{ returnDocument: 'after' },
|
|
389
|
+
);
|
|
393
390
|
const session = updatedUser.activeSessions[updatedUser.activeSessions.length - 1];
|
|
394
391
|
const jwtid = session._id.toString();
|
|
395
392
|
|
package/src/server/backup.js
CHANGED
|
@@ -7,10 +7,8 @@
|
|
|
7
7
|
import fs from 'fs-extra';
|
|
8
8
|
import { loggerFactory } from './logger.js';
|
|
9
9
|
import { shellExec } from './process.js';
|
|
10
|
-
import dotenv from 'dotenv';
|
|
11
10
|
import Underpost from '../index.js';
|
|
12
|
-
|
|
13
|
-
dotenv.config();
|
|
11
|
+
import { loadCronDeployEnv } from './conf.js';
|
|
14
12
|
|
|
15
13
|
const logger = loggerFactory(import.meta);
|
|
16
14
|
|
|
@@ -33,6 +31,7 @@ class BackUp {
|
|
|
33
31
|
* @memberof UnderpostBakcUp
|
|
34
32
|
*/
|
|
35
33
|
static callback = async function (deployList, options = { git: false }) {
|
|
34
|
+
loadCronDeployEnv();
|
|
36
35
|
if ((!deployList || deployList === 'dd') && fs.existsSync(`./engine-private/deploy/dd.router`))
|
|
37
36
|
deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').trim();
|
|
38
37
|
|