mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-06-29 01:27:22 +01:00
Remove Phoenix & Notebooks — Phase 1: Decouple database shells to CloudShell (#2513)
* Add implementation plan for removing Phoenix and notebooks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Phase 1: Decouple database shells from Phoenix to CloudShell-only Migrate all shell terminals to the CloudShell path and remove the legacy Phoenix notebook-server terminal code so shells no longer depend on notebook provisioning. - TerminalTab now always uses CloudShellTerminalComponentAdapter; removed the notebook-server adapter branch, getNotebookServerInfo, and the dead VCoreMongo firewall check - Migrate Postgres and VCore Mongo quickstart tabs to CloudShellTerminalComponent (drop allocateContainer/useNotebook dependencies) - Refactor useTerminal to send input via the CloudShell WebSocket instead of postRobot/iframe; register the socket from CloudShellTerminalComponent - Simplify Explorer.openNotebookTerminal to always open a CloudShell terminal - Delete NotebookTerminalComponent(+test/less/snapshot), NotebookTerminalComponentAdapter, and the src/Terminal/ entry point - Remove the terminal.html webpack entry/HTML plugin and src/Terminal from tsconfig.strict.json Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
# Implementation Plan: Remove Phoenix & Notebooks
|
||||
|
||||
## Problem statement
|
||||
|
||||
Cosmos Explorer contains a large, deeply-integrated **notebooks** feature backed by
|
||||
the **Phoenix** compute-container service and the **Juno** service, plus a **GitHub**
|
||||
integration used to pin/browse notebook repositories. This functionality is being
|
||||
retired. The goal is to remove all notebooks/Phoenix/Juno/GitHub-for-notebooks code,
|
||||
dependencies, UI surfaces, telemetry, and configuration, while **preserving the
|
||||
database shell terminals** (Mongo / Cassandra / Postgres / VCoreMongo), which today
|
||||
share the Terminal infrastructure with notebooks.
|
||||
|
||||
This document is the implementation plan only. No code changes are made here.
|
||||
|
||||
## Scope decisions (confirmed)
|
||||
|
||||
- **Database shells**: Keep the Mongo/Cassandra/Postgres/VCoreMongo shells, but migrate
|
||||
them to use the **CloudShell** path exclusively. Remove the legacy Phoenix
|
||||
notebook-server shell path.
|
||||
- **GitHub integration**: Remove entirely (it exists only for notebook pinned repos).
|
||||
- **Schema Analyzer** (`src/Explorer/Notebook/SchemaAnalyzer`): Remove.
|
||||
- **Phasing**: Every phase must leave the app in a **buildable, shippable** state
|
||||
(build + lint + strict compile + unit tests green; shells still work).
|
||||
- **Localization**: Remove notebook/GitHub strings from **all** resource files —
|
||||
`src/Localization/en/Resources.json` **and** every non-English locale
|
||||
(`src/Localization/<locale>/Resources.json`). (This deletion is an exception to the
|
||||
usual convention of editing only the English file.)
|
||||
|
||||
## Prior art / related commits
|
||||
|
||||
This is a continuation of an in-progress removal effort. Reference commits:
|
||||
|
||||
- `7295d63a` — Remove gallery.html and all associated gallery functionality (#2474)
|
||||
- `a36467f4` — Remove Phoenix `getDbAccountAllowedStatus`; `isPhoenixNotebooks`/
|
||||
`isPhoenixFeatures` now always `false` (#2472)
|
||||
- `31385950` — removed NotebookViewer file (#2281)
|
||||
|
||||
> **Note:** An unmerged branch `users/jawelton/remove-notebooks-terminal-052126`
|
||||
> already contains related work (`5989c77c` "Remove terminal.html webpack entry point
|
||||
> and notebooks terminal code", `c7f9d7e3` "Switch VCore Mongo quickstart to use
|
||||
> CloudShell terminal"). These are **not** in `master`. Reconcile with that branch
|
||||
> before/while starting Phase 1 to avoid duplicate or conflicting work.
|
||||
|
||||
## Current-state survey (what exists today)
|
||||
|
||||
**Core directories / files**
|
||||
- `src/Phoenix/PhoenixClient.ts` — container allocation, heartbeat, status polling.
|
||||
- `src/Juno/JunoClient.ts` (+ test) — pinned-repo / notebook metadata service client.
|
||||
- `src/Explorer/Notebook/` — the bulk of the feature:
|
||||
- `useNotebook.ts` (Zustand store), `NotebookManager.tsx`, `NotebookContentClient.ts`,
|
||||
`NotebookClientV2.ts`, `NotebookContainerClient.ts`, `NotebookContentItem.ts`,
|
||||
`NotebookUtil.ts`, `NTeractUtil.ts`, `FileSystemUtil.ts`
|
||||
- `NotebookComponent/` (nteract redux store, epics, reducers, content providers)
|
||||
- `NotebookRenderer/` (nteract cell rendering, decorators, outputs)
|
||||
- `SchemaAnalyzer/`, `SecurityWarningBar/`
|
||||
- `src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx` (+ less/test)
|
||||
- `src/Explorer/Controls/NotebookViewer/` — read-only viewer + metadata.
|
||||
- `src/Explorer/Tabs/NotebookV2Tab.ts`, `NotebookTabBase.ts`, `SchemaAnalyzerTab.ts`
|
||||
- `src/Explorer/Panes/CopyNotebookPane/`
|
||||
- `src/Explorer/Tabs/ShellAdapters/NotebookTerminalComponentAdapter.tsx`
|
||||
- `src/CellOutputViewer/` — webpack entry `cellOutputViewer`.
|
||||
- `src/Utils/NotebookConfigurationUtils.ts`, `src/hooks/useNotebookSnapshotStore.ts`
|
||||
- `src/Utils/arm/generatedClients/cosmosNotebooks/` — generated ARM client.
|
||||
|
||||
**GitHub integration (notebook-only)**
|
||||
- `src/GitHub/` (`GitHubClient.ts`, `GitHubContentProvider.ts`, `GitHubOAuthService.ts`,
|
||||
`GitHubConnector.ts`), `src/Utils/GitHubUtils.ts`
|
||||
- `src/Explorer/Controls/GitHub/` (AuthorizeAccess, AddRepo, GitHubRepos components)
|
||||
- `src/Explorer/Panes/GitHubReposPanel/`
|
||||
- webpack entry `connectToGitHub` + `src/connectToGitHub.html`
|
||||
|
||||
**Integration / glue points (edited, not deleted)**
|
||||
- `src/Explorer/Explorer.tsx` — `phoenixClient`, `notebookManager`, `gitHubOAuthService`,
|
||||
`initNotebooks`, `initiateAndRefreshNotebookList`, `allocateContainer`,
|
||||
`openNotebook*`, `openNotebookTerminal`, `createNotebookContentItemFile`, etc.
|
||||
- `src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx` (+ adapter, test)
|
||||
— New Notebook / Open Terminal / shell buttons branching on `isShellEnabled`.
|
||||
- `src/Explorer/Tree/treeNodeUtil.tsx` / `ResourceTreeAdapter.tsx` / `ResourceTree.tsx`
|
||||
— "My Notebooks" / "GitHub" tree nodes; `isNotebookEnabled` plumbing.
|
||||
- `src/Explorer/SplashScreen/SplashScreen.tsx` — notebook cards + `openNotebookTerminal`
|
||||
for Postgres/VCoreMongo shells.
|
||||
- `src/Explorer/Tabs/TerminalTab.tsx` — chooses CloudShell vs notebook-server adapter.
|
||||
- `src/Explorer/OpenActions/OpenActions.tsx`, `src/Explorer/ContextMenuButtonFactory.tsx`,
|
||||
`src/Explorer/Tree/Collection.ts`, `src/Explorer/useSelectedNode.ts`,
|
||||
`src/Explorer/MostRecentActivity/MostRecentActivity.ts`
|
||||
- `src/hooks/useKnockoutExplorer.ts`, `src/hooks/useTabs.ts`
|
||||
- `src/ConfigContext.ts`, `src/Common/Constants.ts`, `src/Contracts/DataModels.ts`,
|
||||
`src/Contracts/ViewModels.ts`, `src/Contracts/ActionContracts.ts`,
|
||||
`src/Platform/Hosted/extractFeatures.ts` (+ test)
|
||||
- `src/Shared/Telemetry/TelemetryConstants.ts`
|
||||
- `src/Localization/en/Resources.json` **and all non-English** `src/Localization/<locale>/Resources.json`
|
||||
- `webpack.config.js` — `cellOutputViewer`, `connectToGitHub` entries + HTML plugins.
|
||||
- `package.json` — `@nteract/*`, `@jupyterlab/*`, `@phosphor/widgets`, `rx-jupyter`,
|
||||
and other notebook-only dependencies.
|
||||
|
||||
**Critical coupling — Terminal / shells**
|
||||
- `TerminalTab` uses `CloudShellTerminalComponentAdapter` when
|
||||
`userContext.features.enableCloudShell`, otherwise `NotebookTerminalComponentAdapter`
|
||||
(which needs a Phoenix-allocated notebook server + `terminal.html` iframe).
|
||||
- Command-bar/splash shell buttons branch on
|
||||
`useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell`.
|
||||
- `Explorer.openNotebookTerminal(...)` is the shared entry for opening shells and must
|
||||
be retained (and rewired to CloudShell-only) — only its notebook-server behavior is
|
||||
removed.
|
||||
|
||||
## Phased approach
|
||||
|
||||
Each phase is independently buildable and shippable. Within each phase, **all
|
||||
references to removed code are also removed** so the tree compiles. After every phase
|
||||
run: `npm run compile`, `npm run compile:strict`, `npm run lint`, `npm run format:check`,
|
||||
`npm test`, and a webpack build (`npm run build:ci`); manually verify the four shells
|
||||
still open.
|
||||
|
||||
### Phase 1 — Decouple database shells to CloudShell-only
|
||||
Remove the legacy Phoenix notebook-server terminal path so shells no longer depend on
|
||||
notebook provisioning.
|
||||
- Rewire `TerminalTab` to always use `CloudShellTerminalComponentAdapter`; delete the
|
||||
`NotebookTerminalComponentAdapter` branch and `getNotebookServerInfo`.
|
||||
- Delete `src/Explorer/Tabs/ShellAdapters/NotebookTerminalComponentAdapter.tsx` and
|
||||
`src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx` (+ less/test/snapshot).
|
||||
- Simplify shell buttons in `CommandBarComponentButtonFactory` and `SplashScreen` to
|
||||
drop the `isShellEnabled` branch (CloudShell path only); keep `openNotebookTerminal`.
|
||||
- Verify whether the `terminal`/`terminal.html` webpack entry is still needed by
|
||||
CloudShell. If unused, remove it and `src/Terminal/`; otherwise keep.
|
||||
- **Outcome:** Shells run purely on CloudShell. Phoenix no longer needed for terminals.
|
||||
|
||||
### Phase 2 — Remove the in-app notebook authoring & rendering experience
|
||||
Delete the notebook tabs, the nteract rendering engine, panes, and the read-only viewer,
|
||||
and remove all UI entry points that open notebooks.
|
||||
- Delete: `src/Explorer/Notebook/NotebookComponent/`,
|
||||
`src/Explorer/Notebook/NotebookRenderer/`, `src/Explorer/Notebook/SecurityWarningBar/`,
|
||||
`NotebookClientV2.ts`, `notebookClientV2.test.ts`, `NotebookContentClient.ts`,
|
||||
`NTeractUtil.ts`, `NotebookContentItem.ts`, `NotebookUtil.ts` (+ test),
|
||||
`FileSystemUtil.ts`.
|
||||
- Delete tabs/panes/viewer: `src/Explorer/Tabs/NotebookV2Tab.ts`, `NotebookTabBase.ts`,
|
||||
`src/Explorer/Panes/CopyNotebookPane/`, `src/Explorer/Controls/NotebookViewer/`,
|
||||
`src/CellOutputViewer/` (+ `cellOutputViewer` webpack entry & HTML plugin).
|
||||
- Remove notebook entry points: "New Notebook"/open-notebook buttons in
|
||||
`CommandBarComponentButtonFactory` (+ test), `OpenActions.tsx`,
|
||||
`ContextMenuButtonFactory.tsx`, splash-screen notebook cards & recent-notebook items
|
||||
(`MostRecentActivity` OpenNotebook type), and the `openNotebook*` /
|
||||
`createNotebookContentItemFile` methods on `Explorer`.
|
||||
- Remove notebook deps from `package.json`: `@nteract/*`, `@jupyterlab/*`,
|
||||
`@phosphor/widgets`, `rx-jupyter` (and any now-unused transitive notebook-only libs).
|
||||
- **Outcome:** Notebooks can no longer be authored, opened, or rendered.
|
||||
|
||||
### Phase 3 — Remove Schema Analyzer
|
||||
- Delete `src/Explorer/Notebook/SchemaAnalyzer/` and `src/Explorer/Tabs/SchemaAnalyzerTab.ts`.
|
||||
- Remove Schema Analyzer command-bar button and any tree/menu entry points.
|
||||
|
||||
### Phase 4 — Remove GitHub integration
|
||||
- Delete `src/GitHub/`, `src/Explorer/Controls/GitHub/`,
|
||||
`src/Explorer/Panes/GitHubReposPanel/`, `src/Utils/GitHubUtils.ts`,
|
||||
`src/connectToGitHub.html`, and the `connectToGitHub` webpack entry & HTML plugin.
|
||||
- Remove `gitHubOAuthService`, GitHub pinned-repo wiring, and `gitHubNotebooksContentRoot`
|
||||
usage from `Explorer.tsx`, `useNotebook.ts`, `NotebookManager.tsx`, and `JunoClient`
|
||||
pinned-repo methods.
|
||||
- Remove GitHub-related localization keys from **all** locale files (`en` + non-English).
|
||||
|
||||
### Phase 5 — Remove Phoenix and the notebook container/allocation core
|
||||
- Delete `src/Phoenix/`, `src/Explorer/Notebook/NotebookContainerClient.ts`,
|
||||
`src/Explorer/Notebook/NotebookManager.tsx`, `src/Explorer/Notebook/useNotebook.ts`,
|
||||
`src/Utils/NotebookConfigurationUtils.ts`, `src/hooks/useNotebookSnapshotStore.ts`.
|
||||
- Remove from `Explorer.tsx`: `phoenixClient`, `notebookManager`, `_isInitializingNotebooks`,
|
||||
`initNotebooks`, `initiateAndRefreshNotebookList`, `refreshNotebookList`,
|
||||
`allocateContainer`, container heartbeat/connection logic, and notebook-server URL
|
||||
feature overrides.
|
||||
- Remove notebook tree nodes ("My Notebooks") and `isNotebookEnabled` plumbing from
|
||||
`treeNodeUtil.tsx`, `ResourceTreeAdapter.tsx`, `ResourceTree.tsx`, `Collection.ts`,
|
||||
`useSelectedNode.ts` (+ update tree snapshots/tests).
|
||||
- Remove notebook initialization from `useKnockoutExplorer.ts` and notebook tab handling
|
||||
in `useTabs.ts`.
|
||||
|
||||
### Phase 6 — Remove residual clients, config, contracts, telemetry & strings
|
||||
- Delete `src/Juno/` and `src/Utils/arm/generatedClients/cosmosNotebooks/`.
|
||||
- Remove notebook fields from `ConfigContext.ts`, `Constants.ts` (Notebook namespace),
|
||||
`DataModels.ts` (notebook/Phoenix/container interfaces), `ViewModels.ts`,
|
||||
`ActionContracts.ts`, and notebook feature flags from
|
||||
`extractFeatures.ts` (+ update test).
|
||||
- Remove notebook/Phoenix telemetry actions/areas from `TelemetryConstants.ts` (preserve
|
||||
enum numbering if other systems depend on it — mirror the cautious approach in
|
||||
`a36467f4`).
|
||||
- Remove remaining notebook strings from **all** locale `Resources.json` files (`en` +
|
||||
every non-English locale) and any notebook images (e.g. `images/notebook/`).
|
||||
- Final full build + test sweep; update `EndpointUtils.ts` (`allowedNotebookServerUrls`)
|
||||
and any docs/comments referencing notebooks.
|
||||
|
||||
## Cross-cutting verification (run after each phase)
|
||||
|
||||
```
|
||||
npm run compile
|
||||
npm run compile:strict
|
||||
npm run lint
|
||||
npm run format:check
|
||||
npm test
|
||||
npm run build:ci
|
||||
```
|
||||
Plus manual smoke test: open Mongo, Cassandra, Postgres, and VCoreMongo shells.
|
||||
|
||||
## Notes & considerations
|
||||
|
||||
- **Strict null checks:** any file edited may need to stay in / be removed from
|
||||
`tsconfig.strict.json`. Remove deleted files from that list.
|
||||
- **Snapshots:** several Jest snapshots reference notebook UI
|
||||
(`treeNodeUtil`, `SettingsComponent`, panel snapshots). Regenerate after edits.
|
||||
- **Telemetry enum safety:** prior commit `a36467f4` deliberately reverted removal of
|
||||
enum values to avoid breaking downstream consumers. Prefer leaving enum numeric values
|
||||
intact unless confirmed safe to remove.
|
||||
- **`enableCloudShell` feature flag:** confirm it is enabled in all target environments
|
||||
before removing the Phoenix shell fallback, or shells will break.
|
||||
- **E2E tests:** check `test/` for notebook/terminal specs to update or remove; shells
|
||||
may have E2E coverage that needs the CloudShell-only path.
|
||||
- **Reconcile** with branch `users/jawelton/remove-notebooks-terminal-052126` to avoid
|
||||
rework, especially in Phase 1.
|
||||
@@ -1,13 +0,0 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.notebookTerminalContainer {
|
||||
padding: @DefaultSpace;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { NotebookTerminalComponent, NotebookTerminalComponentProps } from "./NotebookTerminalComponent";
|
||||
|
||||
const testAccount: DataModels.DatabaseAccount = {
|
||||
id: "id",
|
||||
kind: "kind",
|
||||
location: "location",
|
||||
name: "name",
|
||||
properties: {
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
},
|
||||
type: "type",
|
||||
};
|
||||
|
||||
const testMongo32Account: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
};
|
||||
|
||||
const testMongo36Account: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
properties: {
|
||||
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
||||
},
|
||||
};
|
||||
|
||||
const testCassandraAccount: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
properties: {
|
||||
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
||||
},
|
||||
};
|
||||
|
||||
const testPostgresAccount: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
properties: {
|
||||
postgresqlEndpoint: "https://testPostgresEndpoint.azure.com/",
|
||||
},
|
||||
};
|
||||
|
||||
const testVCoreMongoAccount: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
properties: {
|
||||
vcoreMongoEndpoint: "https://testVCoreMongoEndpoint.azure.com/",
|
||||
},
|
||||
};
|
||||
|
||||
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
const testPostgresNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/postgresql",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
const testVCoreMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongovcore",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
describe("NotebookTerminalComponent", () => {
|
||||
it("renders terminal", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testAccount,
|
||||
notebookServerInfo: testNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders mongo 3.2 shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testMongo32Account,
|
||||
notebookServerInfo: testMongoNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders mongo 3.6 shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testMongo36Account,
|
||||
notebookServerInfo: testMongoNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders cassandra shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testCassandraAccount,
|
||||
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders Postgres shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testPostgresAccount,
|
||||
notebookServerInfo: testPostgresNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders vCore Mongo shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testVCoreMongoAccount,
|
||||
notebookServerInfo: testVCoreMongoNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
username: "username",
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Wrapper around Notebook server terminal
|
||||
*/
|
||||
|
||||
import { useTerminal } from "hooks/useTerminal";
|
||||
import postRobot from "post-robot";
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { TerminalProps } from "../../../Terminal/TerminalProps";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as StringUtils from "../../../Utils/StringUtils";
|
||||
|
||||
export interface NotebookTerminalComponentProps {
|
||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||
databaseAccount: DataModels.DatabaseAccount;
|
||||
tabId: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||
private terminalWindow: Window;
|
||||
|
||||
constructor(props: NotebookTerminalComponentProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.sendPropsToTerminalFrame();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="notebookTerminalContainer">
|
||||
<iframe
|
||||
title="Terminal to Notebook Server"
|
||||
onLoad={(event) => this.handleFrameLoad(event)}
|
||||
src="terminal.html"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
|
||||
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
|
||||
useTerminal.getState().setTerminal(this.terminalWindow);
|
||||
this.sendPropsToTerminalFrame();
|
||||
}
|
||||
|
||||
sendPropsToTerminalFrame(): void {
|
||||
if (!this.terminalWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
let props: TerminalProps = {
|
||||
terminalEndpoint: this.tryGetTerminalEndpoint(),
|
||||
notebookServerEndpoint: this.props.notebookServerInfo?.notebookServerEndpoint,
|
||||
authToken: this.props.notebookServerInfo?.authToken,
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
apiType: userContext.apiType,
|
||||
authType: userContext.authType,
|
||||
databaseAccount: userContext.databaseAccount,
|
||||
tabId: this.props.tabId,
|
||||
};
|
||||
|
||||
if (this.props.username) {
|
||||
props = {
|
||||
...props,
|
||||
username: this.props.username,
|
||||
};
|
||||
}
|
||||
|
||||
postRobot.send(this.terminalWindow, "props", props, {
|
||||
domain: window.location.origin,
|
||||
});
|
||||
}
|
||||
|
||||
public tryGetTerminalEndpoint(): string | undefined {
|
||||
let terminalEndpoint: string | undefined;
|
||||
|
||||
const notebookServerEndpoint = this.props.notebookServerInfo?.notebookServerEndpoint;
|
||||
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
|
||||
// mongoEndpoint is only available for Mongo 3.6 and higher, fallback to documentEndpoint otherwise
|
||||
terminalEndpoint =
|
||||
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
|
||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
||||
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "postgresql")) {
|
||||
return this.props.databaseAccount?.properties.postgresqlEndpoint;
|
||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "mongovcore")) {
|
||||
return this.props.databaseAccount?.properties.vcoreMongoEndpoint;
|
||||
}
|
||||
|
||||
if (terminalEndpoint) {
|
||||
return new URL(terminalEndpoint).host;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NotebookTerminalComponent renders Postgres shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders cassandra shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders mongo 3.2 shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders mongo 3.6 shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders terminal 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders vCore Mongo shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -959,25 +959,8 @@ export default class Explorer {
|
||||
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
||||
}
|
||||
|
||||
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
||||
if (userContext.features.enableCloudShell) {
|
||||
public openNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||
this.connectToNotebookTerminal(kind);
|
||||
} else if (useNotebook.getState().isPhoenixFeatures) {
|
||||
await this.allocateContainer();
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||
this.connectToNotebookTerminal(kind);
|
||||
} else {
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkModalDialog(
|
||||
"Failed to connect",
|
||||
"Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again.",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.connectToNotebookTerminal(kind);
|
||||
}
|
||||
}
|
||||
|
||||
private connectToNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTerminal } from "hooks/useTerminal";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Terminal } from "xterm";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
@@ -59,7 +60,14 @@ export const CloudShellTerminalComponent: React.FC<CloudShellTerminalComponentPr
|
||||
});
|
||||
resizeObserver.observe(terminalRef.current);
|
||||
|
||||
socketRef.current = startCloudShellTerminal(terminal, props.shellType);
|
||||
const initTerminal = async () => {
|
||||
const socket = await startCloudShellTerminal(terminal, props.shellType);
|
||||
socketRef.current = socket;
|
||||
if (socket) {
|
||||
useTerminal.getState().setSocket(socket);
|
||||
}
|
||||
};
|
||||
initTerminal();
|
||||
|
||||
// Cleanup function to close WebSocket and dispose terminal
|
||||
return () => {
|
||||
@@ -73,6 +81,7 @@ export const CloudShellTerminalComponent: React.FC<CloudShellTerminalComponentPr
|
||||
resizeObserver.unobserve(terminalRef.current);
|
||||
}
|
||||
terminal.dispose(); // Clean up XTerm instance
|
||||
useTerminal.getState().setSocket(undefined);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
import { Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
||||
import { Stack } from "@fluentui/react";
|
||||
|
||||
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
|
||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import { TerminalKind } from "Contracts/ViewModels";
|
||||
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
|
||||
import { CloudShellTerminalComponent } from "Explorer/Tabs/CloudShellTab/CloudShellTerminalComponent";
|
||||
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
||||
import { userContext } from "UserContext";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
||||
|
||||
interface QuickstartTabProps {
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
|
||||
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
|
||||
export const QuickstartTab: React.FC = (): JSX.Element => {
|
||||
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
|
||||
|
||||
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
||||
authToken: notebookServerInfo.authToken,
|
||||
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
|
||||
forwardingId: notebookServerInfo.forwardingId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
checkFirewallRules(
|
||||
"2022-11-08",
|
||||
@@ -34,10 +21,6 @@ export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: Quicks
|
||||
);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
explorer.allocateContainer();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack style={{ width: "100%" }} horizontal>
|
||||
<Stack style={{ width: "50%" }}>
|
||||
@@ -51,24 +34,13 @@ export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: Quicks
|
||||
shellName="PostgreSQL"
|
||||
/>
|
||||
)}
|
||||
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
|
||||
<NotebookTerminalComponent
|
||||
notebookServerInfo={getNotebookServerInfo()}
|
||||
{isAllPublicIPAddressEnabled && (
|
||||
<CloudShellTerminalComponent
|
||||
databaseAccount={userContext.databaseAccount}
|
||||
tabId="QuickstartPSQLShell"
|
||||
shellType={TerminalKind.Postgres}
|
||||
/>
|
||||
)}
|
||||
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
|
||||
<Stack style={{ margin: "auto 0" }}>
|
||||
<Text block style={{ margin: "auto" }}>
|
||||
Connecting to the PostgreSQL shell.
|
||||
</Text>
|
||||
<Text block style={{ margin: "auto" }}>
|
||||
If the cluster was just created, this could take up to a minute.
|
||||
</Text>
|
||||
<Spinner styles={{ root: { marginTop: 16 } }} size={SpinnerSize.large}></Spinner>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -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()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -333,11 +333,7 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
||||
return <SplashScreen explorer={explorer} />;
|
||||
}
|
||||
case ReactTabKind.Quickstart:
|
||||
return userContext.apiType === "VCoreMongo" ? (
|
||||
<VcoreMongoQuickstartTab explorer={explorer} />
|
||||
) : (
|
||||
<QuickstartTab explorer={explorer} />
|
||||
);
|
||||
return userContext.apiType === "VCoreMongo" ? <VcoreMongoQuickstartTab /> : <QuickstartTab />;
|
||||
default:
|
||||
throw new Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
import { NotebookTerminalComponentAdapter } from "./ShellAdapters/NotebookTerminalComponentAdapter";
|
||||
import TabsBase from "./TabsBase";
|
||||
|
||||
export interface TerminalTabOptions extends ViewModels.TabOptions {
|
||||
@@ -29,41 +27,17 @@ export default class TerminalTab extends TabsBase {
|
||||
this.container = options.container;
|
||||
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
||||
|
||||
const commonArgs: [
|
||||
() => DataModels.DatabaseAccount,
|
||||
() => string,
|
||||
() => string,
|
||||
ko.Observable<boolean>,
|
||||
ViewModels.TerminalKind,
|
||||
] = [
|
||||
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(
|
||||
() => userContext?.databaseAccount,
|
||||
() => this.tabId,
|
||||
() => this.getUsername(),
|
||||
this.isAllPublicIPAddressesEnabled,
|
||||
options.kind,
|
||||
];
|
||||
|
||||
if (userContext.features.enableCloudShell) {
|
||||
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(...commonArgs);
|
||||
);
|
||||
|
||||
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) {
|
||||
checkFirewallRules(
|
||||
@@ -72,16 +46,6 @@ export default class TerminalTab extends TabsBase {
|
||||
this.isAllPublicIPAddressesEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.kind === ViewModels.TerminalKind.VCoreMongo) {
|
||||
checkFirewallRules(
|
||||
"2023-03-01-preview",
|
||||
(rule) =>
|
||||
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
||||
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
|
||||
this.isAllPublicIPAddressesEnabled,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public getContainer(): Explorer {
|
||||
@@ -96,42 +60,6 @@ export default class TerminalTab extends TabsBase {
|
||||
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 {
|
||||
if (userContext.apiType !== "VCoreMongo" || !userContext?.vcoreMongoConnectionParams?.adminLogin) {
|
||||
return undefined;
|
||||
|
||||
@@ -1,79 +1,24 @@
|
||||
import { Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
||||
import { Stack } from "@fluentui/react";
|
||||
|
||||
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
|
||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||
import { TerminalKind } from "Contracts/ViewModels";
|
||||
import { VcoreMongoQuickstartGuide } from "Explorer/Quickstart/VCoreMongoQuickstartGuide";
|
||||
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
||||
import { CloudShellTerminalComponent } from "Explorer/Tabs/CloudShellTab/CloudShellTerminalComponent";
|
||||
import { userContext } from "UserContext";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import FirewallRuleScreenshot from "../../../images/vcoreMongoFirewallRule.png";
|
||||
|
||||
interface VCoreMongoQuickstartTabProps {
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
export const VcoreMongoQuickstartTab: React.FC<VCoreMongoQuickstartTabProps> = ({
|
||||
explorer,
|
||||
}: VCoreMongoQuickstartTabProps): JSX.Element => {
|
||||
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
|
||||
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
|
||||
|
||||
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
||||
authToken: notebookServerInfo.authToken,
|
||||
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/mongovcore`,
|
||||
forwardingId: notebookServerInfo.forwardingId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
checkFirewallRules(
|
||||
"2023-03-01-preview",
|
||||
(rule) =>
|
||||
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
||||
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
|
||||
setIsAllPublicIPAddressEnabled,
|
||||
);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
explorer.allocateContainer();
|
||||
}, []);
|
||||
import React from "react";
|
||||
|
||||
export const VcoreMongoQuickstartTab: React.FC = (): JSX.Element => {
|
||||
return (
|
||||
<Stack style={{ width: "100%" }} horizontal>
|
||||
<Stack style={{ width: "50%" }}>
|
||||
<VcoreMongoQuickstartGuide />
|
||||
</Stack>
|
||||
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
|
||||
{!isAllPublicIPAddressEnabled && (
|
||||
<QuickstartFirewallNotification
|
||||
messageType={MessageTypes.OpenVCoreMongoNetworkingBlade}
|
||||
screenshot={FirewallRuleScreenshot}
|
||||
shellName="MongoDB"
|
||||
/>
|
||||
)}
|
||||
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
|
||||
<NotebookTerminalComponent
|
||||
notebookServerInfo={getNotebookServerInfo()}
|
||||
<CloudShellTerminalComponent
|
||||
databaseAccount={userContext.databaseAccount}
|
||||
tabId="QuickstartVcoreMongoShell"
|
||||
username={userContext.vcoreMongoConnectionParams.adminLogin}
|
||||
username={userContext.vcoreMongoConnectionParams?.adminLogin}
|
||||
shellType={TerminalKind.VCoreMongo}
|
||||
/>
|
||||
)}
|
||||
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
|
||||
<Stack style={{ margin: "auto 0" }}>
|
||||
<Text block style={{ margin: "auto" }}>
|
||||
Connecting to the Mongo shell.
|
||||
</Text>
|
||||
<Text block style={{ margin: "auto" }}>
|
||||
If the cluster was just created, this could take up to a minute.
|
||||
</Text>
|
||||
<Spinner styles={{ root: { marginTop: 16 } }} size={SpinnerSize.large}></Spinner>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -47,7 +47,6 @@ import "./Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.less";
|
||||
import { Dialog } from "./Explorer/Controls/Dialog";
|
||||
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
||||
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
||||
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
||||
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||
import { ErrorBoundary } from "./Explorer/ErrorBoundary";
|
||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||
|
||||
@@ -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);
|
||||
@@ -1,26 +1,20 @@
|
||||
import postRobot from "post-robot";
|
||||
import create, { UseStore } from "zustand";
|
||||
|
||||
interface TerminalState {
|
||||
terminalWindow: Window;
|
||||
setTerminal: (terminalWindow: Window) => void;
|
||||
socket: WebSocket | undefined;
|
||||
setSocket: (socket: WebSocket | undefined) => void;
|
||||
sendMessage: (message: string) => void;
|
||||
}
|
||||
|
||||
export const useTerminal: UseStore<TerminalState> = create((set, get) => ({
|
||||
terminalWindow: undefined,
|
||||
setTerminal: (terminalWindow: Window) => {
|
||||
set({ terminalWindow });
|
||||
socket: undefined,
|
||||
setSocket: (socket: WebSocket | undefined) => {
|
||||
set({ socket });
|
||||
},
|
||||
sendMessage: (message: string) => {
|
||||
const terminalWindow = get().terminalWindow;
|
||||
postRobot.send(
|
||||
terminalWindow,
|
||||
"sendMessage",
|
||||
{ type: "stdin", content: [message] },
|
||||
{
|
||||
domain: window.location.origin,
|
||||
},
|
||||
);
|
||||
const socket = get().socket;
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(message + "\r");
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -145,7 +145,6 @@
|
||||
"src/Platform/Emulator/**/*",
|
||||
"src/SelfServe/Documentation/**/*",
|
||||
"src/Shared/Telemetry/**/*",
|
||||
"src/Terminal/**/*",
|
||||
"src/Utils/arm/**/*"
|
||||
]
|
||||
}
|
||||
@@ -111,7 +111,6 @@ module.exports = function (_env = {}, argv = {}) {
|
||||
index: "./src/Index.tsx",
|
||||
quickstart: "./src/quickstart.ts",
|
||||
hostedExplorer: "./src/HostedExplorer.tsx",
|
||||
terminal: "./src/Terminal/index.ts",
|
||||
cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx",
|
||||
selfServe: "./src/SelfServe/SelfServe.tsx",
|
||||
connectToGitHub: "./src/GitHub/GitHubConnector.ts",
|
||||
@@ -128,11 +127,6 @@ module.exports = function (_env = {}, argv = {}) {
|
||||
template: "src/explorer.html",
|
||||
chunks: ["main"],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "terminal.html",
|
||||
template: "src/Terminal/index.html",
|
||||
chunks: ["terminal"],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "quickstart.html",
|
||||
template: "src/quickstart.html",
|
||||
|
||||
Reference in New Issue
Block a user