@jupyterlite/terminal 0.1.4 → 0.1.6
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/lib/index.d.ts +2 -2
- package/lib/index.js +21 -9
- package/lib/manager.d.ts +32 -0
- package/lib/manager.js +67 -0
- package/lib/shell.d.ts +16 -0
- package/lib/shell.js +23 -0
- package/lib/terminal.d.ts +8 -0
- package/lib/terminal.js +50 -12
- package/lib/tokens.d.ts +14 -5
- package/lib/tokens.js +1 -1
- package/lib/worker.d.ts +1 -0
- package/lib/worker.js +35 -0
- package/package.json +16 -14
- package/src/index.ts +26 -10
- package/src/manager.ts +82 -0
- package/src/shell.ts +25 -0
- package/src/terminal.ts +61 -15
- package/src/tokens.ts +17 -6
- package/src/worker.ts +53 -0
- package/lib/terminals.d.ts +0 -22
- package/lib/terminals.js +0 -48
- package/src/terminals.ts +0 -60
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { JupyterLiteServerPlugin } from '@jupyterlite/server';
|
|
2
|
-
import {
|
|
3
|
-
declare const _default: (JupyterLiteServerPlugin<
|
|
2
|
+
import { ITerminalManager } from './tokens';
|
|
3
|
+
declare const _default: (JupyterLiteServerPlugin<ITerminalManager> | JupyterLiteServerPlugin<void>)[];
|
|
4
4
|
export default _default;
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { TerminalManager } from './manager';
|
|
4
|
+
import { ITerminalManager } from './tokens';
|
|
5
5
|
/**
|
|
6
6
|
* The terminals service plugin.
|
|
7
7
|
*/
|
|
@@ -9,7 +9,7 @@ const terminalsPlugin = {
|
|
|
9
9
|
id: '@jupyterlite/terminal:plugin',
|
|
10
10
|
description: 'A terminal for JupyterLite',
|
|
11
11
|
autoStart: true,
|
|
12
|
-
provides:
|
|
12
|
+
provides: ITerminalManager,
|
|
13
13
|
activate: async (app) => {
|
|
14
14
|
console.log('JupyterLite extension @jupyterlite/terminal:plugin is activated!');
|
|
15
15
|
const { serviceManager } = app;
|
|
@@ -20,7 +20,7 @@ const terminalsPlugin = {
|
|
|
20
20
|
// Not sure this is necessary?
|
|
21
21
|
await terminals.ready;
|
|
22
22
|
console.log('terminals ready after await:', terminals.isReady); // Ready
|
|
23
|
-
return new
|
|
23
|
+
return new TerminalManager(serverSettings.wsUrl);
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
@@ -29,21 +29,33 @@ const terminalsPlugin = {
|
|
|
29
29
|
const terminalsRoutesPlugin = {
|
|
30
30
|
id: '@jupyterlite/terminal:routes-plugin',
|
|
31
31
|
autoStart: true,
|
|
32
|
-
requires: [
|
|
33
|
-
activate: (app,
|
|
34
|
-
console.log('JupyterLite extension @jupyterlite/terminal:routes-plugin is activated!',
|
|
32
|
+
requires: [ITerminalManager],
|
|
33
|
+
activate: (app, terminalManager) => {
|
|
34
|
+
console.log('JupyterLite extension @jupyterlite/terminal:routes-plugin is activated!', terminalManager);
|
|
35
35
|
// GET /api/terminals - List the running terminals
|
|
36
36
|
app.router.get('/api/terminals', async (req) => {
|
|
37
|
-
const res = await
|
|
37
|
+
const res = await terminalManager.listRunning();
|
|
38
38
|
// Should return last_activity for each too,
|
|
39
39
|
return new Response(JSON.stringify(res));
|
|
40
40
|
});
|
|
41
41
|
// POST /api/terminals - Start a terminal
|
|
42
42
|
app.router.post('/api/terminals', async (req) => {
|
|
43
|
-
const res = await
|
|
43
|
+
const res = await terminalManager.startNew();
|
|
44
44
|
// Should return last_activity too.
|
|
45
45
|
return new Response(JSON.stringify(res));
|
|
46
46
|
});
|
|
47
|
+
// DELETE /api/terminals/{terminal name} - Delete a terminal
|
|
48
|
+
app.router.delete('/api/terminals/(.+)', async (req, name) => {
|
|
49
|
+
const exists = terminalManager.has(name);
|
|
50
|
+
if (exists) {
|
|
51
|
+
await terminalManager.shutdownTerminal(name);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const msg = `The terminal session "${name}"" does not exist`;
|
|
55
|
+
console.warn(msg);
|
|
56
|
+
}
|
|
57
|
+
return new Response(null, { status: exists ? 204 : 404 });
|
|
58
|
+
});
|
|
47
59
|
}
|
|
48
60
|
};
|
|
49
61
|
export default [terminalsPlugin, terminalsRoutesPlugin];
|
package/lib/manager.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TerminalAPI } from '@jupyterlab/services';
|
|
2
|
+
import { ITerminalManager } from './tokens';
|
|
3
|
+
/**
|
|
4
|
+
* A class to handle requests to /api/terminals.
|
|
5
|
+
* Although this looks similar to a JupyterLab TerminalManager, it is really a class that
|
|
6
|
+
* implements the terminal REST API.
|
|
7
|
+
*/
|
|
8
|
+
export declare class TerminalManager implements ITerminalManager {
|
|
9
|
+
/**
|
|
10
|
+
* Construct a new TerminalManager object.
|
|
11
|
+
*/
|
|
12
|
+
constructor(wsUrl: string);
|
|
13
|
+
/**
|
|
14
|
+
* Return whether the named terminal exists.
|
|
15
|
+
*/
|
|
16
|
+
has(name: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* List the running terminals.
|
|
19
|
+
*/
|
|
20
|
+
listRunning(): Promise<TerminalAPI.IModel[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Shutdown a terminal by name.
|
|
23
|
+
*/
|
|
24
|
+
shutdownTerminal(name: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Start a new kernel.
|
|
27
|
+
*/
|
|
28
|
+
startNew(): Promise<TerminalAPI.IModel>;
|
|
29
|
+
private _nextAvailableName;
|
|
30
|
+
private _wsUrl;
|
|
31
|
+
private _terminals;
|
|
32
|
+
}
|
package/lib/manager.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
import { PageConfig } from '@jupyterlab/coreutils';
|
|
4
|
+
import { Terminal } from './terminal';
|
|
5
|
+
/**
|
|
6
|
+
* A class to handle requests to /api/terminals.
|
|
7
|
+
* Although this looks similar to a JupyterLab TerminalManager, it is really a class that
|
|
8
|
+
* implements the terminal REST API.
|
|
9
|
+
*/
|
|
10
|
+
export class TerminalManager {
|
|
11
|
+
/**
|
|
12
|
+
* Construct a new TerminalManager object.
|
|
13
|
+
*/
|
|
14
|
+
constructor(wsUrl) {
|
|
15
|
+
this._terminals = new Map();
|
|
16
|
+
this._wsUrl = wsUrl;
|
|
17
|
+
console.log('==> TerminalManager.constructor', this._wsUrl);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Return whether the named terminal exists.
|
|
21
|
+
*/
|
|
22
|
+
has(name) {
|
|
23
|
+
return this._terminals.has(name);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* List the running terminals.
|
|
27
|
+
*/
|
|
28
|
+
async listRunning() {
|
|
29
|
+
const ret = [...this._terminals.values()].map(terminal => ({
|
|
30
|
+
name: terminal.name
|
|
31
|
+
}));
|
|
32
|
+
return ret;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Shutdown a terminal by name.
|
|
36
|
+
*/
|
|
37
|
+
async shutdownTerminal(name) {
|
|
38
|
+
const terminal = this._terminals.get(name);
|
|
39
|
+
if (terminal !== undefined) {
|
|
40
|
+
console.log('==> TerminalManager.shutdownTerminal', name);
|
|
41
|
+
this._terminals.delete(name);
|
|
42
|
+
terminal.dispose();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Start a new kernel.
|
|
47
|
+
*/
|
|
48
|
+
async startNew() {
|
|
49
|
+
const name = this._nextAvailableName();
|
|
50
|
+
console.log('==> TerminalManager.startNew', name);
|
|
51
|
+
const baseUrl = PageConfig.getBaseUrl();
|
|
52
|
+
const terminal = new Terminal({ name, baseUrl });
|
|
53
|
+
this._terminals.set(name, terminal);
|
|
54
|
+
terminal.disposed.connect(() => this.shutdownTerminal(name));
|
|
55
|
+
const url = `${this._wsUrl}terminals/websocket/${name}`;
|
|
56
|
+
await terminal.wsConnect(url);
|
|
57
|
+
return { name };
|
|
58
|
+
}
|
|
59
|
+
_nextAvailableName() {
|
|
60
|
+
for (let i = 1;; ++i) {
|
|
61
|
+
const name = `${i}`;
|
|
62
|
+
if (!this._terminals.has(name)) {
|
|
63
|
+
return name;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
package/lib/shell.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseShell, IShell } from '@jupyterlite/cockle';
|
|
2
|
+
/**
|
|
3
|
+
* Shell class that uses web worker that plugs into a DriveFS via the service worker.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Shell extends BaseShell {
|
|
6
|
+
/**
|
|
7
|
+
* Instantiate a new Shell
|
|
8
|
+
*
|
|
9
|
+
* @param options The instantiation options for a new shell
|
|
10
|
+
*/
|
|
11
|
+
constructor(options: IShell.IOptions);
|
|
12
|
+
/**
|
|
13
|
+
* Load the web worker.
|
|
14
|
+
*/
|
|
15
|
+
protected initWorker(options: IShell.IOptions): Worker;
|
|
16
|
+
}
|
package/lib/shell.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseShell } from '@jupyterlite/cockle';
|
|
2
|
+
/**
|
|
3
|
+
* Shell class that uses web worker that plugs into a DriveFS via the service worker.
|
|
4
|
+
*/
|
|
5
|
+
export class Shell extends BaseShell {
|
|
6
|
+
/**
|
|
7
|
+
* Instantiate a new Shell
|
|
8
|
+
*
|
|
9
|
+
* @param options The instantiation options for a new shell
|
|
10
|
+
*/
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super(options);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Load the web worker.
|
|
16
|
+
*/
|
|
17
|
+
initWorker(options) {
|
|
18
|
+
console.log('Terminal create webworker');
|
|
19
|
+
return new Worker(new URL('./worker.js', import.meta.url), {
|
|
20
|
+
type: 'module'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
package/lib/terminal.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ISignal } from '@lumino/signaling';
|
|
1
2
|
import { ITerminal } from './tokens';
|
|
2
3
|
export declare class Terminal implements ITerminal {
|
|
3
4
|
readonly options: ITerminal.IOptions;
|
|
@@ -6,11 +7,18 @@ export declare class Terminal implements ITerminal {
|
|
|
6
7
|
*/
|
|
7
8
|
constructor(options: ITerminal.IOptions);
|
|
8
9
|
private _outputCallback;
|
|
10
|
+
dispose(): void;
|
|
11
|
+
get disposed(): ISignal<this, void>;
|
|
12
|
+
get isDisposed(): boolean;
|
|
9
13
|
/**
|
|
10
14
|
* Get the name of the terminal.
|
|
11
15
|
*/
|
|
12
16
|
get name(): string;
|
|
13
17
|
wsConnect(url: string): Promise<void>;
|
|
18
|
+
private _disposed;
|
|
19
|
+
private _isDisposed;
|
|
20
|
+
private _server?;
|
|
14
21
|
private _socket?;
|
|
15
22
|
private _shell;
|
|
23
|
+
private _running;
|
|
16
24
|
}
|
package/lib/terminal.js
CHANGED
|
@@ -1,26 +1,56 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
import {
|
|
3
|
+
import { Signal } from '@lumino/signaling';
|
|
4
4
|
import { Server as WebSocketServer } from 'mock-socket';
|
|
5
|
+
import { Shell } from './shell';
|
|
5
6
|
export class Terminal {
|
|
6
7
|
/**
|
|
7
8
|
* Construct a new Terminal.
|
|
8
9
|
*/
|
|
9
10
|
constructor(options) {
|
|
10
11
|
this.options = options;
|
|
12
|
+
this._disposed = new Signal(this);
|
|
13
|
+
this._isDisposed = false;
|
|
14
|
+
this._running = false;
|
|
11
15
|
this._shell = new Shell({
|
|
12
16
|
mountpoint: '/drive',
|
|
13
17
|
driveFsBaseUrl: options.baseUrl,
|
|
14
18
|
wasmBaseUrl: options.baseUrl + 'extensions/@jupyterlite/terminal/static/wasm/',
|
|
15
19
|
outputCallback: this._outputCallback.bind(this)
|
|
16
20
|
});
|
|
21
|
+
this._shell.disposed.connect(() => this.dispose());
|
|
17
22
|
}
|
|
18
|
-
|
|
23
|
+
_outputCallback(text) {
|
|
19
24
|
if (this._socket) {
|
|
20
25
|
const ret = JSON.stringify(['stdout', text]);
|
|
21
26
|
this._socket.send(ret);
|
|
22
27
|
}
|
|
23
28
|
}
|
|
29
|
+
dispose() {
|
|
30
|
+
if (this._isDisposed) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log('Terminal.dispose');
|
|
34
|
+
this._isDisposed = true;
|
|
35
|
+
if (this._socket !== undefined) {
|
|
36
|
+
// Disconnect from frontend.
|
|
37
|
+
this._socket.send(JSON.stringify(['disconnect']));
|
|
38
|
+
this._socket.close();
|
|
39
|
+
this._socket = undefined;
|
|
40
|
+
}
|
|
41
|
+
if (this._server !== undefined) {
|
|
42
|
+
this._server.close();
|
|
43
|
+
this._server = undefined;
|
|
44
|
+
}
|
|
45
|
+
this._shell.dispose();
|
|
46
|
+
this._disposed.emit();
|
|
47
|
+
}
|
|
48
|
+
get disposed() {
|
|
49
|
+
return this._disposed;
|
|
50
|
+
}
|
|
51
|
+
get isDisposed() {
|
|
52
|
+
return this._isDisposed;
|
|
53
|
+
}
|
|
24
54
|
/**
|
|
25
55
|
* Get the name of the terminal.
|
|
26
56
|
*/
|
|
@@ -28,10 +58,15 @@ export class Terminal {
|
|
|
28
58
|
return this.options.name;
|
|
29
59
|
}
|
|
30
60
|
async wsConnect(url) {
|
|
31
|
-
console.log('
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.log('
|
|
61
|
+
console.log('Terminal wsConnect', url);
|
|
62
|
+
this._server = new WebSocketServer(url);
|
|
63
|
+
this._server.on('connection', async (socket) => {
|
|
64
|
+
console.log('Terminal server connection');
|
|
65
|
+
if (this._socket !== undefined) {
|
|
66
|
+
this._socket.send(JSON.stringify(['disconnect']));
|
|
67
|
+
this._socket.close();
|
|
68
|
+
this._socket = undefined;
|
|
69
|
+
}
|
|
35
70
|
this._socket = socket;
|
|
36
71
|
socket.on('message', async (message) => {
|
|
37
72
|
const data = JSON.parse(message);
|
|
@@ -47,17 +82,20 @@ export class Terminal {
|
|
|
47
82
|
await this._shell.setSize(rows, columns);
|
|
48
83
|
}
|
|
49
84
|
});
|
|
50
|
-
socket.on('close',
|
|
51
|
-
console.log('
|
|
85
|
+
socket.on('close', () => {
|
|
86
|
+
console.log('Terminal socket close');
|
|
52
87
|
});
|
|
53
|
-
socket.on('error',
|
|
54
|
-
console.log('
|
|
88
|
+
socket.on('error', () => {
|
|
89
|
+
console.log('Terminal socket error');
|
|
55
90
|
});
|
|
56
91
|
// Return handshake.
|
|
57
92
|
const res = JSON.stringify(['setup']);
|
|
58
|
-
console.log('
|
|
93
|
+
console.log('Terminal returning handshake via socket');
|
|
59
94
|
socket.send(res);
|
|
60
|
-
|
|
95
|
+
if (!this._running) {
|
|
96
|
+
this._running = true;
|
|
97
|
+
await this._shell.start();
|
|
98
|
+
}
|
|
61
99
|
});
|
|
62
100
|
}
|
|
63
101
|
}
|
package/lib/tokens.d.ts
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { TerminalAPI } from '@jupyterlab/services';
|
|
2
2
|
import { Token } from '@lumino/coreutils';
|
|
3
|
+
import { IObservableDisposable } from '@lumino/disposable';
|
|
3
4
|
/**
|
|
4
5
|
* The token for the Terminals service.
|
|
5
6
|
*/
|
|
6
|
-
export declare const
|
|
7
|
+
export declare const ITerminalManager: Token<ITerminalManager>;
|
|
7
8
|
/**
|
|
8
|
-
* An interface for the
|
|
9
|
+
* An interface for the TerminalManager service.
|
|
9
10
|
*/
|
|
10
|
-
export interface
|
|
11
|
+
export interface ITerminalManager {
|
|
12
|
+
/**
|
|
13
|
+
* Return whether the named terminal exists.
|
|
14
|
+
*/
|
|
15
|
+
has(name: string): boolean;
|
|
11
16
|
/**
|
|
12
17
|
* List the running terminals.
|
|
13
18
|
*/
|
|
14
|
-
|
|
19
|
+
listRunning: () => Promise<TerminalAPI.IModel[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Shutdown a terminal by name.
|
|
22
|
+
*/
|
|
23
|
+
shutdownTerminal: (name: string) => Promise<void>;
|
|
15
24
|
/**
|
|
16
25
|
* Start a new kernel.
|
|
17
26
|
*/
|
|
@@ -20,7 +29,7 @@ export interface ITerminals {
|
|
|
20
29
|
/**
|
|
21
30
|
* An interface for a server-side terminal running in the browser.
|
|
22
31
|
*/
|
|
23
|
-
export interface ITerminal {
|
|
32
|
+
export interface ITerminal extends IObservableDisposable {
|
|
24
33
|
/**
|
|
25
34
|
* The name of the server-side terminal.
|
|
26
35
|
*/
|
package/lib/tokens.js
CHANGED
package/lib/worker.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/worker.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expose } from 'comlink';
|
|
2
|
+
import { BaseShellWorker } from '@jupyterlite/cockle';
|
|
3
|
+
import { DriveFS, ServiceWorkerContentsAPI } from '@jupyterlite/contents';
|
|
4
|
+
/**
|
|
5
|
+
* Custom DriveFS implementation using the service worker.
|
|
6
|
+
*/
|
|
7
|
+
class MyDriveFS extends DriveFS {
|
|
8
|
+
createAPI(options) {
|
|
9
|
+
return new ServiceWorkerContentsAPI(options.baseUrl, options.driveName, options.mountpoint, options.FS, options.ERRNO_CODES);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Shell web worker that uses DriveFS via service worker.
|
|
14
|
+
* Note that this is not exported as it is accessed from Shell via the filename.
|
|
15
|
+
*/
|
|
16
|
+
class ShellWorker extends BaseShellWorker {
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the DriveFS to mount an external file system.
|
|
19
|
+
*/
|
|
20
|
+
initDriveFS(driveFsBaseUrl, mountpoint, fileSystem) {
|
|
21
|
+
console.log('Terminal initDriveFS', driveFsBaseUrl, mountpoint);
|
|
22
|
+
const { FS, ERRNO_CODES, PATH } = fileSystem;
|
|
23
|
+
const driveFS = new MyDriveFS({
|
|
24
|
+
FS,
|
|
25
|
+
PATH,
|
|
26
|
+
ERRNO_CODES,
|
|
27
|
+
baseUrl: driveFsBaseUrl,
|
|
28
|
+
driveName: '',
|
|
29
|
+
mountpoint
|
|
30
|
+
});
|
|
31
|
+
FS.mount(driveFS, {}, mountpoint);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const worker = new ShellWorker();
|
|
35
|
+
expose(worker);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/terminal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A terminal for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
"url": "https://github.com/jupyterlite/terminal.git"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
34
|
-
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
33
|
+
"build": "jlpm build:lib && jlpm build:labextension:dev && jlpm build:webworker",
|
|
34
|
+
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension && jlpm build:webworker:prod",
|
|
35
35
|
"build:labextension": "jupyter labextension build .",
|
|
36
36
|
"build:labextension:dev": "jupyter labextension build --development True .",
|
|
37
37
|
"build:lib": "tsc --sourceMap",
|
|
38
38
|
"build:lib:prod": "tsc",
|
|
39
|
+
"build:webworker": "webpack -c webpack.worker.config.js --env=dev",
|
|
40
|
+
"build:webworker:prod": "webpack -c webpack.worker.config.js",
|
|
39
41
|
"clean": "jlpm clean:lib",
|
|
40
42
|
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
|
|
41
43
|
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
|
|
@@ -57,19 +59,19 @@
|
|
|
57
59
|
"watch:labextension": "jupyter labextension watch ."
|
|
58
60
|
},
|
|
59
61
|
"dependencies": {
|
|
60
|
-
"@jupyterlab/coreutils": "^6.
|
|
61
|
-
"@jupyterlab/services": "^7.
|
|
62
|
-
"@jupyterlab/terminal": "^4.
|
|
63
|
-
"@jupyterlab/terminal-extension": "^4.
|
|
64
|
-
"@jupyterlite/cockle": "^0.0.
|
|
65
|
-
"@jupyterlite/contents": "^0.
|
|
66
|
-
"@jupyterlite/server": "^0.
|
|
62
|
+
"@jupyterlab/coreutils": "^6.3.5",
|
|
63
|
+
"@jupyterlab/services": "^7.3.5",
|
|
64
|
+
"@jupyterlab/terminal": "^4.3.5",
|
|
65
|
+
"@jupyterlab/terminal-extension": "^4.3.5",
|
|
66
|
+
"@jupyterlite/cockle": "^0.0.16",
|
|
67
|
+
"@jupyterlite/contents": "^0.5.1",
|
|
68
|
+
"@jupyterlite/server": "^0.5.1",
|
|
67
69
|
"@lumino/coreutils": "^2.2.0",
|
|
68
70
|
"mock-socket": "^9.3.1"
|
|
69
71
|
},
|
|
70
72
|
"devDependencies": {
|
|
71
|
-
"@jupyterlab/builder": "^4.
|
|
72
|
-
"@jupyterlab/testutils": "^4.
|
|
73
|
+
"@jupyterlab/builder": "^4.3.5",
|
|
74
|
+
"@jupyterlab/testutils": "^4.3.5",
|
|
73
75
|
"@types/jest": "^29.2.0",
|
|
74
76
|
"@types/json-schema": "^7.0.11",
|
|
75
77
|
"@types/react": "^18.0.26",
|
|
@@ -91,6 +93,7 @@
|
|
|
91
93
|
"stylelint-config-standard": "^34.0.0",
|
|
92
94
|
"stylelint-csstree-validator": "^3.0.0",
|
|
93
95
|
"stylelint-prettier": "^4.0.0",
|
|
96
|
+
"ts-loader": "^9.5.2",
|
|
94
97
|
"typescript": "~5.0.2",
|
|
95
98
|
"webpack": "^5.87.0",
|
|
96
99
|
"webpack-cli": "^5.1.4",
|
|
@@ -106,8 +109,7 @@
|
|
|
106
109
|
},
|
|
107
110
|
"jupyterlab": {
|
|
108
111
|
"extension": true,
|
|
109
|
-
"outputDir": "jupyterlite_terminal/labextension"
|
|
110
|
-
"webpackConfig": "./webpack.extra.config.js"
|
|
112
|
+
"outputDir": "jupyterlite_terminal/labextension"
|
|
111
113
|
},
|
|
112
114
|
"jupyterlite": {
|
|
113
115
|
"liteExtension": true
|
package/src/index.ts
CHANGED
|
@@ -7,17 +7,17 @@ import {
|
|
|
7
7
|
Router
|
|
8
8
|
} from '@jupyterlite/server';
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { TerminalManager } from './manager';
|
|
11
|
+
import { ITerminalManager } from './tokens';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* The terminals service plugin.
|
|
15
15
|
*/
|
|
16
|
-
const terminalsPlugin: JupyterLiteServerPlugin<
|
|
16
|
+
const terminalsPlugin: JupyterLiteServerPlugin<ITerminalManager> = {
|
|
17
17
|
id: '@jupyterlite/terminal:plugin',
|
|
18
18
|
description: 'A terminal for JupyterLite',
|
|
19
19
|
autoStart: true,
|
|
20
|
-
provides:
|
|
20
|
+
provides: ITerminalManager,
|
|
21
21
|
activate: async (app: JupyterLiteServer) => {
|
|
22
22
|
console.log(
|
|
23
23
|
'JupyterLite extension @jupyterlite/terminal:plugin is activated!'
|
|
@@ -33,7 +33,7 @@ const terminalsPlugin: JupyterLiteServerPlugin<ITerminals> = {
|
|
|
33
33
|
await terminals.ready;
|
|
34
34
|
console.log('terminals ready after await:', terminals.isReady); // Ready
|
|
35
35
|
|
|
36
|
-
return new
|
|
36
|
+
return new TerminalManager(serverSettings.wsUrl);
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|
|
@@ -43,26 +43,42 @@ const terminalsPlugin: JupyterLiteServerPlugin<ITerminals> = {
|
|
|
43
43
|
const terminalsRoutesPlugin: JupyterLiteServerPlugin<void> = {
|
|
44
44
|
id: '@jupyterlite/terminal:routes-plugin',
|
|
45
45
|
autoStart: true,
|
|
46
|
-
requires: [
|
|
47
|
-
activate: (app: JupyterLiteServer,
|
|
46
|
+
requires: [ITerminalManager],
|
|
47
|
+
activate: (app: JupyterLiteServer, terminalManager: ITerminalManager) => {
|
|
48
48
|
console.log(
|
|
49
49
|
'JupyterLite extension @jupyterlite/terminal:routes-plugin is activated!',
|
|
50
|
-
|
|
50
|
+
terminalManager
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
// GET /api/terminals - List the running terminals
|
|
54
54
|
app.router.get('/api/terminals', async (req: Router.IRequest) => {
|
|
55
|
-
const res = await
|
|
55
|
+
const res = await terminalManager.listRunning();
|
|
56
56
|
// Should return last_activity for each too,
|
|
57
57
|
return new Response(JSON.stringify(res));
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
// POST /api/terminals - Start a terminal
|
|
61
61
|
app.router.post('/api/terminals', async (req: Router.IRequest) => {
|
|
62
|
-
const res = await
|
|
62
|
+
const res = await terminalManager.startNew();
|
|
63
63
|
// Should return last_activity too.
|
|
64
64
|
return new Response(JSON.stringify(res));
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
// DELETE /api/terminals/{terminal name} - Delete a terminal
|
|
68
|
+
app.router.delete(
|
|
69
|
+
'/api/terminals/(.+)',
|
|
70
|
+
async (req: Router.IRequest, name: string) => {
|
|
71
|
+
const exists = terminalManager.has(name);
|
|
72
|
+
if (exists) {
|
|
73
|
+
await terminalManager.shutdownTerminal(name);
|
|
74
|
+
} else {
|
|
75
|
+
const msg = `The terminal session "${name}"" does not exist`;
|
|
76
|
+
console.warn(msg);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return new Response(null, { status: exists ? 204 : 404 });
|
|
80
|
+
}
|
|
81
|
+
);
|
|
66
82
|
}
|
|
67
83
|
};
|
|
68
84
|
|
package/src/manager.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { PageConfig } from '@jupyterlab/coreutils';
|
|
5
|
+
import { TerminalAPI } from '@jupyterlab/services';
|
|
6
|
+
|
|
7
|
+
import { Terminal } from './terminal';
|
|
8
|
+
import { ITerminalManager } from './tokens';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A class to handle requests to /api/terminals.
|
|
12
|
+
* Although this looks similar to a JupyterLab TerminalManager, it is really a class that
|
|
13
|
+
* implements the terminal REST API.
|
|
14
|
+
*/
|
|
15
|
+
export class TerminalManager implements ITerminalManager {
|
|
16
|
+
/**
|
|
17
|
+
* Construct a new TerminalManager object.
|
|
18
|
+
*/
|
|
19
|
+
constructor(wsUrl: string) {
|
|
20
|
+
this._wsUrl = wsUrl;
|
|
21
|
+
console.log('==> TerminalManager.constructor', this._wsUrl);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Return whether the named terminal exists.
|
|
26
|
+
*/
|
|
27
|
+
has(name: string): boolean {
|
|
28
|
+
return this._terminals.has(name);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* List the running terminals.
|
|
33
|
+
*/
|
|
34
|
+
async listRunning(): Promise<TerminalAPI.IModel[]> {
|
|
35
|
+
const ret = [...this._terminals.values()].map(terminal => ({
|
|
36
|
+
name: terminal.name
|
|
37
|
+
}));
|
|
38
|
+
return ret;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Shutdown a terminal by name.
|
|
43
|
+
*/
|
|
44
|
+
async shutdownTerminal(name: string): Promise<void> {
|
|
45
|
+
const terminal = this._terminals.get(name);
|
|
46
|
+
if (terminal !== undefined) {
|
|
47
|
+
console.log('==> TerminalManager.shutdownTerminal', name);
|
|
48
|
+
this._terminals.delete(name);
|
|
49
|
+
terminal.dispose();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Start a new kernel.
|
|
55
|
+
*/
|
|
56
|
+
async startNew(): Promise<TerminalAPI.IModel> {
|
|
57
|
+
const name = this._nextAvailableName();
|
|
58
|
+
console.log('==> TerminalManager.startNew', name);
|
|
59
|
+
const baseUrl = PageConfig.getBaseUrl();
|
|
60
|
+
const terminal = new Terminal({ name, baseUrl });
|
|
61
|
+
this._terminals.set(name, terminal);
|
|
62
|
+
|
|
63
|
+
terminal.disposed.connect(() => this.shutdownTerminal(name));
|
|
64
|
+
|
|
65
|
+
const url = `${this._wsUrl}terminals/websocket/${name}`;
|
|
66
|
+
await terminal.wsConnect(url);
|
|
67
|
+
|
|
68
|
+
return { name };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private _nextAvailableName(): string {
|
|
72
|
+
for (let i = 1; ; ++i) {
|
|
73
|
+
const name = `${i}`;
|
|
74
|
+
if (!this._terminals.has(name)) {
|
|
75
|
+
return name;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private _wsUrl: string;
|
|
81
|
+
private _terminals: Map<string, Terminal> = new Map();
|
|
82
|
+
}
|
package/src/shell.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BaseShell, IShell } from '@jupyterlite/cockle';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shell class that uses web worker that plugs into a DriveFS via the service worker.
|
|
5
|
+
*/
|
|
6
|
+
export class Shell extends BaseShell {
|
|
7
|
+
/**
|
|
8
|
+
* Instantiate a new Shell
|
|
9
|
+
*
|
|
10
|
+
* @param options The instantiation options for a new shell
|
|
11
|
+
*/
|
|
12
|
+
constructor(options: IShell.IOptions) {
|
|
13
|
+
super(options);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load the web worker.
|
|
18
|
+
*/
|
|
19
|
+
protected override initWorker(options: IShell.IOptions): Worker {
|
|
20
|
+
console.log('Terminal create webworker');
|
|
21
|
+
return new Worker(new URL('./worker.js', import.meta.url), {
|
|
22
|
+
type: 'module'
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/terminal.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { IShell } from '@jupyterlite/cockle';
|
|
5
5
|
import { JSONPrimitive } from '@lumino/coreutils';
|
|
6
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
Server as WebSocketServer,
|
|
9
10
|
Client as WebSocketClient
|
|
10
11
|
} from 'mock-socket';
|
|
11
12
|
|
|
13
|
+
import { Shell } from './shell';
|
|
12
14
|
import { ITerminal } from './tokens';
|
|
13
15
|
|
|
14
16
|
export class Terminal implements ITerminal {
|
|
@@ -23,15 +25,48 @@ export class Terminal implements ITerminal {
|
|
|
23
25
|
options.baseUrl + 'extensions/@jupyterlite/terminal/static/wasm/',
|
|
24
26
|
outputCallback: this._outputCallback.bind(this)
|
|
25
27
|
});
|
|
28
|
+
this._shell.disposed.connect(() => this.dispose());
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
private
|
|
31
|
+
private _outputCallback(text: string): void {
|
|
29
32
|
if (this._socket) {
|
|
30
33
|
const ret = JSON.stringify(['stdout', text]);
|
|
31
34
|
this._socket.send(ret);
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
dispose(): void {
|
|
39
|
+
if (this._isDisposed) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('Terminal.dispose');
|
|
44
|
+
this._isDisposed = true;
|
|
45
|
+
|
|
46
|
+
if (this._socket !== undefined) {
|
|
47
|
+
// Disconnect from frontend.
|
|
48
|
+
this._socket.send(JSON.stringify(['disconnect']));
|
|
49
|
+
this._socket.close();
|
|
50
|
+
this._socket = undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (this._server !== undefined) {
|
|
54
|
+
this._server.close();
|
|
55
|
+
this._server = undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this._shell.dispose();
|
|
59
|
+
this._disposed.emit();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get disposed(): ISignal<this, void> {
|
|
63
|
+
return this._disposed;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get isDisposed(): boolean {
|
|
67
|
+
return this._isDisposed;
|
|
68
|
+
}
|
|
69
|
+
|
|
35
70
|
/**
|
|
36
71
|
* Get the name of the terminal.
|
|
37
72
|
*/
|
|
@@ -40,12 +75,16 @@ export class Terminal implements ITerminal {
|
|
|
40
75
|
}
|
|
41
76
|
|
|
42
77
|
async wsConnect(url: string) {
|
|
43
|
-
console.log('
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
78
|
+
console.log('Terminal wsConnect', url);
|
|
79
|
+
this._server = new WebSocketServer(url);
|
|
80
|
+
|
|
81
|
+
this._server.on('connection', async (socket: WebSocketClient) => {
|
|
82
|
+
console.log('Terminal server connection');
|
|
83
|
+
if (this._socket !== undefined) {
|
|
84
|
+
this._socket.send(JSON.stringify(['disconnect']));
|
|
85
|
+
this._socket.close();
|
|
86
|
+
this._socket = undefined;
|
|
87
|
+
}
|
|
49
88
|
this._socket = socket;
|
|
50
89
|
|
|
51
90
|
socket.on('message', async (message: any) => {
|
|
@@ -63,23 +102,30 @@ export class Terminal implements ITerminal {
|
|
|
63
102
|
}
|
|
64
103
|
});
|
|
65
104
|
|
|
66
|
-
socket.on('close',
|
|
67
|
-
console.log('
|
|
105
|
+
socket.on('close', () => {
|
|
106
|
+
console.log('Terminal socket close');
|
|
68
107
|
});
|
|
69
108
|
|
|
70
|
-
socket.on('error',
|
|
71
|
-
console.log('
|
|
109
|
+
socket.on('error', () => {
|
|
110
|
+
console.log('Terminal socket error');
|
|
72
111
|
});
|
|
73
112
|
|
|
74
113
|
// Return handshake.
|
|
75
114
|
const res = JSON.stringify(['setup']);
|
|
76
|
-
console.log('
|
|
115
|
+
console.log('Terminal returning handshake via socket');
|
|
77
116
|
socket.send(res);
|
|
78
117
|
|
|
79
|
-
|
|
118
|
+
if (!this._running) {
|
|
119
|
+
this._running = true;
|
|
120
|
+
await this._shell.start();
|
|
121
|
+
}
|
|
80
122
|
});
|
|
81
123
|
}
|
|
82
124
|
|
|
125
|
+
private _disposed = new Signal<this, void>(this);
|
|
126
|
+
private _isDisposed = false;
|
|
127
|
+
private _server?: WebSocketServer;
|
|
83
128
|
private _socket?: WebSocketClient;
|
|
84
|
-
private _shell:
|
|
129
|
+
private _shell: IShell;
|
|
130
|
+
private _running = false;
|
|
85
131
|
}
|
package/src/tokens.ts
CHANGED
|
@@ -4,22 +4,33 @@
|
|
|
4
4
|
import { TerminalAPI } from '@jupyterlab/services';
|
|
5
5
|
|
|
6
6
|
import { Token } from '@lumino/coreutils';
|
|
7
|
+
import { IObservableDisposable } from '@lumino/disposable';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* The token for the Terminals service.
|
|
10
11
|
*/
|
|
11
|
-
export const
|
|
12
|
-
'@jupyterlite/terminal:
|
|
12
|
+
export const ITerminalManager = new Token<ITerminalManager>(
|
|
13
|
+
'@jupyterlite/terminal:ITerminalManager'
|
|
13
14
|
);
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* An interface for the
|
|
17
|
+
* An interface for the TerminalManager service.
|
|
17
18
|
*/
|
|
18
|
-
export interface
|
|
19
|
+
export interface ITerminalManager {
|
|
20
|
+
/**
|
|
21
|
+
* Return whether the named terminal exists.
|
|
22
|
+
*/
|
|
23
|
+
has(name: string): boolean;
|
|
24
|
+
|
|
19
25
|
/**
|
|
20
26
|
* List the running terminals.
|
|
21
27
|
*/
|
|
22
|
-
|
|
28
|
+
listRunning: () => Promise<TerminalAPI.IModel[]>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Shutdown a terminal by name.
|
|
32
|
+
*/
|
|
33
|
+
shutdownTerminal: (name: string) => Promise<void>;
|
|
23
34
|
|
|
24
35
|
/**
|
|
25
36
|
* Start a new kernel.
|
|
@@ -30,7 +41,7 @@ export interface ITerminals {
|
|
|
30
41
|
/**
|
|
31
42
|
* An interface for a server-side terminal running in the browser.
|
|
32
43
|
*/
|
|
33
|
-
export interface ITerminal {
|
|
44
|
+
export interface ITerminal extends IObservableDisposable {
|
|
34
45
|
/**
|
|
35
46
|
* The name of the server-side terminal.
|
|
36
47
|
*/
|
package/src/worker.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { expose } from 'comlink';
|
|
2
|
+
|
|
3
|
+
import { BaseShellWorker, IFileSystem } from '@jupyterlite/cockle';
|
|
4
|
+
import {
|
|
5
|
+
ContentsAPI,
|
|
6
|
+
DriveFS,
|
|
7
|
+
ServiceWorkerContentsAPI
|
|
8
|
+
} from '@jupyterlite/contents';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Custom DriveFS implementation using the service worker.
|
|
12
|
+
*/
|
|
13
|
+
class MyDriveFS extends DriveFS {
|
|
14
|
+
createAPI(options: DriveFS.IOptions): ContentsAPI {
|
|
15
|
+
return new ServiceWorkerContentsAPI(
|
|
16
|
+
options.baseUrl,
|
|
17
|
+
options.driveName,
|
|
18
|
+
options.mountpoint,
|
|
19
|
+
options.FS,
|
|
20
|
+
options.ERRNO_CODES
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Shell web worker that uses DriveFS via service worker.
|
|
27
|
+
* Note that this is not exported as it is accessed from Shell via the filename.
|
|
28
|
+
*/
|
|
29
|
+
class ShellWorker extends BaseShellWorker {
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the DriveFS to mount an external file system.
|
|
32
|
+
*/
|
|
33
|
+
protected override initDriveFS(
|
|
34
|
+
driveFsBaseUrl: string,
|
|
35
|
+
mountpoint: string,
|
|
36
|
+
fileSystem: IFileSystem
|
|
37
|
+
): void {
|
|
38
|
+
console.log('Terminal initDriveFS', driveFsBaseUrl, mountpoint);
|
|
39
|
+
const { FS, ERRNO_CODES, PATH } = fileSystem;
|
|
40
|
+
const driveFS = new MyDriveFS({
|
|
41
|
+
FS,
|
|
42
|
+
PATH,
|
|
43
|
+
ERRNO_CODES,
|
|
44
|
+
baseUrl: driveFsBaseUrl,
|
|
45
|
+
driveName: '',
|
|
46
|
+
mountpoint
|
|
47
|
+
});
|
|
48
|
+
FS.mount(driveFS, {}, mountpoint);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const worker = new ShellWorker();
|
|
53
|
+
expose(worker);
|
package/lib/terminals.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { TerminalAPI } from '@jupyterlab/services';
|
|
2
|
-
import { ITerminals } from './tokens';
|
|
3
|
-
/**
|
|
4
|
-
* A class to handle requests to /api/terminals
|
|
5
|
-
*/
|
|
6
|
-
export declare class Terminals implements ITerminals {
|
|
7
|
-
/**
|
|
8
|
-
* Construct a new Terminals object.
|
|
9
|
-
*/
|
|
10
|
-
constructor(wsUrl: string);
|
|
11
|
-
/**
|
|
12
|
-
* List the running terminals.
|
|
13
|
-
*/
|
|
14
|
-
list(): Promise<TerminalAPI.IModel[]>;
|
|
15
|
-
/**
|
|
16
|
-
* Start a new kernel.
|
|
17
|
-
*/
|
|
18
|
-
startNew(): Promise<TerminalAPI.IModel>;
|
|
19
|
-
private _nextAvailableName;
|
|
20
|
-
private _wsUrl;
|
|
21
|
-
private _terminals;
|
|
22
|
-
}
|
package/lib/terminals.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Jupyter Development Team.
|
|
2
|
-
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
import { PageConfig } from '@jupyterlab/coreutils';
|
|
4
|
-
import { Terminal } from './terminal';
|
|
5
|
-
/**
|
|
6
|
-
* A class to handle requests to /api/terminals
|
|
7
|
-
*/
|
|
8
|
-
export class Terminals {
|
|
9
|
-
/**
|
|
10
|
-
* Construct a new Terminals object.
|
|
11
|
-
*/
|
|
12
|
-
constructor(wsUrl) {
|
|
13
|
-
this._terminals = new Map();
|
|
14
|
-
this._wsUrl = wsUrl;
|
|
15
|
-
console.log('==> Terminals.constructor', this._wsUrl);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* List the running terminals.
|
|
19
|
-
*/
|
|
20
|
-
async list() {
|
|
21
|
-
const ret = [...this._terminals.values()].map(terminal => ({
|
|
22
|
-
name: terminal.name
|
|
23
|
-
}));
|
|
24
|
-
console.log('==> Terminals.list', ret);
|
|
25
|
-
return ret;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Start a new kernel.
|
|
29
|
-
*/
|
|
30
|
-
async startNew() {
|
|
31
|
-
const name = this._nextAvailableName();
|
|
32
|
-
console.log('==> Terminals.new', name);
|
|
33
|
-
const baseUrl = PageConfig.getBaseUrl();
|
|
34
|
-
const term = new Terminal({ name, baseUrl });
|
|
35
|
-
this._terminals.set(name, term);
|
|
36
|
-
const url = `${this._wsUrl}terminals/websocket/${name}`;
|
|
37
|
-
await term.wsConnect(url);
|
|
38
|
-
return { name };
|
|
39
|
-
}
|
|
40
|
-
_nextAvailableName() {
|
|
41
|
-
for (let i = 1;; ++i) {
|
|
42
|
-
const name = `${i}`;
|
|
43
|
-
if (!this._terminals.has(name)) {
|
|
44
|
-
return name;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
package/src/terminals.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Jupyter Development Team.
|
|
2
|
-
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
|
|
4
|
-
import { PageConfig } from '@jupyterlab/coreutils';
|
|
5
|
-
import { TerminalAPI } from '@jupyterlab/services';
|
|
6
|
-
|
|
7
|
-
import { Terminal } from './terminal';
|
|
8
|
-
import { ITerminals } from './tokens';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A class to handle requests to /api/terminals
|
|
12
|
-
*/
|
|
13
|
-
export class Terminals implements ITerminals {
|
|
14
|
-
/**
|
|
15
|
-
* Construct a new Terminals object.
|
|
16
|
-
*/
|
|
17
|
-
constructor(wsUrl: string) {
|
|
18
|
-
this._wsUrl = wsUrl;
|
|
19
|
-
console.log('==> Terminals.constructor', this._wsUrl);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* List the running terminals.
|
|
24
|
-
*/
|
|
25
|
-
async list(): Promise<TerminalAPI.IModel[]> {
|
|
26
|
-
const ret = [...this._terminals.values()].map(terminal => ({
|
|
27
|
-
name: terminal.name
|
|
28
|
-
}));
|
|
29
|
-
console.log('==> Terminals.list', ret);
|
|
30
|
-
return ret;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Start a new kernel.
|
|
35
|
-
*/
|
|
36
|
-
async startNew(): Promise<TerminalAPI.IModel> {
|
|
37
|
-
const name = this._nextAvailableName();
|
|
38
|
-
console.log('==> Terminals.new', name);
|
|
39
|
-
const baseUrl = PageConfig.getBaseUrl();
|
|
40
|
-
const term = new Terminal({ name, baseUrl });
|
|
41
|
-
this._terminals.set(name, term);
|
|
42
|
-
|
|
43
|
-
const url = `${this._wsUrl}terminals/websocket/${name}`;
|
|
44
|
-
await term.wsConnect(url);
|
|
45
|
-
|
|
46
|
-
return { name };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private _nextAvailableName(): string {
|
|
50
|
-
for (let i = 1; ; ++i) {
|
|
51
|
-
const name = `${i}`;
|
|
52
|
-
if (!this._terminals.has(name)) {
|
|
53
|
-
return name;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private _wsUrl: string;
|
|
59
|
-
private _terminals: Map<string, Terminal> = new Map();
|
|
60
|
-
}
|