@underpostnet/underpost 2.96.1 → 2.97.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/.dockerignore +1 -2
- package/.env.development +0 -3
- package/.env.production +0 -3
- package/.env.test +0 -3
- package/.prettierignore +1 -2
- package/README.md +31 -31
- package/baremetal/commission-workflows.json +94 -17
- package/bin/deploy.js +1 -1
- package/cli.md +75 -41
- package/conf.js +1 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +4 -4
- package/package.json +3 -2
- package/packer/scripts/fuse-tar-root +3 -3
- package/scripts/disk-clean.sh +128 -187
- package/scripts/gpu-diag.sh +2 -2
- package/scripts/ip-info.sh +11 -11
- package/scripts/ipxe-setup.sh +197 -0
- package/scripts/maas-upload-boot-resource.sh +1 -1
- package/scripts/nvim.sh +1 -1
- package/scripts/packer-setup.sh +13 -13
- package/scripts/ports-ls.sh +31 -0
- package/scripts/quick-tftp.sh +19 -0
- package/scripts/rocky-setup.sh +2 -2
- package/scripts/rpmfusion-ffmpeg-setup.sh +4 -4
- package/scripts/ssl.sh +7 -7
- package/src/api/document/document.controller.js +15 -0
- package/src/api/document/document.model.js +44 -1
- package/src/api/document/document.router.js +2 -0
- package/src/api/document/document.service.js +398 -26
- package/src/cli/baremetal.js +2001 -463
- package/src/cli/cloud-init.js +354 -231
- package/src/cli/cluster.js +51 -53
- package/src/cli/db.js +22 -0
- package/src/cli/deploy.js +7 -3
- package/src/cli/image.js +1 -0
- package/src/cli/index.js +40 -37
- package/src/cli/lxd.js +3 -3
- package/src/cli/run.js +78 -12
- package/src/cli/ssh.js +1 -1
- package/src/client/components/core/Css.js +16 -2
- package/src/client/components/core/Input.js +3 -1
- package/src/client/components/core/Modal.js +125 -159
- package/src/client/components/core/Panel.js +436 -31
- package/src/client/components/core/PanelForm.js +222 -37
- package/src/client/components/core/SearchBox.js +801 -0
- package/src/client/components/core/Translate.js +11 -0
- package/src/client/services/document/document.service.js +42 -0
- package/src/index.js +1 -1
- package/src/server/dns.js +12 -6
- package/src/server/start.js +14 -6
|
@@ -6,7 +6,7 @@ import { NotificationManager } from './NotificationManager.js';
|
|
|
6
6
|
import { DocumentService } from '../../services/document/document.service.js';
|
|
7
7
|
import { FileService } from '../../services/file/file.service.js';
|
|
8
8
|
import { getSrcFromFileData } from './Input.js';
|
|
9
|
-
import { imageShimmer, renderCssAttr } from './Css.js';
|
|
9
|
+
import { imageShimmer, renderCssAttr, darkTheme, ThemeEvents, subThemeManager, lightenHex, darkenHex } from './Css.js';
|
|
10
10
|
import { Translate } from './Translate.js';
|
|
11
11
|
import { Modal } from './Modal.js';
|
|
12
12
|
import { closeModalRouteChangeEvents, listenQueryPathInstance, setQueryPath, getQueryParams } from './Router.js';
|
|
@@ -27,11 +27,17 @@ const PanelForm = {
|
|
|
27
27
|
route: 'home',
|
|
28
28
|
htmlFormHeader: async () => '',
|
|
29
29
|
firsUpdateEvent: async () => {},
|
|
30
|
+
share: {
|
|
31
|
+
copyLink: false,
|
|
32
|
+
},
|
|
33
|
+
showCreatorProfile: false,
|
|
30
34
|
},
|
|
31
35
|
) {
|
|
32
36
|
const { idPanel, defaultUrlImage, Elements } = options;
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
// Authenticated users don't need 'public' tag - they see all their own posts
|
|
39
|
+
// Only include 'public' for unauthenticated users (handled by backend)
|
|
40
|
+
let prefixTags = [idPanel];
|
|
35
41
|
this.Data[idPanel] = {
|
|
36
42
|
originData: [],
|
|
37
43
|
data: [],
|
|
@@ -48,7 +54,7 @@ const PanelForm = {
|
|
|
48
54
|
id: 'panel-title',
|
|
49
55
|
model: 'title',
|
|
50
56
|
inputType: 'text',
|
|
51
|
-
rules: [
|
|
57
|
+
rules: [],
|
|
52
58
|
panel: { type: 'title' },
|
|
53
59
|
},
|
|
54
60
|
{
|
|
@@ -80,7 +86,7 @@ const PanelForm = {
|
|
|
80
86
|
// value: html``,
|
|
81
87
|
// },
|
|
82
88
|
// },
|
|
83
|
-
rules: [
|
|
89
|
+
rules: [],
|
|
84
90
|
},
|
|
85
91
|
{
|
|
86
92
|
id: 'panel-mdFileId',
|
|
@@ -108,6 +114,8 @@ const PanelForm = {
|
|
|
108
114
|
titleIcon,
|
|
109
115
|
route: options.route,
|
|
110
116
|
formContainerClass: 'session-in-log-in',
|
|
117
|
+
share: options.share,
|
|
118
|
+
showCreatorProfile: options.showCreatorProfile,
|
|
111
119
|
onClick: async function ({ payload }) {
|
|
112
120
|
if (options.route) {
|
|
113
121
|
setQueryPath({ path: options.route, queryPath: payload._id });
|
|
@@ -169,9 +177,113 @@ const PanelForm = {
|
|
|
169
177
|
html: status,
|
|
170
178
|
status,
|
|
171
179
|
});
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
180
|
+
|
|
181
|
+
// Handle cid query param update (supports comma-separated list)
|
|
182
|
+
if (status === 'success') {
|
|
183
|
+
const currentCid = getQueryParams().cid;
|
|
184
|
+
|
|
185
|
+
if (currentCid) {
|
|
186
|
+
// Parse cid as comma-separated list
|
|
187
|
+
const cidList = currentCid
|
|
188
|
+
.split(',')
|
|
189
|
+
.map((id) => id.trim())
|
|
190
|
+
.filter((id) => id);
|
|
191
|
+
|
|
192
|
+
// Remove the deleted panel's id from the list
|
|
193
|
+
const updatedCidList = cidList.filter((id) => id !== data.id);
|
|
194
|
+
|
|
195
|
+
if (updatedCidList.length !== cidList.length) {
|
|
196
|
+
// Wait for DOM cleanup before updating query
|
|
197
|
+
|
|
198
|
+
if (updatedCidList.length === 0) {
|
|
199
|
+
// No cids remain, clear query and reload panels with limit
|
|
200
|
+
logger.warn('All cids removed, clearing query');
|
|
201
|
+
setQueryPath({ path: options.route, queryPath: '' });
|
|
202
|
+
|
|
203
|
+
if (options.parentIdModal) Modal.Data[options.parentIdModal].query = window.location.search;
|
|
204
|
+
if (PanelForm.Data[idPanel].updatePanel) await PanelForm.Data[idPanel].updatePanel();
|
|
205
|
+
} else {
|
|
206
|
+
// Update query params with remaining cids only (without ?cid= prefix)
|
|
207
|
+
const cidValue = updatedCidList.join(',');
|
|
208
|
+
setQueryPath({ path: options.route, queryPath: cidValue });
|
|
209
|
+
const actualQuery = window.location.search;
|
|
210
|
+
if (options.parentIdModal) Modal.Data[options.parentIdModal].query = actualQuery;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Return early to skip smart deletion logic when cid is present
|
|
215
|
+
return { status };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Smart deletion: remove from arrays and intelligently load more if needed
|
|
220
|
+
if (status === 'success') {
|
|
221
|
+
const panelData = PanelForm.Data[idPanel];
|
|
222
|
+
|
|
223
|
+
// Remove the deleted item from all data arrays
|
|
224
|
+
const indexInOrigin = panelData.originData.findIndex((d) => d._id === data._id);
|
|
225
|
+
const indexInData = panelData.data.findIndex((d) => d._id === data._id);
|
|
226
|
+
const indexInFiles = panelData.filesData.findIndex((d) => d._id === data._id);
|
|
227
|
+
|
|
228
|
+
if (indexInOrigin > -1) panelData.originData.splice(indexInOrigin, 1);
|
|
229
|
+
if (indexInData > -1) panelData.data.splice(indexInData, 1);
|
|
230
|
+
if (indexInFiles > -1) panelData.filesData.splice(indexInFiles, 1);
|
|
231
|
+
|
|
232
|
+
// Adjust skip count since we removed an item
|
|
233
|
+
if (panelData.skip > 0) panelData.skip--;
|
|
234
|
+
|
|
235
|
+
// If panels are below limit and there might be more, load them
|
|
236
|
+
if (panelData.data.length < panelData.limit && panelData.hasMore && !panelData.loading) {
|
|
237
|
+
const oldDataCount = panelData.data.length;
|
|
238
|
+
const needed = panelData.limit - panelData.data.length; // Calculate exact number needed
|
|
239
|
+
const originalLimit = panelData.limit;
|
|
240
|
+
|
|
241
|
+
// Temporarily set limit to only fetch what's needed (1-to-1 replacement)
|
|
242
|
+
panelData.limit = needed;
|
|
243
|
+
await getPanelData(true); // Load only the needed items
|
|
244
|
+
panelData.limit = originalLimit; // Restore original limit
|
|
245
|
+
|
|
246
|
+
const newItems = panelData.data.slice(oldDataCount);
|
|
247
|
+
|
|
248
|
+
if (oldDataCount === 0) {
|
|
249
|
+
// List was empty, render all panels
|
|
250
|
+
if (panelData.data.length > 0) {
|
|
251
|
+
const containerSelector = `.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`;
|
|
252
|
+
htmls(
|
|
253
|
+
containerSelector,
|
|
254
|
+
html`
|
|
255
|
+
<div class="in">${await panelRender({ data: panelData.data })}</div>
|
|
256
|
+
<div class="in panel-placeholder-bottom panel-placeholder-bottom-${idPanel}"></div>
|
|
257
|
+
`,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Show spinner if there's potentially more data
|
|
261
|
+
const lastOriginItem = panelData.originData[panelData.originData.length - 1];
|
|
262
|
+
if (
|
|
263
|
+
!panelData.lasIdAvailable ||
|
|
264
|
+
!lastOriginItem ||
|
|
265
|
+
panelData.lasIdAvailable !== lastOriginItem._id
|
|
266
|
+
)
|
|
267
|
+
LoadingAnimation.spinner.play(`.panel-placeholder-bottom-${idPanel}`, 'dual-ring-mini');
|
|
268
|
+
} else {
|
|
269
|
+
// No more data available, show empty state
|
|
270
|
+
const containerSelector = `.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`;
|
|
271
|
+
htmls(
|
|
272
|
+
containerSelector,
|
|
273
|
+
html`
|
|
274
|
+
<div class="in">${await panelRender({ data: [] })}</div>
|
|
275
|
+
<div class="in panel-placeholder-bottom panel-placeholder-bottom-${idPanel}"></div>
|
|
276
|
+
`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
// List had some panels, append new ones
|
|
281
|
+
if (newItems.length > 0) {
|
|
282
|
+
for (const item of newItems)
|
|
283
|
+
append(`.${idPanel}-render`, await Panel.Tokens[idPanel].renderPanel(item));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
175
287
|
}
|
|
176
288
|
|
|
177
289
|
return { status };
|
|
@@ -179,30 +291,61 @@ const PanelForm = {
|
|
|
179
291
|
return { status: 'error' };
|
|
180
292
|
},
|
|
181
293
|
initAdd: async function () {
|
|
182
|
-
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
s(`.modal-${options.route}`).scrollTo({ top: 0, behavior: 'smooth' });
|
|
296
|
+
}, 50);
|
|
183
297
|
},
|
|
184
298
|
initEdit: async function ({ data }) {
|
|
185
|
-
|
|
299
|
+
// Clear file input when entering edit mode
|
|
300
|
+
const fileFormData = formData.find((f) => f.inputType === 'file');
|
|
301
|
+
if (fileFormData && s(`.${fileFormData.id}`)) {
|
|
302
|
+
s(`.${fileFormData.id}`).value = '';
|
|
303
|
+
s(`.${fileFormData.id}`).inputFiles = null;
|
|
304
|
+
htmls(
|
|
305
|
+
`.file-name-render-${fileFormData.id}`,
|
|
306
|
+
`<div class="abs center"><i style="font-size: 25px" class="fa-solid fa-cloud"></i></div>`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
setTimeout(() => {
|
|
310
|
+
s(`.modal-${options.route}`).scrollTo({ top: 0, behavior: 'smooth' });
|
|
311
|
+
}, 50);
|
|
186
312
|
},
|
|
187
313
|
noResultFound: async function () {
|
|
188
314
|
LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
|
|
189
315
|
},
|
|
190
316
|
add: async function ({ data, editId }) {
|
|
317
|
+
// Validate that either mdFileId has content OR fileId has files
|
|
318
|
+
const hasMdContent = data.mdFileId && data.mdFileId.trim().length > 0;
|
|
319
|
+
const hasFiles = data.fileId && data.fileId.length > 0;
|
|
320
|
+
|
|
321
|
+
if (!data.title || (!hasMdContent && !hasFiles)) {
|
|
322
|
+
NotificationManager.Push({
|
|
323
|
+
html: Translate.Render('require-title-and-content-or-file'),
|
|
324
|
+
status: 'error',
|
|
325
|
+
});
|
|
326
|
+
return { data: [], status: 'error', message: 'Must provide either content or attach a file' };
|
|
327
|
+
}
|
|
328
|
+
|
|
191
329
|
let mdFileId;
|
|
192
330
|
const mdFileName = `${getCapVariableName(data.title)}.md`;
|
|
193
331
|
const location = `${prefixTags.join('/')}`;
|
|
194
332
|
const blob = new Blob([data.mdFileId], { type: 'text/markdown' });
|
|
195
333
|
const md = new File([blob], mdFileName, { type: 'text/markdown' });
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
334
|
+
// Parse and normalize tags
|
|
335
|
+
// Note: 'public' tag is automatically extracted by the backend and converted to isPublic field
|
|
336
|
+
// It will be filtered from the tags array to keep visibility control separate from content tags
|
|
337
|
+
const tags = data.tags
|
|
338
|
+
? uniqueArray(
|
|
339
|
+
data.tags
|
|
340
|
+
.replaceAll('/', ',')
|
|
341
|
+
.replaceAll('-', ',')
|
|
342
|
+
.replaceAll(' ', ',')
|
|
343
|
+
.split(',')
|
|
344
|
+
.map((t) => t.trim())
|
|
345
|
+
.filter((t) => t)
|
|
346
|
+
.concat(prefixTags),
|
|
347
|
+
)
|
|
348
|
+
: prefixTags;
|
|
206
349
|
let originObj, originFileObj, indexOriginObj;
|
|
207
350
|
if (editId) {
|
|
208
351
|
indexOriginObj = PanelForm.Data[idPanel].originData.findIndex((d) => d._id === editId);
|
|
@@ -261,6 +404,7 @@ const PanelForm = {
|
|
|
261
404
|
}
|
|
262
405
|
}
|
|
263
406
|
})();
|
|
407
|
+
// Backend will automatically extract 'public' from tags and set isPublic field
|
|
264
408
|
const body = {
|
|
265
409
|
location,
|
|
266
410
|
tags,
|
|
@@ -284,6 +428,9 @@ const PanelForm = {
|
|
|
284
428
|
_id: documentData._id,
|
|
285
429
|
id: documentData._id,
|
|
286
430
|
createdAt: documentData.createdAt,
|
|
431
|
+
// Use server response data - backend has already processed tags and isPublic
|
|
432
|
+
isPublic: documentData.isPublic || false,
|
|
433
|
+
tags: (documentData.tags || []).filter((t) => !prefixTags.includes(t)),
|
|
287
434
|
};
|
|
288
435
|
|
|
289
436
|
if (documentStatus === 'error') status = 'error';
|
|
@@ -341,10 +488,16 @@ const PanelForm = {
|
|
|
341
488
|
const panelData = PanelForm.Data[idPanel];
|
|
342
489
|
logger.warn('getPanelData called, isLoadMore:', isLoadMore);
|
|
343
490
|
try {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
491
|
+
const cidQuery = getQueryParams().cid;
|
|
492
|
+
|
|
493
|
+
// When cid query exists, bypass pagination and loading checks
|
|
494
|
+
if (!cidQuery) {
|
|
495
|
+
if (panelData.loading || !panelData.hasMore) {
|
|
496
|
+
logger.warn('getPanelData early return - loading:', panelData.loading, 'hasMore:', panelData.hasMore);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
347
499
|
}
|
|
500
|
+
|
|
348
501
|
panelData.loading = true;
|
|
349
502
|
|
|
350
503
|
if (!isLoadMore) {
|
|
@@ -353,13 +506,20 @@ const PanelForm = {
|
|
|
353
506
|
panelData.hasMore = true;
|
|
354
507
|
}
|
|
355
508
|
|
|
509
|
+
// When cid query exists, don't apply skip/limit pagination
|
|
510
|
+
const params = {
|
|
511
|
+
tags: prefixTags.join(','),
|
|
512
|
+
...(cidQuery && { cid: cidQuery }),
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// Only apply pagination when there's no cid query
|
|
516
|
+
if (!cidQuery) {
|
|
517
|
+
params.skip = panelData.skip;
|
|
518
|
+
params.limit = panelData.limit;
|
|
519
|
+
}
|
|
520
|
+
|
|
356
521
|
const result = await DocumentService.get({
|
|
357
|
-
params
|
|
358
|
-
tags: prefixTags.join(','),
|
|
359
|
-
...(getQueryParams().cid && { cid: getQueryParams().cid }),
|
|
360
|
-
skip: panelData.skip,
|
|
361
|
-
limit: panelData.limit,
|
|
362
|
-
},
|
|
522
|
+
params,
|
|
363
523
|
id: 'public/',
|
|
364
524
|
});
|
|
365
525
|
|
|
@@ -411,12 +571,24 @@ const PanelForm = {
|
|
|
411
571
|
id: documentObject._id,
|
|
412
572
|
title: documentObject.title,
|
|
413
573
|
createdAt: documentObject.createdAt,
|
|
574
|
+
// Backend filters 'public' tag automatically - it's converted to isPublic field
|
|
414
575
|
tags: documentObject.tags.filter((t) => !prefixTags.includes(t)),
|
|
415
576
|
mdFileId: marked.parse(mdFileId),
|
|
416
577
|
userId: documentObject.userId._id,
|
|
578
|
+
userInfo:
|
|
579
|
+
documentObject.userId && typeof documentObject.userId === 'object'
|
|
580
|
+
? {
|
|
581
|
+
username: documentObject.userId.username,
|
|
582
|
+
email: documentObject.userId.email,
|
|
583
|
+
_id: documentObject.userId._id,
|
|
584
|
+
profileImageId: documentObject.userId.profileImageId,
|
|
585
|
+
}
|
|
586
|
+
: null,
|
|
417
587
|
fileId,
|
|
418
588
|
tools: Elements.Data.user.main.model.user._id === documentObject.userId._id,
|
|
419
589
|
_id: documentObject._id,
|
|
590
|
+
totalCopyShareLinkCount: documentObject.totalCopyShareLinkCount || 0,
|
|
591
|
+
isPublic: documentObject.isPublic || false,
|
|
420
592
|
});
|
|
421
593
|
} catch (fileError) {
|
|
422
594
|
logger.error('Error fetching files for document:', documentObject._id, fileError);
|
|
@@ -425,9 +597,17 @@ const PanelForm = {
|
|
|
425
597
|
}
|
|
426
598
|
}
|
|
427
599
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
600
|
+
// Only update pagination when not using cid query
|
|
601
|
+
if (!cidQuery) {
|
|
602
|
+
panelData.skip += result.data.data.length;
|
|
603
|
+
panelData.hasMore = result.data.data.length === panelData.limit;
|
|
604
|
+
} else {
|
|
605
|
+
// When cid query is used, disable infinite scroll
|
|
606
|
+
panelData.hasMore = false;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const lastItem = result.data.data[result.data.data.length - 1];
|
|
610
|
+
if (result.data.data.length === 0 || (lastItem && lastItem._id === panelData.lasIdAvailable)) {
|
|
431
611
|
LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
|
|
432
612
|
panelData.hasMore = false;
|
|
433
613
|
}
|
|
@@ -538,7 +718,8 @@ const PanelForm = {
|
|
|
538
718
|
loading: false,
|
|
539
719
|
};
|
|
540
720
|
|
|
541
|
-
|
|
721
|
+
// Always reset skip to 0 when reloading (whether cid exists or not)
|
|
722
|
+
this.Data[idPanel].skip = 0;
|
|
542
723
|
|
|
543
724
|
const containerSelector = `.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`;
|
|
544
725
|
htmls(containerSelector, await renderSrrPanelData());
|
|
@@ -553,22 +734,26 @@ const PanelForm = {
|
|
|
553
734
|
`,
|
|
554
735
|
);
|
|
555
736
|
|
|
737
|
+
const lastOriginItem = this.Data[idPanel].originData[this.Data[idPanel].originData.length - 1];
|
|
556
738
|
if (
|
|
557
739
|
!this.Data[idPanel].lasIdAvailable ||
|
|
558
|
-
|
|
559
|
-
|
|
740
|
+
!lastOriginItem ||
|
|
741
|
+
this.Data[idPanel].lasIdAvailable !== lastOriginItem._id
|
|
560
742
|
)
|
|
561
743
|
LoadingAnimation.spinner.play(`.panel-placeholder-bottom-${idPanel}`, 'dual-ring-mini');
|
|
562
744
|
|
|
563
745
|
const scrollContainerSelector = `.modal-${options.route}`;
|
|
564
746
|
|
|
747
|
+
// Always remove old scroll event before setting new one
|
|
748
|
+
if (this.Data[idPanel].removeScrollEvent) {
|
|
749
|
+
this.Data[idPanel].removeScrollEvent();
|
|
750
|
+
}
|
|
751
|
+
|
|
565
752
|
if (cid) {
|
|
566
753
|
LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
|
|
567
754
|
return;
|
|
568
755
|
}
|
|
569
|
-
|
|
570
|
-
this.Data[idPanel].removeScrollEvent();
|
|
571
|
-
}
|
|
756
|
+
|
|
572
757
|
const { removeEvent } = Scroll.setEvent(scrollContainerSelector, async (payload) => {
|
|
573
758
|
const panelData = PanelForm.Data[idPanel];
|
|
574
759
|
if (!panelData) return;
|