lupine.api 1.1.46 → 1.1.48
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.
|
@@ -71,6 +71,14 @@ export class AdminFrameHelper {
|
|
|
71
71
|
this.isDevAdmin = isDevAdmin;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
maxWidthMobileMenu = '700px';
|
|
75
|
+
setMaxWidthMobileMenu(maxWidthMobileMenu: string) {
|
|
76
|
+
this.maxWidthMobileMenu = maxWidthMobileMenu;
|
|
77
|
+
}
|
|
78
|
+
getMaxWidthMobileMenu() {
|
|
79
|
+
return this.maxWidthMobileMenu;
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
maxTabsCount = 20;
|
|
75
83
|
setMaxTabsCount(count: number) {
|
|
76
84
|
this.maxTabsCount = count;
|
package/admin/admin-frame.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bindGlobalStyle, CssProps, RefProps, clearCookie, TabsPageProps } from 'lupine.components';
|
|
1
|
+
import { bindGlobalStyle, CssProps, RefProps, clearCookie, TabsPageProps, getRenderPageProps } from 'lupine.components';
|
|
2
2
|
import { MenuSidebar, ThemeSelector, TabsHookProps, Tabs } from 'lupine.components';
|
|
3
3
|
import { adminCss } from './admin-css';
|
|
4
4
|
import { adminFrameHelper } from './admin-frame-helper';
|
|
@@ -109,6 +109,7 @@ export const AdminFrame = (props: AdminFrameProps) => {
|
|
|
109
109
|
const onLogoutClick = async () => {
|
|
110
110
|
clearCookie('_token_dev', '/');
|
|
111
111
|
await adminFrameHelper.getAppAdminHookLogout()?.();
|
|
112
|
+
await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/logout');
|
|
112
113
|
window.location.href = '/admin_dev';
|
|
113
114
|
};
|
|
114
115
|
|
package/admin/admin-release.tsx
CHANGED
|
@@ -94,7 +94,7 @@ const ReleaseList = (props: ReleaseListProps) => {
|
|
|
94
94
|
</label>
|
|
95
95
|
</div>
|
|
96
96
|
))}
|
|
97
|
-
<label class='label mr-m release-label'>(
|
|
97
|
+
<label class='label mr-m release-label'>(Skip *.js.map files)</label>
|
|
98
98
|
</div>
|
|
99
99
|
</div>
|
|
100
100
|
<LogList logs={props.result.logs} onLogClick={props.onLogClick} />
|
|
@@ -116,12 +116,13 @@ const LogList = (props: {
|
|
|
116
116
|
<div class='row-box mt-m'>
|
|
117
117
|
<label class='label mr-m release-label'>Logs:</label>
|
|
118
118
|
<div type='text'>
|
|
119
|
-
{props.logs &&
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
{props.logs &&
|
|
120
|
+
props.logs.map((log: { name: string; size: number; time: string }) => (
|
|
121
|
+
<div>
|
|
122
|
+
<label class='release-log' onClick={() => props.onLogClick(log.name)}>{`${log.name}`}</label> (
|
|
123
|
+
{log.time}; {formatBytes(log.size)}){' '}
|
|
124
|
+
</div>
|
|
125
|
+
))}
|
|
125
126
|
</div>
|
|
126
127
|
</div>
|
|
127
128
|
</div>
|
|
@@ -131,7 +132,7 @@ const LogList = (props: {
|
|
|
131
132
|
export const AdminReleasePage = () => {
|
|
132
133
|
const fetchData = async (options: { targetUrl: string; accessToken: string; log?: boolean }) => {
|
|
133
134
|
const data = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/check', options);
|
|
134
|
-
console.log('
|
|
135
|
+
console.log('release/check', data);
|
|
135
136
|
return data.json;
|
|
136
137
|
};
|
|
137
138
|
const css: CssProps = {
|
|
@@ -146,11 +147,18 @@ export const AdminReleasePage = () => {
|
|
|
146
147
|
const domLog = new HtmlVar('');
|
|
147
148
|
const domUpdate = new HtmlVar('');
|
|
148
149
|
const getDomData = () => {
|
|
149
|
-
const
|
|
150
|
+
const domFromList = ref.$('.from-list');
|
|
151
|
+
let fromValue = '';
|
|
152
|
+
if (!domFromList) {
|
|
153
|
+
const dataOld = JSON.parse(localStorage.getItem('admin-release') || '{}');
|
|
154
|
+
fromValue = dataOld.fromList;
|
|
155
|
+
} else {
|
|
156
|
+
fromValue = domFromList.value;
|
|
157
|
+
}
|
|
150
158
|
const data = {
|
|
151
|
-
targetUrl:
|
|
152
|
-
accessToken:
|
|
153
|
-
fromList:
|
|
159
|
+
targetUrl: ref.$('.target-url').value,
|
|
160
|
+
accessToken: ref.$('.access-token').value,
|
|
161
|
+
fromList: fromValue,
|
|
154
162
|
};
|
|
155
163
|
localStorage.setItem('admin-release', JSON.stringify(data));
|
|
156
164
|
return data;
|
|
@@ -163,18 +171,23 @@ export const AdminReleasePage = () => {
|
|
|
163
171
|
return;
|
|
164
172
|
}
|
|
165
173
|
|
|
166
|
-
const fromList =
|
|
167
|
-
const toList =
|
|
168
|
-
const chkServer =
|
|
169
|
-
const chkApi =
|
|
170
|
-
const chkWeb =
|
|
171
|
-
const webSub =
|
|
172
|
-
const webSubs =
|
|
174
|
+
const fromList = ref.$('.from-list').value;
|
|
175
|
+
const toList = ref.$('.to-list').value;
|
|
176
|
+
const chkServer = ref.$('.chk-server').checked;
|
|
177
|
+
const chkApi = ref.$('.chk-api').checked;
|
|
178
|
+
const chkWeb = ref.$('.chk-web').checked;
|
|
179
|
+
// const webSub = ref.$('.input-web-sub').value;
|
|
180
|
+
const webSubs = ref.$all('.chk-web-sub') as HTMLInputElement[];
|
|
173
181
|
const webSubsChecked = Array.from(webSubs)
|
|
174
182
|
.filter((input) => input.checked)
|
|
175
183
|
.map((input) => input.value);
|
|
176
|
-
const
|
|
177
|
-
|
|
184
|
+
const wrongWebSubs = webSubsChecked.filter((s) => !s.startsWith(fromList + '_web/'));
|
|
185
|
+
if (wrongWebSubs.length > 0) {
|
|
186
|
+
NotificationMessage.sendMessage(`Some web sub folder is not under ${fromList}`, NotificationColor.Error);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const chkEnv = ref.$('.chk-env').checked;
|
|
190
|
+
const chkBackup = ref.$('.chk-backup').checked;
|
|
178
191
|
if (!chkServer && !chkApi && !chkWeb && !chkEnv) {
|
|
179
192
|
NotificationMessage.sendMessage('Please select the release options', NotificationColor.Error);
|
|
180
193
|
return;
|
|
@@ -195,16 +208,19 @@ export const AdminReleasePage = () => {
|
|
|
195
208
|
chkServer,
|
|
196
209
|
chkApi,
|
|
197
210
|
chkWeb,
|
|
198
|
-
webSub, // will be deprecated
|
|
211
|
+
// webSub, // will be deprecated
|
|
199
212
|
webSubs: webSubsChecked,
|
|
200
213
|
chkEnv,
|
|
201
214
|
chkBackup,
|
|
202
215
|
});
|
|
203
216
|
const dataResponse = await response.json;
|
|
204
|
-
console.log('
|
|
217
|
+
console.log('release/update', dataResponse);
|
|
205
218
|
releaseUpdateBtn.disabled = false;
|
|
206
219
|
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
207
|
-
NotificationMessage.sendMessage(
|
|
220
|
+
NotificationMessage.sendMessage(
|
|
221
|
+
dataResponse.message || 'Failed to update release (timeout, possibly backend is runing, please wait!)',
|
|
222
|
+
NotificationColor.Error
|
|
223
|
+
);
|
|
208
224
|
return;
|
|
209
225
|
}
|
|
210
226
|
NotificationMessage.sendMessage('Release updated successfully', NotificationColor.Success);
|
package/package.json
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { needDevAdminSession } from './admin-auth';
|
|
16
16
|
import { adminTokenHelper } from './admin-token-helper';
|
|
17
|
-
import { Readable } from 'stream';
|
|
18
17
|
|
|
19
18
|
export class AdminRelease implements IApiBase {
|
|
20
19
|
private logger = new Logger('release-api');
|
|
@@ -184,13 +183,25 @@ export class AdminRelease implements IApiBase {
|
|
|
184
183
|
}
|
|
185
184
|
|
|
186
185
|
// local dirs under _web
|
|
187
|
-
const
|
|
186
|
+
const webSub: string[] = [];
|
|
187
|
+
for (let j = 0; j < apps.length; j++) {
|
|
188
|
+
const e = apps[j];
|
|
189
|
+
const p0 = path.join(appData.apiPath, '..');
|
|
190
|
+
const subFolders = await FsUtils.getDirsFullpath(path.join(p0, e + '_web'), 5);
|
|
191
|
+
webSub.push(
|
|
192
|
+
...subFolders
|
|
193
|
+
.filter((i) => i.isDirectory())
|
|
194
|
+
.map((i) => path.join(i.parentPath.substring(p0.length + 1), i.name).replace(/\\/g, '/'))
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
// const webSub = webSubFolders.filter(i => i.isDirectory()).map(i => path.join(i.parentPath.substring(appData.webPath.length + 1), i.name).replace(/\\/g, '/')).sort();
|
|
198
|
+
|
|
188
199
|
const response = {
|
|
189
200
|
status: 'ok',
|
|
190
201
|
message: 'check.',
|
|
191
202
|
appsFrom: apps,
|
|
192
203
|
...remoteResult,
|
|
193
|
-
webSub: webSubFolders.filter((folder) => folder.isDirectory()).map((folder) => folder.name),
|
|
204
|
+
webSub: webSub, // webSubFolders.filter((folder) => folder.isDirectory()).map((folder) => folder.name),
|
|
194
205
|
};
|
|
195
206
|
ApiHelper.sendJson(req, res, response);
|
|
196
207
|
return true;
|
|
@@ -264,6 +275,7 @@ export class AdminRelease implements IApiBase {
|
|
|
264
275
|
return true;
|
|
265
276
|
}
|
|
266
277
|
|
|
278
|
+
const appData = apiCache.getAppData();
|
|
267
279
|
let targetUrl = data.targetUrl as string;
|
|
268
280
|
if (targetUrl.endsWith('/')) {
|
|
269
281
|
targetUrl = targetUrl.slice(0, -1);
|
|
@@ -291,22 +303,42 @@ export class AdminRelease implements IApiBase {
|
|
|
291
303
|
ApiHelper.sendJson(req, res, result);
|
|
292
304
|
return true;
|
|
293
305
|
}
|
|
294
|
-
if (data.webSub) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
306
|
+
// if (data.webSub) {
|
|
307
|
+
// const result2 = await this.updateSendFile(data, 'web-sub');
|
|
308
|
+
// if (!result2 || result2.status !== 'ok') {
|
|
309
|
+
// ApiHelper.sendJson(req, res, result2);
|
|
310
|
+
// return true;
|
|
311
|
+
// }
|
|
312
|
+
// }
|
|
301
313
|
|
|
302
314
|
if (data.webSubs && data.webSubs.length > 0) {
|
|
315
|
+
const subTop = path.join(appData.apiPath, '..', data.fromList + '_web/');
|
|
303
316
|
for (let i = 0; i < data.webSubs.length; i++) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
317
|
+
if (!data.webSubs[i].startsWith(data.fromList + '_web/')) {
|
|
318
|
+
const response = {
|
|
319
|
+
status: 'error',
|
|
320
|
+
message: `Error: ${data.webSubs[i]} is not under ${data.fromList}`,
|
|
321
|
+
};
|
|
322
|
+
ApiHelper.sendJson(req, res, response);
|
|
308
323
|
return true;
|
|
309
324
|
}
|
|
325
|
+
const subFolders = await FsUtils.getDirsFullpath(path.join(appData.apiPath, '..', data.webSubs[i]));
|
|
326
|
+
const subFiles = subFolders
|
|
327
|
+
.filter((e) => e.isFile())
|
|
328
|
+
.map((e) => path.join(e.parentPath.substring(subTop.length), e.name).replace(/\\/g, '/'))
|
|
329
|
+
.sort();
|
|
330
|
+
for (let j = 0; j < subFiles.length; j++) {
|
|
331
|
+
if (subFiles[j].endsWith('.js.map')) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
data.webSub = subFiles[j];
|
|
335
|
+
this.logger.info(`update, webSubs: ${data.webSubs[i]}, subFiles: ${subFiles[j]})`);
|
|
336
|
+
const result2 = await this.updateSendFile(data, 'web-sub');
|
|
337
|
+
if (!result2 || result2.status !== 'ok') {
|
|
338
|
+
ApiHelper.sendJson(req, res, result2);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
310
342
|
}
|
|
311
343
|
}
|
|
312
344
|
}
|
|
@@ -331,6 +363,7 @@ export class AdminRelease implements IApiBase {
|
|
|
331
363
|
message: 'updated',
|
|
332
364
|
};
|
|
333
365
|
ApiHelper.sendJson(req, res, response);
|
|
366
|
+
this.logger.info(`updated, successful`);
|
|
334
367
|
return true;
|
|
335
368
|
}
|
|
336
369
|
|
|
@@ -349,11 +382,13 @@ export class AdminRelease implements IApiBase {
|
|
|
349
382
|
} else if (chkOption === 'web') {
|
|
350
383
|
sendFile = path.join(appData.apiPath, '..', fromList + '_web', 'index.js');
|
|
351
384
|
} else if (chkOption === 'web-sub' && data.webSub) {
|
|
352
|
-
sendFile = path.join(appData.apiPath, '..', fromList + '_web', data.webSub, 'index.js');
|
|
385
|
+
// sendFile = path.join(appData.apiPath, '..', fromList + '_web', data.webSub, 'index.js');
|
|
386
|
+
sendFile = path.join(appData.apiPath, '..', fromList + '_web', data.webSub);
|
|
353
387
|
} else if (chkOption.startsWith('.env')) {
|
|
354
388
|
sendFile = path.join(appData.apiPath, '../../..', chkOption);
|
|
355
389
|
}
|
|
356
390
|
if (!(await FsUtils.pathExist(sendFile))) {
|
|
391
|
+
this.logger.error(`updateSendFile, not found: ${sendFile}`);
|
|
357
392
|
return { status: 'error', message: 'Client file not found: ' + sendFile };
|
|
358
393
|
}
|
|
359
394
|
const fileContent = (await FsUtils.readFile(sendFile))!;
|
|
@@ -368,6 +403,7 @@ export class AdminRelease implements IApiBase {
|
|
|
368
403
|
// })
|
|
369
404
|
const chunkSize = 1024 * 500;
|
|
370
405
|
let cnt = 0;
|
|
406
|
+
this.logger.info(`updateSendFile, sendFile: ${sendFile}, len: ${fileContent.length}`);
|
|
371
407
|
for (let i = 0; i < fileContent.length; i += chunkSize) {
|
|
372
408
|
const chunk = fileContent.slice(i, i + chunkSize);
|
|
373
409
|
if (!chunk) break;
|
|
@@ -376,9 +412,15 @@ export class AdminRelease implements IApiBase {
|
|
|
376
412
|
method: 'POST',
|
|
377
413
|
body: JSON.stringify({ ...data, chkOption, index: cnt, size: fileContent.length }) + '\n\n' + chunk,
|
|
378
414
|
};
|
|
379
|
-
this.logger.
|
|
415
|
+
this.logger.info(
|
|
416
|
+
`updateSendFile, index: ${cnt}, sending: ${chunk.length} (${i + chunk.length} / ${
|
|
417
|
+
fileContent.length
|
|
418
|
+
}), f: ${sendFile}`
|
|
419
|
+
);
|
|
420
|
+
i > 0 && (await new Promise((resolve) => setTimeout(resolve, 1000)));
|
|
380
421
|
const remoteData = await fetch(targetUrl + '/api/admin/release/byClientUpdate', postData);
|
|
381
422
|
const resultText = await remoteData.text();
|
|
423
|
+
this.logger.info(`updateSendFile, index: ${cnt}, resultText: ${resultText}`);
|
|
382
424
|
let remoteResult: any;
|
|
383
425
|
try {
|
|
384
426
|
remoteResult = JSON.parse(resultText);
|
|
@@ -437,11 +479,11 @@ export class AdminRelease implements IApiBase {
|
|
|
437
479
|
} else if (chkOption === 'web') {
|
|
438
480
|
saveFile = path.join(appData.apiPath, '..', toList + '_web', 'index.js');
|
|
439
481
|
} else if (chkOption === 'web-sub' && data.webSub) {
|
|
440
|
-
const folder = path.join(appData.apiPath, '..', toList + '_web', data.webSub);
|
|
482
|
+
const folder = path.join(appData.apiPath, '..', toList + '_web', path.basename(data.webSub));
|
|
441
483
|
if (!(await FsUtils.pathExist(folder))) {
|
|
442
484
|
await FsUtils.mkdir(folder);
|
|
443
485
|
}
|
|
444
|
-
saveFile = path.join(appData.apiPath, '..', toList + '_web', data.webSub
|
|
486
|
+
saveFile = path.join(appData.apiPath, '..', toList + '_web', data.webSub);
|
|
445
487
|
} else if ((chkOption as string).startsWith('.env')) {
|
|
446
488
|
saveFile = path.join(appData.apiPath, '../../..', chkOption);
|
|
447
489
|
}
|
|
@@ -460,6 +502,10 @@ export class AdminRelease implements IApiBase {
|
|
|
460
502
|
await FsUtils.writeFile(bakFile, bakContent);
|
|
461
503
|
}
|
|
462
504
|
}
|
|
505
|
+
|
|
506
|
+
this.logger.info(
|
|
507
|
+
`byClientUpdate, index: ${data.index}, saveFile: ${saveFile}, received len: ${(fileContent || '').length}`
|
|
508
|
+
);
|
|
463
509
|
if (data.index === 0) {
|
|
464
510
|
await FsUtils.writeFile(saveFile, fileContent || '');
|
|
465
511
|
} else {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Dirent } from 'fs';
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
3
4
|
|
|
4
5
|
export type FileInfoProps = {
|
|
5
6
|
size: number;
|
|
@@ -118,13 +119,26 @@ export class FsUtils {
|
|
|
118
119
|
};
|
|
119
120
|
|
|
120
121
|
// return with fullpath list of Dirent
|
|
121
|
-
static getDirsFullpath = async (dirPath: string): Promise<Dirent[]> => {
|
|
122
|
+
static getDirsFullpath = async (dirPath: string, maxDepth = 1): Promise<Dirent[]> => {
|
|
123
|
+
return this.getDirsFullpathDepthSub(dirPath, 0, maxDepth);
|
|
124
|
+
};
|
|
125
|
+
private static getDirsFullpathDepthSub = async (dirPath: string, depth = 0, maxDepth = 1): Promise<Dirent[]> => {
|
|
122
126
|
try {
|
|
127
|
+
const ret = [];
|
|
123
128
|
const files = await fs.readdir(dirPath, {
|
|
124
129
|
recursive: false,
|
|
125
130
|
withFileTypes: true,
|
|
126
131
|
});
|
|
127
|
-
|
|
132
|
+
ret.push(...files);
|
|
133
|
+
if (depth + 1 < maxDepth) {
|
|
134
|
+
for (const entry of files) {
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
137
|
+
ret.push(...(await this.getDirsFullpathDepthSub(fullPath, depth + 1, maxDepth)));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return ret;
|
|
128
142
|
} catch {
|
|
129
143
|
return [];
|
|
130
144
|
}
|