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;
@@ -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
 
@@ -94,7 +94,7 @@ const ReleaseList = (props: ReleaseListProps) => {
94
94
  </label>
95
95
  </div>
96
96
  ))}
97
- <label class='label mr-m release-label'>(Only update index.js file)</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 && props.logs.map((log: { name: string; size: number; time: string }) => (
120
- <div>
121
- <label class='release-log' onClick={() => props.onLogClick(log.name)}>{`${log.name}`}</label> ({log.time};{' '}
122
- {formatBytes(log.size)}){' '}
123
- </div>
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('AdminRelease', data);
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 dataOld = JSON.parse(localStorage.getItem('admin-release') || '{}');
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: DomUtils.getValue('.target-url'),
152
- accessToken: DomUtils.getValue('.access-token'),
153
- fromList: DomUtils.getValue('.from-list') || dataOld.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 = DomUtils.getValue('.from-list');
167
- const toList = DomUtils.getValue('.to-list');
168
- const chkServer = DomUtils.getChecked('.chk-server');
169
- const chkApi = DomUtils.getChecked('.chk-api');
170
- const chkWeb = DomUtils.getChecked('.chk-web');
171
- const webSub = DomUtils.getValue('.input-web-sub');
172
- const webSubs = document.querySelectorAll<HTMLInputElement>('.chk-web-sub');
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 chkEnv = DomUtils.getChecked('.chk-env');
177
- const chkBackup = DomUtils.getChecked('.chk-backup');
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('AdminRelease', dataResponse);
217
+ console.log('release/update', dataResponse);
205
218
  releaseUpdateBtn.disabled = false;
206
219
  if (!dataResponse || dataResponse.status !== 'ok') {
207
- NotificationMessage.sendMessage(dataResponse.message || 'Failed to update release', NotificationColor.Error);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.api",
3
- "version": "1.1.46",
3
+ "version": "1.1.48",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -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 webSubFolders = await FsUtils.getDirsFullpath(appData.webPath);
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
- const result2 = await this.updateSendFile(data, 'web-sub');
296
- if (!result2 || result2.status !== 'ok') {
297
- ApiHelper.sendJson(req, res, result2);
298
- return true;
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
- data.webSub = data.webSubs[i];
305
- const result2 = await this.updateSendFile(data, 'web-sub');
306
- if (!result2 || result2.status !== 'ok') {
307
- ApiHelper.sendJson(req, res, result2);
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.debug(`updateSendFile, index: ${cnt}, sending (max): ${i + chunkSize} / ${fileContent.length}`);
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, 'index.js');
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
- return files;
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
  }