underpost 3.2.8 → 3.2.9
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/CHANGELOG.md +102 -2
- package/CLI-HELP.md +16 -2
- package/README.md +3 -3
- package/bin/build.js +1 -2
- package/bin/file.js +1 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +17 -17
- package/scripts/k3s-node-setup.sh +2 -2
- package/scripts/nat-iptables.sh +103 -18
- package/src/cli/cluster.js +61 -14
- package/src/cli/db.js +47 -2
- package/src/cli/deploy.js +36 -7
- package/src/cli/fs.js +17 -3
- package/src/cli/index.js +21 -0
- package/src/cli/repository.js +34 -28
- package/src/cli/run.js +149 -35
- package/src/client/components/core/Auth.js +15 -5
- package/src/client/components/core/Docs.js +6 -34
- package/src/client/components/core/FileExplorer.js +6 -6
- package/src/client/components/core/Modal.js +65 -2
- package/src/client/components/core/Recover.js +4 -4
- package/src/client/components/core/Worker.js +48 -27
- package/src/client/services/default/default.management.js +20 -25
- package/src/client/services/user/guest.service.js +10 -3
- package/src/index.js +1 -1
- package/src/server/data-query.js +32 -20
- package/src/server/dns.js +22 -0
- package/src/server/start.js +14 -3
- package/src/server/valkey.js +9 -2
- package/typedoc.json +10 -1
|
@@ -675,28 +675,27 @@ class DefaultManagement {
|
|
|
675
675
|
EventsUI.onClick(`.management-table-btn-clear-filter-${id}`, async () => {
|
|
676
676
|
try {
|
|
677
677
|
const gridApi = AgGrid.grids[gridId];
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
gridApi
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
678
|
+
await DefaultManagement.runIsolated(id, async () => {
|
|
679
|
+
// Clear all filters without letting grid/query listeners trigger their own reloads.
|
|
680
|
+
DefaultManagement.clearIdFilter(id);
|
|
681
|
+
if (gridApi) {
|
|
682
|
+
gridApi.setFilterModel({});
|
|
683
|
+
gridApi.applyColumnState({ defaultState: { sort: null } });
|
|
684
|
+
}
|
|
685
|
+
if (DefaultManagement.Tokens[id]) {
|
|
686
|
+
DefaultManagement.Tokens[id].filterModel = {};
|
|
687
|
+
DefaultManagement.Tokens[id].sortModel = [];
|
|
688
|
+
}
|
|
689
|
+
const queryParams = getQueryParams();
|
|
690
|
+
setQueryParams({
|
|
691
|
+
page: queryParams.page || 1,
|
|
692
|
+
limit: queryParams.limit || DefaultManagement.Tokens[id]?.limit || 10,
|
|
693
|
+
filterModel: null,
|
|
694
|
+
sortModel: null,
|
|
695
|
+
id: null,
|
|
696
|
+
});
|
|
697
|
+
await DefaultManagement.loadTable(id, { force: true, reload: true, skipUrlUpdate: true });
|
|
697
698
|
});
|
|
698
|
-
// Reload table
|
|
699
|
-
await DefaultManagement.loadTable(id, { force: true, reload: true });
|
|
700
699
|
NotificationManager.Push({
|
|
701
700
|
html: Translate.instance('success-clear-filter') || 'Filters cleared',
|
|
702
701
|
status: 'success',
|
|
@@ -729,15 +728,11 @@ class DefaultManagement {
|
|
|
729
728
|
s(`#ag-pagination-${gridId}`).addEventListener('page-change', async (event) => {
|
|
730
729
|
const token = DefaultManagement.Tokens[id];
|
|
731
730
|
token.page = event.detail.page;
|
|
732
|
-
// Skip URL update since Pagination component already updated it
|
|
733
|
-
await DefaultManagement.loadTable(id, { skipUrlUpdate: true });
|
|
734
731
|
});
|
|
735
732
|
s(`#ag-pagination-${gridId}`).addEventListener('limit-change', async (event) => {
|
|
736
733
|
const token = DefaultManagement.Tokens[id];
|
|
737
734
|
token.limit = event.detail.limit;
|
|
738
735
|
token.page = 1; // Reset to first page
|
|
739
|
-
// Skip URL update since Pagination component already updated it
|
|
740
|
-
await DefaultManagement.loadTable(id, { skipUrlUpdate: true });
|
|
741
736
|
});
|
|
742
737
|
RouterEvents[id] = async (...args) => {
|
|
743
738
|
const queryParams = getQueryParams();
|
|
@@ -15,7 +15,14 @@ class SessionMetaDb extends Dexie {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// Lazy singleton — avoids opening IndexedDB at module-load time.
|
|
19
|
+
// Firefox is measurably slower at IDB open than Chromium; deferring the
|
|
20
|
+
// open until first actual read/write eliminates that latency from page startup.
|
|
21
|
+
let _db = null;
|
|
22
|
+
const getDb = () => {
|
|
23
|
+
if (!_db) _db = new SessionMetaDb();
|
|
24
|
+
return _db;
|
|
25
|
+
};
|
|
19
26
|
|
|
20
27
|
class GuestService {
|
|
21
28
|
static setUserToken(value = '') {
|
|
@@ -59,7 +66,7 @@ class GuestService {
|
|
|
59
66
|
|
|
60
67
|
static async setMeta(key, value) {
|
|
61
68
|
try {
|
|
62
|
-
await
|
|
69
|
+
await getDb().meta.put({ key, value, updatedAt: Date.now() });
|
|
63
70
|
} catch (error) {
|
|
64
71
|
logger.warn('session meta write failed', { key, error: error?.message });
|
|
65
72
|
}
|
|
@@ -67,7 +74,7 @@ class GuestService {
|
|
|
67
74
|
|
|
68
75
|
static async getMeta(key) {
|
|
69
76
|
try {
|
|
70
|
-
const row = await
|
|
77
|
+
const row = await getDb().meta.get(key);
|
|
71
78
|
return row ? row.value : null;
|
|
72
79
|
} catch (error) {
|
|
73
80
|
logger.warn('session meta read failed', { key, error: error?.message });
|
package/src/index.js
CHANGED
package/src/server/data-query.js
CHANGED
|
@@ -5,7 +5,16 @@
|
|
|
5
5
|
* @namespace DataQuery
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* @class DataQuery
|
|
10
|
+
* @description Utility class for parsing request query parameters into Mongoose query options,
|
|
11
|
+
* including support for AG Grid filterModel and sortModel. Provides a static `parse`
|
|
12
|
+
* method that takes in request query parameters and returns an object containing the MongoDB
|
|
13
|
+
* query, sort options, pagination skip/limit, and page number. Designed to be used in API
|
|
14
|
+
* controllers to handle complex querying needs from the frontend.
|
|
15
|
+
* @memberof DataQuery
|
|
16
|
+
*/
|
|
17
|
+
class DataQuery {
|
|
9
18
|
/**
|
|
10
19
|
* Parse request query parameters into Mongoose query options
|
|
11
20
|
* @param {Object} params - The request query parameters (req.query)
|
|
@@ -20,7 +29,7 @@ export const DataQuery = {
|
|
|
20
29
|
* @memberof DataQuery
|
|
21
30
|
* @returns {Object} { query, sort, skip, limit, page }
|
|
22
31
|
*/
|
|
23
|
-
parse
|
|
32
|
+
static parse(params = {}) {
|
|
24
33
|
let { filterModel, sortModel, page, limit, sort: sortParam, asc, order, query: defaultQuery } = params;
|
|
25
34
|
|
|
26
35
|
// === 1. Pagination ===
|
|
@@ -35,7 +44,7 @@ export const DataQuery = {
|
|
|
35
44
|
const query = DataQuery._parseFilter(filterModel, defaultQuery);
|
|
36
45
|
|
|
37
46
|
return { query, sort, skip, limit, page };
|
|
38
|
-
}
|
|
47
|
+
}
|
|
39
48
|
|
|
40
49
|
/**
|
|
41
50
|
* Parse sort parameters from AG Grid sortModel or simple sort params
|
|
@@ -47,7 +56,7 @@ export const DataQuery = {
|
|
|
47
56
|
* @return {Object} sort object for Mongoose
|
|
48
57
|
* @memberof DataQuery
|
|
49
58
|
*/
|
|
50
|
-
_parseSort
|
|
59
|
+
static _parseSort(sortModel, sortParam, asc, order) {
|
|
51
60
|
const sort = {};
|
|
52
61
|
|
|
53
62
|
// Parse sortModel from string if needed
|
|
@@ -90,7 +99,7 @@ export const DataQuery = {
|
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
return sort;
|
|
93
|
-
}
|
|
102
|
+
}
|
|
94
103
|
|
|
95
104
|
/**
|
|
96
105
|
* Parse filter parameters from AG Grid filterModel
|
|
@@ -100,7 +109,7 @@ export const DataQuery = {
|
|
|
100
109
|
* @return {Object} query object for Mongoose
|
|
101
110
|
* @memberof DataQuery
|
|
102
111
|
*/
|
|
103
|
-
_parseFilter
|
|
112
|
+
static _parseFilter(filterModel, defaultQuery) {
|
|
104
113
|
let query = defaultQuery ? { ...defaultQuery } : {};
|
|
105
114
|
|
|
106
115
|
// Parse filterModel from string if needed
|
|
@@ -127,7 +136,7 @@ export const DataQuery = {
|
|
|
127
136
|
});
|
|
128
137
|
|
|
129
138
|
return query;
|
|
130
|
-
}
|
|
139
|
+
}
|
|
131
140
|
|
|
132
141
|
/**
|
|
133
142
|
* Parse a single field filter
|
|
@@ -137,7 +146,7 @@ export const DataQuery = {
|
|
|
137
146
|
* @return {Object|null} query condition for the field or null if invalid
|
|
138
147
|
* @memberof DataQuery
|
|
139
148
|
*/
|
|
140
|
-
_parseFieldFilter
|
|
149
|
+
static _parseFieldFilter(field, filter) {
|
|
141
150
|
if (!filter || !filter.filterType) {
|
|
142
151
|
return null;
|
|
143
152
|
}
|
|
@@ -158,7 +167,7 @@ export const DataQuery = {
|
|
|
158
167
|
default:
|
|
159
168
|
return null;
|
|
160
169
|
}
|
|
161
|
-
}
|
|
170
|
+
}
|
|
162
171
|
|
|
163
172
|
/**
|
|
164
173
|
* Parse text filter
|
|
@@ -168,7 +177,7 @@ export const DataQuery = {
|
|
|
168
177
|
* @return {Object|null} query condition for the text field or null if invalid
|
|
169
178
|
* @memberof DataQuery
|
|
170
179
|
*/
|
|
171
|
-
_parseTextFilter
|
|
180
|
+
static _parseTextFilter(field, filter) {
|
|
172
181
|
const { type, filter: filterValue } = filter;
|
|
173
182
|
|
|
174
183
|
if (filterValue === null || filterValue === undefined || filterValue === '') {
|
|
@@ -219,7 +228,7 @@ export const DataQuery = {
|
|
|
219
228
|
}
|
|
220
229
|
|
|
221
230
|
return query;
|
|
222
|
-
}
|
|
231
|
+
}
|
|
223
232
|
|
|
224
233
|
/**
|
|
225
234
|
* Parse number filter
|
|
@@ -229,7 +238,7 @@ export const DataQuery = {
|
|
|
229
238
|
* @return {Object|null} query condition for the number field or null if invalid
|
|
230
239
|
* @memberof DataQuery
|
|
231
240
|
*/
|
|
232
|
-
_parseNumberFilter
|
|
241
|
+
static _parseNumberFilter(field, filter) {
|
|
233
242
|
const { type, filter: filterValue, filterTo } = filter;
|
|
234
243
|
|
|
235
244
|
if (filterValue === null || filterValue === undefined) {
|
|
@@ -281,7 +290,7 @@ export const DataQuery = {
|
|
|
281
290
|
}
|
|
282
291
|
|
|
283
292
|
return query;
|
|
284
|
-
}
|
|
293
|
+
}
|
|
285
294
|
|
|
286
295
|
/**
|
|
287
296
|
* Parse date filter
|
|
@@ -291,7 +300,7 @@ export const DataQuery = {
|
|
|
291
300
|
* @return {Object|null} query condition for the date field or null if invalid
|
|
292
301
|
* @memberof DataQuery
|
|
293
302
|
*/
|
|
294
|
-
_parseDateFilter
|
|
303
|
+
static _parseDateFilter(field, filter) {
|
|
295
304
|
const { type, dateFrom, dateTo } = filter;
|
|
296
305
|
|
|
297
306
|
// Handle blank/notBlank without dates
|
|
@@ -391,7 +400,7 @@ export const DataQuery = {
|
|
|
391
400
|
}
|
|
392
401
|
|
|
393
402
|
return query;
|
|
394
|
-
}
|
|
403
|
+
}
|
|
395
404
|
|
|
396
405
|
/**
|
|
397
406
|
* Parse set filter
|
|
@@ -401,7 +410,7 @@ export const DataQuery = {
|
|
|
401
410
|
* @return {Object|null} query condition for the set field or null if invalid
|
|
402
411
|
* @memberof DataQuery
|
|
403
412
|
*/
|
|
404
|
-
_parseSetFilter
|
|
413
|
+
static _parseSetFilter(field, filter) {
|
|
405
414
|
const { values } = filter;
|
|
406
415
|
|
|
407
416
|
if (!Array.isArray(values) || values.length === 0) {
|
|
@@ -409,7 +418,7 @@ export const DataQuery = {
|
|
|
409
418
|
}
|
|
410
419
|
|
|
411
420
|
return { [field]: { $in: values } };
|
|
412
|
-
}
|
|
421
|
+
}
|
|
413
422
|
|
|
414
423
|
/**
|
|
415
424
|
* Parse multi filter (combines multiple filters with AND/OR)
|
|
@@ -419,7 +428,7 @@ export const DataQuery = {
|
|
|
419
428
|
* @return {Object|null} query condition for the multi filter or null if invalid
|
|
420
429
|
* @memberof DataQuery
|
|
421
430
|
*/
|
|
422
|
-
_parseMultiFilter
|
|
431
|
+
static _parseMultiFilter(field, filter) {
|
|
423
432
|
const { filterModels, operator } = filter;
|
|
424
433
|
|
|
425
434
|
if (!Array.isArray(filterModels) || filterModels.length === 0) {
|
|
@@ -445,5 +454,8 @@ export const DataQuery = {
|
|
|
445
454
|
// AND operator (default)
|
|
446
455
|
return { $and: conditions };
|
|
447
456
|
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export { DataQuery };
|
|
461
|
+
export default DataQuery;
|
package/src/server/dns.js
CHANGED
|
@@ -101,6 +101,22 @@ class Dns {
|
|
|
101
101
|
return ipv4.address;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Gets the MAC address of the main (default route) network interface.
|
|
106
|
+
* @static
|
|
107
|
+
* @memberof UnderpostDns
|
|
108
|
+
* @returns {string|null} The MAC address, or null if not found.
|
|
109
|
+
*/
|
|
110
|
+
static getMainInterfaceMac() {
|
|
111
|
+
const interfaceName = Dns.getDefaultNetworkInterface();
|
|
112
|
+
const networkInfo = os.networkInterfaces()[interfaceName];
|
|
113
|
+
if (!networkInfo || networkInfo.length === 0) {
|
|
114
|
+
logger.error(`Could not find network interface: ${interfaceName}`);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return networkInfo[0].mac;
|
|
118
|
+
}
|
|
119
|
+
|
|
104
120
|
/**
|
|
105
121
|
* Setup nftables tables and chains if they don't exist.
|
|
106
122
|
* @static
|
|
@@ -491,6 +507,12 @@ class Dns {
|
|
|
491
507
|
});
|
|
492
508
|
}
|
|
493
509
|
|
|
510
|
+
if (options.mac) {
|
|
511
|
+
const mac = Dns.getMainInterfaceMac();
|
|
512
|
+
console.log(mac);
|
|
513
|
+
return mac;
|
|
514
|
+
}
|
|
515
|
+
|
|
494
516
|
let ip;
|
|
495
517
|
if (options.dhcp) ip = Dns.getLocalIPv4Address();
|
|
496
518
|
else ip = await Dns.getPublicIp();
|
package/src/server/start.js
CHANGED
|
@@ -133,11 +133,19 @@ class UnderpostStartUp {
|
|
|
133
133
|
* @param {boolean} options.underpostQuicklyInstall - Whether to use underpost quickly install.
|
|
134
134
|
* @param {boolean} options.skipPullBase - Whether to skip pulling the base code.
|
|
135
135
|
* @param {boolean} options.skipFullBuild - Whether to skip building the full client bundle.
|
|
136
|
+
* @param {boolean} options.pullBundle - When true, download pre-built client bundle from Cloudinary via pull-bundle before starting.
|
|
136
137
|
*/
|
|
137
138
|
async callback(
|
|
138
139
|
deployId = 'dd-default',
|
|
139
140
|
env = 'development',
|
|
140
|
-
options = {
|
|
141
|
+
options = {
|
|
142
|
+
build: false,
|
|
143
|
+
run: false,
|
|
144
|
+
underpostQuicklyInstall: false,
|
|
145
|
+
skipPullBase: false,
|
|
146
|
+
skipFullBuild: false,
|
|
147
|
+
pullBundle: false,
|
|
148
|
+
},
|
|
141
149
|
) {
|
|
142
150
|
Underpost.env.set('container-status', `${deployId}-${env}-build-deployment`);
|
|
143
151
|
if (options.build === true) await Underpost.start.build(deployId, env, options);
|
|
@@ -152,12 +160,14 @@ class UnderpostStartUp {
|
|
|
152
160
|
* @param {boolean} options.skipPullBase - Whether to skip pulling the base code and use the current workspace code directly.
|
|
153
161
|
* @param {boolean} options.underpostQuicklyInstall - Whether to use underpost quickly install.
|
|
154
162
|
* @param {boolean} options.skipFullBuild - Whether to skip building the full client bundle.
|
|
163
|
+
* @param {boolean} options.pullBundle - When true, download pre-built client bundle from Cloudinary via pull-bundle (must be pushed first with push-bundle).
|
|
164
|
+
* This flag is independent of skipFullBuild: it can be combined with skipFullBuild or used alone.
|
|
155
165
|
* @memberof UnderpostStartUp
|
|
156
166
|
*/
|
|
157
167
|
async build(
|
|
158
168
|
deployId = 'dd-default',
|
|
159
169
|
env = 'development',
|
|
160
|
-
options = { underpostQuicklyInstall: false, skipPullBase: false, skipFullBuild: false },
|
|
170
|
+
options = { underpostQuicklyInstall: false, skipPullBase: false, skipFullBuild: false, pullBundle: false },
|
|
161
171
|
) {
|
|
162
172
|
const buildBasePath = `/home/dd`;
|
|
163
173
|
const repoName = `engine-${deployId.split('-')[1]}`;
|
|
@@ -176,7 +186,8 @@ class UnderpostStartUp {
|
|
|
176
186
|
for (const itcScript of itcScripts)
|
|
177
187
|
if (itcScript.match(deployId)) shellExec(`node ./engine-private/itc-scripts/${itcScript}`);
|
|
178
188
|
}
|
|
179
|
-
if (
|
|
189
|
+
if (options.pullBundle === true) shellExec(`node bin run pull-bundle --deploy-id ${deployId}`);
|
|
190
|
+
else if (!options.skipFullBuild) shellExec(`node bin client ${deployId}`);
|
|
180
191
|
},
|
|
181
192
|
/**
|
|
182
193
|
* Runs a deployment.
|
package/src/server/valkey.js
CHANGED
|
@@ -22,7 +22,7 @@ const logger = loggerFactory(import.meta);
|
|
|
22
22
|
/** @type {Record<string, import('iovalkey').default>} */
|
|
23
23
|
const ValkeyInstances = {};
|
|
24
24
|
|
|
25
|
-
/** @type {Record<string, 'connected' | 'error'>} */
|
|
25
|
+
/** @type {Record<string, 'connected' | 'reconnecting' | 'error'>} */
|
|
26
26
|
const ValkeyStatus = {};
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -57,7 +57,10 @@ const createValkeyConnection = async (instance = {}, connectionOptions = {}) =>
|
|
|
57
57
|
const client = new Valkey({
|
|
58
58
|
port: connectionOptions.port ?? undefined,
|
|
59
59
|
host: connectionOptions.host ?? undefined,
|
|
60
|
-
|
|
60
|
+
// Retry indefinitely with capped exponential backoff (1 s → 30 s)
|
|
61
|
+
retryStrategy: (attempt) => Math.min(attempt * 1000, 30000),
|
|
62
|
+
// Fail commands immediately when not connected; do NOT queue them
|
|
63
|
+
maxRetriesPerRequest: 0,
|
|
61
64
|
});
|
|
62
65
|
|
|
63
66
|
client.on('ready', () => {
|
|
@@ -68,6 +71,10 @@ const createValkeyConnection = async (instance = {}, connectionOptions = {}) =>
|
|
|
68
71
|
ValkeyStatus[key] = 'error';
|
|
69
72
|
logger.error('Valkey error', { err: err?.message, instance });
|
|
70
73
|
});
|
|
74
|
+
client.on('reconnecting', () => {
|
|
75
|
+
ValkeyStatus[key] = 'reconnecting';
|
|
76
|
+
logger.warn('Valkey reconnecting...', { instance });
|
|
77
|
+
});
|
|
71
78
|
client.on('end', () => {
|
|
72
79
|
ValkeyStatus[key] = 'error';
|
|
73
80
|
logger.warn('Valkey connection ended', { instance });
|
package/typedoc.json
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Nexodev - ERP, CRM Development & Cloud DevOps Services",
|
|
3
|
-
"entryPoints": [
|
|
3
|
+
"entryPoints": [
|
|
4
|
+
"./src/server",
|
|
5
|
+
"./src/api",
|
|
6
|
+
"./src/cli",
|
|
7
|
+
"./src/db",
|
|
8
|
+
"./src/ws",
|
|
9
|
+
"./src/grpc",
|
|
10
|
+
"./src/mailer",
|
|
11
|
+
"./src/runtime"
|
|
12
|
+
],
|
|
4
13
|
"entryPointStrategy": "expand",
|
|
5
14
|
"exclude": ["**/node_modules/**", "**/docs/**", "**/client/**"],
|
|
6
15
|
"out": "./public/www.nexodev.org/docs/",
|