@underpostnet/underpost 2.97.5 → 2.98.1
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/.vscode/settings.json +7 -8
- package/README.md +2 -2
- package/bin/build.js +21 -5
- package/bin/deploy.js +1 -0
- package/bin/file.js +2 -1
- package/bin/util.js +0 -17
- package/cli.md +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 +2 -4
- package/scripts/rocky-pwa.sh +200 -0
- package/scripts/rocky-setup.sh +12 -39
- package/src/api/document/document.model.js +1 -1
- package/src/api/document/document.service.js +88 -98
- package/src/cli/cluster.js +5 -9
- package/src/cli/repository.js +9 -9
- package/src/cli/run.js +108 -106
- package/src/client/components/core/Auth.js +2 -0
- package/src/client/components/core/Content.js +52 -4
- package/src/client/components/core/Css.js +30 -0
- package/src/client/components/core/FileExplorer.js +699 -42
- package/src/client/components/core/Input.js +3 -1
- package/src/client/components/core/Panel.js +93 -23
- package/src/client/components/core/PanelForm.js +1 -0
- package/src/client/components/core/Responsive.js +15 -7
- package/src/client/components/core/SearchBox.js +0 -110
- package/src/client/components/core/Translate.js +58 -0
- package/src/client/services/default/default.management.js +327 -148
- package/src/client/sw/default.sw.js +107 -184
- package/src/index.js +58 -20
- package/src/client/components/core/ObjectLayerEngine.js +0 -1520
- package/src/client/components/core/ObjectLayerEngineModal.js +0 -1245
- package/src/client/components/core/ObjectLayerEngineViewer.js +0 -880
- package/src/server/object-layer.js +0 -335
|
@@ -31,6 +31,8 @@ const DocumentService = {
|
|
|
31
31
|
const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
|
|
32
32
|
/** @type {import('../user/user.model.js').UserModel} */
|
|
33
33
|
const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
|
|
34
|
+
/** @type {import('../file/file.model.js').FileModel} */
|
|
35
|
+
const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
|
|
34
36
|
|
|
35
37
|
// High-query endpoint for typeahead search
|
|
36
38
|
//
|
|
@@ -38,21 +40,8 @@ const DocumentService = {
|
|
|
38
40
|
// - Unauthenticated users: CAN see public documents (isPublic=true) from publishers (admin/moderator)
|
|
39
41
|
// - Authenticated users: CAN see public documents from publishers + ALL their own documents (public or private)
|
|
40
42
|
// - No user can see private documents from other users
|
|
41
|
-
//
|
|
42
|
-
// Search Optimization Strategy:
|
|
43
|
-
// 1. Case-insensitive matching ($options: 'i') - maximizes matches across case variations
|
|
44
|
-
// 2. Multi-term search - splits "hello world" into ["hello", "world"] and matches ANY term
|
|
45
|
-
// 3. Multi-field search - searches BOTH title AND tags array
|
|
46
|
-
// 4. OR logic - ANY term matching ANY field counts as a match
|
|
47
|
-
// 5. Minimum length: 1 character - allows maximum user flexibility
|
|
48
|
-
//
|
|
49
|
-
// Example: Query "javascript tutorial"
|
|
50
|
-
// - Matches documents with title "JavaScript Guide" (term 1, case-insensitive)
|
|
51
|
-
// - Matches documents with tag "tutorial" (term 2, tag match)
|
|
52
|
-
// - Matches documents with both terms in different fields
|
|
53
|
-
//
|
|
43
|
+
// - PANEL FILTER: Only documents with idPanel tag are returned (prevents out-of-panel context results)
|
|
54
44
|
if (req.path.startsWith('/public/high') && req.query['q']) {
|
|
55
|
-
// Input validation
|
|
56
45
|
const rawQuery = req.query['q'];
|
|
57
46
|
if (!rawQuery || typeof rawQuery !== 'string') {
|
|
58
47
|
throw new Error('Invalid search query');
|
|
@@ -68,6 +57,13 @@ const DocumentService = {
|
|
|
68
57
|
throw new Error('Search query too long (max 100 characters)');
|
|
69
58
|
}
|
|
70
59
|
|
|
60
|
+
// Get idPanel filter to prevent out-of-panel context results
|
|
61
|
+
const idPanel = req.query['idPanel'];
|
|
62
|
+
if (!idPanel || typeof idPanel !== 'string') {
|
|
63
|
+
logger.warn('Missing idPanel parameter for high-query search');
|
|
64
|
+
return { data: [] };
|
|
65
|
+
}
|
|
66
|
+
|
|
71
67
|
const publisherUsers = await User.find({ $or: [{ role: 'admin' }, { role: 'moderator' }] });
|
|
72
68
|
|
|
73
69
|
const token = getBearerToken(req);
|
|
@@ -97,15 +93,9 @@ const DocumentService = {
|
|
|
97
93
|
const queryPayload = {
|
|
98
94
|
isPublic: true,
|
|
99
95
|
userId: { $in: publisherUsers.map((p) => p._id) },
|
|
96
|
+
tags: { $in: [idPanel] }, // Filter by idPanel to prevent out-of-panel context results (tags is array)
|
|
100
97
|
};
|
|
101
98
|
|
|
102
|
-
logger.info('Special "public" search query', {
|
|
103
|
-
authenticated: !!user,
|
|
104
|
-
userId: user?._id?.toString(),
|
|
105
|
-
role: user?.role,
|
|
106
|
-
limit,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
99
|
const data = await Document.find(queryPayload)
|
|
110
100
|
.sort({ createdAt: -1 })
|
|
111
101
|
.limit(limit)
|
|
@@ -151,21 +141,7 @@ const DocumentService = {
|
|
|
151
141
|
.map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); // Escape each term for regex safety
|
|
152
142
|
|
|
153
143
|
// Build query based on authentication status
|
|
154
|
-
//
|
|
155
|
-
// OPTIMIZED FOR MAXIMUM RESULTS:
|
|
156
|
-
// - Multi-term search: matches ANY term
|
|
157
|
-
// - Case-insensitive: $options: 'i' flag
|
|
158
|
-
// - Multi-field: searches title AND tags
|
|
159
|
-
// - Minimum match: ANY term in ANY field = result
|
|
160
|
-
//
|
|
161
|
-
// Example Query: "javascript react"
|
|
162
|
-
// Matches:
|
|
163
|
-
// ✓ Document with title "JavaScript Tutorial" (term 1 in title)
|
|
164
|
-
// ✓ Document with tag "react" (term 2 in tags)
|
|
165
|
-
// ✓ Document with title "Learn React JS" (term 2 in title, case-insensitive)
|
|
166
|
-
// ✓ Document with tags ["javascript", "tutorial"] (term 1 in tags)
|
|
167
|
-
|
|
168
|
-
// Build search conditions for maximum permissiveness
|
|
144
|
+
// and search conditions for maximum permissiveness
|
|
169
145
|
const buildSearchConditions = () => {
|
|
170
146
|
const conditions = [];
|
|
171
147
|
|
|
@@ -185,14 +161,11 @@ const DocumentService = {
|
|
|
185
161
|
// Authenticated user can see:
|
|
186
162
|
// 1. ALL their own documents (public AND private - no tag restriction)
|
|
187
163
|
// 2. Public-tagged documents from publishers (admin/moderator only)
|
|
188
|
-
|
|
189
|
-
// MAXIMUM RESULTS STRATEGY:
|
|
190
|
-
// - Search by: ANY term matches title OR ANY tag
|
|
191
|
-
// - Case-insensitive matching
|
|
192
|
-
// - No minimum match threshold beyond 1 character
|
|
164
|
+
|
|
193
165
|
const searchConditions = buildSearchConditions();
|
|
194
166
|
|
|
195
167
|
queryPayload = {
|
|
168
|
+
tags: { $in: [idPanel] }, // Filter by idPanel to prevent out-of-panel context results (tags is array)
|
|
196
169
|
$or: [
|
|
197
170
|
{
|
|
198
171
|
// Public documents from publishers (admin/moderator)
|
|
@@ -210,14 +183,11 @@ const DocumentService = {
|
|
|
210
183
|
};
|
|
211
184
|
} else {
|
|
212
185
|
// Public/unauthenticated user: ONLY public-tagged documents from publishers (admin/moderator)
|
|
213
|
-
|
|
214
|
-
// MAXIMUM RESULTS STRATEGY for public users:
|
|
215
|
-
// - Search by: ANY term matches title OR ANY tag
|
|
216
|
-
// - Case-insensitive matching
|
|
217
|
-
// - Still respects security: only public docs from trusted publishers
|
|
186
|
+
|
|
218
187
|
const searchConditions = buildSearchConditions();
|
|
219
188
|
|
|
220
189
|
queryPayload = {
|
|
190
|
+
tags: { $in: [idPanel] }, // Filter by idPanel to prevent out-of-panel context results (tags is array)
|
|
221
191
|
userId: { $in: publisherUsers.map((p) => p._id) },
|
|
222
192
|
isPublic: true,
|
|
223
193
|
$or: searchConditions, // ANY term in title OR tags
|
|
@@ -230,19 +200,6 @@ const DocumentService = {
|
|
|
230
200
|
return { data: [] };
|
|
231
201
|
}
|
|
232
202
|
|
|
233
|
-
// Security audit logging
|
|
234
|
-
logger.info('High-query search (OPTIMIZED FOR MAX RESULTS)', {
|
|
235
|
-
query: searchQuery.substring(0, 50), // Log only first 50 chars for privacy
|
|
236
|
-
terms: searchTerms.length, // Number of search terms
|
|
237
|
-
searchStrategy: 'multi-term OR matching, case-insensitive, title+tags',
|
|
238
|
-
authenticated: !!user,
|
|
239
|
-
userId: user?._id?.toString(),
|
|
240
|
-
role: user?.role,
|
|
241
|
-
limit,
|
|
242
|
-
publishersCount: publisherUsers.length,
|
|
243
|
-
timestamp: new Date().toISOString(),
|
|
244
|
-
});
|
|
245
|
-
|
|
246
203
|
const data = await Document.find(queryPayload)
|
|
247
204
|
.sort({ createdAt: -1 })
|
|
248
205
|
.limit(limit)
|
|
@@ -265,10 +222,7 @@ const DocumentService = {
|
|
|
265
222
|
}
|
|
266
223
|
}
|
|
267
224
|
// Remove role field from userId before sending to client (all users)
|
|
268
|
-
|
|
269
|
-
const { role, ...userWithoutRole } = filteredDoc.userId;
|
|
270
|
-
filteredDoc.userId = userWithoutRole;
|
|
271
|
-
}
|
|
225
|
+
delete filteredDoc.userId.role;
|
|
272
226
|
return filteredDoc;
|
|
273
227
|
});
|
|
274
228
|
|
|
@@ -372,18 +326,7 @@ const DocumentService = {
|
|
|
372
326
|
};
|
|
373
327
|
}
|
|
374
328
|
}
|
|
375
|
-
|
|
376
|
-
logger.info('Public tag search', {
|
|
377
|
-
authenticated: !!user,
|
|
378
|
-
userId: user?._id?.toString(),
|
|
379
|
-
role: user?.role,
|
|
380
|
-
requestedTags,
|
|
381
|
-
hasPublicTag,
|
|
382
|
-
hasCidFilter: !!req.query.cid,
|
|
383
|
-
limit,
|
|
384
|
-
skip,
|
|
385
|
-
publishersCount: publisherUsers.length,
|
|
386
|
-
});
|
|
329
|
+
|
|
387
330
|
// sort in descending (-1) order by length
|
|
388
331
|
const sort = { createdAt: -1 };
|
|
389
332
|
|
|
@@ -417,9 +360,11 @@ const DocumentService = {
|
|
|
417
360
|
if ((!docObj.isPublic || !isPublisher) && !isOwnDoc) userInfo = undefined;
|
|
418
361
|
return {
|
|
419
362
|
...docObj,
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
363
|
+
userId: {
|
|
364
|
+
...userInfo,
|
|
365
|
+
role: undefined,
|
|
366
|
+
email: undefined,
|
|
367
|
+
},
|
|
423
368
|
tags: DocumentDto.filterPublicTag(docObj.tags),
|
|
424
369
|
totalCopyShareLinkCount: DocumentDto.getTotalCopyShareLinkCount(doc),
|
|
425
370
|
};
|
|
@@ -430,32 +375,65 @@ const DocumentService = {
|
|
|
430
375
|
|
|
431
376
|
switch (req.params.id) {
|
|
432
377
|
default: {
|
|
433
|
-
|
|
378
|
+
// Simple pagination support for FileExplorer
|
|
379
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 50;
|
|
380
|
+
const skip = req.query.skip ? parseInt(req.query.skip, 10) : 0;
|
|
381
|
+
|
|
382
|
+
// Search filter parameters
|
|
383
|
+
const searchTitle = req.query.searchTitle ? req.query.searchTitle.trim() : '';
|
|
384
|
+
const searchMdFile = req.query.searchMdFile ? req.query.searchMdFile.trim() : '';
|
|
385
|
+
const searchFile = req.query.searchFile ? req.query.searchFile.trim() : '';
|
|
386
|
+
|
|
387
|
+
const query = {
|
|
434
388
|
userId: req.auth.user._id,
|
|
435
389
|
...(req.params.id ? { _id: req.params.id } : undefined),
|
|
436
|
-
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Filter by title
|
|
393
|
+
if (searchTitle) {
|
|
394
|
+
const searchRegex = searchTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
395
|
+
query.title = { $regex: searchRegex, $options: 'i' };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Filter by markdown file name
|
|
399
|
+
if (searchMdFile) {
|
|
400
|
+
const searchRegex = searchMdFile.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
401
|
+
const files = await File.find({ name: { $regex: searchRegex, $options: 'i' } }).select('_id');
|
|
402
|
+
query.mdFileId = { $in: files.map((f) => f._id) };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Filter by generic file name
|
|
406
|
+
if (searchFile) {
|
|
407
|
+
const searchRegex = searchFile.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
408
|
+
const files = await File.find({ name: { $regex: searchRegex, $options: 'i' } }).select('_id');
|
|
409
|
+
query.fileId = { $in: files.map((f) => f._id) };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Get total count for pagination
|
|
413
|
+
const totalCount = await Document.countDocuments(query);
|
|
414
|
+
|
|
415
|
+
const data = await Document.find(query)
|
|
416
|
+
.sort({ createdAt: -1 })
|
|
417
|
+
.limit(limit)
|
|
418
|
+
.skip(skip)
|
|
437
419
|
.populate(DocumentDto.populate.file())
|
|
438
420
|
.populate(DocumentDto.populate.mdFile())
|
|
439
421
|
.populate(DocumentDto.populate.user());
|
|
440
422
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
tags: DocumentDto.filterPublicTag(docObj.tags),
|
|
456
|
-
totalCopyShareLinkCount: DocumentDto.getTotalCopyShareLinkCount(doc),
|
|
457
|
-
};
|
|
458
|
-
});
|
|
423
|
+
return {
|
|
424
|
+
data,
|
|
425
|
+
pagination: {
|
|
426
|
+
totalCount,
|
|
427
|
+
limit,
|
|
428
|
+
skip,
|
|
429
|
+
hasMore: skip + data.length < totalCount,
|
|
430
|
+
search: {
|
|
431
|
+
title: searchTitle,
|
|
432
|
+
mdFile: searchMdFile,
|
|
433
|
+
file: searchFile,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
};
|
|
459
437
|
}
|
|
460
438
|
}
|
|
461
439
|
},
|
|
@@ -502,6 +480,18 @@ const DocumentService = {
|
|
|
502
480
|
File,
|
|
503
481
|
});
|
|
504
482
|
|
|
483
|
+
// Update file names if provided
|
|
484
|
+
if (req.body.mdFileName && document.mdFileId) {
|
|
485
|
+
await File.findByIdAndUpdate(document.mdFileId, { name: req.body.mdFileName });
|
|
486
|
+
}
|
|
487
|
+
if (req.body.fileName && document.fileId) {
|
|
488
|
+
await File.findByIdAndUpdate(document.fileId, { name: req.body.fileName });
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Remove file name fields from body as they are not part of Document schema
|
|
492
|
+
delete req.body.mdFileName;
|
|
493
|
+
delete req.body.fileName;
|
|
494
|
+
|
|
505
495
|
// Extract 'public' from tags and set isPublic field on update
|
|
506
496
|
const { isPublic, tags } = DocumentDto.extractPublicFromTags(req.body.tags);
|
|
507
497
|
req.body.isPublic = isPublic;
|
package/src/cli/cluster.js
CHANGED
|
@@ -249,15 +249,11 @@ class UnderpostCluster {
|
|
|
249
249
|
} else {
|
|
250
250
|
// Kind cluster initialization (if not using kubeadm or k3s)
|
|
251
251
|
logger.info('Initializing Kind cluster...');
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
options?.dev === true ? '-dev' : ''
|
|
258
|
-
}.yaml`,
|
|
259
|
-
);
|
|
260
|
-
}
|
|
252
|
+
shellExec(
|
|
253
|
+
`cd ${underpostRoot}/manifests && kind create cluster --config kind-config${
|
|
254
|
+
options?.dev === true ? '-dev' : ''
|
|
255
|
+
}.yaml`,
|
|
256
|
+
);
|
|
261
257
|
UnderpostCluster.API.chown('kind'); // Pass 'kind' to chown
|
|
262
258
|
}
|
|
263
259
|
} else if (options.worker === true) {
|
package/src/cli/repository.js
CHANGED
|
@@ -31,13 +31,13 @@ class UnderpostRepository {
|
|
|
31
31
|
/**
|
|
32
32
|
* Clones a Git repository from GitHub.
|
|
33
33
|
* @param {string} [gitUri=`${process.env.GITHUB_USERNAME}/pwa-microservices-template`] - The URI of the GitHub repository (e.g., "username/repository").
|
|
34
|
-
* @param {object} [options={ bare: false,
|
|
34
|
+
* @param {object} [options={ bare: false, G8: false }] - Cloning options.
|
|
35
35
|
* @param {boolean} [options.bare=false] - If true, performs a bare clone.
|
|
36
36
|
* @param {boolean} [options.g8=false] - If true, uses the .g8 extension.
|
|
37
37
|
* @memberof UnderpostRepository
|
|
38
38
|
*/
|
|
39
|
-
clone(gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`, options = { bare: false,
|
|
40
|
-
const gExtension = options.
|
|
39
|
+
clone(gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`, options = { bare: false, G8: false }) {
|
|
40
|
+
const gExtension = options.G8 === true ? '.g8' : '.git';
|
|
41
41
|
const repoName = gitUri.split('/').pop();
|
|
42
42
|
if (fs.existsSync(`./${repoName}`)) fs.removeSync(`./${repoName}`);
|
|
43
43
|
shellExec(
|
|
@@ -53,16 +53,16 @@ class UnderpostRepository {
|
|
|
53
53
|
* Pulls updates from a GitHub repository.
|
|
54
54
|
* @param {string} [repoPath='./'] - The local path to the repository.
|
|
55
55
|
* @param {string} [gitUri=`${process.env.GITHUB_USERNAME}/pwa-microservices-template`] - The URI of the GitHub repository.
|
|
56
|
-
* @param {object} [options={
|
|
56
|
+
* @param {object} [options={ G8: false }] - Pulling options.
|
|
57
57
|
* @param {boolean} [options.g8=false] - If true, uses the .g8 extension.
|
|
58
58
|
* @memberof UnderpostRepository
|
|
59
59
|
*/
|
|
60
60
|
pull(
|
|
61
61
|
repoPath = './',
|
|
62
62
|
gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`,
|
|
63
|
-
options = {
|
|
63
|
+
options = { G8: false },
|
|
64
64
|
) {
|
|
65
|
-
const gExtension = options.
|
|
65
|
+
const gExtension = options.G8 === true ? '.g8' : '.git';
|
|
66
66
|
shellExec(
|
|
67
67
|
`cd ${repoPath} && git pull https://${
|
|
68
68
|
process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN}@` : ''
|
|
@@ -201,7 +201,7 @@ class UnderpostRepository {
|
|
|
201
201
|
* Pushes commits to a remote GitHub repository.
|
|
202
202
|
* @param {string} [repoPath='./'] - The local path to the repository.
|
|
203
203
|
* @param {string} [gitUri=`${process.env.GITHUB_USERNAME}/pwa-microservices-template`] - The URI of the GitHub repository.
|
|
204
|
-
* @param {object} [options={ f: false,
|
|
204
|
+
* @param {object} [options={ f: false, G8: false }] - Push options.
|
|
205
205
|
* @param {boolean} [options.f=false] - If true, forces the push.
|
|
206
206
|
* @param {boolean} [options.g8=false] - If true, uses the .g8 extension.
|
|
207
207
|
* @memberof UnderpostRepository
|
|
@@ -209,9 +209,9 @@ class UnderpostRepository {
|
|
|
209
209
|
push(
|
|
210
210
|
repoPath = './',
|
|
211
211
|
gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`,
|
|
212
|
-
options = { f: false,
|
|
212
|
+
options = { f: false, G8: false },
|
|
213
213
|
) {
|
|
214
|
-
const gExtension = options.
|
|
214
|
+
const gExtension = options.G8 === true ? '.g8' : '.git';
|
|
215
215
|
shellExec(
|
|
216
216
|
`cd ${repoPath} && git push https://${process.env.GITHUB_TOKEN}@github.com/${gitUri}${gExtension}${
|
|
217
217
|
options?.f === true ? ' --force' : ''
|
package/src/cli/run.js
CHANGED
|
@@ -146,111 +146,6 @@ class UnderpostRun {
|
|
|
146
146
|
* @memberof UnderpostRun
|
|
147
147
|
*/
|
|
148
148
|
static RUNNERS = {
|
|
149
|
-
/**
|
|
150
|
-
* @method spark-template
|
|
151
|
-
* @description Creates a new Spark template project using `sbt new` in `/home/dd/spark-template`, initializes a Git repository, and runs `replace_params.sh` and `build.sh`.
|
|
152
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
153
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
154
|
-
* @memberof UnderpostRun
|
|
155
|
-
*/
|
|
156
|
-
'spark-template': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
157
|
-
const dir = '/home/dd/spark-template';
|
|
158
|
-
shellExec(`sudo rm -rf ${dir}`);
|
|
159
|
-
shellCd('/home/dd');
|
|
160
|
-
|
|
161
|
-
// pbcopy(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8`);
|
|
162
|
-
// await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
|
|
163
|
-
shellExec(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8 '--name=spark-template'`);
|
|
164
|
-
|
|
165
|
-
shellCd(dir);
|
|
166
|
-
|
|
167
|
-
shellExec(`git init && git add . && git commit -m "Base implementation"`);
|
|
168
|
-
shellExec(`chmod +x ./replace_params.sh`);
|
|
169
|
-
shellExec(`chmod +x ./build.sh`);
|
|
170
|
-
|
|
171
|
-
shellExec(`./replace_params.sh`);
|
|
172
|
-
shellExec(`./build.sh`);
|
|
173
|
-
|
|
174
|
-
shellCd('/home/dd/engine');
|
|
175
|
-
},
|
|
176
|
-
/**
|
|
177
|
-
* @method rmi
|
|
178
|
-
* @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
|
|
179
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
180
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
181
|
-
* @memberof UnderpostRun
|
|
182
|
-
*/
|
|
183
|
-
rmi: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
184
|
-
shellExec(`podman rmi $(podman images -qa) --force`);
|
|
185
|
-
},
|
|
186
|
-
/**
|
|
187
|
-
* @method kill
|
|
188
|
-
* @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
|
|
189
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the port number).
|
|
190
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
191
|
-
* @memberof UnderpostRun
|
|
192
|
-
*/
|
|
193
|
-
kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
194
|
-
if (options.pid) return shellExec(`sudo kill -9 ${options.pid}`);
|
|
195
|
-
for (const _path of path.split(',')) {
|
|
196
|
-
if (_path.split('+')[1]) {
|
|
197
|
-
let [port, sumPortOffSet] = _path.split('+');
|
|
198
|
-
port = parseInt(port);
|
|
199
|
-
sumPortOffSet = parseInt(sumPortOffSet);
|
|
200
|
-
for (const sumPort of range(0, sumPortOffSet))
|
|
201
|
-
shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
|
|
202
|
-
} else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
/**
|
|
206
|
-
* @method secret
|
|
207
|
-
* @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
|
|
208
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
|
|
209
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
210
|
-
* @memberof UnderpostRun
|
|
211
|
-
*/
|
|
212
|
-
secret: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
213
|
-
shellExec(
|
|
214
|
-
`underpost secret underpost --create-from-file ${
|
|
215
|
-
path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`
|
|
216
|
-
}`,
|
|
217
|
-
);
|
|
218
|
-
},
|
|
219
|
-
/**
|
|
220
|
-
* @method underpost-config
|
|
221
|
-
* @description Calls `UnderpostDeploy.API.configMap` to create a Kubernetes ConfigMap, defaulting to the 'production' environment.
|
|
222
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the optional configuration name/environment).
|
|
223
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
224
|
-
* @memberof UnderpostRun
|
|
225
|
-
*/
|
|
226
|
-
'underpost-config': (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
227
|
-
UnderpostDeploy.API.configMap(path ? path : 'production', options.namespace);
|
|
228
|
-
},
|
|
229
|
-
/**
|
|
230
|
-
* @method gpu-env
|
|
231
|
-
* @description Sets up a dedicated GPU development environment cluster, resetting and then setting up the cluster with `--dedicated-gpu` and monitoring the pods.
|
|
232
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
233
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
234
|
-
* @memberof UnderpostRun
|
|
235
|
-
*/
|
|
236
|
-
'gpu-env': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
237
|
-
shellExec(
|
|
238
|
-
`node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu --kubeadm && kubectl get pods --all-namespaces -o wide -w`,
|
|
239
|
-
);
|
|
240
|
-
},
|
|
241
|
-
/**
|
|
242
|
-
* @method tf-gpu-test
|
|
243
|
-
* @description Deletes existing `tf-gpu-test-script` ConfigMap and `tf-gpu-test-pod`, and applies the test manifest from `manifests/deployment/tensorflow/tf-gpu-test.yaml`.
|
|
244
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
245
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
246
|
-
* @memberof UnderpostRun
|
|
247
|
-
*/
|
|
248
|
-
'tf-gpu-test': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
249
|
-
const { underpostRoot, namespace } = options;
|
|
250
|
-
shellExec(`kubectl delete configmap tf-gpu-test-script -n ${namespace} --ignore-not-found`);
|
|
251
|
-
shellExec(`kubectl delete pod tf-gpu-test-pod -n ${namespace} --ignore-not-found`);
|
|
252
|
-
shellExec(`kubectl apply -f ${underpostRoot}/manifests/deployment/tensorflow/tf-gpu-test.yaml -n ${namespace}`);
|
|
253
|
-
},
|
|
254
149
|
/**
|
|
255
150
|
* @method dev-cluster
|
|
256
151
|
* @description Resets and deploys a full development cluster including MongoDB, Valkey, exposes services, and updates `/etc/hosts` for local access.
|
|
@@ -899,7 +794,7 @@ EOF
|
|
|
899
794
|
'host-update': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
900
795
|
// const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
901
796
|
shellExec(`chmod +x ${options.underpostRoot}/scripts/rocky-setup.sh`);
|
|
902
|
-
shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh
|
|
797
|
+
shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
|
|
903
798
|
},
|
|
904
799
|
|
|
905
800
|
/**
|
|
@@ -1600,6 +1495,113 @@ EOF
|
|
|
1600
1495
|
],
|
|
1601
1496
|
});
|
|
1602
1497
|
},
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* @method spark-template
|
|
1501
|
+
* @description Creates a new Spark template project using `sbt new` in `/home/dd/spark-template`, initializes a Git repository, and runs `replace_params.sh` and `build.sh`.
|
|
1502
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1503
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1504
|
+
* @memberof UnderpostRun
|
|
1505
|
+
*/
|
|
1506
|
+
'spark-template': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1507
|
+
const dir = '/home/dd/spark-template';
|
|
1508
|
+
shellExec(`sudo rm -rf ${dir}`);
|
|
1509
|
+
shellCd('/home/dd');
|
|
1510
|
+
|
|
1511
|
+
// pbcopy(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8`);
|
|
1512
|
+
// await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
|
|
1513
|
+
shellExec(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8 '--name=spark-template'`);
|
|
1514
|
+
|
|
1515
|
+
shellCd(dir);
|
|
1516
|
+
|
|
1517
|
+
shellExec(`git init && git add . && git commit -m "Base implementation"`);
|
|
1518
|
+
shellExec(`chmod +x ./replace_params.sh`);
|
|
1519
|
+
shellExec(`chmod +x ./build.sh`);
|
|
1520
|
+
|
|
1521
|
+
shellExec(`./replace_params.sh`);
|
|
1522
|
+
shellExec(`./build.sh`);
|
|
1523
|
+
|
|
1524
|
+
shellCd('/home/dd/engine');
|
|
1525
|
+
},
|
|
1526
|
+
/**
|
|
1527
|
+
* @method rmi
|
|
1528
|
+
* @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
|
|
1529
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1530
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1531
|
+
* @memberof UnderpostRun
|
|
1532
|
+
*/
|
|
1533
|
+
rmi: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1534
|
+
shellExec(`podman rmi $(podman images -qa) --force`);
|
|
1535
|
+
},
|
|
1536
|
+
/**
|
|
1537
|
+
* @method kill
|
|
1538
|
+
* @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
|
|
1539
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the port number).
|
|
1540
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1541
|
+
* @memberof UnderpostRun
|
|
1542
|
+
*/
|
|
1543
|
+
kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1544
|
+
if (options.pid) return shellExec(`sudo kill -9 ${options.pid}`);
|
|
1545
|
+
for (const _path of path.split(',')) {
|
|
1546
|
+
if (_path.split('+')[1]) {
|
|
1547
|
+
let [port, sumPortOffSet] = _path.split('+');
|
|
1548
|
+
port = parseInt(port);
|
|
1549
|
+
sumPortOffSet = parseInt(sumPortOffSet);
|
|
1550
|
+
for (const sumPort of range(0, sumPortOffSet))
|
|
1551
|
+
shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
|
|
1552
|
+
} else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
|
|
1553
|
+
}
|
|
1554
|
+
},
|
|
1555
|
+
/**
|
|
1556
|
+
* @method secret
|
|
1557
|
+
* @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
|
|
1558
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
|
|
1559
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1560
|
+
* @memberof UnderpostRun
|
|
1561
|
+
*/
|
|
1562
|
+
secret: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1563
|
+
const secretPath = path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`;
|
|
1564
|
+
const command = options.dev
|
|
1565
|
+
? `node bin secret underpost --create-from-file ${secretPath}`
|
|
1566
|
+
: `underpost secret underpost --create-from-file ${secretPath}`;
|
|
1567
|
+
shellExec(command);
|
|
1568
|
+
},
|
|
1569
|
+
/**
|
|
1570
|
+
* @method underpost-config
|
|
1571
|
+
* @description Calls `UnderpostDeploy.API.configMap` to create a Kubernetes ConfigMap, defaulting to the 'production' environment.
|
|
1572
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional configuration name/environment).
|
|
1573
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1574
|
+
* @memberof UnderpostRun
|
|
1575
|
+
*/
|
|
1576
|
+
'underpost-config': (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1577
|
+
UnderpostDeploy.API.configMap(path ? path : 'production', options.namespace);
|
|
1578
|
+
},
|
|
1579
|
+
/**
|
|
1580
|
+
* @method gpu-env
|
|
1581
|
+
* @description Sets up a dedicated GPU development environment cluster, resetting and then setting up the cluster with `--dedicated-gpu` and monitoring the pods.
|
|
1582
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1583
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1584
|
+
* @memberof UnderpostRun
|
|
1585
|
+
*/
|
|
1586
|
+
'gpu-env': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1587
|
+
shellExec(
|
|
1588
|
+
`node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu --kubeadm && kubectl get pods --all-namespaces -o wide -w`,
|
|
1589
|
+
);
|
|
1590
|
+
},
|
|
1591
|
+
/**
|
|
1592
|
+
* @method tf-gpu-test
|
|
1593
|
+
* @description Deletes existing `tf-gpu-test-script` ConfigMap and `tf-gpu-test-pod`, and applies the test manifest from `manifests/deployment/tensorflow/tf-gpu-test.yaml`.
|
|
1594
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1595
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1596
|
+
* @memberof UnderpostRun
|
|
1597
|
+
*/
|
|
1598
|
+
'tf-gpu-test': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1599
|
+
const { underpostRoot, namespace } = options;
|
|
1600
|
+
shellExec(`kubectl delete configmap tf-gpu-test-script -n ${namespace} --ignore-not-found`);
|
|
1601
|
+
shellExec(`kubectl delete pod tf-gpu-test-pod -n ${namespace} --ignore-not-found`);
|
|
1602
|
+
shellExec(`kubectl apply -f ${underpostRoot}/manifests/deployment/tensorflow/tf-gpu-test.yaml -n ${namespace}`);
|
|
1603
|
+
},
|
|
1604
|
+
|
|
1603
1605
|
/**
|
|
1604
1606
|
* @method deploy-job
|
|
1605
1607
|
* @description Creates and applies a custom Kubernetes Pod manifest (Job) for running arbitrary commands inside a container image (defaulting to a TensorFlow/NVIDIA image).
|
|
@@ -11,6 +11,7 @@ import { loggerFactory } from './Logger.js';
|
|
|
11
11
|
import { LogIn } from './LogIn.js';
|
|
12
12
|
import { LogOut } from './LogOut.js';
|
|
13
13
|
import { NotificationManager } from './NotificationManager.js';
|
|
14
|
+
import { SearchBox } from './SearchBox.js';
|
|
14
15
|
import { Translate } from './Translate.js';
|
|
15
16
|
import { s } from './VanillaJs.js';
|
|
16
17
|
|
|
@@ -267,6 +268,7 @@ class Auth {
|
|
|
267
268
|
try {
|
|
268
269
|
const result = await UserService.delete({ id: 'logout' });
|
|
269
270
|
localStorage.removeItem('jwt');
|
|
271
|
+
SearchBox.RecentResults.clear();
|
|
270
272
|
this.deleteToken();
|
|
271
273
|
if (this.#refreshTimeout) {
|
|
272
274
|
clearTimeout(this.#refreshTimeout);
|