lupine.api 1.1.56 → 1.1.58
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-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
|
|
|
@@ -332,6 +333,45 @@ export const AdminReleasePage = () => {
|
|
|
332
333
|
NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
|
|
333
334
|
};
|
|
334
335
|
|
|
336
|
+
const onShellLocal = async () => {
|
|
337
|
+
return onShell(true);
|
|
338
|
+
};
|
|
339
|
+
const onShellRemote = async () => {
|
|
340
|
+
return onShell(false);
|
|
341
|
+
};
|
|
342
|
+
const onShell = async (isLocal?: boolean) => {
|
|
343
|
+
const data = getDomData();
|
|
344
|
+
if (!isLocal) {
|
|
345
|
+
if (!data.targetUrl || !data.accessToken) {
|
|
346
|
+
NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const index = await ActionSheetSelectPromise({
|
|
352
|
+
title: 'Run Cmd ?',
|
|
353
|
+
options: ['OK'],
|
|
354
|
+
cancelButtonText: 'Cancel',
|
|
355
|
+
});
|
|
356
|
+
if (index !== 0) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/shell', {
|
|
361
|
+
...data,
|
|
362
|
+
isLocal,
|
|
363
|
+
cmd: ref.$('.release-cmd').value,
|
|
364
|
+
});
|
|
365
|
+
const dataResponse = await response.json;
|
|
366
|
+
console.log('shell', dataResponse);
|
|
367
|
+
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
368
|
+
NotificationMessage.sendMessage(dataResponse.message || 'Failed to run cmd', NotificationColor.Error);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
domLog.value = <pre>{encodeHtml(dataResponse.message) + '\r\n<br>' + encodeHtml(dataResponse.result)}</pre>;
|
|
372
|
+
NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
|
|
373
|
+
};
|
|
374
|
+
|
|
335
375
|
const ref: RefProps = {
|
|
336
376
|
onLoad: async () => {
|
|
337
377
|
const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
|
|
@@ -341,19 +381,19 @@ export const AdminReleasePage = () => {
|
|
|
341
381
|
};
|
|
342
382
|
return (
|
|
343
383
|
<div ref={ref} css={css} class='admin-release-top'>
|
|
344
|
-
<div class='row-box
|
|
384
|
+
<div class='row-box mt-m'>
|
|
345
385
|
<label class='label mr-m release-label'>Target Url:</label>
|
|
346
386
|
<div class='w-50p'>
|
|
347
387
|
<input type='text' class='input-base w-100p target-url' placeholder='Target Url' />
|
|
348
388
|
</div>
|
|
349
389
|
</div>
|
|
350
|
-
<div class='row-box
|
|
390
|
+
<div class='row-box mt-m'>
|
|
351
391
|
<label class='label mr-m release-label'>Access token:</label>
|
|
352
392
|
<div class='w-50p'>
|
|
353
393
|
<input type='text' class='input-base w-100p access-token' placeholder='Access token' />
|
|
354
394
|
</div>
|
|
355
395
|
</div>
|
|
356
|
-
<div class='row-box
|
|
396
|
+
<div class='row-box mt-m'>
|
|
357
397
|
<button onClick={onCheck} class='button-base mr-m'>
|
|
358
398
|
Check
|
|
359
399
|
</button>
|
|
@@ -370,6 +410,15 @@ export const AdminReleasePage = () => {
|
|
|
370
410
|
Restart App (Local)
|
|
371
411
|
</button>
|
|
372
412
|
</div>
|
|
413
|
+
<div class='row-box mt-m mb-m'>
|
|
414
|
+
<input type='text' class='input-base w-50p release-cmd mr-m' placeholder='Command' />
|
|
415
|
+
<button onClick={onShellRemote} class='button-base color-red mr-m'>
|
|
416
|
+
Run Cmd (Remote)
|
|
417
|
+
</button>
|
|
418
|
+
<button onClick={onShellLocal} class='button-base'>
|
|
419
|
+
Run Cmd (Local)
|
|
420
|
+
</button>
|
|
421
|
+
</div>
|
|
373
422
|
{domUpdate.node}
|
|
374
423
|
{domLog.node}
|
|
375
424
|
</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
|
@@ -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)));
|
|
@@ -637,4 +682,19 @@ export class AdminRelease implements IApiBase {
|
|
|
637
682
|
ApiHelper.sendJson(req, res, response);
|
|
638
683
|
return true;
|
|
639
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
|
+
}
|
|
640
700
|
}
|
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
|
}
|
|
@@ -6,6 +6,7 @@ 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) {
|
|
@@ -76,6 +77,16 @@ export async function processRestartApp(req: ServerRequest) {
|
|
|
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();
|