lupine.api 1.1.55 → 1.1.57
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/admin/admin-config.tsx +1 -1
- package/admin/admin-release.tsx +36 -9
- package/admin/admin-table-data.tsx +3 -3
- package/package.json +2 -2
- package/src/admin-api/admin-config.ts +3 -1
- package/src/admin-api/admin-release.ts +65 -4
- package/src/api/api-module.ts +1 -1
- package/src/api/shell-service.ts +11 -3
- package/src/app/{app-loader.ts → app-helper.ts} +3 -3
- package/src/app/app-message.ts +2 -2
- package/src/app/app-shared-storage.ts +25 -4
- package/src/app/app-start.ts +2 -2
- package/src/app/index.ts +1 -1
- package/src/app/process-dev-requests.ts +16 -5
- package/src/models/app-shared-storage-props.ts +2 -1
- package/src/models/app-start-props.ts +1 -1
- package/src/models/index.ts +1 -1
- /package/src/models/{app-loader-props.ts → app-helper-props.ts} +0 -0
package/admin/admin-config.tsx
CHANGED
|
@@ -12,7 +12,7 @@ export const AdminConfigPage = () => {
|
|
|
12
12
|
const json = JSON.parse(ref.$('.input-cfg').value);
|
|
13
13
|
const data = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/config/save', { json });
|
|
14
14
|
if (data.json && data.json.status === 'ok') {
|
|
15
|
-
NotificationMessage.sendMessage('Saved', NotificationColor.Success);
|
|
15
|
+
NotificationMessage.sendMessage('Saved, and please refresh the page to load new configs', NotificationColor.Success);
|
|
16
16
|
}
|
|
17
17
|
} catch (e) {
|
|
18
18
|
NotificationMessage.sendMessage('Config is not valid JSON', NotificationColor.Error);
|
package/admin/admin-release.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
formatBytes,
|
|
10
10
|
downloadStream,
|
|
11
11
|
ActionSheetSelectPromise,
|
|
12
|
+
encodeHtml,
|
|
12
13
|
} from 'lupine.components';
|
|
13
14
|
|
|
14
15
|
interface ReleaseListProps {
|
|
@@ -290,7 +291,7 @@ export const AdminReleasePage = () => {
|
|
|
290
291
|
NotificationMessage.sendMessage(dataResponse.message || 'Failed to refresh cache', NotificationColor.Error);
|
|
291
292
|
return;
|
|
292
293
|
}
|
|
293
|
-
domLog.value = <pre>{JSON.stringify(dataResponse, null, 2)}</pre>;
|
|
294
|
+
domLog.value = <pre>{encodeHtml(JSON.stringify(dataResponse, null, 2))}</pre>;
|
|
294
295
|
NotificationMessage.sendMessage('Cache refreshed successfully', NotificationColor.Success);
|
|
295
296
|
};
|
|
296
297
|
|
|
@@ -308,9 +309,25 @@ export const AdminReleasePage = () => {
|
|
|
308
309
|
return;
|
|
309
310
|
}
|
|
310
311
|
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const onShellLocal = async () => {
|
|
315
|
+
return onShell(true);
|
|
316
|
+
};
|
|
317
|
+
const onShellRemote = async () => {
|
|
318
|
+
return onShell(false);
|
|
319
|
+
};
|
|
320
|
+
const onShell = async (isLocal?: boolean) => {
|
|
321
|
+
const data = getDomData();
|
|
322
|
+
if (!isLocal) {
|
|
323
|
+
if (!data.targetUrl || !data.accessToken) {
|
|
324
|
+
NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
311
328
|
|
|
312
329
|
const index = await ActionSheetSelectPromise({
|
|
313
|
-
title: '
|
|
330
|
+
title: 'Run Cmd ?',
|
|
314
331
|
options: ['OK'],
|
|
315
332
|
cancelButtonText: 'Cancel',
|
|
316
333
|
});
|
|
@@ -318,17 +335,18 @@ export const AdminReleasePage = () => {
|
|
|
318
335
|
return;
|
|
319
336
|
}
|
|
320
337
|
|
|
321
|
-
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/
|
|
338
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/shell', {
|
|
322
339
|
...data,
|
|
323
340
|
isLocal,
|
|
341
|
+
cmd: ref.$('.release-cmd').value,
|
|
324
342
|
});
|
|
325
343
|
const dataResponse = await response.json;
|
|
326
|
-
console.log('
|
|
344
|
+
console.log('shell', dataResponse);
|
|
327
345
|
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
328
|
-
NotificationMessage.sendMessage(dataResponse.message || 'Failed to
|
|
346
|
+
NotificationMessage.sendMessage(dataResponse.message || 'Failed to run cmd', NotificationColor.Error);
|
|
329
347
|
return;
|
|
330
348
|
}
|
|
331
|
-
domLog.value = <pre>{
|
|
349
|
+
domLog.value = <pre>{encodeHtml(dataResponse.message) + '\r\n<br>' + encodeHtml(dataResponse.result)}</pre>;
|
|
332
350
|
NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
|
|
333
351
|
};
|
|
334
352
|
|
|
@@ -341,19 +359,19 @@ export const AdminReleasePage = () => {
|
|
|
341
359
|
};
|
|
342
360
|
return (
|
|
343
361
|
<div ref={ref} css={css} class='admin-release-top'>
|
|
344
|
-
<div class='row-box
|
|
362
|
+
<div class='row-box mt-m'>
|
|
345
363
|
<label class='label mr-m release-label'>Target Url:</label>
|
|
346
364
|
<div class='w-50p'>
|
|
347
365
|
<input type='text' class='input-base w-100p target-url' placeholder='Target Url' />
|
|
348
366
|
</div>
|
|
349
367
|
</div>
|
|
350
|
-
<div class='row-box
|
|
368
|
+
<div class='row-box mt-m'>
|
|
351
369
|
<label class='label mr-m release-label'>Access token:</label>
|
|
352
370
|
<div class='w-50p'>
|
|
353
371
|
<input type='text' class='input-base w-100p access-token' placeholder='Access token' />
|
|
354
372
|
</div>
|
|
355
373
|
</div>
|
|
356
|
-
<div class='row-box
|
|
374
|
+
<div class='row-box mt-m'>
|
|
357
375
|
<button onClick={onCheck} class='button-base mr-m'>
|
|
358
376
|
Check
|
|
359
377
|
</button>
|
|
@@ -370,6 +388,15 @@ export const AdminReleasePage = () => {
|
|
|
370
388
|
Restart App (Local)
|
|
371
389
|
</button>
|
|
372
390
|
</div>
|
|
391
|
+
<div class='row-box mt-m mb-m'>
|
|
392
|
+
<input type='text' class='input-base w-50p release-cmd' placeholder='Command' />
|
|
393
|
+
<button onClick={onShellRemote} class='button-base color-red'>
|
|
394
|
+
Run Cmd (Remote)
|
|
395
|
+
</button>
|
|
396
|
+
<button onClick={onShellLocal} class='button-base'>
|
|
397
|
+
Run Cmd (Local)
|
|
398
|
+
</button>
|
|
399
|
+
</div>
|
|
373
400
|
{domUpdate.node}
|
|
374
401
|
{domLog.node}
|
|
375
402
|
</div>
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getDefaultPageLimit,
|
|
7
7
|
HtmlVar,
|
|
8
8
|
PagingLink,
|
|
9
|
-
|
|
9
|
+
encodeHtml,
|
|
10
10
|
} from 'lupine.components';
|
|
11
11
|
|
|
12
12
|
const loadData = async (
|
|
@@ -34,7 +34,7 @@ const loadData = async (
|
|
|
34
34
|
<div class='table'>
|
|
35
35
|
<div class='fields bg-gray'>
|
|
36
36
|
{Object.keys(data.json.result[0]).map((field: any, index: number) => {
|
|
37
|
-
return <div class='p1 item'>{
|
|
37
|
+
return <div class='p1 item'>{encodeHtml(field)}</div>;
|
|
38
38
|
})}
|
|
39
39
|
{update && update.onDelete && update.onEdit && <div class='p1 item'>Action</div>}
|
|
40
40
|
</div>
|
|
@@ -43,7 +43,7 @@ const loadData = async (
|
|
|
43
43
|
return (
|
|
44
44
|
<div class='values'>
|
|
45
45
|
{Object.keys(item).map((field: any, index: number) => {
|
|
46
|
-
return <div class='p1 item'>{
|
|
46
|
+
return <div class='p1 item'>{encodeHtml(item[field])}</div>;
|
|
47
47
|
})}
|
|
48
48
|
{update && update.onDelete && update.onEdit && (
|
|
49
49
|
<div class='p1 item'>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lupine.api",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.57",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "uuware.com",
|
|
6
6
|
"homepage": "https://github.com/uuware/lupine.js",
|
|
@@ -52,4 +52,4 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/better-sqlite3": "^7.6.12"
|
|
54
54
|
}
|
|
55
|
-
}
|
|
55
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ServerResponse } from 'http';
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
|
-
import { IApiBase, Logger, apiCache, ServerRequest, ApiRouter, ApiHelper, langHelper, FsUtils } from 'lupine.api';
|
|
3
|
+
import { IApiBase, Logger, apiCache, ServerRequest, ApiRouter, ApiHelper, langHelper, FsUtils, appStorage } from 'lupine.api';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
|
|
6
6
|
export class AdminConfig implements IApiBase {
|
|
@@ -72,6 +72,8 @@ export class AdminConfig implements IApiBase {
|
|
|
72
72
|
const cfgPath = path.join(appData.dataPath, 'config.json');
|
|
73
73
|
await fs.writeFile(cfgPath, JSON.stringify(data.json));
|
|
74
74
|
|
|
75
|
+
await appStorage.load(appData.appName, appData.dataPath);
|
|
76
|
+
|
|
75
77
|
const response = {
|
|
76
78
|
status: 'ok',
|
|
77
79
|
message: langHelper.getLang('shared:operation_success'),
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
processRefreshCache,
|
|
13
13
|
apiStorage,
|
|
14
14
|
processRestartApp,
|
|
15
|
+
processShell,
|
|
15
16
|
} from 'lupine.api';
|
|
16
17
|
import path from 'path';
|
|
17
18
|
import { needDevAdminSession } from './admin-auth';
|
|
@@ -39,12 +40,16 @@ export class AdminRelease implements IApiBase {
|
|
|
39
40
|
this.router.use('/refresh-cache', needDevAdminSession, this.refreshCache.bind(this));
|
|
40
41
|
this.router.use('/restart-app', needDevAdminSession, this.restartApp.bind(this));
|
|
41
42
|
|
|
43
|
+
this.router.use('/shell', needDevAdminSession, this.shell.bind(this));
|
|
44
|
+
|
|
42
45
|
// ...ByClient will verify credentials from post, so it doesn't need AdminSession
|
|
43
46
|
this.router.use('/byClientCheck', this.byClientCheck.bind(this));
|
|
44
47
|
this.router.use('/byClientUpdate', this.byClientUpdate.bind(this));
|
|
45
48
|
this.router.use('/byClientRefreshCache', this.byClientRefreshCache.bind(this));
|
|
46
49
|
this.router.use('/byClientRestartApp', this.byClientRestartApp.bind(this));
|
|
47
50
|
this.router.use('/byClientViewLog', this.byClientViewLog.bind(this));
|
|
51
|
+
|
|
52
|
+
this.router.use('/byClientShell', this.byClientShell.bind(this));
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
async viewLog(req: ServerRequest, res: ServerResponse) {
|
|
@@ -170,6 +175,48 @@ export class AdminRelease implements IApiBase {
|
|
|
170
175
|
return true;
|
|
171
176
|
}
|
|
172
177
|
|
|
178
|
+
async shell(req: ServerRequest, res: ServerResponse) {
|
|
179
|
+
// check whether it's from online admin
|
|
180
|
+
const json = await adminApiHelper.getDevAdminFromCookie(req, res, false);
|
|
181
|
+
const jsonData = req.locals.json();
|
|
182
|
+
if (json && jsonData && !Array.isArray(jsonData) && jsonData.isLocal) {
|
|
183
|
+
const result = await processShell(req);
|
|
184
|
+
const response = {
|
|
185
|
+
status: 'ok',
|
|
186
|
+
message: 'Shell executed successfully.',
|
|
187
|
+
result,
|
|
188
|
+
};
|
|
189
|
+
ApiHelper.sendJson(req, res, response);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
194
|
+
if (!data) return true;
|
|
195
|
+
|
|
196
|
+
let targetUrl = data.targetUrl as string;
|
|
197
|
+
if (targetUrl.endsWith('/')) {
|
|
198
|
+
targetUrl = targetUrl.slice(0, -1);
|
|
199
|
+
}
|
|
200
|
+
const remoteData = await fetch(targetUrl + '/api/admin/release/byClientShell', {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
body: JSON.stringify(data),
|
|
203
|
+
});
|
|
204
|
+
const resultText = await remoteData.text();
|
|
205
|
+
let remoteResult: any;
|
|
206
|
+
try {
|
|
207
|
+
remoteResult = JSON.parse(resultText);
|
|
208
|
+
} catch (e: any) {
|
|
209
|
+
remoteResult = { status: 'error', message: resultText };
|
|
210
|
+
}
|
|
211
|
+
const response = {
|
|
212
|
+
status: 'ok',
|
|
213
|
+
message: 'check.',
|
|
214
|
+
...remoteResult,
|
|
215
|
+
};
|
|
216
|
+
ApiHelper.sendJson(req, res, response);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
173
220
|
public async chkData(data: any, req: ServerRequest, res: ServerResponse, chkCredential: boolean) {
|
|
174
221
|
// add access token
|
|
175
222
|
if (!data || Array.isArray(data) || typeof data !== 'object' || !data.accessToken || !data.targetUrl) {
|
|
@@ -486,14 +533,12 @@ export class AdminRelease implements IApiBase {
|
|
|
486
533
|
body: JSON.stringify({ ...data, chkOption, index: cnt, size: fileContent.length }) + '\n\n' + chunk,
|
|
487
534
|
};
|
|
488
535
|
this.logger.info(
|
|
489
|
-
`updateSendFile, index: ${cnt}, sending: ${chunk.length} (${i + chunk.length} / ${
|
|
490
|
-
fileContent.length
|
|
536
|
+
`updateSendFile, index: ${cnt}, sending: ${chunk.length} (${i + chunk.length} / ${fileContent.length
|
|
491
537
|
}), f: ${sendFile}`
|
|
492
538
|
);
|
|
493
539
|
apiStorage.set(
|
|
494
540
|
releaseProgress,
|
|
495
|
-
`updateSendFile, index: ${cnt}, sending: ${chunk.length} (${i + chunk.length} / ${
|
|
496
|
-
fileContent.length
|
|
541
|
+
`updateSendFile, index: ${cnt}, sending: ${chunk.length} (${i + chunk.length} / ${fileContent.length
|
|
497
542
|
}), f: ${sendFile}`
|
|
498
543
|
);
|
|
499
544
|
i > 0 && (await new Promise((resolve) => setTimeout(resolve, 1000)));
|
|
@@ -536,6 +581,7 @@ export class AdminRelease implements IApiBase {
|
|
|
536
581
|
!chkOption ||
|
|
537
582
|
!toList ||
|
|
538
583
|
(chkOption !== 'server' &&
|
|
584
|
+
chkOption !== 'app-loader' &&
|
|
539
585
|
chkOption !== 'api' &&
|
|
540
586
|
chkOption !== 'web' &&
|
|
541
587
|
chkOption !== 'web-sub' &&
|
|
@@ -636,4 +682,19 @@ export class AdminRelease implements IApiBase {
|
|
|
636
682
|
ApiHelper.sendJson(req, res, response);
|
|
637
683
|
return true;
|
|
638
684
|
}
|
|
685
|
+
|
|
686
|
+
async byClientShell(req: ServerRequest, res: ServerResponse) {
|
|
687
|
+
const jsonData = req.locals.json();
|
|
688
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
689
|
+
if (!data) return true;
|
|
690
|
+
|
|
691
|
+
const result = await processShell(req);
|
|
692
|
+
const response = {
|
|
693
|
+
status: 'ok',
|
|
694
|
+
message: 'Shell executed successfully.',
|
|
695
|
+
result,
|
|
696
|
+
};
|
|
697
|
+
ApiHelper.sendJson(req, res, response);
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
639
700
|
}
|
package/src/api/api-module.ts
CHANGED
|
@@ -22,7 +22,7 @@ export class ApiModule implements IApiModule {
|
|
|
22
22
|
return result;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// appCache is from app-
|
|
25
|
+
// appCache is from app-helper (parent scope), not the same in current scope
|
|
26
26
|
public async initApi(appConfig: HostToPathProps, appCacheFromApp: IAppCache, appStorageFromApp: IAppSharedStorage) {
|
|
27
27
|
|
|
28
28
|
// const evnFile = appCacheFromApp.get(AppCacheGlobal, AppCacheKeys.APP_ENV_FILE);
|
package/src/api/shell-service.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
|
|
1
|
+
import { spawn, exec, ChildProcessWithoutNullStreams } from 'child_process';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import { MiniWebSocket } from './mini-web-socket';
|
|
4
4
|
import { Duplex } from 'stream';
|
|
@@ -13,7 +13,7 @@ export class ShellService {
|
|
|
13
13
|
this._socket = socket;
|
|
14
14
|
this._miniWebSocket = miniWebSocket;
|
|
15
15
|
try {
|
|
16
|
-
const shellCmd: string =
|
|
16
|
+
const shellCmd: string = ShellService.getDefaultShell();
|
|
17
17
|
this._shell = spawn(shellCmd, [], {
|
|
18
18
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
19
19
|
});
|
|
@@ -35,7 +35,7 @@ export class ShellService {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
getDefaultShell() {
|
|
38
|
+
static getDefaultShell() {
|
|
39
39
|
const platform = os.platform();
|
|
40
40
|
if (platform === 'win32') {
|
|
41
41
|
return process.env.COMSPEC || 'cmd.exe';
|
|
@@ -63,4 +63,12 @@ export class ShellService {
|
|
|
63
63
|
this._miniWebSocket.sendMessage(this._socket!, 'Shell is not available.');
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
public static async directCmd(cmd: string): Promise<string> {
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
exec(cmd, { shell: this.getDefaultShell() }, (error, stdout, stderr) => {
|
|
70
|
+
resolve(stdout + stderr);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
66
74
|
}
|
|
@@ -4,8 +4,8 @@ import { appCache } from './app-cache';
|
|
|
4
4
|
import { AppCacheKeys, AppLoaderProps, HostToPathProps, IApiModule, setAppCache } from '../models';
|
|
5
5
|
import { appStorage } from './app-shared-storage';
|
|
6
6
|
|
|
7
|
-
class
|
|
8
|
-
logger: Logger = new Logger('app-
|
|
7
|
+
class AppHelper {
|
|
8
|
+
logger: Logger = new Logger('app-helper');
|
|
9
9
|
|
|
10
10
|
constructor() {}
|
|
11
11
|
|
|
@@ -59,4 +59,4 @@ class AppLoader {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
export const
|
|
62
|
+
export const appHelper = /* @__PURE__ */ new AppHelper();
|
package/src/app/app-message.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import cluster from 'cluster';
|
|
2
2
|
import { Logger, LogWriter, LogWriterMessageId } from '../lib';
|
|
3
|
-
import { processDebugMessage,
|
|
3
|
+
import { processDebugMessage, sendRestartAppMsgToLoader } from './process-dev-requests';
|
|
4
4
|
import { cleanupAndExit } from './cleanup-exit';
|
|
5
5
|
|
|
6
6
|
export type AppMessageProps = {
|
|
@@ -79,7 +79,7 @@ export const processMessageFromWorker = (msgObject: AppMessageProps) => {
|
|
|
79
79
|
`Message from worker ${cluster.worker?.id}, message: ${msgObject.message}, appName: ${msgObject.appName}`
|
|
80
80
|
);
|
|
81
81
|
if (msgObject.message === 'restartApp') {
|
|
82
|
-
|
|
82
|
+
sendRestartAppMsgToLoader();
|
|
83
83
|
return;
|
|
84
84
|
} else if (msgObject.message === 'refresh') {
|
|
85
85
|
broadcast(msgObject);
|
|
@@ -99,6 +99,8 @@ export class AppSharedStorage implements IAppSharedStorage {
|
|
|
99
99
|
} else if (msgObject.action === 'set') {
|
|
100
100
|
const storage = this.getStorage(msgObject.appName);
|
|
101
101
|
storage.set(msgObject.key, msgObject.value);
|
|
102
|
+
} else if (msgObject.action === 'load') {
|
|
103
|
+
this.load(msgObject.appName, msgObject.rootPath || '');
|
|
102
104
|
} else if (msgObject.action === 'save') {
|
|
103
105
|
this.save(msgObject.appName);
|
|
104
106
|
} else {
|
|
@@ -116,7 +118,9 @@ export class AppSharedStorage implements IAppSharedStorage {
|
|
|
116
118
|
// should be only called from primary when the app is starting
|
|
117
119
|
async load(appName: string, rootPath: string) {
|
|
118
120
|
if (!cluster.isPrimary) {
|
|
119
|
-
throw new Error('AppStorage.load should be only called from primary');
|
|
121
|
+
// throw new Error('AppStorage.load should be only called from primary');
|
|
122
|
+
await AppSharedStorageWorker.load(appName, rootPath);
|
|
123
|
+
return;
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
const map = this.getStorageMap(appName);
|
|
@@ -147,6 +151,7 @@ export class AppSharedStorage implements IAppSharedStorage {
|
|
|
147
151
|
return;
|
|
148
152
|
}
|
|
149
153
|
|
|
154
|
+
console.log(`${process.pid} - AppStorage save, appName: ${appName}, exit: ${exit}`);
|
|
150
155
|
if (appName) {
|
|
151
156
|
const map = this.configMap[appName];
|
|
152
157
|
if (map && map.fPath && map.storage.size() > 0 && map.storage.Dirty) {
|
|
@@ -166,7 +171,7 @@ export class AppSharedStorage implements IAppSharedStorage {
|
|
|
166
171
|
// this can be called in primary or worker
|
|
167
172
|
get(appName: string, key: string): Promise<string> {
|
|
168
173
|
return new Promise((resolve, reject) => {
|
|
169
|
-
console.log(`${process.pid} - AppStorage get value for key: ${key}`);
|
|
174
|
+
// console.log(`${process.pid} - AppStorage get value for key: ${key}`);
|
|
170
175
|
|
|
171
176
|
if (!cluster.isPrimary) {
|
|
172
177
|
AppSharedStorageWorker.get(appName, key, resolve, reject);
|
|
@@ -196,7 +201,7 @@ export class AppSharedStorage implements IAppSharedStorage {
|
|
|
196
201
|
}
|
|
197
202
|
getWithPrefix(appName: string, prefixKey: string): Promise<SimpleStorageDataProps> {
|
|
198
203
|
return new Promise((resolve, reject) => {
|
|
199
|
-
console.log(`${process.pid} - AppStorage getWithPrefix for prefixKey: ${prefixKey}`);
|
|
204
|
+
// console.log(`${process.pid} - AppStorage getWithPrefix for prefixKey: ${prefixKey}`);
|
|
200
205
|
|
|
201
206
|
if (!cluster.isPrimary) {
|
|
202
207
|
AppSharedStorageWorker.getWithPrefix(appName, prefixKey, resolve, reject);
|
|
@@ -238,7 +243,7 @@ class AppSharedStorageWorker {
|
|
|
238
243
|
}
|
|
239
244
|
|
|
240
245
|
if (msgObject.action === 'get') {
|
|
241
|
-
console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
|
|
246
|
+
// console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
|
|
242
247
|
|
|
243
248
|
const value = msgObject.value;
|
|
244
249
|
// how to pass the value to the caller
|
|
@@ -265,6 +270,22 @@ class AppSharedStorageWorker {
|
|
|
265
270
|
}
|
|
266
271
|
}
|
|
267
272
|
|
|
273
|
+
static async load(appName: string, rootPath: string) {
|
|
274
|
+
if (cluster.isPrimary) {
|
|
275
|
+
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
276
|
+
}
|
|
277
|
+
const obj: StorageMessageFromSubProcess = {
|
|
278
|
+
id: AppSharedStorageMessageId,
|
|
279
|
+
pid: process.pid,
|
|
280
|
+
workerId: cluster.worker?.id || 0,
|
|
281
|
+
action: 'load',
|
|
282
|
+
appName: appName,
|
|
283
|
+
rootPath: rootPath,
|
|
284
|
+
key: 'load',
|
|
285
|
+
};
|
|
286
|
+
process.send!(obj);
|
|
287
|
+
}
|
|
288
|
+
|
|
268
289
|
static async save(appName?: string) {
|
|
269
290
|
if (cluster.isPrimary) {
|
|
270
291
|
throw new Error('AppSharedStorageWorker should be only called from workers');
|
package/src/app/app-start.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import cluster from 'cluster';
|
|
2
2
|
import { WebProcessor } from './web-processor';
|
|
3
|
-
import {
|
|
3
|
+
import { appHelper } from './app-helper';
|
|
4
4
|
import { processMessageFromPrimary, processMessageFromWorker } from './app-message';
|
|
5
5
|
import { WebServer } from './web-server';
|
|
6
6
|
import { processDevRequests } from './process-dev-requests';
|
|
@@ -59,7 +59,7 @@ class AppStart {
|
|
|
59
59
|
process.on('message', processMessageFromPrimary);
|
|
60
60
|
|
|
61
61
|
HostToPath.setHostToPathList(props.apiConfig.webHostMap);
|
|
62
|
-
|
|
62
|
+
appHelper.loadApi(props.apiConfig);
|
|
63
63
|
this.initServer(props.serverConfig);
|
|
64
64
|
} else if (cluster.isPrimary) {
|
|
65
65
|
const numCPUs = props.debug ? 1 : require('os').cpus().length;
|
package/src/app/index.ts
CHANGED
|
@@ -2,10 +2,11 @@ import cluster from 'cluster';
|
|
|
2
2
|
import { Logger } from '../lib/logger';
|
|
3
3
|
import { ServerResponse } from 'http';
|
|
4
4
|
import { AddressInfo } from 'net';
|
|
5
|
-
import {
|
|
5
|
+
import { appHelper } from './app-helper';
|
|
6
6
|
import { DebugService } from '../api/debug-service';
|
|
7
7
|
import { AppCacheGlobal, AppCacheKeys, getAppCache, ServerRequest } from '../models';
|
|
8
8
|
import { cleanupAndExit } from './cleanup-exit';
|
|
9
|
+
import { ShellService } from '../api/shell-service';
|
|
9
10
|
const logger = new Logger('process-dev-requests');
|
|
10
11
|
|
|
11
12
|
function deleteRequireCache(moduleName: string) {
|
|
@@ -27,13 +28,13 @@ export const processDebugMessage = async (msgObject: any) => {
|
|
|
27
28
|
if (msgObject.id === 'debug' && msgObject.message === 'refresh') {
|
|
28
29
|
if (msgObject.appName) {
|
|
29
30
|
const appConfig = getAppCache().get(msgObject.appName, AppCacheKeys.API_CONFIG);
|
|
30
|
-
|
|
31
|
+
appHelper.refreshApi(appConfig);
|
|
31
32
|
} else {
|
|
32
33
|
// refresh all in a worker (app scope)
|
|
33
34
|
let appList = getAppCache().get(AppCacheGlobal, AppCacheKeys.APP_LIST);
|
|
34
35
|
for (const appName of appList) {
|
|
35
36
|
const appConfig = getAppCache().get(appName, AppCacheKeys.API_CONFIG);
|
|
36
|
-
|
|
37
|
+
appHelper.refreshApi(appConfig);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -72,10 +73,20 @@ export async function processRestartApp(req: ServerRequest) {
|
|
|
72
73
|
}
|
|
73
74
|
// in case if it's only one process (primary process)
|
|
74
75
|
else {
|
|
75
|
-
|
|
76
|
+
sendRestartAppMsgToLoader();
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
export async function processShell(req: ServerRequest) {
|
|
81
|
+
const data = req.locals.json();
|
|
82
|
+
if (!data || Array.isArray(data) || !data.cmd) {
|
|
83
|
+
return 'Wrong data.';
|
|
84
|
+
}
|
|
85
|
+
const cmd = data.cmd as string;
|
|
86
|
+
const shell = await ShellService.directCmd(cmd);
|
|
87
|
+
return shell;
|
|
88
|
+
}
|
|
89
|
+
|
|
79
90
|
// this is called from a request in debug mode
|
|
80
91
|
export async function processDevRequests(req: ServerRequest, res: ServerResponse, rootUrl?: string) {
|
|
81
92
|
res.end();
|
|
@@ -103,7 +114,7 @@ export async function processDevRequests(req: ServerRequest, res: ServerResponse
|
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
// this is called from a request and passes the restartApp message to loader
|
|
106
|
-
export const
|
|
117
|
+
export const sendRestartAppMsgToLoader = async () => {
|
|
107
118
|
if (!cluster.isPrimary) {
|
|
108
119
|
console.warn(`restartApp: shouldn't come here`);
|
|
109
120
|
return;
|
|
@@ -2,7 +2,7 @@ import { ISimpleStorage, SimpleStorageDataProps } from './simple-storage-props';
|
|
|
2
2
|
|
|
3
3
|
// interface of AppSharedStorage
|
|
4
4
|
export const AppSharedStorageMessageId = 'AppSharedStorage';
|
|
5
|
-
export type StorageMessageAction = 'get' | 'set' | 'save' | 'getWithPrefix';
|
|
5
|
+
export type StorageMessageAction = 'get' | 'set' | 'load' | 'save' | 'getWithPrefix';
|
|
6
6
|
export interface StorageMessageFromSubProcess {
|
|
7
7
|
id: string;
|
|
8
8
|
pid: number | undefined;
|
|
@@ -13,6 +13,7 @@ export interface StorageMessageFromSubProcess {
|
|
|
13
13
|
value?: any;
|
|
14
14
|
uniqueKey?: string;
|
|
15
15
|
appName: string;
|
|
16
|
+
rootPath?: string;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
// also used in packages\lupine.api\src\common-js\web-env.js
|
package/src/models/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ export * from './api-module-props';
|
|
|
3
3
|
export * from './api-router-props';
|
|
4
4
|
export * from './app-cache-props';
|
|
5
5
|
export * from './app-data-props';
|
|
6
|
-
export * from './app-
|
|
6
|
+
export * from './app-helper-props';
|
|
7
7
|
export * from './app-shared-storage-props';
|
|
8
8
|
export * from './app-start-props';
|
|
9
9
|
export * from './async-storage-props';
|
|
File without changes
|