mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-26 07:26:58 +00:00
Close mongo and casssandra terminal tabs once the shells are exited (#1183)
* initial commit for closing terminal * added extra case * lint changes and hostee explorer fixes * fixed lint errors * fixed compile error * fixed review comments
This commit is contained in:
parent
591782195d
commit
b765cae088
@ -33,6 +33,7 @@ export enum MessageTypes {
|
|||||||
CreateWorkspace,
|
CreateWorkspace,
|
||||||
CreateSparkPool,
|
CreateSparkPool,
|
||||||
RefreshDatabaseAccount,
|
RefreshDatabaseAccount,
|
||||||
|
CloseTab,
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Versions, ActionContracts, Diagnostics };
|
export { Versions, ActionContracts, Diagnostics };
|
||||||
|
@ -55,6 +55,7 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testAccount,
|
databaseAccount: testAccount,
|
||||||
notebookServerInfo: testNotebookServerInfo,
|
notebookServerInfo: testNotebookServerInfo,
|
||||||
|
tabId: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@ -65,6 +66,7 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testMongo32Account,
|
databaseAccount: testMongo32Account,
|
||||||
notebookServerInfo: testMongoNotebookServerInfo,
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
|
tabId: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@ -75,6 +77,7 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testMongo36Account,
|
databaseAccount: testMongo36Account,
|
||||||
notebookServerInfo: testMongoNotebookServerInfo,
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
|
tabId: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@ -85,6 +88,7 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testCassandraAccount,
|
databaseAccount: testCassandraAccount,
|
||||||
notebookServerInfo: testCassandraNotebookServerInfo,
|
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||||
|
tabId: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
|
@ -12,6 +12,7 @@ import * as StringUtils from "../../../Utils/StringUtils";
|
|||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
databaseAccount: DataModels.DatabaseAccount;
|
||||||
|
tabId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||||
@ -55,6 +56,7 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
apiType: userContext.apiType,
|
apiType: userContext.apiType,
|
||||||
authType: userContext.authType,
|
authType: userContext.authType,
|
||||||
databaseAccount: userContext.databaseAccount,
|
databaseAccount: userContext.databaseAccount,
|
||||||
|
tabId: this.props.tabId,
|
||||||
};
|
};
|
||||||
|
|
||||||
postRobot.send(this.terminalWindow, "props", props, {
|
postRobot.send(this.terminalWindow, "props", props, {
|
||||||
|
@ -1096,7 +1096,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
const terminalTabs: TerminalTab[] = useTabs
|
const terminalTabs: TerminalTab[] = useTabs
|
||||||
.getState()
|
.getState()
|
||||||
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle() === title) as TerminalTab[];
|
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle().startsWith(title)) as TerminalTab[];
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
if (terminalTabs.length > 0) {
|
if (terminalTabs.length > 0) {
|
||||||
|
@ -25,7 +25,8 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
public parameters: ko.Computed<boolean>;
|
public parameters: ko.Computed<boolean>;
|
||||||
constructor(
|
constructor(
|
||||||
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
||||||
private getDatabaseAccount: () => DataModels.DatabaseAccount
|
private getDatabaseAccount: () => DataModels.DatabaseAccount,
|
||||||
|
private getTabId: () => string
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
@ -33,6 +34,7 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
<NotebookTerminalComponent
|
<NotebookTerminalComponent
|
||||||
notebookServerInfo={this.getNotebookServerInfo()}
|
notebookServerInfo={this.getNotebookServerInfo()}
|
||||||
databaseAccount={this.getDatabaseAccount()}
|
databaseAccount={this.getDatabaseAccount()}
|
||||||
|
tabId={this.getTabId()}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
||||||
@ -50,7 +52,8 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
||||||
() => this.getNotebookServerInfo(options),
|
() => this.getNotebookServerInfo(options),
|
||||||
() => userContext?.databaseAccount
|
() => userContext?.databaseAccount,
|
||||||
|
() => this.tabId
|
||||||
);
|
);
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
if (
|
if (
|
||||||
|
@ -2,15 +2,48 @@
|
|||||||
* JupyterLab applications based on jupyterLab components
|
* JupyterLab applications based on jupyterLab components
|
||||||
*/
|
*/
|
||||||
import { ServerConnection, TerminalManager } from "@jupyterlab/services";
|
import { ServerConnection, TerminalManager } from "@jupyterlab/services";
|
||||||
|
import { IMessage } from "@jupyterlab/services/lib/terminal/terminal";
|
||||||
import { Terminal } from "@jupyterlab/terminal";
|
import { Terminal } from "@jupyterlab/terminal";
|
||||||
import { Panel, Widget } from "@phosphor/widgets";
|
import { Panel, Widget } from "@phosphor/widgets";
|
||||||
|
import { userContext } from "UserContext";
|
||||||
|
|
||||||
export class JupyterLabAppFactory {
|
export class JupyterLabAppFactory {
|
||||||
public static async createTerminalApp(serverSettings: ServerConnection.ISettings) {
|
private isShellClosed: boolean;
|
||||||
|
private onShellExited: () => void;
|
||||||
|
private checkShellClosed: ((content: string | undefined) => boolean | undefined) | undefined;
|
||||||
|
|
||||||
|
constructor(closeTab: () => void) {
|
||||||
|
this.onShellExited = closeTab;
|
||||||
|
this.isShellClosed = false;
|
||||||
|
this.checkShellClosed = undefined;
|
||||||
|
|
||||||
|
switch (userContext.apiType) {
|
||||||
|
case "Mongo":
|
||||||
|
this.checkShellClosed = JupyterLabAppFactory.isMongoShellClosed;
|
||||||
|
break;
|
||||||
|
case "Cassandra":
|
||||||
|
this.checkShellClosed = JupyterLabAppFactory.isCassandraShellClosed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createTerminalApp(serverSettings: ServerConnection.ISettings) {
|
||||||
const manager = new TerminalManager({
|
const manager = new TerminalManager({
|
||||||
serverSettings: serverSettings,
|
serverSettings: serverSettings,
|
||||||
});
|
});
|
||||||
const session = await manager.startNew();
|
const session = await manager.startNew();
|
||||||
|
session.messageReceived.connect(async (_, message: IMessage) => {
|
||||||
|
const content = message.content && message.content[0]?.toString();
|
||||||
|
if (this.checkShellClosed && message.type == "stdout") {
|
||||||
|
//Close the terminal tab once the shell closed messages are received
|
||||||
|
if (this.checkShellClosed(content)) {
|
||||||
|
this.isShellClosed = true;
|
||||||
|
} else if (content?.includes("cosmosuser@") && this.isShellClosed) {
|
||||||
|
this.onShellExited();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
const term = new Terminal(session, { theme: "dark", shutdownOnClose: true });
|
const term = new Terminal(session, { theme: "dark", shutdownOnClose: true });
|
||||||
|
|
||||||
if (!term) {
|
if (!term) {
|
||||||
@ -38,4 +71,12 @@ export class JupyterLabAppFactory {
|
|||||||
panel.dispose();
|
panel.dispose();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static isMongoShellClosed(content: string | undefined) {
|
||||||
|
return content?.endsWith("bye\r\n") || (content?.includes("Stopped") && content?.includes("mongo --host"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static isCassandraShellClosed(content: string | undefined) {
|
||||||
|
return content == "\r\n" || (content?.includes("Stopped") && content?.includes("cqlsh"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,5 @@ export interface TerminalProps {
|
|||||||
authType: AuthType;
|
authType: AuthType;
|
||||||
apiType: ApiType;
|
apiType: ApiType;
|
||||||
subscriptionId: string;
|
subscriptionId: string;
|
||||||
|
tabId: string;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ServerConnection } from "@jupyterlab/services";
|
import { ServerConnection } from "@jupyterlab/services";
|
||||||
import "@jupyterlab/terminal/style/index.css";
|
import "@jupyterlab/terminal/style/index.css";
|
||||||
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import postRobot from "post-robot";
|
import postRobot from "post-robot";
|
||||||
import { HttpHeaders } from "../Common/Constants";
|
import { HttpHeaders } from "../Common/Constants";
|
||||||
import { Action } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
@ -54,13 +55,20 @@ const initTerminal = async (props: TerminalProps) => {
|
|||||||
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
|
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await JupyterLabAppFactory.createTerminalApp(serverSettings);
|
await new JupyterLabAppFactory(() => closeTab(props.tabId)).createTerminalApp(serverSettings);
|
||||||
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
|
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
|
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeTab = (tabId: string): void => {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{ type: MessageTypes.CloseTab, data: { tabId: tabId }, signature: "pcIframe" },
|
||||||
|
window.document.referrer
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const main = async (): Promise<void> => {
|
const main = async (): Promise<void> => {
|
||||||
postRobot.on(
|
postRobot.on(
|
||||||
"props",
|
"props",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useTabs } from "hooks/useTabs";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { applyExplorerBindings } from "../applyExplorerBindings";
|
import { applyExplorerBindings } from "../applyExplorerBindings";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
@ -69,16 +70,38 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
|||||||
|
|
||||||
async function configureHosted(): Promise<Explorer> {
|
async function configureHosted(): Promise<Explorer> {
|
||||||
const win = (window as unknown) as HostedExplorerChildFrame;
|
const win = (window as unknown) as HostedExplorerChildFrame;
|
||||||
|
let explorer: Explorer;
|
||||||
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
||||||
return configureHostedWithEncryptedToken(win.hostedConfig);
|
explorer = configureHostedWithEncryptedToken(win.hostedConfig);
|
||||||
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
||||||
return configureHostedWithResourceToken(win.hostedConfig);
|
explorer = configureHostedWithResourceToken(win.hostedConfig);
|
||||||
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
||||||
return configureHostedWithConnectionString(win.hostedConfig);
|
explorer = configureHostedWithConnectionString(win.hostedConfig);
|
||||||
} else if (win.hostedConfig.authType === AuthType.AAD) {
|
} else if (win.hostedConfig.authType === AuthType.AAD) {
|
||||||
return configureHostedWithAAD(win.hostedConfig);
|
explorer = await configureHostedWithAAD(win.hostedConfig);
|
||||||
}
|
} else {
|
||||||
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
|
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
"message",
|
||||||
|
(event) => {
|
||||||
|
if (isInvalidParentFrameOrigin(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldProcessMessage(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data?.type === MessageTypes.CloseTab) {
|
||||||
|
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
||||||
@ -261,6 +284,8 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
}
|
}
|
||||||
} else if (shouldForwardMessage(message, event.origin)) {
|
} else if (shouldForwardMessage(message, event.origin)) {
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
|
} else if (event.data?.type === MessageTypes.CloseTab) {
|
||||||
|
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
|
Loading…
Reference in New Issue
Block a user