mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-06-08 13:37:29 +01:00
Remove terminal.html webpack entry point and notebooks terminal code
- Delete src/Terminal/ directory (JupyterLabAppFactory, index.ts/html/css, TerminalProps) - Delete NotebookTerminalComponentAdapter (no longer needed) - Simplify TerminalTab to always use CloudShellTerminalComponentAdapter - Inline TerminalProps interface into NotebookTerminalComponent - Remove src/Terminal/**/* from tsconfig.strict.json Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -2,14 +2,27 @@
|
|||||||
* Wrapper around Notebook server terminal
|
* Wrapper around Notebook server terminal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { AuthType } from "../../../AuthType";
|
||||||
import { useTerminal } from "hooks/useTerminal";
|
import { useTerminal } from "hooks/useTerminal";
|
||||||
import postRobot from "post-robot";
|
import postRobot from "post-robot";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import { TerminalProps } from "../../../Terminal/TerminalProps";
|
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { ApiType } from "../../../UserContext";
|
||||||
import * as StringUtils from "../../../Utils/StringUtils";
|
import * as StringUtils from "../../../Utils/StringUtils";
|
||||||
|
|
||||||
|
interface TerminalProps {
|
||||||
|
authToken: string;
|
||||||
|
notebookServerEndpoint: string;
|
||||||
|
terminalEndpoint: string;
|
||||||
|
databaseAccount: DataModels.DatabaseAccount;
|
||||||
|
authType: AuthType;
|
||||||
|
apiType: ApiType;
|
||||||
|
subscriptionId: string;
|
||||||
|
tabId: string;
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
databaseAccount: DataModels.DatabaseAccount;
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
|
||||||
import * as React from "react";
|
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { BaseTerminalComponentAdapter } from "./BaseTerminalComponentAdapter";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notebook terminal tab
|
|
||||||
*/
|
|
||||||
export class NotebookTerminalComponentAdapter extends BaseTerminalComponentAdapter {
|
|
||||||
constructor(
|
|
||||||
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
|
||||||
getDatabaseAccount: () => DataModels.DatabaseAccount,
|
|
||||||
getTabId: () => string,
|
|
||||||
getUsername: () => string,
|
|
||||||
isAllPublicIPAddressesEnabled: ko.Observable<boolean>,
|
|
||||||
kind: ViewModels.TerminalKind,
|
|
||||||
) {
|
|
||||||
super(getDatabaseAccount, getTabId, getUsername, isAllPublicIPAddressesEnabled, kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderTerminalComponent(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<NotebookTerminalComponent
|
|
||||||
notebookServerInfo={this.getNotebookServerInfo()}
|
|
||||||
databaseAccount={this.getDatabaseAccount()}
|
|
||||||
tabId={this.getTabId()}
|
|
||||||
username={this.getUsername()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { useNotebook } from "../Notebook/useNotebook";
|
|
||||||
import { NotebookTerminalComponentAdapter } from "./ShellAdapters/NotebookTerminalComponentAdapter";
|
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
export interface TerminalTabOptions extends ViewModels.TabOptions {
|
export interface TerminalTabOptions extends ViewModels.TabOptions {
|
||||||
@@ -29,41 +27,17 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
||||||
|
|
||||||
const commonArgs: [
|
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(
|
||||||
() => DataModels.DatabaseAccount,
|
|
||||||
() => string,
|
|
||||||
() => string,
|
|
||||||
ko.Observable<boolean>,
|
|
||||||
ViewModels.TerminalKind,
|
|
||||||
] = [
|
|
||||||
() => userContext?.databaseAccount,
|
() => userContext?.databaseAccount,
|
||||||
() => this.tabId,
|
() => this.tabId,
|
||||||
() => this.getUsername(),
|
() => this.getUsername(),
|
||||||
this.isAllPublicIPAddressesEnabled,
|
this.isAllPublicIPAddressesEnabled,
|
||||||
options.kind,
|
options.kind,
|
||||||
];
|
);
|
||||||
|
|
||||||
if (userContext.features.enableCloudShell) {
|
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(...commonArgs);
|
return this.isTemplateReady() && this.isAllPublicIPAddressesEnabled();
|
||||||
|
});
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
|
||||||
return this.isTemplateReady() && this.isAllPublicIPAddressesEnabled();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
|
||||||
() => this.getNotebookServerInfo(options),
|
|
||||||
...commonArgs,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
|
||||||
return (
|
|
||||||
this.isTemplateReady() &&
|
|
||||||
useNotebook.getState().isNotebookEnabled &&
|
|
||||||
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint &&
|
|
||||||
this.isAllPublicIPAddressesEnabled()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
||||||
checkFirewallRules(
|
checkFirewallRules(
|
||||||
@@ -96,42 +70,6 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.updateNavbarWithTabsButtons();
|
this.updateNavbarWithTabsButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNotebookServerInfo(options: TerminalTabOptions): DataModels.NotebookWorkspaceConnectionInfo {
|
|
||||||
let endpointSuffix: string;
|
|
||||||
|
|
||||||
switch (options.kind) {
|
|
||||||
case ViewModels.TerminalKind.Default:
|
|
||||||
endpointSuffix = "";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ViewModels.TerminalKind.Mongo:
|
|
||||||
endpointSuffix = "mongo";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ViewModels.TerminalKind.Cassandra:
|
|
||||||
endpointSuffix = "cassandra";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ViewModels.TerminalKind.Postgres:
|
|
||||||
endpointSuffix = "postgresql";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ViewModels.TerminalKind.VCoreMongo:
|
|
||||||
endpointSuffix = "mongovcore";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Terminal kind: ${options.kind} not supported`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const info: DataModels.NotebookWorkspaceConnectionInfo = useNotebook.getState().notebookServerInfo;
|
|
||||||
return {
|
|
||||||
authToken: info.authToken,
|
|
||||||
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
|
|
||||||
forwardingId: info.forwardingId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private getUsername(): string {
|
private getUsername(): string {
|
||||||
if (userContext.apiType !== "VCoreMongo" || !userContext?.vcoreMongoConnectionParams?.adminLogin) {
|
if (userContext.apiType !== "VCoreMongo" || !userContext?.vcoreMongoConnectionParams?.adminLogin) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
/**
|
|
||||||
* JupyterLab applications based on jupyterLab components
|
|
||||||
*/
|
|
||||||
import { ServerConnection, TerminalManager } from "@jupyterlab/services";
|
|
||||||
import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal/terminal";
|
|
||||||
import { Terminal } from "@jupyterlab/terminal";
|
|
||||||
import { Panel, Widget } from "@phosphor/widgets";
|
|
||||||
import { userContext } from "UserContext";
|
|
||||||
|
|
||||||
export class JupyterLabAppFactory {
|
|
||||||
private isShellStarted: boolean | undefined;
|
|
||||||
private checkShellStarted: ((content: string | undefined) => void) | undefined;
|
|
||||||
private onShellExited: (restartShell: boolean) => void;
|
|
||||||
private restartShell: boolean;
|
|
||||||
|
|
||||||
private isShellExited(content: string | undefined) {
|
|
||||||
if (userContext.apiType === "VCoreMongo" && content?.includes("MongoServerError: Invalid key")) {
|
|
||||||
this.restartShell = true;
|
|
||||||
}
|
|
||||||
return content?.includes("cosmosshelluser@");
|
|
||||||
}
|
|
||||||
|
|
||||||
private isMongoShellStarted(content: string | undefined) {
|
|
||||||
this.isShellStarted = content?.includes("MongoDB shell version");
|
|
||||||
}
|
|
||||||
|
|
||||||
private isCassandraShellStarted(content: string | undefined) {
|
|
||||||
this.isShellStarted = content?.includes("Connected to") && content?.includes("cqlsh");
|
|
||||||
}
|
|
||||||
|
|
||||||
private isPostgresShellStarted(content: string | undefined) {
|
|
||||||
this.isShellStarted = content?.includes("citus=>");
|
|
||||||
}
|
|
||||||
|
|
||||||
private isVCoreMongoShellStarted(content: string | undefined) {
|
|
||||||
this.isShellStarted = content?.includes("Enter password");
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(closeTab: (restartShell: boolean) => void) {
|
|
||||||
this.onShellExited = closeTab;
|
|
||||||
this.isShellStarted = false;
|
|
||||||
this.checkShellStarted = undefined;
|
|
||||||
this.restartShell = false;
|
|
||||||
|
|
||||||
switch (userContext.apiType) {
|
|
||||||
case "Mongo":
|
|
||||||
this.checkShellStarted = this.isMongoShellStarted;
|
|
||||||
break;
|
|
||||||
case "Cassandra":
|
|
||||||
this.checkShellStarted = this.isCassandraShellStarted;
|
|
||||||
break;
|
|
||||||
case "Postgres":
|
|
||||||
this.checkShellStarted = this.isPostgresShellStarted;
|
|
||||||
break;
|
|
||||||
case "VCoreMongo":
|
|
||||||
this.checkShellStarted = this.isVCoreMongoShellStarted;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createTerminalApp(serverSettings: ServerConnection.ISettings): Promise<ITerminalConnection | undefined> {
|
|
||||||
const configurationSettings: Partial<ServerConnection.ISettings> = serverSettings;
|
|
||||||
(configurationSettings.appendToken as boolean) = false;
|
|
||||||
serverSettings = ServerConnection.makeSettings(configurationSettings);
|
|
||||||
const manager = new TerminalManager({
|
|
||||||
serverSettings: serverSettings,
|
|
||||||
});
|
|
||||||
const session = await manager.startNew();
|
|
||||||
session.messageReceived.connect(async (_, message: IMessage) => {
|
|
||||||
const content = message.content && message.content[0]?.toString();
|
|
||||||
if (this.checkShellStarted && message.type == "stdout") {
|
|
||||||
//Close the terminal tab once the shell closed messages are received
|
|
||||||
if (!this.isShellStarted) {
|
|
||||||
this.checkShellStarted(content);
|
|
||||||
} else if (this.isShellExited(content)) {
|
|
||||||
this.onShellExited(this.restartShell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
let internalSend = session.send;
|
|
||||||
session.send = (message: IMessage) => {
|
|
||||||
message?.content?.push(serverSettings?.token);
|
|
||||||
internalSend.call(session, message);
|
|
||||||
};
|
|
||||||
const term = new Terminal(session, { theme: "dark", shutdownOnClose: true });
|
|
||||||
|
|
||||||
if (!term) {
|
|
||||||
console.error("Failed starting terminal");
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
term.title.closable = false;
|
|
||||||
term.addClass("terminalWidget");
|
|
||||||
|
|
||||||
let panel = new Panel();
|
|
||||||
panel.addWidget(term as any);
|
|
||||||
panel.id = "main";
|
|
||||||
|
|
||||||
// Attach the widget to the dom.
|
|
||||||
Widget.attach(panel, document.body);
|
|
||||||
|
|
||||||
// Switch focus to the terminal
|
|
||||||
term.activate();
|
|
||||||
|
|
||||||
// Handle resize events.
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
panel.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dispose terminal when unloading.
|
|
||||||
window.addEventListener("unload", () => {
|
|
||||||
panel.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close terminal when Ctrl key is pressed
|
|
||||||
term.node.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
||||||
if (event.ctrlKey) {
|
|
||||||
this.onShellExited(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-111
@@ -1,111 +0,0 @@
|
|||||||
/**
|
|
||||||
* Message handling with iframe parent
|
|
||||||
*/
|
|
||||||
export interface UpdateMessage {
|
|
||||||
command: string;
|
|
||||||
arg?: any;
|
|
||||||
}
|
|
||||||
export declare type ContentType = "notebook" | "file" | "directory";
|
|
||||||
export interface ContentItem {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
type: ContentType;
|
|
||||||
}
|
|
||||||
export interface UploadData {
|
|
||||||
filepath: string;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
export interface RenameFileData {
|
|
||||||
sourcePath: string;
|
|
||||||
targetPath: string;
|
|
||||||
}
|
|
||||||
export interface RenameFileResult {
|
|
||||||
source: string;
|
|
||||||
target: ContentItem;
|
|
||||||
}
|
|
||||||
export interface FromDataExplorerMessage {
|
|
||||||
type: MessageTypes;
|
|
||||||
params: any;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
export declare type KernelStatusStates =
|
|
||||||
| "unknown"
|
|
||||||
| "starting"
|
|
||||||
| "reconnecting"
|
|
||||||
| "idle"
|
|
||||||
| "busy"
|
|
||||||
| "restarting"
|
|
||||||
| "autorestarting"
|
|
||||||
| "dead"
|
|
||||||
| "connected";
|
|
||||||
/**
|
|
||||||
* Unsolicited message
|
|
||||||
*/
|
|
||||||
export interface FromNotebookUpdateMessage {
|
|
||||||
type: NotebookUpdateTypes;
|
|
||||||
arg?: any;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Response to a Data Explorer request
|
|
||||||
*/
|
|
||||||
export interface FromNotebookResponseMessage {
|
|
||||||
id: string;
|
|
||||||
data?: any;
|
|
||||||
error?: any;
|
|
||||||
}
|
|
||||||
export interface FromNotebookMessage {
|
|
||||||
actionType: ActionTypes;
|
|
||||||
message: FromNotebookUpdateMessage | FromNotebookResponseMessage;
|
|
||||||
}
|
|
||||||
export declare type KernelOption = {
|
|
||||||
name: string;
|
|
||||||
displayName: string;
|
|
||||||
};
|
|
||||||
export interface KernelSpecs {
|
|
||||||
defaultName: string;
|
|
||||||
kernelSpecs: {
|
|
||||||
[name: string]: KernelOption;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export declare enum ActionTypes {
|
|
||||||
Update = 0,
|
|
||||||
Response = 1,
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Messages Data Explorer -> JupyterLabApp
|
|
||||||
*/
|
|
||||||
export declare enum MessageTypes {
|
|
||||||
FileList = 0,
|
|
||||||
CreateInDir = 1,
|
|
||||||
DeleteFile = 2,
|
|
||||||
UploadFile = 3,
|
|
||||||
RenameFile = 4,
|
|
||||||
ReadFileContent = 5,
|
|
||||||
CreateDirectory = 6,
|
|
||||||
InsertBelow = 7,
|
|
||||||
RunAndAdvance = 8,
|
|
||||||
Copy = 9,
|
|
||||||
Cut = 10,
|
|
||||||
Paste = 11,
|
|
||||||
Undo = 12,
|
|
||||||
ClearAllOutputs = 13,
|
|
||||||
RunAll = 14,
|
|
||||||
Redo = 15,
|
|
||||||
Save = 16,
|
|
||||||
RestartKernel = 17,
|
|
||||||
ChangeCellType = 18,
|
|
||||||
SwitchKernel = 19,
|
|
||||||
ChangeKernel = 20,
|
|
||||||
Status = 21,
|
|
||||||
KernelList = 22,
|
|
||||||
IsDirty = 23,
|
|
||||||
Shutdown = 24,
|
|
||||||
}
|
|
||||||
export declare enum NotebookUpdateTypes {
|
|
||||||
Ready = 0,
|
|
||||||
ClickEvent = 1,
|
|
||||||
ActiveCellType = 2,
|
|
||||||
KernelChange = 3,
|
|
||||||
FileSaved = 4,
|
|
||||||
SessionStatusChange = 5,
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Summary
|
|
||||||
This describes how to run a custom version of the Data Explorer in the Emulator which can open a jupyter notebook from with a tab.
|
|
||||||
|
|
||||||
# Requirements
|
|
||||||
This requires:
|
|
||||||
* a running instance of CosmosDB Emulator
|
|
||||||
* a running instance of the jupyter server
|
|
||||||
* access to the cosmosdb-dataexplorer git repository
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
## Install CosmosDB Emulator
|
|
||||||
* Download from https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator
|
|
||||||
* Open the Emulator and create at least one Collection
|
|
||||||
|
|
||||||
## Install Jupyter server on local machine (Windows)
|
|
||||||
We use the Anaconda distribution which comes with a packaged jupyter and python.
|
|
||||||
* Download and install Anaconda from https://www.anaconda.com/distribution/ (python3 64-bit version)
|
|
||||||
Keep all default options. Install Visual Studio Code as well.
|
|
||||||
### Verify Jupyter installation and create mynotebook
|
|
||||||
* Open an "Anaconda Prompt" (hit the Window key, type "Anaconda", select "Anaconda Prompt" hit Enter)
|
|
||||||
> cd src/jupyter-server (the notebooks will be saved in this directory)
|
|
||||||
> jupyter notebook
|
|
||||||
* It should open the browser at http://localhost:8888/ with the jupyter notebook.
|
|
||||||
* Edit the notebook and save it as "mynotebook" (This should create a file: mynotebook.ipynb).
|
|
||||||
We do this, because right now, the notebook filename is hardcoded as mynotebook.
|
|
||||||
|
|
||||||
### Modify jupyter server install
|
|
||||||
In order to serve the jupyter frontend from the emulator, we need to turn off a bunch of things.
|
|
||||||
* Stop the jupyter server (Ctrl-C twice from the Anaconda Prompt where you started jupyter notebook)
|
|
||||||
* From the Anaconda Prompt, type: juypter notebook --generate-config
|
|
||||||
* This should create the file: .jupyter/jupyter_notebook in your home directory.
|
|
||||||
* Edit this file:
|
|
||||||
|
|
||||||
Enable embedding the jupyter frontend inside an iFrame in DataExplorer:
|
|
||||||
c.NotebookApp.tornado_settings = { 'headers': { 'Content-Security-Policy': "frame-ancestors * localhost:1234 localhost:12900"} }
|
|
||||||
|
|
||||||
Enable a remotely-served jupyter frontend to still talk to the jupyter server:
|
|
||||||
c.NotebookApp.allow_origin = '*'
|
|
||||||
c.NotebookApp.allow_remote_access = True <--- not sure if this one matters
|
|
||||||
c.NotebookApp.token = ''
|
|
||||||
c.NotebookApp.disable_check_xsrf = True
|
|
||||||
|
|
||||||
## Install custom Data Explorer in Emulator
|
|
||||||
* Install git from https://git-scm.com/download/win (keep all default options)
|
|
||||||
* Install nodejs and npm from: https://nodejs.org/en/ (10.15.1 LTS)
|
|
||||||
|
|
||||||
### Download and build Data Explorer
|
|
||||||
* From the Git Bash terminal:
|
|
||||||
* cd ~/src
|
|
||||||
* git clone https://msdata.visualstudio.com/DefaultCollection/CosmosDB/_git/cosmosdb-dataexplorer
|
|
||||||
* cd cosmosdb-dataexplorer/Product/Portal
|
|
||||||
* git checkout users/languye/spark-in-dataexplorer
|
|
||||||
* cd JupyterLab
|
|
||||||
* npm i
|
|
||||||
* npm run build (this builds jupyterlab (the frontend of jupyter) and copies it into ../DataExplorer/notebookapp/)
|
|
||||||
* cd ../DataExplorer
|
|
||||||
* npm i
|
|
||||||
* npm run build (this builds and copies DataExplorer into the Emulator folder)
|
|
||||||
|
|
||||||
# How to run the setup
|
|
||||||
* Run the jupter-server by opening an Anaconda Prompt and typing: jupyter notebook
|
|
||||||
* Open the emulator at: http://localhost:8081/_explorer/index.html
|
|
||||||
* Click on any Collection
|
|
||||||
* Click on "New Notebook" button in the Command bar
|
|
||||||
* You should see the "mynotebook" jupyter notebook displayed in tab (inside an iframe).
|
|
||||||
* There is a "New Cell" button in the CommandBar outside the jupyter iframe which will add a cell inside the notebook.
|
|
||||||
|
|
||||||
# Notes
|
|
||||||
* The Emulator is located in: C:\Program Files\Azure Cosmos DB Emulator\Packages\DataExplorer
|
|
||||||
* Running "jupyter notebook" serves the jupyter traditional frontend. There is an alternate frontend also developed by jupyter which is modular and customizable called: JupyterLab. We use their "notebook" example in this project slightly modified to pass the server and notebook pathname via iframe url's parameters:
|
|
||||||
https://github.com/jupyterlab/jupyterlab/tree/master/examples/notebook
|
|
||||||
jupyterlab uses the same communication protocol as the traditional frontend, so it can connect to any jupyter-server,
|
|
||||||
so one can use multiple frontends (at the same time) to connect to a given jupyter-server.
|
|
||||||
* The jupyter frontend and the server use websockets to communicate.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { AuthType } from "../AuthType";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import { ApiType } from "../UserContext";
|
|
||||||
|
|
||||||
export interface TerminalProps {
|
|
||||||
authToken: string;
|
|
||||||
notebookServerEndpoint: string;
|
|
||||||
terminalEndpoint: string;
|
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
|
||||||
authType: AuthType;
|
|
||||||
apiType: ApiType;
|
|
||||||
subscriptionId: string;
|
|
||||||
tabId: string;
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*-----------------------------------------------------------------------------
|
|
||||||
| Copyright (c) Jupyter Development Team.
|
|
||||||
| Distributed under the terms of the Modified BSD License.
|
|
||||||
|----------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: white;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#main {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jp-NotebookPanel {
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminalWidget {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Notebook</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script id="jupyter-config-data" type="application/json">
|
|
||||||
{
|
|
||||||
"terminalsAvailable": "true"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
import { ServerConnection } from "@jupyterlab/services";
|
|
||||||
import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal/terminal";
|
|
||||||
import "@jupyterlab/terminal/style/index.css";
|
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
|
||||||
import postRobot from "post-robot";
|
|
||||||
import { HttpHeaders } from "../Common/Constants";
|
|
||||||
import { Action } from "../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { updateUserContext } from "../UserContext";
|
|
||||||
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
|
|
||||||
import { TerminalProps } from "./TerminalProps";
|
|
||||||
import "./index.css";
|
|
||||||
|
|
||||||
let session: ITerminalConnection | undefined;
|
|
||||||
|
|
||||||
const createServerSettings = (props: TerminalProps): ServerConnection.ISettings => {
|
|
||||||
let body: BodyInit | undefined;
|
|
||||||
let headers: HeadersInit | undefined;
|
|
||||||
if (props.terminalEndpoint) {
|
|
||||||
let bodyObj: { endpoint: string; username?: string } = {
|
|
||||||
endpoint: props.terminalEndpoint,
|
|
||||||
};
|
|
||||||
if (props.username) {
|
|
||||||
bodyObj = {
|
|
||||||
...bodyObj,
|
|
||||||
username: props.username,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
body = JSON.stringify(bodyObj);
|
|
||||||
headers = {
|
|
||||||
[HttpHeaders.contentType]: "application/json",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = props.notebookServerEndpoint;
|
|
||||||
let options: Partial<ServerConnection.ISettings> = {
|
|
||||||
baseUrl: server,
|
|
||||||
init: { body, headers },
|
|
||||||
fetch: window.parent.fetch,
|
|
||||||
};
|
|
||||||
if (props.authToken) {
|
|
||||||
options = {
|
|
||||||
baseUrl: server,
|
|
||||||
token: props.authToken,
|
|
||||||
appendToken: true,
|
|
||||||
init: { body, headers },
|
|
||||||
fetch: window.parent.fetch,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ServerConnection.makeSettings(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initTerminal = async (props: TerminalProps): Promise<void> => {
|
|
||||||
// Initialize userContext (only properties which are needed by TelemetryProcessor)
|
|
||||||
updateUserContext({
|
|
||||||
subscriptionId: props.subscriptionId,
|
|
||||||
apiType: props.apiType,
|
|
||||||
authType: props.authType,
|
|
||||||
databaseAccount: props.databaseAccount,
|
|
||||||
});
|
|
||||||
|
|
||||||
const serverSettings = createServerSettings(props);
|
|
||||||
|
|
||||||
createTerminalApp(props, serverSettings);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createTerminalApp = async (props: TerminalProps, serverSettings: ServerConnection.ISettings) => {
|
|
||||||
const data = { baseUrl: serverSettings.baseUrl };
|
|
||||||
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
|
|
||||||
|
|
||||||
try {
|
|
||||||
session = await new JupyterLabAppFactory((restartShell: boolean) =>
|
|
||||||
closeTab(props, serverSettings, restartShell),
|
|
||||||
).createTerminalApp(serverSettings);
|
|
||||||
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
|
|
||||||
} catch (error) {
|
|
||||||
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
|
|
||||||
session = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeTab = (props: TerminalProps, serverSettings: ServerConnection.ISettings, restartShell: boolean): void => {
|
|
||||||
if (restartShell) {
|
|
||||||
createTerminalApp(props, serverSettings);
|
|
||||||
} else {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{ type: MessageTypes.CloseTab, data: { tabId: props.tabId }, signature: "pcIframe" },
|
|
||||||
window.document.referrer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const main = async (): Promise<void> => {
|
|
||||||
postRobot.on(
|
|
||||||
"props",
|
|
||||||
{
|
|
||||||
window: window.parent,
|
|
||||||
domain: window.location.origin,
|
|
||||||
},
|
|
||||||
async (event) => {
|
|
||||||
// Typescript definition for event is wrong. So read props by casting to <any>
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const props = (event as any).data as TerminalProps;
|
|
||||||
await initTerminal(props);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
postRobot.on(
|
|
||||||
"sendMessage",
|
|
||||||
{
|
|
||||||
window: window.parent,
|
|
||||||
domain: window.location.origin,
|
|
||||||
},
|
|
||||||
async (event) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const message = (event as any).data as IMessage;
|
|
||||||
if (session) {
|
|
||||||
session.send(message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("load", main);
|
|
||||||
@@ -145,7 +145,6 @@
|
|||||||
"src/Platform/Emulator/**/*",
|
"src/Platform/Emulator/**/*",
|
||||||
"src/SelfServe/Documentation/**/*",
|
"src/SelfServe/Documentation/**/*",
|
||||||
"src/Shared/Telemetry/**/*",
|
"src/Shared/Telemetry/**/*",
|
||||||
"src/Terminal/**/*",
|
|
||||||
"src/Utils/arm/**/*"
|
"src/Utils/arm/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,6 @@ module.exports = function (_env = {}, argv = {}) {
|
|||||||
index: "./src/Index.tsx",
|
index: "./src/Index.tsx",
|
||||||
quickstart: "./src/quickstart.ts",
|
quickstart: "./src/quickstart.ts",
|
||||||
hostedExplorer: "./src/HostedExplorer.tsx",
|
hostedExplorer: "./src/HostedExplorer.tsx",
|
||||||
terminal: "./src/Terminal/index.ts",
|
|
||||||
cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx",
|
cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx",
|
||||||
selfServe: "./src/SelfServe/SelfServe.tsx",
|
selfServe: "./src/SelfServe/SelfServe.tsx",
|
||||||
connectToGitHub: "./src/GitHub/GitHubConnector.ts",
|
connectToGitHub: "./src/GitHub/GitHubConnector.ts",
|
||||||
@@ -128,11 +127,6 @@ module.exports = function (_env = {}, argv = {}) {
|
|||||||
template: "src/explorer.html",
|
template: "src/explorer.html",
|
||||||
chunks: ["main"],
|
chunks: ["main"],
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
filename: "terminal.html",
|
|
||||||
template: "src/Terminal/index.html",
|
|
||||||
chunks: ["terminal"],
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: "quickstart.html",
|
filename: "quickstart.html",
|
||||||
template: "src/quickstart.html",
|
template: "src/quickstart.html",
|
||||||
|
|||||||
Reference in New Issue
Block a user