mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-24 04:04:13 +00:00
Compare commits
29 Commits
user/bchou
...
platform-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f50f6b1f | ||
|
|
d385764027 | ||
|
|
5db83f9a85 | ||
|
|
f4276adca9 | ||
|
|
36ded95be0 | ||
|
|
41d132b041 | ||
|
|
f6c8db9f37 | ||
|
|
46e6695cba | ||
|
|
392d0edcd1 | ||
|
|
b2ea464b6c | ||
|
|
c84beeb8c3 | ||
|
|
3bf0f09d37 | ||
|
|
eb85b2959d | ||
|
|
ec472a3d4c | ||
|
|
e3055b121f | ||
|
|
e489a66ae2 | ||
|
|
601a335839 | ||
|
|
0029b04af1 | ||
|
|
f4aa74ad6f | ||
|
|
2afb2d82e4 | ||
|
|
679e4e56df | ||
|
|
df2c1b2345 | ||
|
|
7007368a1a | ||
|
|
079965d199 | ||
|
|
fc774e1089 | ||
|
|
d9d90ac6d9 | ||
|
|
c101f7de74 | ||
|
|
ca396cdfbe | ||
|
|
ff1e733679 |
@@ -144,3 +144,5 @@ __mocks__/monaco-editor.ts
|
|||||||
src/Explorer/Tree/ResourceTree.tsx
|
src/Explorer/Tree/ResourceTree.tsx
|
||||||
src/Utils/EndpointUtils.ts
|
src/Utils/EndpointUtils.ts
|
||||||
src/Utils/PriorityBasedExecutionUtils.ts
|
src/Utils/PriorityBasedExecutionUtils.ts
|
||||||
|
|
||||||
|
utils/local-proxy/**
|
||||||
23
.vscode/settings.json
vendored
23
.vscode/settings.json
vendored
@@ -1,6 +1,22 @@
|
|||||||
// Place your settings in this file to overwrite default and user settings.
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
{
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
".vs": true,
|
||||||
|
".vscode/**": true,
|
||||||
|
"*.trx": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.hg": true,
|
||||||
|
"**/.svn": true,
|
||||||
|
"built/**": true,
|
||||||
|
"coverage/**": true,
|
||||||
|
"libs/**": true,
|
||||||
|
"node_modules/**": true,
|
||||||
|
"package-lock.json": true,
|
||||||
|
"quickstart/**": true,
|
||||||
|
"test/out/**": true,
|
||||||
|
"workers/libs/**": true
|
||||||
|
},
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
@@ -8,8 +24,5 @@
|
|||||||
"source.organizeImports": "explicit"
|
"source.organizeImports": "explicit"
|
||||||
},
|
},
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
"[typescript]": {
|
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
3
configs/emulator-http.json
Normal file
3
configs/emulator-http.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"EMULATOR_ENDPOINT": "http://localhost:8081"
|
||||||
|
}
|
||||||
37739
package-lock.json
generated
37739
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
348
package.json
348
package.json
@@ -4,270 +4,200 @@
|
|||||||
"description": "Cosmos Explorer",
|
"description": "Cosmos Explorer",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "16.3.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "4.5.0",
|
"@azure/cosmos": "4.5.0",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "4.10.1",
|
"@azure/identity": "4.5.0",
|
||||||
"@azure/msal-browser": "4.24.0",
|
"@azure/msal-browser": "2.14.2",
|
||||||
"@babel/plugin-transform-class-properties": "^7.24.7",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.28.0",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
"@fluentui/react": "8.123.6",
|
"@fluentui/react": "8.119.0",
|
||||||
"@fluentui/react-components": "9.70.0",
|
"@fluentui/react-components": "9.54.2",
|
||||||
"@jupyterlab/services": "7.4.9",
|
"@jupyterlab/services": "6.0.2",
|
||||||
"@jupyterlab/terminal": "4.4.9",
|
"@jupyterlab/terminal": "3.0.3",
|
||||||
"@microsoft/applicationinsights-web": "3.3.10",
|
"@microsoft/applicationinsights-web": "2.6.1",
|
||||||
"@nteract/commutable": "7.5.1",
|
"@nteract/commutable": "7.5.1",
|
||||||
"@nteract/connected-components": "6.9.0",
|
"@nteract/connected-components": "6.8.2",
|
||||||
"@nteract/core": "15.1.9",
|
"@nteract/core": "15.1.9",
|
||||||
"@nteract/data-explorer": "8.2.12",
|
"@nteract/data-explorer": "8.0.3",
|
||||||
"@nteract/directory-listing": "2.1.0",
|
"@nteract/directory-listing": "2.0.6",
|
||||||
"@nteract/dropdown-menu": "1.1.9",
|
"@nteract/dropdown-menu": "1.0.1",
|
||||||
"@nteract/editor": "10.1.12",
|
"@nteract/editor": "10.1.12",
|
||||||
"@nteract/fixtures": "2.3.19",
|
"@nteract/fixtures": "2.3.0",
|
||||||
"@nteract/iron-icons": "1.0.0",
|
"@nteract/iron-icons": "1.0.0",
|
||||||
"@nteract/jupyter-widgets": "4.1.19",
|
"@nteract/jupyter-widgets": "2.0.0",
|
||||||
"@nteract/logos": "1.0.0",
|
"@nteract/logos": "1.0.0",
|
||||||
"@nteract/markdown": "4.6.2",
|
"@nteract/markdown": "4.6.0",
|
||||||
"@nteract/monaco-editor": "3.2.2",
|
"@nteract/monaco-editor": "3.2.2",
|
||||||
"@nteract/octicons": "2.0.0",
|
"@nteract/octicons": "2.0.0",
|
||||||
"@nteract/outputs": "5.1.14",
|
"@nteract/outputs": "3.0.9",
|
||||||
"@nteract/presentational-components": "3.4.12",
|
"@nteract/presentational-components": "3.0.7",
|
||||||
"@nteract/stateful-components": "1.7.15",
|
"@nteract/stateful-components": "1.7.0",
|
||||||
"@nteract/styles": "2.2.11",
|
"@nteract/styles": "2.0.2",
|
||||||
"@nteract/transform-geojson": "5.1.13",
|
"@nteract/transform-geojson": "5.1.8",
|
||||||
"@nteract/transform-model-debug": "5.0.1",
|
"@nteract/transform-model-debug": "5.0.1",
|
||||||
"@nteract/transform-plotly": "7.0.1",
|
"@nteract/transform-plotly": "6.1.6",
|
||||||
"@nteract/transform-vdom": "4.0.15",
|
"@nteract/transform-vdom": "4.0.11",
|
||||||
"@nteract/transform-vega": "7.0.6",
|
"@nteract/transform-vega": "7.0.6",
|
||||||
"@octokit/rest": "21.1.1",
|
"@octokit/rest": "17.9.2",
|
||||||
"@phosphor/widgets": "1.9.3",
|
"@phosphor/widgets": "1.9.3",
|
||||||
"@testing-library/jest-dom": "6.8.0",
|
"@testing-library/jest-dom": "6.4.6",
|
||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.14.171",
|
||||||
"@types/mkdirp": "1.0.2",
|
"@types/mkdirp": "1.0.1",
|
||||||
"@types/node-fetch": "2.6.13",
|
"@types/node-fetch": "2.5.7",
|
||||||
"@xmldom/xmldom": "0.9.8",
|
"@xmldom/xmldom": "0.7.13",
|
||||||
"@xterm/xterm": "5.5.0",
|
"@xterm/xterm": "5.5.0",
|
||||||
"@xterm/addon-fit": "0.10.0",
|
"@xterm/addon-fit": "0.10.0",
|
||||||
"allotment": "1.20.4",
|
"allotment": "1.20.2",
|
||||||
"applicationinsights": "3.12.0",
|
"applicationinsights": "1.8.0",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"canvas": "3.2.0",
|
"canvas": "2.11.2",
|
||||||
"clean-webpack-plugin": "4.0.0",
|
"clean-webpack-plugin": "4.0.0",
|
||||||
"clipboard-copy": "4.0.1",
|
"clipboard-copy": "4.0.1",
|
||||||
"copy-webpack-plugin": "13.0.1",
|
"copy-webpack-plugin": "11.0.0",
|
||||||
"crossroads": "0.12.2",
|
"crossroads": "0.12.2",
|
||||||
"css-element-queries": "1.2.3",
|
"css-element-queries": "1.1.1",
|
||||||
"d3": "7.9.0",
|
"d3": "7.8.5",
|
||||||
"datatables.net-colreorder-dt": "2.1.1",
|
"datatables.net-colreorder-dt": "1.7.0",
|
||||||
"datatables.net-dt": "2.3.4",
|
"datatables.net-dt": "1.13.8",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "1.29.0",
|
||||||
"dayjs": "1.11.18",
|
"dayjs": "1.8.19",
|
||||||
"dom-to-image": "2.6.0",
|
"dom-to-image": "2.6.0",
|
||||||
"dotenv": "17.2.3",
|
"dotenv": "8.2.0",
|
||||||
"eslint-plugin-jest": "28.14.0",
|
"eslint-plugin-jest": "27.4.2",
|
||||||
"eslint-plugin-react": "7.37.5",
|
"eslint-plugin-react": "7.33.2",
|
||||||
"hasher": "1.2.0",
|
"hasher": "1.2.0",
|
||||||
"html2canvas": "1.4.1",
|
"html2canvas": "1.0.0-rc.5",
|
||||||
"i18next": "25.5.2",
|
"i18next": "23.11.5",
|
||||||
"i18next-browser-languagedetector": "8.2.0",
|
"i18next-browser-languagedetector": "6.0.1",
|
||||||
"i18next-http-backend": "3.0.2",
|
"i18next-http-backend": "1.0.23",
|
||||||
"iframe-resizer-react": "5.1.5",
|
"iframe-resizer-react": "1.1.0",
|
||||||
"immer": "10.1.3",
|
"immer": "9.0.6",
|
||||||
"immutable": "4.0.0-rc.12",
|
"immutable": "4.0.0-rc.12",
|
||||||
"is-ci": "4.1.0",
|
"is-ci": "2.0.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"jquery-typeahead": "2.11.1",
|
"jquery-typeahead": "2.11.1",
|
||||||
"jquery-ui-dist": "1.13.3",
|
"jquery-ui-dist": "1.13.2",
|
||||||
"knockout": "3.5.1",
|
"knockout": "3.5.1",
|
||||||
"loader-utils": "3.3.1",
|
"loader-utils": "2.0.3",
|
||||||
"mkdirp": "3.0.1",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.53.0",
|
"monaco-editor": "0.44.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"p-retry": "6.2.1",
|
"p-retry": "6.2.1",
|
||||||
"patch-package": "8.0.1",
|
"patch-package": "8.0.0",
|
||||||
"plotly.js-cartesian-dist-min": "3.1.1",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
"q": "2.0.3",
|
"q": "1.5.1",
|
||||||
"react": "18.2.0",
|
"react": "16.14.0",
|
||||||
"react-animate-height": "3.2.3",
|
"react-animate-height": "2.0.8",
|
||||||
"react-dnd": "16.0.1",
|
"react-dnd": "14.0.2",
|
||||||
"react-dnd-html5-backend": "16.0.1",
|
"react-dnd-html5-backend": "14.0.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "16.14.0",
|
||||||
"react-hotkeys": "2.0.0",
|
"react-hotkeys": "2.0.0",
|
||||||
"react-i18next": "16.0.0",
|
"react-i18next": "14.1.2",
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
"react-redux": "7.2.9",
|
"react-redux": "7.1.3",
|
||||||
"react-splitter-layout": "4.0.0",
|
"react-splitter-layout": "4.0.0",
|
||||||
"react-string-format": "1.2.0",
|
"react-string-format": "1.0.1",
|
||||||
"react-window": "1.8.10",
|
"react-window": "1.8.10",
|
||||||
"react-youtube": "10.1.0",
|
"react-youtube": "9.0.1",
|
||||||
"reflect-metadata": "0.2.2",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.21",
|
"rx-jupyter": "5.5.12",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.3.3",
|
||||||
"shell-quote": "1.8.3",
|
"shell-quote": "1.7.3",
|
||||||
"styled-components": "6.1.19",
|
"styled-components": "5.0.1",
|
||||||
"swr": "2.3.6",
|
"swr": "0.4.0",
|
||||||
"terser-webpack-plugin": "5.3.14",
|
"terser-webpack-plugin": "5.3.9",
|
||||||
"tinykeys": "3.0.0",
|
"tinykeys": "2.1.0",
|
||||||
"underscore": "1.13.7",
|
"underscore": "1.12.1",
|
||||||
"utility-types": "3.11.0",
|
"utility-types": "3.10.0",
|
||||||
"zustand": "5.0.8"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.28.4",
|
"@babel/core": "7.24.7",
|
||||||
"@babel/preset-env": "7.28.3",
|
"@babel/preset-env": "7.24.7",
|
||||||
"@babel/preset-react": "7.27.1",
|
"@babel/preset-react": "7.24.7",
|
||||||
"@babel/preset-typescript": "7.27.1",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@playwright/test": "1.55.1",
|
"@playwright/test": "1.49.1",
|
||||||
"@testing-library/react": "16.3.0",
|
"@testing-library/react": "11.2.3",
|
||||||
"@types/applicationinsights-js": "1.0.9",
|
"@types/applicationinsights-js": "1.0.7",
|
||||||
"@types/codemirror": "5.60.16",
|
"@types/codemirror": "0.0.56",
|
||||||
"@types/crossroads": "0.0.33",
|
"@types/crossroads": "0.0.30",
|
||||||
"@types/d3": "7.4.3",
|
"@types/d3": "5.9.2",
|
||||||
"@types/datatables.net": "1.10.28",
|
"@types/datatables.net": "1.10.28",
|
||||||
"@types/datatables.net-colreorder": "1.4.5",
|
"@types/datatables.net-colreorder": "1.4.5",
|
||||||
"@types/dom-to-image": "2.6.7",
|
"@types/dom-to-image": "2.6.2",
|
||||||
"@types/enzyme": "3.10.19",
|
"@types/enzyme": "3.10.12",
|
||||||
"@types/enzyme-adapter-react-16": "1.0.9",
|
"@types/enzyme-adapter-react-16": "1.0.9",
|
||||||
"@types/hasher": "0.0.35",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "30.0.0",
|
"@types/jest": "29.5.12",
|
||||||
"@types/jquery": "3.5.33",
|
"@types/jquery": "3.5.29",
|
||||||
"@types/node": "24.6.0",
|
"@types/node": "12.11.1",
|
||||||
"@types/post-robot": "10.0.6",
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/q": "1.5.8",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "18.3.7",
|
"@types/react": "17.0.44",
|
||||||
"@types/react-dom": "18.3.7",
|
"@types/react-dom": "17.0.15",
|
||||||
"@types/react-notification-system": "0.2.46",
|
"@types/react-notification-system": "0.2.39",
|
||||||
"@types/react-redux": "7.1.34",
|
"@types/react-redux": "7.1.7",
|
||||||
"@types/react-splitter-layout": "4.0.0",
|
"@types/react-splitter-layout": "3.0.1",
|
||||||
"@types/react-window": "1.8.8",
|
"@types/react-window": "1.8.8",
|
||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "1.27.2",
|
||||||
"@types/sinon": "17.0.4",
|
"@types/sinon": "2.3.3",
|
||||||
"@types/styled-components": "5.1.34",
|
"@types/styled-components": "5.1.1",
|
||||||
"@types/underscore": "1.13.0",
|
"@types/underscore": "1.7.36",
|
||||||
"@types/youtube-player": "5.5.11",
|
"@types/youtube-player": "5.5.6",
|
||||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
"@typescript-eslint/eslint-plugin": "6.7.4",
|
||||||
"@typescript-eslint/parser": "8.45.0",
|
"@typescript-eslint/parser": "6.7.4",
|
||||||
"@webpack-cli/serve": "3.0.1",
|
"@webpack-cli/serve": "2.0.5",
|
||||||
"babel-jest": "30.2.0",
|
"babel-jest": "29.7.0",
|
||||||
"babel-loader": "10.0.0",
|
"babel-loader": "8.1.0",
|
||||||
"buffer": "6.0.3",
|
"buffer": "5.1.0",
|
||||||
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
||||||
"create-file-webpack": "1.0.2",
|
"create-file-webpack": "1.0.2",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "6.8.1",
|
||||||
"enzyme": "3.11.0",
|
"enzyme": "3.11.0",
|
||||||
"enzyme-adapter-react-16": "1.15.8",
|
"enzyme-adapter-react-16": "1.15.8",
|
||||||
"enzyme-to-json": "3.6.2",
|
"enzyme-to-json": "3.6.2",
|
||||||
"eslint": "9.36.0",
|
"eslint": "8.50.0",
|
||||||
"eslint-cli": "1.1.1",
|
"eslint-cli": "1.1.1",
|
||||||
"eslint-plugin-no-null": "1.0.2",
|
"eslint-plugin-no-null": "1.0.2",
|
||||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||||
"eslint-plugin-react-hooks": "5.2.0",
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.2.5",
|
||||||
"fs-extra": "11.3.2",
|
"fs-extra": "7.0.0",
|
||||||
"html-inline-css-webpack-plugin": "1.11.2",
|
"html-inline-css-webpack-plugin": "1.11.2",
|
||||||
"html-loader": "5.1.0",
|
"html-loader": "5.0.0",
|
||||||
"html-webpack-plugin": "5.6.4",
|
"html-webpack-plugin": "5.5.3",
|
||||||
"jest": "30.2.0",
|
"jest": "29.7.0",
|
||||||
"jest-canvas-mock": "2.5.2",
|
"jest-canvas-mock": "2.5.2",
|
||||||
"jest-circus": "30.2.0",
|
"jest-circus": "29.7.0",
|
||||||
"jest-environment-jsdom": "30.2.0",
|
"jest-environment-jsdom": "29.7.0",
|
||||||
"jest-html-loader": "1.0.0",
|
"jest-html-loader": "1.0.0",
|
||||||
"jest-react-hooks-shallow": "1.5.1",
|
"jest-react-hooks-shallow": "1.5.1",
|
||||||
"jest-trx-results-processor": "3.0.2",
|
"jest-trx-results-processor": "3.0.2",
|
||||||
"less": "4.4.1",
|
"less": "3.8.1",
|
||||||
"less-loader": "12.3.0",
|
"less-loader": "11.1.3",
|
||||||
"less-vars-loader": "1.1.0",
|
"less-vars-loader": "1.1.0",
|
||||||
"mini-css-extract-plugin": "2.9.4",
|
"mini-css-extract-plugin": "2.1.0",
|
||||||
"monaco-editor-webpack-plugin": "7.1.0",
|
"monaco-editor-webpack-plugin": "7.1.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "2.6.7",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.0.3",
|
||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
"querystring-es3": "0.2.1",
|
"querystring-es3": "0.2.1",
|
||||||
"raw-loader": "4.0.2",
|
"raw-loader": "0.5.1",
|
||||||
"react-dev-utils": "12.0.1",
|
"react-dev-utils": "12.0.1",
|
||||||
"rimraf": "5.0.10",
|
"rimraf": "3.0.0",
|
||||||
"sinon": "21.0.0",
|
"sinon": "3.2.1",
|
||||||
"style-loader": "4.0.0",
|
"style-loader": "0.23.0",
|
||||||
"ts-loader": "9.5.4",
|
"ts-loader": "9.2.4",
|
||||||
"typedoc": "0.28.13",
|
"typedoc": "0.26.2",
|
||||||
"typescript": "5.9.2",
|
"typescript": "4.9.5",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"wait-on": "8.0.5",
|
"wait-on": "4.0.2",
|
||||||
"webpack": "5.102.0",
|
"webpack": "5.88.2",
|
||||||
"webpack-bundle-analyzer": "4.10.2",
|
"webpack-bundle-analyzer": "4.9.1",
|
||||||
"webpack-cli": "6.0.1",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.2.2"
|
"webpack-dev-server": "4.15.2"
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"@nteract/connected-components": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/core": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/data-explorer": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/dropdown-menu": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/editor": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/iron-icons": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/jupyter-widgets": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/logos": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/markdown": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/monaco-editor": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/octicons": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/outputs": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/presentational-components": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/stateful-components": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-geojson": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-model-debug": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-plotly": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-vdom": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-vega": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
@@ -276,9 +206,11 @@
|
|||||||
"build:dataExplorer:ci": "npm run build:ci",
|
"build:dataExplorer:ci": "npm run build:ci",
|
||||||
"build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers",
|
"build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers",
|
||||||
"build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast",
|
"build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast",
|
||||||
|
"build:proxy": "npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToProxy",
|
||||||
"pack:prod": "webpack --mode production",
|
"pack:prod": "webpack --mode production",
|
||||||
"pack:fast": "webpack --mode development --progress",
|
"pack:fast": "webpack --mode development --progress",
|
||||||
"copyToConsumers": "node copyToConsumers",
|
"copyToConsumers": "node copyToConsumers",
|
||||||
|
"copyToProxy": "rm -rf ./utils/local-proxy/dist && cp -r ./dist ./utils/local-proxy",
|
||||||
"test": "rimraf coverage && jest",
|
"test": "rimraf coverage && jest",
|
||||||
"test:debug": "jest --runInBand",
|
"test:debug": "jest --runInBand",
|
||||||
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
|
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ index e5dc283..1930c2b 100644
|
|||||||
|
|
||||||
/// <reference types="jquery" />
|
/// <reference types="jquery" />
|
||||||
|
|
||||||
-import DataTables, {Api, ColumnSelector} from 'datatables.net';
|
-import DataTables, {Api} from 'datatables.net';
|
||||||
+import DataTables, { Api, ColumnSelector } from 'datatables.net';
|
+import DataTables, { Api } from 'datatables.net';
|
||||||
|
|
||||||
export default DataTables;
|
export default DataTables;
|
||||||
|
|
||||||
@@ -17,6 +17,6 @@ index e5dc283..1930c2b 100644
|
|||||||
*/
|
*/
|
||||||
+ // Ignore this error: error TS7013: Construct signature, which lacks return-type annotation, implicitly has an 'any' return type.
|
+ // Ignore this error: error TS7013: Construct signature, which lacks return-type annotation, implicitly has an 'any' return type.
|
||||||
+ // @ts-ignore
|
+ // @ts-ignore
|
||||||
new (dt: Api<any>, settings: boolean | ConfigColReorder): DataTablesStatic['ColReorder'];
|
new (dt: Api<any>, settings: boolean | ConfigColReorder);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[defaults]
|
[defaults]
|
||||||
group = dataexplorer-preview
|
group = dataexplorer-preview
|
||||||
sku = P1v2
|
sku = P1V2
|
||||||
appserviceplan = dataexplorer-preview
|
appserviceplan = dataexplorer-preview
|
||||||
location = westus2
|
location = westus2
|
||||||
web = dataexplorer-preview
|
web = dataexplorer-preview
|
||||||
|
|||||||
36687
preview/package-lock.json
generated
36687
preview/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,18 +4,16 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "az webapp up --name \"dataexplorer-preview\" --subscription \"cosmosdb-portalteam-runners\" --resource-group \"dataexplorer-preview\" --runtime \"NODE:20-lts\" --sku P1V2",
|
"deploy": "az webapp up --name \"dataexplorer-preview\" --subscription \"cosmosdb-portalteam-runners\" --resource-group \"dataexplorer-preview\" --runtime \"NODE:18-lts\" --sku P1V2",
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Microsoft Corporation",
|
"author": "Microsoft Corporation",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.3",
|
"express": "^4.17.1",
|
||||||
"express": "^4.21.2",
|
|
||||||
"http-proxy-middleware": "^3.0.3",
|
"http-proxy-middleware": "^3.0.3",
|
||||||
"node": "^20.19.5",
|
"node": "^18.20.6",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1"
|
||||||
"path-to-regexp": "^0.1.12"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,14 +138,6 @@ export enum MongoBackendEndpointType {
|
|||||||
remote,
|
remote,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AadScopeEndpoints {
|
|
||||||
public static readonly Development: string = "https://cosmos.azure.com";
|
|
||||||
public static readonly MPAC: string = "https://cosmos.azure.com";
|
|
||||||
public static readonly Prod: string = "https://cosmos.azure.com";
|
|
||||||
public static readonly Fairfax: string = "https://cosmos.azure.us";
|
|
||||||
public static readonly Mooncake: string = "https://cosmos.azure.cn";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PortalBackendEndpoints {
|
export class PortalBackendEndpoints {
|
||||||
public static readonly Development: string = "https://localhost:7235";
|
public static readonly Development: string = "https://localhost:7235";
|
||||||
public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com";
|
public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com";
|
||||||
@@ -263,7 +255,6 @@ export class HttpHeaders {
|
|||||||
public static activityId: string = "x-ms-activity-id";
|
public static activityId: string = "x-ms-activity-id";
|
||||||
public static apiType: string = "x-ms-cosmos-apitype";
|
public static apiType: string = "x-ms-cosmos-apitype";
|
||||||
public static authorization: string = "authorization";
|
public static authorization: string = "authorization";
|
||||||
public static entraIdToken: string = "x-ms-entraid-token";
|
|
||||||
public static collectionIndexTransformationProgress: string =
|
public static collectionIndexTransformationProgress: string =
|
||||||
"x-ms-documentdb-collection-index-transformation-progress";
|
"x-ms-documentdb-collection-index-transformation-progress";
|
||||||
public static continuation: string = "x-ms-continuation";
|
public static continuation: string = "x-ms-continuation";
|
||||||
@@ -774,10 +765,3 @@ export const ShortenedQueryCopilotSampleContainerSchema = {
|
|||||||
|
|
||||||
userPrompt: "find all products",
|
userPrompt: "find all products",
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum MongoGuidRepresentation {
|
|
||||||
Standard = "Standard",
|
|
||||||
CSharpLegacy = "CSharpLegacy",
|
|
||||||
JavaLegacy = "JavaLegacy",
|
|
||||||
PythonLegacy = "PythonLegacy",
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,39 +28,3 @@ describe("Environment Utility Test", () => {
|
|||||||
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Development);
|
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Development);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("normalizeArmEndpoint", () => {
|
|
||||||
it("should append '/' if not present", () => {
|
|
||||||
expect(EnvironmentUtility.normalizeArmEndpoint("https://example.com")).toBe("https://example.com/");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the same uri if '/' is present at the end", () => {
|
|
||||||
expect(EnvironmentUtility.normalizeArmEndpoint("https://example.com/")).toBe("https://example.com/");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle empty string", () => {
|
|
||||||
expect(EnvironmentUtility.normalizeArmEndpoint("")).toBe("");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getEnvironment", () => {
|
|
||||||
it("should return Prod environment", () => {
|
|
||||||
updateConfigContext({
|
|
||||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
|
|
||||||
});
|
|
||||||
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Prod);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return Fairfax environment", () => {
|
|
||||||
updateConfigContext({
|
|
||||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Fairfax,
|
|
||||||
});
|
|
||||||
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Fairfax);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return Mooncake environment", () => {
|
|
||||||
updateConfigContext({
|
|
||||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mooncake,
|
|
||||||
});
|
|
||||||
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Mooncake);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { AadScopeEndpoints, PortalBackendEndpoints } from "Common/Constants";
|
import { PortalBackendEndpoints } from "Common/Constants";
|
||||||
import * as Logger from "Common/Logger";
|
|
||||||
import { configContext } from "ConfigContext";
|
import { configContext } from "ConfigContext";
|
||||||
|
|
||||||
export function normalizeArmEndpoint(uri: string): string {
|
export function normalizeArmEndpoint(uri: string): string {
|
||||||
@@ -28,17 +27,3 @@ export const getEnvironment = (): Environment => {
|
|||||||
|
|
||||||
return environmentMap[configContext.PORTAL_BACKEND_ENDPOINT];
|
return environmentMap[configContext.PORTAL_BACKEND_ENDPOINT];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEnvironmentScopeEndpoint = (): string => {
|
|
||||||
const environment = getEnvironment();
|
|
||||||
const endpoint = AadScopeEndpoints[environment];
|
|
||||||
if (!endpoint) {
|
|
||||||
throw new Error("Cannot determine AAD scope endpoint");
|
|
||||||
}
|
|
||||||
const hrefEndpoint = new URL(endpoint).href.replace(/\/+$/, "/.default");
|
|
||||||
Logger.logInfo(
|
|
||||||
`Using AAD scope endpoint: ${hrefEndpoint}, Environment: ${environment}`,
|
|
||||||
"EnvironmentUtility/getEnvironmentScopeEndpoint",
|
|
||||||
);
|
|
||||||
return hrefEndpoint;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
import { getMongoGuidRepresentation } from "Shared/StorageUtility";
|
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
@@ -7,7 +6,6 @@ import { MessageTypes } from "../Contracts/ExplorerContracts";
|
|||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { isDataplaneRbacEnabledForProxyApi } from "../Utils/AuthorizationUtils";
|
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, ContentType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, ContentType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
import { MinimalQueryIterator } from "./IteratorUtilities";
|
import { MinimalQueryIterator } from "./IteratorUtilities";
|
||||||
@@ -23,13 +21,7 @@ function authHeaders() {
|
|||||||
if (userContext.authType === AuthType.EncryptedToken) {
|
if (userContext.authType === AuthType.EncryptedToken) {
|
||||||
return { [HttpHeaders.guestAccessToken]: userContext.accessToken };
|
return { [HttpHeaders.guestAccessToken]: userContext.accessToken };
|
||||||
} else {
|
} else {
|
||||||
const headers: { [key: string]: string } = {
|
return { [HttpHeaders.authorization]: userContext.authorizationToken };
|
||||||
[HttpHeaders.authorization]: userContext.authorizationToken,
|
|
||||||
};
|
|
||||||
if (isDataplaneRbacEnabledForProxyApi(userContext)) {
|
|
||||||
headers[HttpHeaders.entraIdToken] = userContext.aadToken;
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,9 +139,6 @@ export function readDocument(
|
|||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperties?.[0]
|
||||||
: "",
|
: "",
|
||||||
clientSettings: {
|
|
||||||
guidRepresentation: getMongoGuidRepresentation(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
@@ -192,9 +181,6 @@ export function createDocument(
|
|||||||
partitionKey:
|
partitionKey:
|
||||||
collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
||||||
documentContent: JSON.stringify(documentContent),
|
documentContent: JSON.stringify(documentContent),
|
||||||
clientSettings: {
|
|
||||||
guidRepresentation: getMongoGuidRepresentation(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
@@ -242,9 +228,6 @@ export function updateDocument(
|
|||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperties?.[0]
|
||||||
: "",
|
: "",
|
||||||
documentContent,
|
documentContent,
|
||||||
clientSettings: {
|
|
||||||
guidRepresentation: getMongoGuidRepresentation(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
@@ -291,9 +274,6 @@ export function deleteDocuments(
|
|||||||
subscriptionID: userContext.subscriptionId,
|
subscriptionID: userContext.subscriptionId,
|
||||||
resourceGroup: userContext.resourceGroup,
|
resourceGroup: userContext.resourceGroup,
|
||||||
databaseAccountName: databaseAccount.name,
|
databaseAccountName: databaseAccount.name,
|
||||||
clientSettings: {
|
|
||||||
guidRepresentation: getMongoGuidRepresentation(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export enum Platform {
|
|||||||
Hosted = "Hosted",
|
Hosted = "Hosted",
|
||||||
Emulator = "Emulator",
|
Emulator = "Emulator",
|
||||||
Fabric = "Fabric",
|
Fabric = "Fabric",
|
||||||
|
VNextEmulator = "VNextEmulator",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigContext {
|
export interface ConfigContext {
|
||||||
@@ -225,6 +226,7 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
case Platform.Fabric:
|
case Platform.Fabric:
|
||||||
case Platform.Hosted:
|
case Platform.Hosted:
|
||||||
case Platform.Emulator:
|
case Platform.Emulator:
|
||||||
|
case Platform.VNextEmulator:
|
||||||
updateConfigContext({ platform });
|
updateConfigContext({ platform });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import React, { FC, useEffect } from "react";
|
import React, { FC, useEffect } from "react";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
export interface DialogState {
|
export interface DialogState {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -38,7 +38,7 @@ export interface DialogState {
|
|||||||
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps) => void;
|
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDialog = create<DialogState>((set, get) => ({
|
export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||||
visible: false,
|
visible: false,
|
||||||
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
|
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
|
||||||
closeDialog: () =>
|
closeDialog: () =>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { useDatabases } from "Explorer/useDatabases";
|
|||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||||
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
||||||
|
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import DiscardIcon from "../../../../images/discard.svg";
|
import DiscardIcon from "../../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||||
@@ -60,15 +61,15 @@ import {
|
|||||||
AddMongoIndexProps,
|
AddMongoIndexProps,
|
||||||
ChangeFeedPolicyState,
|
ChangeFeedPolicyState,
|
||||||
GeospatialConfigType,
|
GeospatialConfigType,
|
||||||
MongoIndexTypes,
|
|
||||||
SettingsV2TabTypes,
|
|
||||||
TtlType,
|
|
||||||
getMongoNotification,
|
getMongoNotification,
|
||||||
getTabTitle,
|
getTabTitle,
|
||||||
hasDatabaseSharedThroughput,
|
hasDatabaseSharedThroughput,
|
||||||
isDirty,
|
isDirty,
|
||||||
|
MongoIndexTypes,
|
||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
parseConflictResolutionProcedure,
|
parseConflictResolutionProcedure,
|
||||||
|
SettingsV2TabTypes,
|
||||||
|
TtlType,
|
||||||
} from "./SettingsUtils";
|
} from "./SettingsUtils";
|
||||||
|
|
||||||
interface SettingsV2TabInfo {
|
interface SettingsV2TabInfo {
|
||||||
@@ -276,14 +277,14 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.saveSettingsButton = {
|
this.saveSettingsButton = {
|
||||||
isEnabled: this.isSaveSettingsButtonEnabled,
|
isEnabled: this.isSaveSettingsButtonEnabled,
|
||||||
isVisible: () => {
|
isVisible: () => {
|
||||||
return true;
|
return isFeatureSupported(PlatformFeature.UpdateCollection);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.discardSettingsChangesButton = {
|
this.discardSettingsChangesButton = {
|
||||||
isEnabled: this.isDiscardSettingsButtonEnabled,
|
isEnabled: this.isDiscardSettingsButtonEnabled,
|
||||||
isVisible: () => {
|
isVisible: () => {
|
||||||
return true;
|
return isFeatureSupported(PlatformFeature.UpdateCollection);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Link } from "@fluentui/react/lib/Link";
|
|||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||||
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
|
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
|
||||||
import { sendMessage } from "Common/MessageHandler";
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { configContext, Platform } from "ConfigContext";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
||||||
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
@@ -18,12 +18,13 @@ import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
|||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
|
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
|
||||||
|
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
|
||||||
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { shallow } from "zustand/shallow";
|
import shallow from "zustand/shallow";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
@@ -112,8 +113,8 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
(state) => state.isNotebooksEnabledForAccount,
|
|
||||||
() => this.refreshCommandBarButtons(),
|
() => this.refreshCommandBarButtons(),
|
||||||
|
(state) => state.isNotebooksEnabledForAccount,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
@@ -136,13 +137,13 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useTabs.subscribe(
|
useTabs.subscribe(
|
||||||
(state) => state.openedTabs,
|
|
||||||
(openedTabs: TabsBase[]) => {
|
(openedTabs: TabsBase[]) => {
|
||||||
if (openedTabs.length === 0) {
|
if (openedTabs.length === 0) {
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
(state) => state.openedTabs,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.isTabsContentExpanded = ko.observable(false);
|
this.isTabsContentExpanded = ko.observable(false);
|
||||||
@@ -170,9 +171,9 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
(state) => [state.isNotebookEnabled, state.isRefreshed],
|
|
||||||
async () => this.initiateAndRefreshNotebookList(),
|
async () => this.initiateAndRefreshNotebookList(),
|
||||||
{ equalityFn: shallow },
|
(state) => [state.isNotebookEnabled, state.isRefreshed],
|
||||||
|
shallow,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.resourceTree = new ResourceTreeAdapter(this);
|
this.resourceTree = new ResourceTreeAdapter(this);
|
||||||
@@ -1187,6 +1188,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
||||||
const isNotebookEnabled =
|
const isNotebookEnabled =
|
||||||
|
isFeatureSupported(PlatformFeature.Notebooks) &&
|
||||||
configContext.platform !== Platform.Fabric &&
|
configContext.platform !== Platform.Fabric &&
|
||||||
(userContext.features.notebooksDownBanner ||
|
(userContext.features.notebooksDownBanner ||
|
||||||
useNotebook.getState().isPhoenixNotebooks ||
|
useNotebook.getState().isPhoenixNotebooks ||
|
||||||
@@ -1194,7 +1196,11 @@ export default class Explorer {
|
|||||||
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
||||||
useNotebook
|
useNotebook
|
||||||
.getState()
|
.getState()
|
||||||
.setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed());
|
.setIsShellEnabled(
|
||||||
|
isFeatureSupported(PlatformFeature.CloudShell) &&
|
||||||
|
useNotebook.getState().isPhoenixFeatures &&
|
||||||
|
isPublicInternetAccessAllowed(),
|
||||||
|
);
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
||||||
isNotebookEnabled,
|
isNotebookEnabled,
|
||||||
@@ -1215,6 +1221,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public async configureCopilot(): Promise<void> {
|
public async configureCopilot(): Promise<void> {
|
||||||
if (
|
if (
|
||||||
|
!isFeatureSupported(PlatformFeature.Copilot) ||
|
||||||
userContext.apiType !== "SQL" ||
|
userContext.apiType !== "SQL" ||
|
||||||
!userContext.subscriptionId ||
|
!userContext.subscriptionId ||
|
||||||
![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment())
|
![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment())
|
||||||
|
|||||||
@@ -5,12 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
|
||||||
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
||||||
import { StyleConstants } from "../../../Common/StyleConstants";
|
import { StyleConstants } from "../../../Common/StyleConstants";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
@@ -30,8 +29,8 @@ export interface CommandBarStore {
|
|||||||
setIsHidden: (isHidden: boolean) => void;
|
setIsHidden: (isHidden: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCommandBar = create<CommandBarStore>((set) => ({
|
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
|
||||||
contextButtons: [] as CommandButtonComponentProps[],
|
contextButtons: [],
|
||||||
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
|
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
|
||||||
@@ -44,15 +43,6 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const backgroundColor = StyleConstants.BaseLight;
|
const backgroundColor = StyleConstants.BaseLight;
|
||||||
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
|
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
|
||||||
|
|
||||||
// Subscribe to the store changes that affect button creation
|
|
||||||
const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled);
|
|
||||||
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
|
|
||||||
|
|
||||||
// Memoize the expensive button creation
|
|
||||||
const staticButtons = React.useMemo(() => {
|
|
||||||
return CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
|
|
||||||
}, [container, selectedNodeState, dataPlaneRbacEnabled, aadTokenUpdated]);
|
|
||||||
|
|
||||||
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
||||||
const buttons =
|
const buttons =
|
||||||
userContext.apiType === "Postgres"
|
userContext.apiType === "Postgres"
|
||||||
@@ -72,6 +62,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
|
||||||
const contextButtons = (buttons || []).concat(
|
const contextButtons = (buttons || []).concat(
|
||||||
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState),
|
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { KeyboardAction } from "KeyboardShortcuts";
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
||||||
|
import { areAdvancedScriptsSupported, isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
|
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
|
||||||
import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
|
import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
|
||||||
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
|
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
|
||||||
@@ -16,7 +18,7 @@ import SynapseIcon from "../../../../images/synapse-link.svg";
|
|||||||
import VSCodeIcon from "../../../../images/vscode.svg";
|
import VSCodeIcon from "../../../../images/vscode.svg";
|
||||||
import { AuthType } from "../../../AuthType";
|
import { AuthType } from "../../../AuthType";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Platform, configContext } from "../../../ConfigContext";
|
import { configContext, Platform } from "../../../ConfigContext";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
||||||
@@ -62,12 +64,22 @@ export function createStaticCommandBarButtons(
|
|||||||
}
|
}
|
||||||
if (userContext.apiType !== "Gremlin") {
|
if (userContext.apiType !== "Gremlin") {
|
||||||
const addVsCode = createOpenVsCodeDialogButton(container);
|
const addVsCode = createOpenVsCodeDialogButton(container);
|
||||||
buttons.push(addVsCode);
|
if (addVsCode) {
|
||||||
|
buttons.push(addVsCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDataplaneRbacSupported(userContext.apiType)) {
|
if (isDataplaneRbacSupported(userContext.apiType)) {
|
||||||
const loginButtonProps = createLoginForEntraIDButton(container);
|
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined);
|
||||||
|
const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled);
|
||||||
|
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const buttonProps = createLoginForEntraIDButton(container);
|
||||||
|
setLoginButtonProps(buttonProps);
|
||||||
|
}, [dataPlaneRbacEnabled, aadTokenUpdated, container]);
|
||||||
|
|
||||||
if (loginButtonProps) {
|
if (loginButtonProps) {
|
||||||
addDivider();
|
addDivider();
|
||||||
buttons.push(loginButtonProps);
|
buttons.push(loginButtonProps);
|
||||||
@@ -233,11 +245,17 @@ export function createDivider(): CommandButtonComponentProps {
|
|||||||
|
|
||||||
function areScriptsSupported(): boolean {
|
function areScriptsSupported(): boolean {
|
||||||
return (
|
return (
|
||||||
configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
|
areAdvancedScriptsSupported() &&
|
||||||
|
configContext.platform !== Platform.Fabric &&
|
||||||
|
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
|
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
|
||||||
|
if (!isFeatureSupported(PlatformFeature.SynapseLink)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (configContext.platform === Platform.Emulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -265,6 +283,10 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps {
|
function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps {
|
||||||
|
if (!isFeatureSupported(PlatformFeature.VSCodeIntegration)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const label = "Visual Studio Code";
|
const label = "Visual Studio Code";
|
||||||
return {
|
return {
|
||||||
iconSrc: VSCodeIcon,
|
iconSrc: VSCodeIcon,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppState, ContentRef, selectors } from "@nteract/core";
|
import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
@@ -59,7 +59,7 @@ export class StatusBar extends React.Component<Props> {
|
|||||||
<Bar data-test="notebookStatusBar">
|
<Bar data-test="notebookStatusBar">
|
||||||
<RightStatus>
|
<RightStatus>
|
||||||
{this.props.lastSaved ? (
|
{this.props.lastSaved ? (
|
||||||
<p data-test="saveStatus"> Last saved {formatDistanceToNow(this.props.lastSaved)} ago </p>
|
<p data-test="saveStatus"> Last saved {distanceInWordsToNow(this.props.lastSaved)} </p>
|
||||||
) : (
|
) : (
|
||||||
<p> Not saved yet </p>
|
<p> Not saved yet </p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from 'zustand/middleware';
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { ConnectionStatusType, HttpStatusCodes } from "../../Common/Constants";
|
import { ConnectionStatusType, HttpStatusCodes } from "../../Common/Constants";
|
||||||
@@ -67,274 +66,270 @@ interface NotebookState {
|
|||||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
|
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebook = create<NotebookState>()(
|
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||||
subscribeWithSelector(
|
isNotebookEnabled: false,
|
||||||
(set, get) => ({
|
isNotebooksEnabledForAccount: false,
|
||||||
isNotebookEnabled: false,
|
notebookServerInfo: {
|
||||||
isNotebooksEnabledForAccount: false,
|
notebookServerEndpoint: undefined,
|
||||||
notebookServerInfo: {
|
authToken: undefined,
|
||||||
notebookServerEndpoint: "",
|
forwardingId: undefined,
|
||||||
authToken: "",
|
},
|
||||||
forwardingId: "",
|
sparkClusterConnectionInfo: {
|
||||||
},
|
userName: undefined,
|
||||||
sparkClusterConnectionInfo: {
|
password: undefined,
|
||||||
userName: "",
|
endpoints: [],
|
||||||
password: "",
|
},
|
||||||
endpoints: [] as DataModels.SparkClusterEndpoint[],
|
isSynapseLinkUpdating: false,
|
||||||
},
|
memoryUsageInfo: undefined,
|
||||||
isSynapseLinkUpdating: false,
|
isShellEnabled: false,
|
||||||
memoryUsageInfo: undefined as DataModels.MemoryUsageInfo,
|
notebookBasePath: Constants.Notebook.defaultBasePath,
|
||||||
isShellEnabled: false,
|
isInitializingNotebooks: false,
|
||||||
notebookBasePath: Constants.Notebook.defaultBasePath,
|
myNotebooksContentRoot: undefined,
|
||||||
isInitializingNotebooks: false,
|
gitHubNotebooksContentRoot: undefined,
|
||||||
myNotebooksContentRoot: undefined as NotebookContentItem,
|
galleryContentRoot: undefined,
|
||||||
gitHubNotebooksContentRoot: undefined as NotebookContentItem,
|
connectionInfo: {
|
||||||
galleryContentRoot: undefined as NotebookContentItem,
|
status: ConnectionStatusType.Connect,
|
||||||
connectionInfo: {
|
},
|
||||||
status: ConnectionStatusType.Connect,
|
notebookFolderName: undefined,
|
||||||
},
|
isAllocating: false,
|
||||||
notebookFolderName: "",
|
isRefreshed: false,
|
||||||
isAllocating: false,
|
containerStatus: {
|
||||||
isRefreshed: false,
|
status: undefined,
|
||||||
containerStatus: {
|
durationLeftInMinutes: undefined,
|
||||||
status: undefined,
|
phoenixServerInfo: undefined,
|
||||||
durationLeftInMinutes: undefined,
|
},
|
||||||
phoenixServerInfo: undefined,
|
isPhoenixNotebooks: undefined,
|
||||||
} as ContainerInfo,
|
isPhoenixFeatures: undefined,
|
||||||
isPhoenixNotebooks: undefined as boolean,
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
isPhoenixFeatures: undefined as boolean,
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
set({ notebookServerInfo }),
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) =>
|
||||||
set({ notebookServerInfo }),
|
set({ sparkClusterConnectionInfo }),
|
||||||
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) =>
|
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }),
|
||||||
set({ sparkClusterConnectionInfo }),
|
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
||||||
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }),
|
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
||||||
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||||
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
||||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||||
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
await get().getPhoenixStatus();
|
||||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
const { databaseAccount, authType } = userContext;
|
||||||
await get().getPhoenixStatus();
|
if (
|
||||||
const { databaseAccount, authType } = userContext;
|
authType === AuthType.EncryptedToken ||
|
||||||
if (
|
authType === AuthType.ResourceToken ||
|
||||||
authType === AuthType.EncryptedToken ||
|
authType === AuthType.MasterKey
|
||||||
authType === AuthType.ResourceToken ||
|
) {
|
||||||
authType === AuthType.MasterKey
|
set({ isNotebooksEnabledForAccount: false });
|
||||||
) {
|
return;
|
||||||
set({ isNotebooksEnabledForAccount: false });
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstWriteLocation =
|
const firstWriteLocation =
|
||||||
userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
|
userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
|
||||||
? databaseAccount?.location
|
? databaseAccount?.location
|
||||||
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
|
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
|
||||||
const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`;
|
const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`;
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(disallowedLocationsUri, {
|
const response = await fetch(disallowedLocationsUri, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces],
|
resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces],
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
[Constants.HttpHeaders.contentType]: "application/json",
|
[Constants.HttpHeaders.contentType]: "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch disallowed locations");
|
throw new Error("Failed to fetch disallowed locations");
|
||||||
}
|
}
|
||||||
|
|
||||||
const disallowedLocations: string[] = await response.json();
|
const disallowedLocations: string[] = await response.json();
|
||||||
if (!disallowedLocations) {
|
if (!disallowedLocations) {
|
||||||
Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount");
|
Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount");
|
||||||
set({ isNotebooksEnabledForAccount: true });
|
set({ isNotebooksEnabledForAccount: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// firstWriteLocation should not be disallowed
|
// firstWriteLocation should not be disallowed
|
||||||
const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1;
|
const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1;
|
||||||
set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation });
|
set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
|
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
|
||||||
set({ isNotebooksEnabledForAccount: false });
|
set({ isNotebooksEnabledForAccount: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
findItem: (root: NotebookContentItem, item: NotebookContentItem): NotebookContentItem => {
|
findItem: (root: NotebookContentItem, item: NotebookContentItem): NotebookContentItem => {
|
||||||
const currentItem = root || get().myNotebooksContentRoot;
|
const currentItem = root || get().myNotebooksContentRoot;
|
||||||
|
|
||||||
if (currentItem) {
|
if (currentItem) {
|
||||||
if (currentItem.path === item.path && currentItem.name === item.name) {
|
if (currentItem.path === item.path && currentItem.name === item.name) {
|
||||||
return currentItem;
|
return currentItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentItem.children) {
|
if (currentItem.children) {
|
||||||
for (const childItem of currentItem.children) {
|
for (const childItem of currentItem.children) {
|
||||||
const result = get().findItem(childItem, item);
|
const result = get().findItem(childItem, item);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean): void => {
|
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||||
const parentItem = get().findItem(root, parent);
|
const parentItem = get().findItem(root, parent);
|
||||||
item.parent = parentItem;
|
item.parent = parentItem;
|
||||||
if (parentItem.children) {
|
if (parentItem.children) {
|
||||||
parentItem.children.push(item);
|
parentItem.children.push(item);
|
||||||
} else {
|
} else {
|
||||||
parentItem.children = [item];
|
parentItem.children = [item];
|
||||||
}
|
}
|
||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||||
const parentItem = get().findItem(root, item.parent);
|
const parentItem = get().findItem(root, item.parent);
|
||||||
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
||||||
parentItem.children.push(item);
|
parentItem.children.push(item);
|
||||||
item.parent = parentItem;
|
item.parent = parentItem;
|
||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||||
const parentItem = get().findItem(root, item.parent);
|
const parentItem = get().findItem(root, item.parent);
|
||||||
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
|
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
|
||||||
set({ notebookFolderName });
|
set({ notebookFolderName });
|
||||||
const myNotebooksContentRoot = {
|
const myNotebooksContentRoot = {
|
||||||
name: get().notebookFolderName,
|
name: get().notebookFolderName,
|
||||||
path: get().notebookBasePath,
|
path: get().notebookBasePath,
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
};
|
||||||
|
const galleryContentRoot = {
|
||||||
|
name: "Gallery",
|
||||||
|
path: "Gallery",
|
||||||
|
type: NotebookContentItemType.File,
|
||||||
|
};
|
||||||
|
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
||||||
|
? {
|
||||||
|
name: "GitHub repos",
|
||||||
|
path: "PsuedoDir",
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
set({
|
||||||
|
myNotebooksContentRoot,
|
||||||
|
galleryContentRoot,
|
||||||
|
gitHubNotebooksContentRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (get().notebookServerInfo?.notebookServerEndpoint) {
|
||||||
|
const updatedRoot = await notebookManager?.notebookContentClient?.updateItemChildren(myNotebooksContentRoot);
|
||||||
|
set({ myNotebooksContentRoot: updatedRoot });
|
||||||
|
|
||||||
|
if (updatedRoot?.children) {
|
||||||
|
// Count 1st generation children (tree is lazy-loaded)
|
||||||
|
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
|
||||||
|
updatedRoot.children.forEach((notebookItem) => {
|
||||||
|
switch (notebookItem.type) {
|
||||||
|
case NotebookContentItemType.File:
|
||||||
|
nodeCounts.files++;
|
||||||
|
break;
|
||||||
|
case NotebookContentItemType.Directory:
|
||||||
|
nodeCounts.directories++;
|
||||||
|
break;
|
||||||
|
case NotebookContentItemType.Notebook:
|
||||||
|
nodeCounts.notebooks++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]): void => {
|
||||||
|
const gitHubNotebooksContentRoot = cloneDeep(get().gitHubNotebooksContentRoot);
|
||||||
|
if (gitHubNotebooksContentRoot) {
|
||||||
|
gitHubNotebooksContentRoot.children = [];
|
||||||
|
pinnedRepos?.forEach((pinnedRepo) => {
|
||||||
|
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
||||||
|
const repoTreeItem: NotebookContentItem = {
|
||||||
|
name: repoFullName,
|
||||||
|
path: "PsuedoDir",
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
children: [],
|
||||||
|
parent: gitHubNotebooksContentRoot,
|
||||||
};
|
};
|
||||||
const galleryContentRoot = {
|
|
||||||
name: "Gallery",
|
pinnedRepo.branches.forEach((branch) => {
|
||||||
path: "Gallery",
|
repoTreeItem.children.push({
|
||||||
type: NotebookContentItemType.File,
|
name: branch.name,
|
||||||
};
|
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
|
||||||
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
|
||||||
? {
|
|
||||||
name: "GitHub repos",
|
|
||||||
path: "PsuedoDir",
|
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
}
|
parent: repoTreeItem,
|
||||||
: undefined;
|
|
||||||
|
|
||||||
set({
|
|
||||||
myNotebooksContentRoot,
|
|
||||||
galleryContentRoot,
|
|
||||||
gitHubNotebooksContentRoot,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (get().notebookServerInfo?.notebookServerEndpoint) {
|
|
||||||
const updatedRoot = await notebookManager?.notebookContentClient?.updateItemChildren(myNotebooksContentRoot);
|
|
||||||
set({ myNotebooksContentRoot: updatedRoot });
|
|
||||||
|
|
||||||
if (updatedRoot?.children) {
|
|
||||||
// Count 1st generation children (tree is lazy-loaded)
|
|
||||||
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
|
|
||||||
updatedRoot.children.forEach((notebookItem) => {
|
|
||||||
switch (notebookItem.type) {
|
|
||||||
case NotebookContentItemType.File:
|
|
||||||
nodeCounts.files++;
|
|
||||||
break;
|
|
||||||
case NotebookContentItemType.Directory:
|
|
||||||
nodeCounts.directories++;
|
|
||||||
break;
|
|
||||||
case NotebookContentItemType.Notebook:
|
|
||||||
nodeCounts.notebooks++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]): void => {
|
|
||||||
const gitHubNotebooksContentRoot = cloneDeep(get().gitHubNotebooksContentRoot);
|
|
||||||
if (gitHubNotebooksContentRoot) {
|
|
||||||
gitHubNotebooksContentRoot.children = [];
|
|
||||||
pinnedRepos?.forEach((pinnedRepo) => {
|
|
||||||
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
|
||||||
const repoTreeItem: NotebookContentItem = {
|
|
||||||
name: repoFullName,
|
|
||||||
path: "PsuedoDir",
|
|
||||||
type: NotebookContentItemType.Directory,
|
|
||||||
children: [],
|
|
||||||
parent: gitHubNotebooksContentRoot,
|
|
||||||
};
|
|
||||||
|
|
||||||
pinnedRepo.branches.forEach((branch) => {
|
|
||||||
repoTreeItem.children.push({
|
|
||||||
name: branch.name,
|
|
||||||
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
|
|
||||||
type: NotebookContentItemType.Directory,
|
|
||||||
parent: repoTreeItem,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gitHubNotebooksContentRoot.children.push(repoTreeItem);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
set({ gitHubNotebooksContentRoot });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
|
||||||
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
|
||||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
|
||||||
useTabs.getState().closeAllNotebookTabs(true);
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
useNotebook.getState().setNotebookServerInfo(undefined);
|
|
||||||
useNotebook.getState().setIsAllocating(false);
|
|
||||||
useNotebook.getState().setContainerStatus({
|
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
phoenixServerInfo: undefined,
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
|
||||||
getPhoenixStatus: async () => {
|
|
||||||
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
|
|
||||||
let isPhoenixNotebooks = false;
|
|
||||||
let isPhoenixFeatures = false;
|
|
||||||
|
|
||||||
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
|
gitHubNotebooksContentRoot.children.push(repoTreeItem);
|
||||||
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
});
|
||||||
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
|
|
||||||
|
|
||||||
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
|
set({ gitHubNotebooksContentRoot });
|
||||||
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
|
}
|
||||||
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
|
},
|
||||||
isPhoenixFeatures =
|
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
||||||
isPublicInternetAllowed &&
|
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
||||||
// phoenix needs to be enabled for Postgres and VCoreMongo accounts since the PSQL and mongo shell requires phoenix containers
|
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||||
(userContext.features.phoenixFeatures === true ||
|
useTabs.getState().closeAllNotebookTabs(true);
|
||||||
userContext.apiType === "Postgres" ||
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
userContext.apiType === "VCoreMongo");
|
useNotebook.getState().setNotebookServerInfo(undefined);
|
||||||
} else {
|
useNotebook.getState().setIsAllocating(false);
|
||||||
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
|
useNotebook.getState().setContainerStatus({
|
||||||
}
|
status: undefined,
|
||||||
} else {
|
durationLeftInMinutes: undefined,
|
||||||
isPhoenixNotebooks = isPhoenixFeatures = false;
|
phoenixServerInfo: undefined,
|
||||||
}
|
});
|
||||||
set({ isPhoenixNotebooks: isPhoenixNotebooks });
|
},
|
||||||
set({ isPhoenixFeatures: isPhoenixFeatures });
|
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
||||||
|
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
||||||
|
getPhoenixStatus: async () => {
|
||||||
|
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
|
||||||
|
let isPhoenixNotebooks = false;
|
||||||
|
let isPhoenixFeatures = false;
|
||||||
|
|
||||||
|
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
|
||||||
|
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||||
|
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
|
||||||
|
|
||||||
|
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
|
||||||
|
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
|
||||||
|
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
|
||||||
|
isPhoenixFeatures =
|
||||||
|
isPublicInternetAllowed &&
|
||||||
|
// phoenix needs to be enabled for Postgres and VCoreMongo accounts since the PSQL and mongo shell requires phoenix containers
|
||||||
|
(userContext.features.phoenixFeatures === true ||
|
||||||
|
userContext.apiType === "Postgres" ||
|
||||||
|
userContext.apiType === "VCoreMongo");
|
||||||
|
} else {
|
||||||
|
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }),
|
isPhoenixNotebooks = isPhoenixFeatures = false;
|
||||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }),
|
}
|
||||||
})
|
set({ isPhoenixNotebooks: isPhoenixNotebooks });
|
||||||
)
|
set({ isPhoenixFeatures: isPhoenixFeatures });
|
||||||
);
|
}
|
||||||
|
},
|
||||||
|
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }),
|
||||||
|
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }),
|
||||||
|
}));
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
|||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { getCollectionName } from "Utils/APITypeUtils";
|
import { getCollectionName } from "Utils/APITypeUtils";
|
||||||
import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||||
|
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
|
||||||
import { getUpsellMessage } from "Utils/PricingUtils";
|
import { getUpsellMessage } from "Utils/PricingUtils";
|
||||||
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
|
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
@@ -1160,11 +1161,15 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private shouldShowVectorSearchParameters() {
|
private shouldShowVectorSearchParameters() {
|
||||||
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
|
return (
|
||||||
|
isFeatureSupported(PlatformFeature.VectorSearch) &&
|
||||||
|
isVectorSearchEnabled() &&
|
||||||
|
(isServerlessAccount() || this.shouldShowCollectionThroughputInput())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldShowFullTextSearchParameters() {
|
private shouldShowFullTextSearchParameters() {
|
||||||
return !isFabricNative() && this.showFullTextSearch;
|
return isFeatureSupported(PlatformFeature.FullTextSearch) && !isFabricNative() && this.showFullTextSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
|
private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getFullTextLanguageOptions } from "Explorer/Controls/FullTextSeach/Full
|
|||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
|
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
|
||||||
|
|
||||||
export function getPartitionKeyTooltipText(): string {
|
export function getPartitionKeyTooltipText(): string {
|
||||||
if (userContext.apiType === "Mongo") {
|
if (userContext.apiType === "Mongo") {
|
||||||
@@ -85,7 +86,11 @@ export function UniqueKeysHeader(): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function shouldShowAnalyticalStoreOptions(): boolean {
|
export function shouldShowAnalyticalStoreOptions(): boolean {
|
||||||
if (isFabricNative() || configContext.platform === Platform.Emulator) {
|
if (
|
||||||
|
!isFeatureSupported(PlatformFeature.AnalyticalStore) ||
|
||||||
|
isFabricNative() ||
|
||||||
|
configContext.platform === Platform.Emulator
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
|
|||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
@@ -65,6 +65,8 @@ export interface DataPlaneRbacState {
|
|||||||
setAadDataPlaneUpdated: (aadTokenUpdated: boolean) => void;
|
setAadDataPlaneUpdated: (aadTokenUpdated: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DataPlaneRbacStore = UseStore<Partial<DataPlaneRbacState>>;
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
bulletList: {
|
bulletList: {
|
||||||
listStyleType: "disc",
|
listStyleType: "disc",
|
||||||
@@ -98,7 +100,7 @@ const useStyles = makeStyles({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useDataPlaneRbac = create<Partial<DataPlaneRbacState>>(() => ({
|
export const useDataPlaneRbac: DataPlaneRbacStore = create(() => ({
|
||||||
dataPlaneRbacEnabled: false,
|
dataPlaneRbacEnabled: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -197,12 +199,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
|
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
|
||||||
);
|
);
|
||||||
|
|
||||||
const [mongoGuidRepresentation, setMongoGuidRepresentation] = useState<Constants.MongoGuidRepresentation>(
|
|
||||||
LocalStorageUtility.hasItem(StorageKey.MongoGuidRepresentation)
|
|
||||||
? (LocalStorageUtility.getEntryString(StorageKey.MongoGuidRepresentation) as Constants.MongoGuidRepresentation)
|
|
||||||
: Constants.MongoGuidRepresentation.CSharpLegacy,
|
|
||||||
);
|
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
const explorerVersion = configContext.gitSha;
|
const explorerVersion = configContext.gitSha;
|
||||||
@@ -265,8 +261,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
useDatabases.getState().sampleDataResourceTokenCollection &&
|
useDatabases.getState().sampleDataResourceTokenCollection &&
|
||||||
!isEmulator;
|
!isEmulator;
|
||||||
|
|
||||||
const shouldShowMongoGuidRepresentationOption = userContext.apiType === "Mongo";
|
|
||||||
|
|
||||||
const handlerOnSubmit = async () => {
|
const handlerOnSubmit = async () => {
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
@@ -418,10 +412,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldShowMongoGuidRepresentationOption) {
|
|
||||||
LocalStorageUtility.setEntryString(StorageKey.MongoGuidRepresentation, mongoGuidRepresentation);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
logConsoleInfo(
|
logConsoleInfo(
|
||||||
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
||||||
@@ -443,14 +433,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldShowMongoGuidRepresentationOption) {
|
|
||||||
logConsoleInfo(
|
|
||||||
`Updated Mongo Guid Representation to ${LocalStorageUtility.getEntryString(
|
|
||||||
StorageKey.MongoGuidRepresentation,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshExplorer && (await explorer.refreshExplorer());
|
refreshExplorer && (await explorer.refreshExplorer());
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
@@ -495,13 +477,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
|
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const mongoGuidRepresentationDropdownOptions: IDropdownOption[] = [
|
|
||||||
{ key: Constants.MongoGuidRepresentation.CSharpLegacy, text: Constants.MongoGuidRepresentation.CSharpLegacy },
|
|
||||||
{ key: Constants.MongoGuidRepresentation.JavaLegacy, text: Constants.MongoGuidRepresentation.JavaLegacy },
|
|
||||||
{ key: Constants.MongoGuidRepresentation.PythonLegacy, text: Constants.MongoGuidRepresentation.PythonLegacy },
|
|
||||||
{ key: Constants.MongoGuidRepresentation.Standard, text: Constants.MongoGuidRepresentation.Standard },
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleOnPriorityLevelOptionChange = (
|
const handleOnPriorityLevelOptionChange = (
|
||||||
ev: React.FormEvent<HTMLInputElement>,
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
option: IChoiceGroupOption,
|
option: IChoiceGroupOption,
|
||||||
@@ -584,13 +559,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
setRefreshExplorer(false);
|
setRefreshExplorer(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnMongoGuidRepresentationOptionChange = (
|
|
||||||
ev: React.FormEvent<HTMLInputElement>,
|
|
||||||
option: IDropdownOption,
|
|
||||||
): void => {
|
|
||||||
setMongoGuidRepresentation(option.key as Constants.MongoGuidRepresentation);
|
|
||||||
};
|
|
||||||
|
|
||||||
const choiceButtonStyles = {
|
const choiceButtonStyles = {
|
||||||
root: {
|
root: {
|
||||||
clear: "both",
|
clear: "both",
|
||||||
@@ -1097,15 +1065,15 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
<div className={styles.settingsSectionContainer}>
|
<div className={styles.settingsSectionContainer}>
|
||||||
<div className={styles.settingsSectionDescription}>
|
<div className={styles.settingsSectionDescription}>
|
||||||
This is a sample database and collection with synthetic product data you can use to explore using
|
This is a sample database and collection with synthetic product data you can use to explore using
|
||||||
NoSQL queries. This will appear as another database in the Data Explorer UI, and is created by,
|
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and
|
||||||
and maintained by Microsoft at no cost to you.
|
is created by, and maintained by Microsoft at no cost to you.
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
styles={{
|
styles={{
|
||||||
label: { padding: 0 },
|
label: { padding: 0 },
|
||||||
}}
|
}}
|
||||||
className="padding"
|
className="padding"
|
||||||
ariaLabel="Enable sample db for query exploration"
|
ariaLabel="Enable sample db for Query Advisor"
|
||||||
checked={copilotSampleDBEnabled}
|
checked={copilotSampleDBEnabled}
|
||||||
onChange={handleSampleDatabaseChange}
|
onChange={handleSampleDatabaseChange}
|
||||||
label="Enable sample database"
|
label="Enable sample database"
|
||||||
@@ -1114,27 +1082,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)}
|
)}
|
||||||
{shouldShowMongoGuidRepresentationOption && (
|
|
||||||
<AccordionItem value="14">
|
|
||||||
<AccordionHeader>
|
|
||||||
<div className={styles.header}>Guid Representation</div>
|
|
||||||
</AccordionHeader>
|
|
||||||
<AccordionPanel>
|
|
||||||
<div className={styles.settingsSectionContainer}>
|
|
||||||
<div className={styles.settingsSectionDescription}>
|
|
||||||
GuidRepresentation in MongoDB refers to how Globally Unique Identifiers (GUIDs) are serialized and
|
|
||||||
deserialized when stored in BSON documents. This will apply to all document operations.
|
|
||||||
</div>
|
|
||||||
<Dropdown
|
|
||||||
aria-labelledby="mongoGuidRepresentation"
|
|
||||||
selectedKey={mongoGuidRepresentation}
|
|
||||||
options={mongoGuidRepresentationDropdownOptions}
|
|
||||||
onChange={handleOnMongoGuidRepresentationOptionChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionPanel>
|
|
||||||
</AccordionItem>
|
|
||||||
)}
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||||
import QueryError from "Common/QueryError";
|
import QueryError from "Common/QueryError";
|
||||||
import * as DataModels from "Contracts/DataModels";
|
|
||||||
import { QueryResults } from "Contracts/ViewModels";
|
import { QueryResults } from "Contracts/ViewModels";
|
||||||
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||||
import { guid } from "Explorer/Tables/Utilities";
|
import { guid } from "Explorer/Tables/Utilities";
|
||||||
import { QueryCopilotState } from "hooks/useQueryCopilot";
|
import { QueryCopilotState } from "hooks/useQueryCopilot";
|
||||||
|
|
||||||
import React, { createContext, useContext, useState } from "react";
|
import React, { createContext, useContext, useState } from "react";
|
||||||
import { create } from "zustand";
|
import create from "zustand";
|
||||||
|
|
||||||
const context = createContext(null);
|
const context = createContext(null);
|
||||||
const useCopilotStore = (): Partial<QueryCopilotState> => useContext(context);
|
const useCopilotStore = (): Partial<QueryCopilotState> => useContext(context);
|
||||||
|
|
||||||
@@ -26,12 +24,12 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
isGeneratingQuery: false,
|
isGeneratingQuery: false,
|
||||||
isGeneratingExplanation: false,
|
isGeneratingExplanation: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
dislikeQuery: undefined as boolean,
|
dislikeQuery: undefined,
|
||||||
showCallout: false,
|
showCallout: false,
|
||||||
showSamplePrompts: false,
|
showSamplePrompts: false,
|
||||||
queryIterator: undefined as MinimalQueryIterator,
|
queryIterator: undefined,
|
||||||
queryResults: undefined as QueryResults,
|
queryResults: undefined,
|
||||||
errors: [] as QueryError[],
|
errors: [],
|
||||||
isSamplePromptsOpen: false,
|
isSamplePromptsOpen: false,
|
||||||
showPromptTeachingBubble: true,
|
showPromptTeachingBubble: true,
|
||||||
showDeletePopup: false,
|
showDeletePopup: false,
|
||||||
@@ -43,7 +41,7 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
wasCopilotUsed: false,
|
wasCopilotUsed: false,
|
||||||
showWelcomeSidebar: true,
|
showWelcomeSidebar: true,
|
||||||
showCopilotSidebar: false,
|
showCopilotSidebar: false,
|
||||||
chatMessages: [] as CopilotMessage[],
|
chatMessages: [],
|
||||||
shouldIncludeInMessages: true,
|
shouldIncludeInMessages: true,
|
||||||
showExplanationBubble: false,
|
showExplanationBubble: false,
|
||||||
isAllocatingContainer: false,
|
isAllocatingContainer: false,
|
||||||
@@ -88,7 +86,7 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
},
|
},
|
||||||
|
|
||||||
resetQueryCopilotStates: () => {
|
resetQueryCopilotStates: () => {
|
||||||
set((state: QueryCopilotState) => ({
|
set((state) => ({
|
||||||
...state,
|
...state,
|
||||||
generatedQuery: "",
|
generatedQuery: "",
|
||||||
likeQuery: false,
|
likeQuery: false,
|
||||||
@@ -101,11 +99,11 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
isGeneratingQuery: false,
|
isGeneratingQuery: false,
|
||||||
isGeneratingExplanation: false,
|
isGeneratingExplanation: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
dislikeQuery: undefined as boolean,
|
dislikeQuery: undefined,
|
||||||
showCallout: false,
|
showCallout: false,
|
||||||
showSamplePrompts: false,
|
showSamplePrompts: false,
|
||||||
queryIterator: undefined as MinimalQueryIterator,
|
queryIterator: undefined,
|
||||||
queryResults: undefined as QueryResults,
|
queryResults: undefined,
|
||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
isSamplePromptsOpen: false,
|
isSamplePromptsOpen: false,
|
||||||
showPromptTeachingBubble: true,
|
showPromptTeachingBubble: true,
|
||||||
@@ -117,19 +115,19 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
generatedQueryComments: "",
|
generatedQueryComments: "",
|
||||||
wasCopilotUsed: false,
|
wasCopilotUsed: false,
|
||||||
showCopilotSidebar: false,
|
showCopilotSidebar: false,
|
||||||
chatMessages: [] as CopilotMessage[],
|
chatMessages: [],
|
||||||
shouldIncludeInMessages: true,
|
shouldIncludeInMessages: true,
|
||||||
showExplanationBubble: false,
|
showExplanationBubble: false,
|
||||||
notebookServerInfo: {
|
notebookServerInfo: {
|
||||||
notebookServerEndpoint: undefined,
|
notebookServerEndpoint: undefined,
|
||||||
authToken: undefined,
|
authToken: undefined,
|
||||||
forwardingId: undefined,
|
forwardingId: undefined,
|
||||||
} as DataModels.NotebookWorkspaceConnectionInfo,
|
},
|
||||||
containerStatus: {
|
containerStatus: {
|
||||||
status: undefined,
|
status: undefined,
|
||||||
durationLeftInMinutes: undefined,
|
durationLeftInMinutes: undefined,
|
||||||
phoenixServerInfo: undefined,
|
phoenixServerInfo: undefined,
|
||||||
} as DataModels.ContainerInfo,
|
},
|
||||||
isAllocatingContainer: false,
|
isAllocatingContainer: false,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
@@ -139,4 +137,3 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
};
|
};
|
||||||
|
|
||||||
export { CopilotProvider, useCopilotStore };
|
export { CopilotProvider, useCopilotStore };
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { Stack } from "@fluentui/react";
|
import { Stack } from "@fluentui/react";
|
||||||
|
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
||||||
|
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
|
||||||
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||||
@@ -11,6 +13,7 @@ import { QueryCopilotResults } from "Explorer/QueryCopilot/Shared/QueryCopilotRe
|
|||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
|
import { ReactTabKind, TabsState, useTabs } from "hooks/useTabs";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import SplitterLayout from "react-splitter-layout";
|
import SplitterLayout from "react-splitter-layout";
|
||||||
import QueryCommandIcon from "../../../images/CopilotCommand.svg";
|
import QueryCommandIcon from "../../../images/CopilotCommand.svg";
|
||||||
@@ -23,8 +26,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
const [copilotActive, setCopilotActive] = useState<boolean>(() =>
|
const [copilotActive, setCopilotActive] = useState<boolean>(() =>
|
||||||
readCopilotToggleStatus(userContext.databaseAccount),
|
readCopilotToggleStatus(userContext.databaseAccount),
|
||||||
);
|
);
|
||||||
//TODO: Uncomment this useState when query copilot is reinstated in DE
|
const [tabActive, setTabActive] = useState<boolean>(true);
|
||||||
// const [tabActive, setTabActive] = useState<boolean>(true);
|
|
||||||
|
|
||||||
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
||||||
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
||||||
@@ -68,18 +70,17 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
||||||
}, [query, selectedQuery, copilotActive]);
|
}, [query, selectedQuery, copilotActive]);
|
||||||
|
|
||||||
//TODO: Uncomment this effect when query copilot is reinstated in DE
|
React.useEffect(() => {
|
||||||
// React.useEffect(() => {
|
return () => {
|
||||||
// return () => {
|
useTabs.subscribe((state: TabsState) => {
|
||||||
// useTabs.subscribe((state: TabsState) => {
|
if (state.activeReactTab === ReactTabKind.QueryCopilot) {
|
||||||
// if (state.activeReactTab === ReactTabKind.QueryCopilot) {
|
setTabActive(true);
|
||||||
// setTabActive(true);
|
} else {
|
||||||
// } else {
|
setTabActive(false);
|
||||||
// setTabActive(false);
|
}
|
||||||
// }
|
});
|
||||||
// });
|
};
|
||||||
// };
|
}, []);
|
||||||
// }, []);
|
|
||||||
|
|
||||||
const toggleCopilot = (toggle: boolean) => {
|
const toggleCopilot = (toggle: boolean) => {
|
||||||
setCopilotActive(toggle);
|
setCopilotActive(toggle);
|
||||||
@@ -89,7 +90,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
return (
|
return (
|
||||||
<Stack className="tab-pane" style={{ width: "100%" }}>
|
<Stack className="tab-pane" style={{ width: "100%" }}>
|
||||||
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
|
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
|
||||||
{/*TODO: Uncomment this section when query copilot is reinstated in DE
|
|
||||||
{tabActive && copilotActive && (
|
{tabActive && copilotActive && (
|
||||||
<QueryCopilotPromptbar
|
<QueryCopilotPromptbar
|
||||||
explorer={explorer}
|
explorer={explorer}
|
||||||
@@ -97,7 +97,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
databaseId={QueryCopilotSampleDatabaseId}
|
databaseId={QueryCopilotSampleDatabaseId}
|
||||||
containerId={QueryCopilotSampleContainerId}
|
containerId={QueryCopilotSampleContainerId}
|
||||||
></QueryCopilotPromptbar>
|
></QueryCopilotPromptbar>
|
||||||
)} */}
|
)}
|
||||||
<Stack className="tabPaneContentContainer">
|
<Stack className="tabPaneContentContainer">
|
||||||
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
|
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Accordion top class
|
* Accordion top class
|
||||||
*/
|
*/
|
||||||
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
import { makeStyles, tokens } from "@fluentui/react-components";
|
||||||
import { DocumentAddRegular, LinkMultipleRegular, OpenRegular } from "@fluentui/react-icons";
|
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
|
||||||
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
||||||
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
||||||
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
||||||
@@ -119,7 +119,7 @@ const FabricHomeScreenButton: React.FC<FabricHomeScreenButtonProps & { className
|
|||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
return (
|
return (
|
||||||
<div role="button" className={`${styles.buttonContainer} ${className}`} onClick={onClick} tabIndex={0}>
|
<div role="button" className={`${styles.buttonContainer} ${className}`} onClick={onClick}>
|
||||||
<div className={styles.buttonUpperPart}>{icon}</div>
|
<div className={styles.buttonUpperPart}>{icon}</div>
|
||||||
<div aria-label={title} className={styles.buttonLowerPart}>
|
<div aria-label={title} className={styles.buttonLowerPart}>
|
||||||
<div>{title}</div>
|
<div>{title}</div>
|
||||||
@@ -147,7 +147,7 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
{
|
{
|
||||||
title: "Sample data",
|
title: "Sample data",
|
||||||
description: "Automatically load sample data in your database",
|
description: "Automatically load sample data in your database",
|
||||||
icon: <img src={CosmosDbBlackIcon} alt={"Azure Cosmos DB icon"} aria-hidden="true" />,
|
icon: <img src={CosmosDbBlackIcon} />,
|
||||||
onClick: () => setOpenSampleDataImportDialog(true),
|
onClick: () => setOpenSampleDataImportDialog(true),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -181,18 +181,16 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
explorer={props.explorer}
|
explorer={props.explorer}
|
||||||
databaseName={userContext.fabricContext?.databaseName}
|
databaseName={userContext.fabricContext?.databaseName}
|
||||||
/>
|
/>
|
||||||
<div className={styles.title} role="heading" aria-label={title} aria-level={1}>
|
<div className={styles.title} role="heading" aria-label={title}>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
{getSplashScreenButtons()}
|
{getSplashScreenButtons()}
|
||||||
{
|
{/* <div className={styles.footer}>
|
||||||
<div className={styles.footer}>
|
Need help?{" "}
|
||||||
Need help?{" "}
|
<Link href="https://aka.ms/cosmosdbfabricdocs" target="_blank">
|
||||||
<Link href="https://learn.microsoft.com/fabric/database/cosmos-db/overview" target="_blank">
|
Learn more <img src={LinkIcon} alt="Learn more" />
|
||||||
Learn more <OpenRegular />
|
</Link>
|
||||||
</Link>
|
</div> */}
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</CosmosFluentProvider>
|
</CosmosFluentProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { ReactTabKind, useTabs } from "hooks/useTabs";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ConnectIcon from "../../../images/Connect_color.svg";
|
import ConnectIcon from "../../../images/Connect_color.svg";
|
||||||
import ContainersIcon from "../../../images/Containers.svg";
|
import ContainersIcon from "../../../images/Containers.svg";
|
||||||
import CosmosDBIcon from "../../../images/CosmosDB-logo.svg";
|
|
||||||
import LinkIcon from "../../../images/Link_blue.svg";
|
import LinkIcon from "../../../images/Link_blue.svg";
|
||||||
import PowerShellIcon from "../../../images/PowerShell.svg";
|
import PowerShellIcon from "../../../images/PowerShell.svg";
|
||||||
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
||||||
@@ -77,39 +76,39 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
{
|
{
|
||||||
dispose: useNotebook.subscribe(
|
dispose: useNotebook.subscribe(
|
||||||
(state) => state.isNotebookEnabled,
|
|
||||||
() => this.setState({}),
|
() => this.setState({}),
|
||||||
|
(state) => state.isNotebookEnabled,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ dispose: useSelectedNode.subscribe(() => this.setState({})) },
|
{ dispose: useSelectedNode.subscribe(() => this.setState({})) },
|
||||||
{
|
{
|
||||||
dispose: useCarousel.subscribe(
|
dispose: useCarousel.subscribe(
|
||||||
|
() => this.setState({}),
|
||||||
(state) => state.showCoachMark,
|
(state) => state.showCoachMark,
|
||||||
() => this.setState({}),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispose: usePostgres.subscribe(
|
dispose: usePostgres.subscribe(
|
||||||
|
() => this.setState({}),
|
||||||
(state) => state.showPostgreTeachingBubble,
|
(state) => state.showPostgreTeachingBubble,
|
||||||
() => this.setState({}),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispose: usePostgres.subscribe(
|
dispose: usePostgres.subscribe(
|
||||||
(state) => state.showResetPasswordBubble,
|
|
||||||
() => this.setState({}),
|
() => this.setState({}),
|
||||||
|
(state) => state.showResetPasswordBubble,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispose: useDatabases.subscribe(
|
dispose: useDatabases.subscribe(
|
||||||
(state) => state.sampleDataResourceTokenCollection,
|
|
||||||
() => this.setState({}),
|
() => this.setState({}),
|
||||||
|
(state) => state.sampleDataResourceTokenCollection,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispose: useQueryCopilot.subscribe(
|
dispose: useQueryCopilot.subscribe(
|
||||||
(state) => state.copilotEnabled,
|
|
||||||
() => this.setState({}),
|
() => this.setState({}),
|
||||||
|
(state) => state.copilotEnabled,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -121,7 +120,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private getSplashScreenButtons = (): JSX.Element => {
|
private getSplashScreenButtons = (): JSX.Element => {
|
||||||
if (userContext.apiType === "SQL") {
|
if (
|
||||||
|
userContext.apiType === "SQL" &&
|
||||||
|
useQueryCopilot.getState().copilotEnabled &&
|
||||||
|
useDatabases.getState().sampleDataResourceTokenCollection
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
className="splashStackContainer"
|
className="splashStackContainer"
|
||||||
@@ -149,18 +152,25 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack className="splashStackRow" horizontal>
|
<Stack className="splashStackRow" horizontal>
|
||||||
<SplashScreenButton
|
{useQueryCopilot.getState().copilotEnabled && (
|
||||||
imgSrc={CosmosDBIcon}
|
<SplashScreenButton
|
||||||
imgSize={35}
|
imgSrc={CopilotIcon}
|
||||||
title={"Azure Cosmos DB Samples Gallery"}
|
title={"Query faster with Query Advisor"}
|
||||||
description={
|
description={
|
||||||
"Discover samples that showcase scalable, intelligent app patterns. Try one now to see how fast you can go from concept to code with Cosmos DB"
|
"Query Advisor is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open("https://azurecosmosdb.github.io/gallery/?tags=example", "_blank");
|
const copilotVersion = userContext.features.copilotVersion;
|
||||||
traceOpen(Action.LearningResourcesClicked, { apiType: userContext.apiType });
|
if (copilotVersion === "v1.0") {
|
||||||
}}
|
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||||
/>
|
} else if (copilotVersion === "v2.0") {
|
||||||
|
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
||||||
|
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
||||||
|
}
|
||||||
|
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<SplashScreenButton
|
<SplashScreenButton
|
||||||
imgSrc={ConnectIcon}
|
imgSrc={ConnectIcon}
|
||||||
title={"Connect"}
|
title={"Connect"}
|
||||||
@@ -202,7 +212,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
sample data, query.
|
sample data, query.
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
)}
|
)}
|
||||||
{/*TODO: convert below to use SplashScreenButton */}
|
|
||||||
{mainItems.map((item) => (
|
{mainItems.map((item) => (
|
||||||
<Stack
|
<Stack
|
||||||
id={`mainButton-${item.id}`}
|
id={`mainButton-${item.id}`}
|
||||||
@@ -468,34 +477,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Re-enable lint rule when query copilot is reinstated in DE
|
|
||||||
/* eslint-disable-next-line no-unused-vars */
|
|
||||||
private getQueryCopilotCard = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{useQueryCopilot.getState().copilotEnabled && (
|
|
||||||
<SplashScreenButton
|
|
||||||
imgSrc={CopilotIcon}
|
|
||||||
title={"Query faster with Query Advisor"}
|
|
||||||
description={
|
|
||||||
"Query Advisor is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
const copilotVersion = userContext.features.copilotVersion;
|
|
||||||
if (copilotVersion === "v1.0") {
|
|
||||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
|
||||||
} else if (copilotVersion === "v2.0") {
|
|
||||||
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
|
||||||
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
|
||||||
}
|
|
||||||
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
||||||
return {
|
return {
|
||||||
iconSrc: CollectionIcon,
|
iconSrc: CollectionIcon,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ interface SplashScreenButtonProps {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
imgSize?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
||||||
@@ -15,7 +14,6 @@ export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
onClick,
|
onClick,
|
||||||
imgSize,
|
|
||||||
}: SplashScreenButtonProps): JSX.Element => {
|
}: SplashScreenButtonProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
@@ -41,7 +39,7 @@ export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img src={imgSrc} alt={title} aria-hidden="true" {...(imgSize ? { height: imgSize, width: imgSize } : {})} />
|
<img src={imgSrc} alt={title} aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<Stack style={{ marginLeft: 16 }}>
|
<Stack style={{ marginLeft: 16 }}>
|
||||||
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { updateDocument } from "../../Common/dataAccess/updateDocument";
|
|||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { getAuthorizationHeader, isDataplaneRbacEnabledForProxyApi } from "../../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
@@ -551,10 +551,6 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
const authorizationHeaderMetadata: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
const authorizationHeaderMetadata: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||||
xhr.setRequestHeader(authorizationHeaderMetadata.header, authorizationHeaderMetadata.token);
|
xhr.setRequestHeader(authorizationHeaderMetadata.header, authorizationHeaderMetadata.token);
|
||||||
|
|
||||||
if (isDataplaneRbacEnabledForProxyApi(userContext)) {
|
|
||||||
xhr.setRequestHeader(Constants.HttpHeaders.entraIdToken, userContext.aadToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FitAddon } from "@xterm/addon-fit";
|
import { FitAddon } from "@xterm/addon-fit";
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import "@xterm/xterm/css/xterm.css";
|
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import "xterm/css/xterm.css";
|
||||||
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
||||||
import { TerminalKind } from "../../../Contracts/ViewModels";
|
import { TerminalKind } from "../../../Contracts/ViewModels";
|
||||||
import { startCloudShellTerminal } from "./CloudShellTerminalCore";
|
import { startCloudShellTerminal } from "./CloudShellTerminalCore";
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { formatErrorMessage, formatInfoMessage, formatWarningMessage } from "./U
|
|||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const DEFAULT_CLOUDSHELL_REGION = "westus";
|
const DEFAULT_CLOUDSHELL_REGION = "westus";
|
||||||
const DEFAULT_FAIRFAX_CLOUDSHELL_REGION = "usgovvirginia";
|
|
||||||
const POLLING_INTERVAL_MS = 2000;
|
const POLLING_INTERVAL_MS = 2000;
|
||||||
const MAX_RETRY_COUNT = 10;
|
const MAX_RETRY_COUNT = 10;
|
||||||
const MAX_PING_COUNT = 120 * 60; // 120 minutes (60 seconds/minute)
|
const MAX_PING_COUNT = 120 * 60; // 120 minutes (60 seconds/minute)
|
||||||
@@ -154,9 +153,7 @@ export const ensureCloudShellProviderRegistered = async (): Promise<void> => {
|
|||||||
* Determines the appropriate CloudShell region
|
* Determines the appropriate CloudShell region
|
||||||
*/
|
*/
|
||||||
export const determineCloudShellRegion = (): string => {
|
export const determineCloudShellRegion = (): string => {
|
||||||
const defaultRegion =
|
return getNormalizedRegion(userContext.databaseAccount?.location, DEFAULT_CLOUDSHELL_REGION);
|
||||||
userContext.portalEnv === "fairfax" ? DEFAULT_FAIRFAX_CLOUDSHELL_REGION : DEFAULT_CLOUDSHELL_REGION;
|
|
||||||
return getNormalizedRegion(userContext.databaseAccount?.location, defaultRegion);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,17 +14,12 @@ export const DISABLE_HISTORY = `set +o history`;
|
|||||||
* Used when shell initialization or connection fails.
|
* Used when shell initialization or connection fails.
|
||||||
*/
|
*/
|
||||||
export const EXIT_COMMAND = ` printf "\\033[1;31mSession ended. Please close this tab and initiate a new shell session if needed.\\033[0m\\n" && disown -a && exit`;
|
export const EXIT_COMMAND = ` printf "\\033[1;31mSession ended. Please close this tab and initiate a new shell session if needed.\\033[0m\\n" && disown -a && exit`;
|
||||||
/**
|
|
||||||
* Command that displays error message with MongoDB networking guidance and exits the shell session.
|
|
||||||
* Used when MongoDB shell connection fails due to networking issues.
|
|
||||||
*/
|
|
||||||
export const EXIT_COMMAND_MONGO = ` printf "\\033[1;31mSession ended. Please close this tab and initiate a new shell session if needed.\\033[0m\\n" && printf "\\033[1;36mPlease use the 'Add Azure Cloud Shell IPs' button in the Networking blade to allow Cloud Shell access, if not already configured.\\033[0m\\n" && disown -a && exit`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This command runs mongosh in no-database and quiet mode,
|
* This command runs mongosh in no-database and quiet mode,
|
||||||
* and evaluates the `disableTelemetry()` function to turn off telemetry collection.
|
* and evaluates the `disableTelemetry()` function to turn off telemetry collection.
|
||||||
*/
|
*/
|
||||||
export const DISABLE_TELEMETRY_COMMAND = `mongosh --nodb --quiet --eval 'disableTelemetry()'`;
|
export const DISABLE_TELEMETRY_COMMAND = `mongosh --nodb --quiet --eval "disableTelemetry()"`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class that defines the interface for shell-specific handlers
|
* Abstract class that defines the interface for shell-specific handlers
|
||||||
@@ -45,14 +40,6 @@ export abstract class AbstractShellHandler {
|
|||||||
abstract getTerminalSuppressedData(): string[];
|
abstract getTerminalSuppressedData(): string[];
|
||||||
updateTerminalData?(data: string): string;
|
updateTerminalData?(data: string): string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the exit command to use when connection fails.
|
|
||||||
* Can be overridden by subclasses to provide custom exit commands.
|
|
||||||
*/
|
|
||||||
protected getExitCommand(): string {
|
|
||||||
return EXIT_COMMAND;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the complete initialization command sequence for the shell.
|
* Constructs the complete initialization command sequence for the shell.
|
||||||
*
|
*
|
||||||
@@ -77,7 +64,7 @@ export abstract class AbstractShellHandler {
|
|||||||
START_MARKER,
|
START_MARKER,
|
||||||
DISABLE_HISTORY,
|
DISABLE_HISTORY,
|
||||||
...setupCommands,
|
...setupCommands,
|
||||||
`{ ${connectionCommand}; } || true;${this.getExitCommand()}`,
|
`{ ${connectionCommand}; } || true;${EXIT_COMMAND}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
return allCommands.join("\n").concat("\n");
|
return allCommands.join("\n").concat("\n");
|
||||||
@@ -97,7 +84,7 @@ export abstract class AbstractShellHandler {
|
|||||||
* is not already present in the environment.
|
* is not already present in the environment.
|
||||||
*/
|
*/
|
||||||
protected mongoShellSetupCommands(): string[] {
|
protected mongoShellSetupCommands(): string[] {
|
||||||
const PACKAGE_VERSION: string = "2.5.6";
|
const PACKAGE_VERSION: string = "2.5.5";
|
||||||
return [
|
return [
|
||||||
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
|
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
|
||||||
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||||
|
|||||||
@@ -18,12 +18,6 @@ interface DatabaseAccount {
|
|||||||
|
|
||||||
interface UserContextType {
|
interface UserContextType {
|
||||||
databaseAccount: DatabaseAccount;
|
databaseAccount: DatabaseAccount;
|
||||||
features: {
|
|
||||||
enableAadDataPlane: boolean;
|
|
||||||
};
|
|
||||||
apiType: string;
|
|
||||||
dataPlaneRbacEnabled: boolean;
|
|
||||||
aadToken?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
@@ -35,13 +29,10 @@ jest.mock("../../../../UserContext", () => ({
|
|||||||
mongoEndpoint: "https://test-mongo.documents.azure.com:443/",
|
mongoEndpoint: "https://test-mongo.documents.azure.com:443/",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
features: { enableAadDataPlane: false },
|
|
||||||
apiType: "Mongo",
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../Utils/CommonUtils", () => ({
|
jest.mock("../Utils/CommonUtils", () => ({
|
||||||
...jest.requireActual("../Utils/CommonUtils"),
|
|
||||||
getHostFromUrl: jest.fn().mockReturnValue("test-mongo.documents.azure.com"),
|
getHostFromUrl: jest.fn().mockReturnValue("test-mongo.documents.azure.com"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -78,7 +69,7 @@ describe("MongoShellHandler", () => {
|
|||||||
|
|
||||||
expect(Array.isArray(commands)).toBe(true);
|
expect(Array.isArray(commands)).toBe(true);
|
||||||
expect(commands.length).toBe(7);
|
expect(commands.length).toBe(7);
|
||||||
expect(commands[1]).toContain("mongosh-2.5.6-linux-x64.tgz");
|
expect(commands[1]).toContain("mongosh-2.5.5-linux-x64.tgz");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,12 +87,11 @@ describe("MongoShellHandler", () => {
|
|||||||
kind: "test-kind",
|
kind: "test-kind",
|
||||||
properties: { mongoEndpoint: "https://test-mongo.documents.azure.com:443/" },
|
properties: { mongoEndpoint: "https://test-mongo.documents.azure.com:443/" },
|
||||||
};
|
};
|
||||||
(userContext as UserContextType).dataPlaneRbacEnabled = false;
|
|
||||||
|
|
||||||
const command = mongoShellHandler.getConnectionCommand();
|
const command = mongoShellHandler.getConnectionCommand();
|
||||||
|
|
||||||
expect(command).toBe(
|
expect(command).toBe(
|
||||||
"mongosh --nodb --quiet --eval 'disableTelemetry()'; mongosh mongodb://test-mongo.documents.azure.com:10255?appName=CosmosExplorerTerminal --username test-account --password test-key --tls --tlsAllowInvalidCertificates",
|
'mongosh --nodb --quiet --eval "disableTelemetry()" && mongosh mongodb://test-mongo.documents.azure.com:10255?appName=CosmosExplorerTerminal --username test-account --password test-key --tls --tlsAllowInvalidCertificates',
|
||||||
);
|
);
|
||||||
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-mongo.documents.azure.com:443/");
|
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-mongo.documents.azure.com:443/");
|
||||||
|
|
||||||
@@ -124,55 +114,17 @@ describe("MongoShellHandler", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const command = mongoShellHandler.getConnectionCommand();
|
const command = mongoShellHandler.getConnectionCommand();
|
||||||
|
|
||||||
expect(command).toBe("echo 'Database name not found.'");
|
expect(command).toBe("echo 'Database name not found.'");
|
||||||
|
|
||||||
// Restore original
|
// Restore original
|
||||||
(userContext as UserContextType).databaseAccount = originalDatabaseAccount;
|
(userContext as UserContextType).databaseAccount = originalDatabaseAccount;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return echo if endpoint is missing", () => {
|
|
||||||
const testKey = "test-key";
|
|
||||||
(userContext as UserContextType).databaseAccount = {
|
|
||||||
id: "test-id",
|
|
||||||
name: "", // Empty name to simulate missing name
|
|
||||||
location: "test-location",
|
|
||||||
type: "test-type",
|
|
||||||
kind: "test-kind",
|
|
||||||
properties: { mongoEndpoint: "" },
|
|
||||||
};
|
|
||||||
const mongoShellHandler = new MongoShellHandler(testKey);
|
|
||||||
const command = mongoShellHandler.getConnectionCommand();
|
|
||||||
expect(command).toBe("echo 'MongoDB endpoint not found.'");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should use _getAadConnectionCommand when _isEntraIdEnabled is true", () => {
|
|
||||||
const testKey = "aad-key";
|
|
||||||
(userContext as UserContextType).databaseAccount = {
|
|
||||||
id: "test-id",
|
|
||||||
name: "test-account",
|
|
||||||
location: "test-location",
|
|
||||||
type: "test-type",
|
|
||||||
kind: "test-kind",
|
|
||||||
properties: { mongoEndpoint: "https://test-mongo.documents.azure.com:443/" },
|
|
||||||
};
|
|
||||||
(userContext as UserContextType).dataPlaneRbacEnabled = true;
|
|
||||||
|
|
||||||
const mongoShellHandler = new MongoShellHandler(testKey);
|
|
||||||
|
|
||||||
const command = mongoShellHandler.getConnectionCommand();
|
|
||||||
expect(command).toContain(
|
|
||||||
"mongosh 'mongodb://test-account:aad-key@test-account.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&authMechanism=PLAIN&retryWrites=false' --tls --tlsAllowInvalidCertificates",
|
|
||||||
);
|
|
||||||
expect(command.startsWith("mongosh --nodb")).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getTerminalSuppressedData", () => {
|
describe("getTerminalSuppressedData", () => {
|
||||||
it("should return the correct warning message", () => {
|
it("should return the correct warning message", () => {
|
||||||
expect(mongoShellHandler.getTerminalSuppressedData()).toEqual([
|
expect(mongoShellHandler.getTerminalSuppressedData()).toEqual(["Warning: Non-Genuine MongoDB Detected"]);
|
||||||
"Warning: Non-Genuine MongoDB Detected",
|
|
||||||
"Telemetry is now disabled.",
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,29 +1,16 @@
|
|||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import { isDataplaneRbacEnabledForProxyApi } from "../../../../Utils/AuthorizationUtils";
|
import { getHostFromUrl } from "../Utils/CommonUtils";
|
||||||
import { filterAndCleanTerminalOutput, getHostFromUrl, getMongoShellRemoveInfoText } from "../Utils/CommonUtils";
|
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND } from "./AbstractShellHandler";
|
||||||
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND, EXIT_COMMAND_MONGO } from "./AbstractShellHandler";
|
|
||||||
|
|
||||||
export class MongoShellHandler extends AbstractShellHandler {
|
export class MongoShellHandler extends AbstractShellHandler {
|
||||||
private _key: string;
|
private _key: string;
|
||||||
private _endpoint: string | undefined;
|
private _endpoint: string | undefined;
|
||||||
private _removeInfoText: string[] = getMongoShellRemoveInfoText();
|
|
||||||
private _isEntraIdEnabled: boolean = isDataplaneRbacEnabledForProxyApi(userContext);
|
|
||||||
constructor(private key: string) {
|
constructor(private key: string) {
|
||||||
super();
|
super();
|
||||||
this._key = key;
|
this._key = key;
|
||||||
this._endpoint = userContext?.databaseAccount?.properties?.mongoEndpoint;
|
this._endpoint = userContext?.databaseAccount?.properties?.mongoEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getKeyConnectionCommand(dbName: string): string {
|
|
||||||
return `mongosh mongodb://${getHostFromUrl(this._endpoint)}:10255?appName=${
|
|
||||||
this.APP_NAME
|
|
||||||
} --username ${dbName} --password ${this._key} --tls --tlsAllowInvalidCertificates`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getAadConnectionCommand(dbName: string): string {
|
|
||||||
return `mongosh 'mongodb://${dbName}:${this._key}@${dbName}.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&authMechanism=PLAIN&retryWrites=false' --tls --tlsAllowInvalidCertificates`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getShellName(): string {
|
public getShellName(): string {
|
||||||
return "MongoDB";
|
return "MongoDB";
|
||||||
}
|
}
|
||||||
@@ -41,22 +28,22 @@ export class MongoShellHandler extends AbstractShellHandler {
|
|||||||
if (!dbName) {
|
if (!dbName) {
|
||||||
return "echo 'Database name not found.'";
|
return "echo 'Database name not found.'";
|
||||||
}
|
}
|
||||||
const connectionCommand = this._isEntraIdEnabled
|
return (
|
||||||
? this._getAadConnectionCommand(dbName)
|
DISABLE_TELEMETRY_COMMAND +
|
||||||
: this._getKeyConnectionCommand(dbName);
|
" && " +
|
||||||
const fullCommand = `${DISABLE_TELEMETRY_COMMAND}; ${connectionCommand}`;
|
"mongosh mongodb://" +
|
||||||
return fullCommand;
|
getHostFromUrl(this._endpoint) +
|
||||||
|
":10255?appName=" +
|
||||||
|
this.APP_NAME +
|
||||||
|
" --username " +
|
||||||
|
dbName +
|
||||||
|
" --password " +
|
||||||
|
this._key +
|
||||||
|
" --tls --tlsAllowInvalidCertificates"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTerminalSuppressedData(): string[] {
|
public getTerminalSuppressedData(): string[] {
|
||||||
return ["Warning: Non-Genuine MongoDB Detected", "Telemetry is now disabled."];
|
return ["Warning: Non-Genuine MongoDB Detected"];
|
||||||
}
|
|
||||||
|
|
||||||
protected getExitCommand(): string {
|
|
||||||
return EXIT_COMMAND_MONGO;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTerminalData(data: string): string {
|
|
||||||
return filterAndCleanTerminalOutput(data, this._removeInfoText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,12 @@ import { PostgresShellHandler } from "./PostgresShellHandler";
|
|||||||
import { getHandler, getKey } from "./ShellTypeFactory";
|
import { getHandler, getKey } from "./ShellTypeFactory";
|
||||||
import { VCoreMongoShellHandler } from "./VCoreMongoShellHandler";
|
import { VCoreMongoShellHandler } from "./VCoreMongoShellHandler";
|
||||||
|
|
||||||
interface UserContextType {
|
|
||||||
databaseAccount: { name: string };
|
|
||||||
subscriptionId: string;
|
|
||||||
resourceGroup: string;
|
|
||||||
features: { enableAadDataPlane: boolean };
|
|
||||||
dataPlaneRbacEnabled: boolean;
|
|
||||||
aadToken?: string;
|
|
||||||
apiType?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
jest.mock("../../../../UserContext", () => ({
|
jest.mock("../../../../UserContext", () => ({
|
||||||
userContext: {
|
userContext: {
|
||||||
databaseAccount: { name: "testDbName" },
|
databaseAccount: { name: "testDbName" },
|
||||||
subscriptionId: "testSubId",
|
subscriptionId: "testSubId",
|
||||||
resourceGroup: "testResourceGroup",
|
resourceGroup: "testResourceGroup",
|
||||||
features: { enableAadDataPlane: false },
|
|
||||||
dataPlaneRbacEnabled: false,
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -121,33 +109,5 @@ describe("ShellTypeHandlerFactory", () => {
|
|||||||
expect(key).toBe(mockKey);
|
expect(key).toBe(mockKey);
|
||||||
expect(listKeys).toHaveBeenCalledWith("testSubId", "testResourceGroup", "testDbName");
|
expect(listKeys).toHaveBeenCalledWith("testSubId", "testResourceGroup", "testDbName");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return MongoShellHandler with primaryMasterKey for TerminalKind.Mongo when RBAC is disabled", async () => {
|
|
||||||
(listKeys as jest.Mock).mockResolvedValue({ primaryMasterKey: "primaryKey123" });
|
|
||||||
(userContext as UserContextType).features.enableAadDataPlane = false;
|
|
||||||
(userContext as UserContextType).dataPlaneRbacEnabled = false;
|
|
||||||
const handler = await getHandler(TerminalKind.Mongo);
|
|
||||||
expect(handler).toBeInstanceOf(MongoShellHandler);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
expect(handler.key).toBe("primaryKey123");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return MongoShellHandler with aadToken for TerminalKind.Mongo when RBAC is enabled", async () => {
|
|
||||||
(userContext as UserContextType).aadToken = "aadToken123";
|
|
||||||
(userContext as UserContextType).features.enableAadDataPlane = true;
|
|
||||||
(userContext as UserContextType).dataPlaneRbacEnabled = true;
|
|
||||||
(userContext as UserContextType).apiType = "Mongo";
|
|
||||||
const handler = await getHandler(TerminalKind.Mongo);
|
|
||||||
expect(handler).toBeInstanceOf(MongoShellHandler);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
expect(handler.key).toBe("aadToken123");
|
|
||||||
});
|
|
||||||
it("should throw error for unsupported shell type", async () => {
|
|
||||||
await expect(getHandler("UnknownShell" as unknown as TerminalKind)).rejects.toThrow(
|
|
||||||
"Unsupported shell type: UnknownShell",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { TerminalKind } from "../../../../Contracts/ViewModels";
|
import { TerminalKind } from "../../../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import { listKeys } from "../../../../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { listKeys } from "../../../../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
import { isDataplaneRbacEnabledForProxyApi } from "../../../../Utils/AuthorizationUtils";
|
|
||||||
import { AbstractShellHandler } from "./AbstractShellHandler";
|
import { AbstractShellHandler } from "./AbstractShellHandler";
|
||||||
import { CassandraShellHandler } from "./CassandraShellHandler";
|
import { CassandraShellHandler } from "./CassandraShellHandler";
|
||||||
import { MongoShellHandler } from "./MongoShellHandler";
|
import { MongoShellHandler } from "./MongoShellHandler";
|
||||||
@@ -31,9 +30,6 @@ export async function getKey(): Promise<string> {
|
|||||||
if (!dbName) {
|
if (!dbName) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (isDataplaneRbacEnabledForProxyApi(userContext)) {
|
|
||||||
return userContext.aadToken || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = await listKeys(userContext.subscriptionId, userContext.resourceGroup, dbName);
|
const keys = await listKeys(userContext.subscriptionId, userContext.resourceGroup, dbName);
|
||||||
return keys?.primaryMasterKey || "";
|
return keys?.primaryMasterKey || "";
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ describe("VCoreMongoShellHandler", () => {
|
|||||||
|
|
||||||
expect(Array.isArray(commands)).toBe(true);
|
expect(Array.isArray(commands)).toBe(true);
|
||||||
expect(commands.length).toBe(7);
|
expect(commands.length).toBe(7);
|
||||||
expect(commands[1]).toContain("mongosh-2.5.6-linux-x64.tgz");
|
expect(commands[1]).toContain("mongosh-2.5.5-linux-x64.tgz");
|
||||||
expect(commands[0]).toContain("mongosh not found");
|
expect(commands[0]).toContain("mongosh not found");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import { filterAndCleanTerminalOutput, getMongoShellRemoveInfoText } from "../Utils/CommonUtils";
|
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND } from "./AbstractShellHandler";
|
||||||
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND, EXIT_COMMAND_MONGO } from "./AbstractShellHandler";
|
|
||||||
|
|
||||||
export class VCoreMongoShellHandler extends AbstractShellHandler {
|
export class VCoreMongoShellHandler extends AbstractShellHandler {
|
||||||
private _endpoint: string | undefined;
|
private _endpoint: string | undefined;
|
||||||
private _removeInfoText: string[] = getMongoShellRemoveInfoText();
|
private _textFilterRules: string[] = [
|
||||||
|
"For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/",
|
||||||
|
"disableTelemetry() command",
|
||||||
|
"https://www.mongodb.com/legal/privacy-policy",
|
||||||
|
];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -35,14 +38,12 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
|
|||||||
return ["Warning: Non-Genuine MongoDB Detected", "Telemetry is now disabled."];
|
return ["Warning: Non-Genuine MongoDB Detected", "Telemetry is now disabled."];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
updateTerminalData(content: string): string {
|
||||||
* Override getExitCommand to include MongoDB networking guidance
|
const updatedContent = content
|
||||||
*/
|
.split("\n")
|
||||||
protected getExitCommand(): string {
|
.filter((line) => !this._textFilterRules.some((part) => line.includes(part)))
|
||||||
return EXIT_COMMAND_MONGO;
|
.filter((line, idx, arr) => (arr.length > 3 && idx <= arr.length - 3 ? !["", "\r"].includes(line) : true)) // Filter out empty lines and carriage returns, but keep the last 3 lines if they exist
|
||||||
}
|
.join("\n");
|
||||||
|
return updatedContent;
|
||||||
updateTerminalData(data: string): string {
|
|
||||||
return filterAndCleanTerminalOutput(data, this._removeInfoText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,18 +92,6 @@ export class AttachAddon implements ITerminalAddon {
|
|||||||
* @param {Terminal} terminal - The XTerm terminal instance
|
* @param {Terminal} terminal - The XTerm terminal instance
|
||||||
*/
|
*/
|
||||||
public addMessageListener(terminal: Terminal): void {
|
public addMessageListener(terminal: Terminal): void {
|
||||||
let messageBuffer = "";
|
|
||||||
let bufferTimeout: NodeJS.Timeout | null = null;
|
|
||||||
const BUFFER_TIMEOUT = 50; // ms - short timeout for prompt detection
|
|
||||||
|
|
||||||
const processBuffer = () => {
|
|
||||||
if (messageBuffer.length > 0) {
|
|
||||||
this.handleCompleteTerminalData(terminal, messageBuffer);
|
|
||||||
messageBuffer = "";
|
|
||||||
}
|
|
||||||
bufferTimeout = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
this._disposables.push(
|
this._disposables.push(
|
||||||
addSocketListener(this._socket, "message", (ev) => {
|
addSocketListener(this._socket, "message", (ev) => {
|
||||||
let data: ArrayBuffer | string = ev.data;
|
let data: ArrayBuffer | string = ev.data;
|
||||||
@@ -115,136 +103,53 @@ export class AttachAddon implements ITerminalAddon {
|
|||||||
data = enc.decode(ev.data as ArrayBuffer);
|
data = enc.decode(ev.data as ArrayBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle status messages
|
// for example of json object look in TerminalHelper in the socket.onMessage
|
||||||
let processedStatusData = data;
|
if (data.includes(startStatusJson) && data.includes(endStatusJson)) {
|
||||||
|
// process as one line
|
||||||
// Process status messages with delimiters
|
const statusData = data.split(startStatusJson)[1].split(endStatusJson)[0];
|
||||||
// eslint-disable-next-line no-constant-condition
|
data = data.replace(statusData, "");
|
||||||
while (true) {
|
data = data.replace(startStatusJson, "");
|
||||||
const startIndex = processedStatusData.indexOf(startStatusJson);
|
data = data.replace(endStatusJson, "");
|
||||||
if (startIndex === -1) {
|
} else if (data.includes(startStatusJson)) {
|
||||||
break;
|
// check for start
|
||||||
}
|
const partialStatusData = data.split(startStatusJson)[1];
|
||||||
|
this._socketData += partialStatusData;
|
||||||
const afterStart = processedStatusData.substring(startIndex + startStatusJson.length);
|
data = data.replace(partialStatusData, "");
|
||||||
const endIndex = afterStart.indexOf(endStatusJson);
|
data = data.replace(startStatusJson, "");
|
||||||
|
} else if (data.includes(endStatusJson)) {
|
||||||
if (endIndex === -1) {
|
// check for end and process the command
|
||||||
// Incomplete status message
|
const partialStatusData = data.split(endStatusJson)[0];
|
||||||
this._socketData += processedStatusData.substring(startIndex);
|
this._socketData += partialStatusData;
|
||||||
processedStatusData = processedStatusData.substring(0, startIndex);
|
data = data.replace(partialStatusData, "");
|
||||||
break;
|
data = data.replace(endStatusJson, "");
|
||||||
}
|
this._socketData = "";
|
||||||
|
} else if (this._socketData.length > 0) {
|
||||||
// Remove processed status message
|
// check if the line is all data then just concatenate
|
||||||
processedStatusData =
|
this._socketData += data;
|
||||||
processedStatusData.substring(0, startIndex) + afterStart.substring(endIndex + endStatusJson.length);
|
data = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to message buffer
|
if (this._allowTerminalWrite && data.includes(this._startMarker)) {
|
||||||
messageBuffer += processedStatusData;
|
this._allowTerminalWrite = false;
|
||||||
|
terminal.write(`Preparing ${this._shellHandler.getShellName()} environment...\r\n`);
|
||||||
// Clear existing timeout
|
|
||||||
if (bufferTimeout) {
|
|
||||||
clearTimeout(bufferTimeout);
|
|
||||||
bufferTimeout = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this looks like a complete message/command
|
if (this._allowTerminalWrite) {
|
||||||
const isComplete = this.isMessageComplete(messageBuffer, processedStatusData);
|
const updatedData = this._shellHandler?.updateTerminalData(data) ?? data;
|
||||||
|
const suppressedData = this._shellHandler?.getTerminalSuppressedData();
|
||||||
|
|
||||||
if (isComplete) {
|
const shouldNotWrite = suppressedData.filter(Boolean).some((item) => updatedData.includes(item));
|
||||||
// Message marked as complete, processing immediately
|
|
||||||
processBuffer();
|
if (!shouldNotWrite) {
|
||||||
} else {
|
terminal.write(updatedData);
|
||||||
// Set timeout to process buffer after delay
|
}
|
||||||
bufferTimeout = setTimeout(processBuffer, BUFFER_TIMEOUT);
|
}
|
||||||
|
|
||||||
|
if (data.includes(this._shellHandler.getConnectionCommand())) {
|
||||||
|
this._allowTerminalWrite = true;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up timeout on dispose
|
|
||||||
this._disposables.push({
|
|
||||||
dispose: () => {
|
|
||||||
if (bufferTimeout) {
|
|
||||||
clearTimeout(bufferTimeout);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private isMessageComplete(fullBuffer: string, currentChunk: string): boolean {
|
|
||||||
// Immediate completion indicators
|
|
||||||
const immediateCompletionPatterns = [
|
|
||||||
/\n$/, // Ends with newline
|
|
||||||
/\r$/, // Ends with carriage return
|
|
||||||
/\r\n$/, // Ends with CRLF
|
|
||||||
/; \} \|\| true;$/, // Your command pattern
|
|
||||||
/disown -a && exit$/, // Exit commands
|
|
||||||
/printf.*?\\033\[0m\\n"$/, // Your printf pattern
|
|
||||||
];
|
|
||||||
|
|
||||||
// Check current chunk for immediate completion
|
|
||||||
for (const pattern of immediateCompletionPatterns) {
|
|
||||||
if (pattern.test(currentChunk)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ANSI sequence detection - these might be complete prompts
|
|
||||||
const ansiPromptPatterns = [
|
|
||||||
/\[\d+G\[0J.*>\s*\[\d+G$/, // Your specific pattern: [1G[0J...> [26G
|
|
||||||
/\[\d+;\d+H/, // Cursor position sequences
|
|
||||||
/\]\s*\[\d+G$/, // Ends with cursor positioning
|
|
||||||
/>\s*\[\d+G$/, // Prompt followed by cursor position
|
|
||||||
];
|
|
||||||
|
|
||||||
// Check if buffer ends with what looks like a complete prompt
|
|
||||||
for (const pattern of ansiPromptPatterns) {
|
|
||||||
if (pattern.test(fullBuffer)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for MongoDB shell prompts specifically
|
|
||||||
const mongoPromptPatterns = [
|
|
||||||
/globaldb \[primary\] \w+>\s*\[\d+G$/, // MongoDB replica set prompt
|
|
||||||
/>\s*\[\d+G$/, // General prompt with cursor positioning
|
|
||||||
/\w+>\s*$/, // Simple shell prompt
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const pattern of mongoPromptPatterns) {
|
|
||||||
if (pattern.test(fullBuffer)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleCompleteTerminalData(terminal: Terminal, data: string): void {
|
|
||||||
if (this._allowTerminalWrite && data.includes(this._startMarker)) {
|
|
||||||
this._allowTerminalWrite = false;
|
|
||||||
terminal.write(`Preparing ${this._shellHandler.getShellName()} environment...\r\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._allowTerminalWrite) {
|
|
||||||
const updatedData =
|
|
||||||
typeof this._shellHandler?.updateTerminalData === "function"
|
|
||||||
? this._shellHandler.updateTerminalData(data)
|
|
||||||
: data;
|
|
||||||
|
|
||||||
const suppressedData = this._shellHandler?.getTerminalSuppressedData();
|
|
||||||
const shouldNotWrite = suppressedData.filter(Boolean).some((item) => updatedData.includes(item));
|
|
||||||
|
|
||||||
if (!shouldNotWrite) {
|
|
||||||
terminal.write(updatedData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.includes(this._shellHandler.getConnectionCommand())) {
|
|
||||||
this._allowTerminalWrite = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
|
|||||||
@@ -50,34 +50,3 @@ export const getShellNameForDisplay = (terminalKind: TerminalKind): string => {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get MongoDB shell information text that should be removed from terminal output
|
|
||||||
*/
|
|
||||||
export const getMongoShellRemoveInfoText = (): string[] => {
|
|
||||||
return [
|
|
||||||
"For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/",
|
|
||||||
"disableTelemetry() command",
|
|
||||||
"https://www.mongodb.com/legal/privacy-policy",
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const filterAndCleanTerminalOutput = (data: string, removeInfoText: string[]): string => {
|
|
||||||
if (!data || removeInfoText.length === 0) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = data.split("\n");
|
|
||||||
const filteredLines: string[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const line = lines[i];
|
|
||||||
const shouldRemove = removeInfoText.some((text) => line.includes(text));
|
|
||||||
|
|
||||||
if (!shouldRemove) {
|
|
||||||
filteredLines.push(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredLines.join("\n").replace(/((\r\n)|\n|\r){2,}/g, "\r\n");
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ const validCloudShellRegions = new Set([
|
|||||||
"westeurope",
|
"westeurope",
|
||||||
"centralindia",
|
"centralindia",
|
||||||
"southeastasia",
|
"southeastasia",
|
||||||
"usgovvirginia",
|
"westcentralus",
|
||||||
"usgovarizona",
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,6 +39,7 @@ export const getNormalizedRegion = (region: string, defaultCloudshellRegion: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
const regionMap: Record<string, string> = {
|
const regionMap: Record<string, string> = {
|
||||||
|
centralus: "westcentralus",
|
||||||
eastus2: "eastus",
|
eastus2: "eastus",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -146,16 +146,10 @@ describe("Documents tab (Mongo API)", () => {
|
|||||||
updateConfigContext({ platform: Platform.Hosted });
|
updateConfigContext({ platform: Platform.Hosted });
|
||||||
|
|
||||||
const props: IDocumentsTabComponentProps = createMockProps();
|
const props: IDocumentsTabComponentProps = createMockProps();
|
||||||
|
|
||||||
wrapper = mount(<DocumentsTabComponent {...props} />);
|
wrapper = mount(<DocumentsTabComponent {...props} />);
|
||||||
|
wrapper = await waitForComponentToPaint(wrapper);
|
||||||
// Wait for all pending promises
|
});
|
||||||
await act(async () => {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for any async operations to complete
|
|
||||||
wrapper = await waitForComponentToPaint(wrapper, 100);
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
(state) => state.notebookServerInfo,
|
|
||||||
() => logConsoleInfo("New notebook server info received."),
|
() => logConsoleInfo("New notebook server info received."),
|
||||||
|
(state) => state.notebookServerInfo,
|
||||||
);
|
);
|
||||||
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
||||||
contentItem: options.notebookContentItem,
|
contentItem: options.notebookContentItem,
|
||||||
@@ -165,7 +165,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
iconAlt: kernelLabel,
|
iconAlt: kernelLabel,
|
||||||
onCommandClick: () => { },
|
onCommandClick: () => {},
|
||||||
commandButtonLabel: null,
|
commandButtonLabel: null,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: availableKernels.length < 1,
|
disabled: availableKernels.length < 1,
|
||||||
@@ -276,7 +276,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
iconAlt: null,
|
iconAlt: null,
|
||||||
onCommandClick: () => { },
|
onCommandClick: () => {},
|
||||||
commandButtonLabel: null,
|
commandButtonLabel: null,
|
||||||
ariaLabel: cellTypeLabel,
|
ariaLabel: cellTypeLabel,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
|
|||||||
@@ -106,6 +106,6 @@ describe("QueryTabComponent", () => {
|
|||||||
<QueryTabCopilotComponent {...propsMock} />
|
<QueryTabCopilotComponent {...propsMock} />
|
||||||
</CopilotProvider>,
|
</CopilotProvider>,
|
||||||
);
|
);
|
||||||
expect(container.find(QueryCopilotPromptbar).exists()).toBe(false);
|
expect(container.find(QueryCopilotPromptbar).exists()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useDialog } from "Explorer/Controls/Dialog";
|
|||||||
import { monaco } from "Explorer/LazyMonaco";
|
import { monaco } from "Explorer/LazyMonaco";
|
||||||
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
||||||
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||||
|
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
|
||||||
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||||
@@ -27,9 +28,8 @@ import { TabsState, useTabs } from "hooks/useTabs";
|
|||||||
import React, { Fragment, createRef } from "react";
|
import React, { Fragment, createRef } from "react";
|
||||||
import "react-splitter-layout/lib/index.css";
|
import "react-splitter-layout/lib/index.css";
|
||||||
import { format } from "react-string-format";
|
import { format } from "react-string-format";
|
||||||
//TODO: Uncomment next two lines when query copilot is reinstated in DE
|
import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
||||||
// import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||||
// import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
|
||||||
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
||||||
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
||||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||||
@@ -494,55 +494,53 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Uncomment next section when query copilot is reinstated in DE
|
if (this.launchCopilotButton.visible && this.isCopilotTabActive) {
|
||||||
// if (this.launchCopilotButton.visible && this.isCopilotTabActive) {
|
const mainButtonLabel = "Launch Copilot";
|
||||||
// const mainButtonLabel = "Launch Copilot";
|
const chatPaneLabel = "Open Copilot in chat pane (ALT+C)";
|
||||||
// const chatPaneLabel = "Open Copilot in chat pane (ALT+C)";
|
const copilotSettingLabel = "Copilot settings";
|
||||||
// const copilotSettingLabel = "Copilot settings";
|
|
||||||
|
|
||||||
// const openCopilotChatButton: CommandButtonComponentProps = {
|
const openCopilotChatButton: CommandButtonComponentProps = {
|
||||||
// iconAlt: chatPaneLabel,
|
iconAlt: chatPaneLabel,
|
||||||
// onCommandClick: this.launchQueryCopilotChat,
|
onCommandClick: this.launchQueryCopilotChat,
|
||||||
// commandButtonLabel: chatPaneLabel,
|
commandButtonLabel: chatPaneLabel,
|
||||||
// ariaLabel: chatPaneLabel,
|
ariaLabel: chatPaneLabel,
|
||||||
// hasPopup: false,
|
hasPopup: false,
|
||||||
// };
|
};
|
||||||
|
|
||||||
// const copilotSettingsButton: CommandButtonComponentProps = {
|
const copilotSettingsButton: CommandButtonComponentProps = {
|
||||||
// iconAlt: copilotSettingLabel,
|
iconAlt: copilotSettingLabel,
|
||||||
// onCommandClick: () => undefined,
|
onCommandClick: () => undefined,
|
||||||
// commandButtonLabel: copilotSettingLabel,
|
commandButtonLabel: copilotSettingLabel,
|
||||||
// ariaLabel: copilotSettingLabel,
|
ariaLabel: copilotSettingLabel,
|
||||||
// hasPopup: false,
|
hasPopup: false,
|
||||||
// };
|
};
|
||||||
|
|
||||||
// const launchCopilotButton: CommandButtonComponentProps = {
|
const launchCopilotButton: CommandButtonComponentProps = {
|
||||||
// iconSrc: LaunchCopilot,
|
iconSrc: LaunchCopilot,
|
||||||
// iconAlt: mainButtonLabel,
|
iconAlt: mainButtonLabel,
|
||||||
// onCommandClick: this.launchQueryCopilotChat,
|
onCommandClick: this.launchQueryCopilotChat,
|
||||||
// commandButtonLabel: mainButtonLabel,
|
commandButtonLabel: mainButtonLabel,
|
||||||
// ariaLabel: mainButtonLabel,
|
ariaLabel: mainButtonLabel,
|
||||||
// hasPopup: false,
|
hasPopup: false,
|
||||||
// children: [openCopilotChatButton, copilotSettingsButton],
|
children: [openCopilotChatButton, copilotSettingsButton],
|
||||||
// };
|
};
|
||||||
// buttons.push(launchCopilotButton);
|
buttons.push(launchCopilotButton);
|
||||||
// }
|
}
|
||||||
|
|
||||||
//TODO: Uncomment next section when query copilot is reinstated in DE
|
if (this.props.copilotEnabled) {
|
||||||
// if (this.props.copilotEnabled) {
|
const toggleCopilotButton: CommandButtonComponentProps = {
|
||||||
// const toggleCopilotButton: CommandButtonComponentProps = {
|
iconSrc: QueryCommandIcon,
|
||||||
// iconSrc: QueryCommandIcon,
|
iconAlt: "Query Advisor",
|
||||||
// iconAlt: "Query Advisor",
|
keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
||||||
// keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
onCommandClick: () => {
|
||||||
// onCommandClick: () => {
|
this._toggleCopilot(!this.state.copilotActive);
|
||||||
// this._toggleCopilot(!this.state.copilotActive);
|
},
|
||||||
// },
|
commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||||
// commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||||
// ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
hasPopup: false,
|
||||||
// hasPopup: false,
|
};
|
||||||
// };
|
buttons.push(toggleCopilotButton);
|
||||||
// buttons.push(toggleCopilotButton);
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
|
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
|
||||||
const label = "Cancel query";
|
const label = "Cancel query";
|
||||||
@@ -727,7 +725,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<CosmosFluentProvider id={this.props.tabId} className={this.props.styles.queryTab} role="tabpanel">
|
<CosmosFluentProvider id={this.props.tabId} className={this.props.styles.queryTab} role="tabpanel">
|
||||||
{/*TODO: Uncomment this section when query copilot is reinstated in DE
|
|
||||||
{this.props.copilotEnabled && this.state.currentTabActive && this.state.copilotActive && (
|
{this.props.copilotEnabled && this.state.currentTabActive && this.state.copilotActive && (
|
||||||
<QueryCopilotPromptbar
|
<QueryCopilotPromptbar
|
||||||
explorer={this.props.collection.container}
|
explorer={this.props.collection.container}
|
||||||
@@ -735,7 +732,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
databaseId={this.props.collection.databaseId}
|
databaseId={this.props.collection.databaseId}
|
||||||
containerId={this.props.collection.id()}
|
containerId={this.props.collection.id()}
|
||||||
></QueryCopilotPromptbar>
|
></QueryCopilotPromptbar>
|
||||||
)} */}
|
)}
|
||||||
{/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */}
|
{/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */}
|
||||||
<Allotment
|
<Allotment
|
||||||
key={vertical.toString()}
|
key={vertical.toString()}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFi
|
|||||||
import { getShellNameForDisplay } from "Explorer/Tabs/CloudShellTab/Utils/CommonUtils";
|
import { getShellNameForDisplay } from "Explorer/Tabs/CloudShellTab/Utils/CommonUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import FirewallRuleScreenshot from "../../../../images/firewallRule.png";
|
import FirewallRuleScreenshot from "../../../../images/firewallRule.png";
|
||||||
|
import VcoreFirewallRuleScreenshot from "../../../../images/vcoreMongoFirewallRule.png";
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
@@ -24,15 +25,15 @@ export abstract class BaseTerminalComponentAdapter implements ReactAdapter {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
if (this.kind === ViewModels.TerminalKind.Mongo || this.kind === ViewModels.TerminalKind.VCoreMongo) {
|
|
||||||
return this.renderTerminalComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isAllPublicIPAddressesEnabled()) {
|
if (!this.isAllPublicIPAddressesEnabled()) {
|
||||||
return (
|
return (
|
||||||
<QuickstartFirewallNotification
|
<QuickstartFirewallNotification
|
||||||
messageType={this.getMessageType()}
|
messageType={this.getMessageType()}
|
||||||
screenshot={FirewallRuleScreenshot}
|
screenshot={
|
||||||
|
this.kind === ViewModels.TerminalKind.Mongo || this.kind === ViewModels.TerminalKind.VCoreMongo
|
||||||
|
? VcoreFirewallRuleScreenshot
|
||||||
|
: FirewallRuleScreenshot
|
||||||
|
}
|
||||||
shellName={getShellNameForDisplay(this.kind)}
|
shellName={getShellNameForDisplay(this.kind)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useQueryCopilot } from "hooks/useQueryCopilot";
|
|||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import shallow from "zustand/shallow";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { useNotebook } from "../Notebook/useNotebook";
|
import { useNotebook } from "../Notebook/useNotebook";
|
||||||
|
|
||||||
@@ -37,7 +38,12 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ explorer }: Resource
|
|||||||
const [openItems, setOpenItems] = React.useState<TreeItemValue[]>([]);
|
const [openItems, setOpenItems] = React.useState<TreeItemValue[]>([]);
|
||||||
const treeStyles = useTreeStyles();
|
const treeStyles = useTreeStyles();
|
||||||
|
|
||||||
const isNotebookEnabled = useNotebook((state) => state.isNotebookEnabled)
|
const { isNotebookEnabled } = useNotebook(
|
||||||
|
(state) => ({
|
||||||
|
isNotebookEnabled: state.isNotebookEnabled,
|
||||||
|
}),
|
||||||
|
shallow,
|
||||||
|
);
|
||||||
|
|
||||||
// We intentionally avoid using a state selector here because we want to re-render the tree if the active tab changes.
|
// We intentionally avoid using a state selector here because we want to re-render the tree if the active tab changes.
|
||||||
const { refreshActiveTab } = useTabs();
|
const { refreshActiveTab } = useTabs();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import PublishIcon from "../../../images/notebook/publish_content.svg";
|
|||||||
import RefreshIcon from "../../../images/refresh-cosmos.svg";
|
import RefreshIcon from "../../../images/refresh-cosmos.svg";
|
||||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||||
|
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||||
@@ -57,12 +58,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
|
|
||||||
useSelectedNode.subscribe(() => this.triggerRender());
|
useSelectedNode.subscribe(() => this.triggerRender());
|
||||||
useTabs.subscribe(
|
useTabs.subscribe(
|
||||||
(state) => state.activeTab,
|
|
||||||
() => this.triggerRender(),
|
() => this.triggerRender(),
|
||||||
|
(state) => state.activeTab,
|
||||||
);
|
);
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
(state) => state.isNotebookEnabled,
|
|
||||||
() => this.triggerRender(),
|
() => this.triggerRender(),
|
||||||
|
(state) => state.isNotebookEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
useDatabases.subscribe(() => this.triggerRender());
|
useDatabases.subscribe(() => this.triggerRender());
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
|||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
|
|
||||||
export const shouldShowScriptNodes = (): boolean => {
|
export const shouldShowScriptNodes = (): boolean => {
|
||||||
return !isFabric() && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
|
return !isFabric() && configContext.platform !== Platform.Emulator && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
|
||||||
};
|
};
|
||||||
|
|
||||||
const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />;
|
const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
@@ -27,147 +26,143 @@ interface DatabasesState {
|
|||||||
validateCollectionId: (databaseId: string, collectionId: string) => Promise<boolean>;
|
validateCollectionId: (databaseId: string, collectionId: string) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDatabases = create<DatabasesState>()(
|
export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
||||||
subscribeWithSelector(
|
databases: [],
|
||||||
(set, get) => ({
|
resourceTokenCollection: undefined,
|
||||||
databases: [] as ViewModels.Database[],
|
sampleDataResourceTokenCollection: undefined,
|
||||||
resourceTokenCollection: undefined as ViewModels.CollectionBase,
|
updateDatabase: (updatedDatabase: ViewModels.Database) =>
|
||||||
sampleDataResourceTokenCollection: undefined as ViewModels.CollectionBase,
|
set((state) => {
|
||||||
updateDatabase: (updatedDatabase: ViewModels.Database) =>
|
const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
|
||||||
set((state) => {
|
if (database?.id() === updatedDatabase?.id()) {
|
||||||
const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
|
return updatedDatabase;
|
||||||
if (database?.id() === updatedDatabase?.id()) {
|
|
||||||
return updatedDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
return database;
|
|
||||||
});
|
|
||||||
return { databases: updatedDatabases };
|
|
||||||
}),
|
|
||||||
addDatabases: (databases: ViewModels.Database[]) =>
|
|
||||||
set((state) => ({
|
|
||||||
databases: [...state.databases, ...databases].sort((db1, db2) => db1.id().localeCompare(db2.id())),
|
|
||||||
})),
|
|
||||||
deleteDatabase: (database: ViewModels.Database) =>
|
|
||||||
set((state) => ({ databases: state.databases.filter((db) => database.id() !== db.id()) })),
|
|
||||||
clearDatabases: () => set(() => ({ databases: [] })),
|
|
||||||
isSaveQueryEnabled: () => {
|
|
||||||
const savedQueriesDatabase: ViewModels.Database = _.find(
|
|
||||||
get().databases,
|
|
||||||
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName,
|
|
||||||
);
|
|
||||||
if (!savedQueriesDatabase) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const savedQueriesCollection: ViewModels.Collection =
|
|
||||||
savedQueriesDatabase &&
|
|
||||||
_.find(
|
|
||||||
savedQueriesDatabase.collections(),
|
|
||||||
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName,
|
|
||||||
);
|
|
||||||
if (!savedQueriesCollection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
findDatabaseWithId: (databaseId: string, isSampleDatabase?: boolean) => {
|
|
||||||
return isSampleDatabase === undefined
|
|
||||||
? get().databases.find((db) => databaseId === db.id())
|
|
||||||
: get().databases.find((db) => databaseId === db.id() && db.isSampleDB === isSampleDatabase);
|
|
||||||
},
|
|
||||||
isLastNonEmptyDatabase: () => {
|
|
||||||
const databases = get().databases;
|
|
||||||
return databases.length === 1 && (databases[0].collections()?.length > 0 || !!databases[0].offer());
|
|
||||||
},
|
|
||||||
findCollection: (databaseId: string, collectionId: string) => {
|
|
||||||
const database = get().findDatabaseWithId(databaseId);
|
|
||||||
return database?.collections()?.find((collection) => collection.id() === collectionId);
|
|
||||||
},
|
|
||||||
isLastCollection: () => {
|
|
||||||
const databases = get().databases;
|
|
||||||
if (databases.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let collectionCount = 0;
|
return database;
|
||||||
for (let i = 0; i < databases.length; i++) {
|
});
|
||||||
const database = databases[i];
|
return { databases: updatedDatabases };
|
||||||
collectionCount += database.collections().length;
|
}),
|
||||||
if (collectionCount > 1) {
|
addDatabases: (databases: ViewModels.Database[]) =>
|
||||||
return false;
|
set((state) => ({
|
||||||
}
|
databases: [...state.databases, ...databases].sort((db1, db2) => db1.id().localeCompare(db2.id())),
|
||||||
}
|
})),
|
||||||
|
deleteDatabase: (database: ViewModels.Database) =>
|
||||||
|
set((state) => ({ databases: state.databases.filter((db) => database.id() !== db.id()) })),
|
||||||
|
clearDatabases: () => set(() => ({ databases: [] })),
|
||||||
|
isSaveQueryEnabled: () => {
|
||||||
|
const savedQueriesDatabase: ViewModels.Database = _.find(
|
||||||
|
get().databases,
|
||||||
|
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName,
|
||||||
|
);
|
||||||
|
if (!savedQueriesDatabase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const savedQueriesCollection: ViewModels.Collection =
|
||||||
|
savedQueriesDatabase &&
|
||||||
|
_.find(
|
||||||
|
savedQueriesDatabase.collections(),
|
||||||
|
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName,
|
||||||
|
);
|
||||||
|
if (!savedQueriesCollection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
findDatabaseWithId: (databaseId: string, isSampleDatabase?: boolean) => {
|
||||||
|
return isSampleDatabase === undefined
|
||||||
|
? get().databases.find((db) => databaseId === db.id())
|
||||||
|
: get().databases.find((db) => databaseId === db.id() && db.isSampleDB === isSampleDatabase);
|
||||||
|
},
|
||||||
|
isLastNonEmptyDatabase: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
return databases.length === 1 && (databases[0].collections()?.length > 0 || !!databases[0].offer());
|
||||||
|
},
|
||||||
|
findCollection: (databaseId: string, collectionId: string) => {
|
||||||
|
const database = get().findDatabaseWithId(databaseId);
|
||||||
|
return database?.collections()?.find((collection) => collection.id() === collectionId);
|
||||||
|
},
|
||||||
|
isLastCollection: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
if (databases.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
let collectionCount = 0;
|
||||||
},
|
for (let i = 0; i < databases.length; i++) {
|
||||||
loadDatabaseOffers: async () => {
|
const database = databases[i];
|
||||||
await Promise.all(
|
collectionCount += database.collections().length;
|
||||||
get().databases?.map(async (database: ViewModels.Database) => {
|
if (collectionCount > 1) {
|
||||||
await database.loadOffer();
|
return false;
|
||||||
}),
|
}
|
||||||
);
|
}
|
||||||
},
|
|
||||||
loadAllOffers: async () => {
|
|
||||||
await Promise.all(
|
|
||||||
get().databases?.map(async (database: ViewModels.Database) => {
|
|
||||||
await database.loadOffer();
|
|
||||||
await database.loadCollections();
|
|
||||||
await Promise.all(
|
|
||||||
(database.collections() || []).map(async (collection: ViewModels.Collection) => {
|
|
||||||
await collection.loadOffer();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isFirstResourceCreated: () => {
|
|
||||||
const databases = get().databases;
|
|
||||||
|
|
||||||
if (!databases || databases.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return databases.some((database) => {
|
|
||||||
// user has created at least one collection
|
|
||||||
if (database.collections()?.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// user has created a database with shared throughput
|
|
||||||
if (database.offer()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// use has created an empty database without shared throughput
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
findSelectedDatabase: (): ViewModels.Database => {
|
|
||||||
const selectedNode = useSelectedNode.getState().selectedNode;
|
|
||||||
if (!selectedNode) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (selectedNode.nodeKind === "Database") {
|
|
||||||
return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedNode.nodeKind === "Collection") {
|
|
||||||
return selectedNode.database;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedNode.collection?.database;
|
|
||||||
},
|
|
||||||
validateDatabaseId: (id: string): boolean => {
|
|
||||||
return !get().databases.some((database) => database.id() === id);
|
|
||||||
},
|
|
||||||
validateCollectionId: async (databaseId: string, collectionId: string): Promise<boolean> => {
|
|
||||||
const database = get().databases.find((db) => db.id() === databaseId);
|
|
||||||
// For a new tables account, database is undefined when creating the first table
|
|
||||||
if (!database && userContext.apiType === "Tables") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
loadDatabaseOffers: async () => {
|
||||||
|
await Promise.all(
|
||||||
|
get().databases?.map(async (database: ViewModels.Database) => {
|
||||||
|
await database.loadOffer();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loadAllOffers: async () => {
|
||||||
|
await Promise.all(
|
||||||
|
get().databases?.map(async (database: ViewModels.Database) => {
|
||||||
|
await database.loadOffer();
|
||||||
await database.loadCollections();
|
await database.loadCollections();
|
||||||
return !database.collections().some((collection) => collection.id() === collectionId);
|
await Promise.all(
|
||||||
},
|
(database.collections() || []).map(async (collection: ViewModels.Collection) => {
|
||||||
})
|
await collection.loadOffer();
|
||||||
)
|
}),
|
||||||
);
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isFirstResourceCreated: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
|
||||||
|
if (!databases || databases.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return databases.some((database) => {
|
||||||
|
// user has created at least one collection
|
||||||
|
if (database.collections()?.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// user has created a database with shared throughput
|
||||||
|
if (database.offer()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// use has created an empty database without shared throughput
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
findSelectedDatabase: (): ViewModels.Database => {
|
||||||
|
const selectedNode = useSelectedNode.getState().selectedNode;
|
||||||
|
if (!selectedNode) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (selectedNode.nodeKind === "Database") {
|
||||||
|
return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedNode.nodeKind === "Collection") {
|
||||||
|
return selectedNode.database;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedNode.collection?.database;
|
||||||
|
},
|
||||||
|
validateDatabaseId: (id: string): boolean => {
|
||||||
|
return !get().databases.some((database) => database.id() === id);
|
||||||
|
},
|
||||||
|
validateCollectionId: async (databaseId: string, collectionId: string): Promise<boolean> => {
|
||||||
|
const database = get().databases.find((db) => db.id() === databaseId);
|
||||||
|
// For a new tables account, database is undefined when creating the first table
|
||||||
|
if (!database && userContext.apiType === "Tables") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await database.loadCollections();
|
||||||
|
return !database.collections().some((collection) => collection.id() === collectionId);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ConnectionStatusType, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
import { ConnectionStatusType, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { useTabs } from "../hooks/useTabs";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
export interface SelectedNodeState {
|
export interface SelectedNodeState {
|
||||||
@@ -17,7 +17,7 @@ export interface SelectedNodeState {
|
|||||||
isQueryCopilotCollectionSelected: () => boolean;
|
isQueryCopilotCollectionSelected: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSelectedNode = create<SelectedNodeState>((set, get) => ({
|
export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) => ({
|
||||||
selectedNode: undefined,
|
selectedNode: undefined,
|
||||||
setSelectedNode: (node: ViewModels.TreeNode) => set({ selectedNode: node }),
|
setSelectedNode: (node: ViewModels.TreeNode) => set({ selectedNode: node }),
|
||||||
isDatabaseNodeOrNoneSelected: (): boolean => {
|
isDatabaseNodeOrNoneSelected: (): boolean => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { PropsWithChildren, useEffect } from "react";
|
import { PropsWithChildren, useEffect } from "react";
|
||||||
import { KeyBindingMap, tinykeys } from "tinykeys";
|
import { KeyBindingMap, tinykeys } from "tinykeys";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a keyboard shortcut handler.
|
* Represents a keyboard shortcut handler.
|
||||||
@@ -126,7 +126,7 @@ export const clearKeyboardActionGroup = (group: KeyboardActionGroup) => {
|
|||||||
useKeyboardActionHandlers.getState().setHandlers(group, {});
|
useKeyboardActionHandlers.getState().setHandlers(group, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
const useKeyboardActionHandlers = create<KeyboardShortcutState>((set, get) => ({
|
const useKeyboardActionHandlers: UseStore<KeyboardShortcutState> = create((set, get) => ({
|
||||||
allHandlers: {},
|
allHandlers: {},
|
||||||
groups: {},
|
groups: {},
|
||||||
setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => {
|
setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { MongoGuidRepresentation } from "Common/Constants";
|
|
||||||
import { SplitterDirection } from "Common/Splitter";
|
import { SplitterDirection } from "Common/Splitter";
|
||||||
import * as LocalStorageUtility from "./LocalStorageUtility";
|
import * as LocalStorageUtility from "./LocalStorageUtility";
|
||||||
import * as SessionStorageUtility from "./SessionStorageUtility";
|
import * as SessionStorageUtility from "./SessionStorageUtility";
|
||||||
@@ -34,7 +33,6 @@ export enum StorageKey {
|
|||||||
DocumentsTabPrefs,
|
DocumentsTabPrefs,
|
||||||
DefaultQueryResultsView,
|
DefaultQueryResultsView,
|
||||||
AppState,
|
AppState,
|
||||||
MongoGuidRepresentation,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hasRUThresholdBeenConfigured = (): boolean => {
|
export const hasRUThresholdBeenConfigured = (): boolean => {
|
||||||
@@ -67,13 +65,4 @@ export const getDefaultQueryResultsView = (): SplitterDirection => {
|
|||||||
return SplitterDirection.Horizontal;
|
return SplitterDirection.Horizontal;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMongoGuidRepresentation = (): MongoGuidRepresentation => {
|
|
||||||
const mongoGuidRepresentation: string | null = LocalStorageUtility.getEntryString(StorageKey.MongoGuidRepresentation);
|
|
||||||
if (mongoGuidRepresentation) {
|
|
||||||
return mongoGuidRepresentation as MongoGuidRepresentation;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MongoGuidRepresentation.CSharpLegacy;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DefaultRUThreshold = 5000;
|
export const DefaultRUThreshold = 5000;
|
||||||
|
|||||||
@@ -91,11 +91,5 @@ export const getItemName = (): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isDataplaneRbacSupported = (apiType: string): boolean => {
|
export const isDataplaneRbacSupported = (apiType: string): boolean => {
|
||||||
return (
|
return apiType === "SQL" || apiType === "Tables" || apiType === "Gremlin";
|
||||||
apiType === "SQL" || apiType === "Tables" || apiType === "Gremlin" || apiType === "Mongo" || apiType === "Cassandra"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hasProxyServer = (apiType: string): boolean => {
|
|
||||||
return apiType === "Mongo" || apiType === "Cassandra";
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ describe("AuthorizationUtils", () => {
|
|||||||
|
|
||||||
it("should return true if dataPlaneRbacEnabled is set to true and API supports RBAC", () => {
|
it("should return true if dataPlaneRbacEnabled is set to true and API supports RBAC", () => {
|
||||||
setAadDataPlane(false);
|
setAadDataPlane(false);
|
||||||
["SQL", "Tables", "Gremlin", "Mongo", "Cassandra"].forEach((type) => {
|
["SQL", "Tables", "Gremlin"].forEach((type) => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
dataPlaneRbacEnabled: true,
|
dataPlaneRbacEnabled: true,
|
||||||
apiType: type as ApiType,
|
apiType: type as ApiType,
|
||||||
@@ -115,7 +115,7 @@ describe("AuthorizationUtils", () => {
|
|||||||
|
|
||||||
it("should return false if dataPlaneRbacEnabled is set to true and API does not support RBAC", () => {
|
it("should return false if dataPlaneRbacEnabled is set to true and API does not support RBAC", () => {
|
||||||
setAadDataPlane(false);
|
setAadDataPlane(false);
|
||||||
["Postgres", "VCoreMongo"].forEach((type) => {
|
["Mongo", "Cassandra", "Postgres", "VCoreMongo"].forEach((type) => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
dataPlaneRbacEnabled: true,
|
dataPlaneRbacEnabled: true,
|
||||||
apiType: type as ApiType,
|
apiType: type as ApiType,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as msal from "@azure/msal-browser";
|
import * as msal from "@azure/msal-browser";
|
||||||
import { getEnvironmentScopeEndpoint } from "Common/EnvironmentUtility";
|
|
||||||
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { hasProxyServer, isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
@@ -75,12 +74,10 @@ export async function acquireMsalTokenForAccount(
|
|||||||
if (userContext.databaseAccount.properties?.documentEndpoint === undefined) {
|
if (userContext.databaseAccount.properties?.documentEndpoint === undefined) {
|
||||||
throw new Error("Database account has no document endpoint defined");
|
throw new Error("Database account has no document endpoint defined");
|
||||||
}
|
}
|
||||||
let hrefEndpoint = "";
|
const hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace(
|
||||||
if (isDataplaneRbacEnabledForProxyApi(userContext)) {
|
/\/+$/,
|
||||||
hrefEndpoint = getEnvironmentScopeEndpoint();
|
"/.default",
|
||||||
} else {
|
);
|
||||||
hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace(/\/+$/, "/.default");
|
|
||||||
}
|
|
||||||
const msalInstance = await getMsalInstance();
|
const msalInstance = await getMsalInstance();
|
||||||
const knownAccounts = msalInstance.getAllAccounts();
|
const knownAccounts = msalInstance.getAllAccounts();
|
||||||
// If user_hint is provided, we will try to use it to find the account.
|
// If user_hint is provided, we will try to use it to find the account.
|
||||||
@@ -186,11 +183,7 @@ export async function acquireTokenWithMsal(
|
|||||||
|
|
||||||
export function useDataplaneRbacAuthorization(userContext: UserContext): boolean {
|
export function useDataplaneRbacAuthorization(userContext: UserContext): boolean {
|
||||||
return (
|
return (
|
||||||
userContext.features?.enableAadDataPlane ||
|
userContext.features.enableAadDataPlane ||
|
||||||
(userContext.dataPlaneRbacEnabled && isDataplaneRbacSupported(userContext.apiType))
|
(userContext.dataPlaneRbacEnabled && isDataplaneRbacSupported(userContext.apiType))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDataplaneRbacEnabledForProxyApi(userContext: UserContext): boolean {
|
|
||||||
return useDataplaneRbacAuthorization(userContext) && hasProxyServer(userContext.apiType);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -79,7 +79,22 @@ export const defaultAllowedCassandraProxyEndpoints: ReadonlyArray<string> = [
|
|||||||
CassandraProxyEndpoints.Mooncake,
|
CassandraProxyEndpoints.Mooncake,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const allowedEmulatorEndpoints: ReadonlyArray<string> = ["https://localhost:8081"];
|
export const allowedCassandraProxyEndpoints_ToBeDeprecated: ReadonlyArray<string> = [
|
||||||
|
"https://main.documentdb.ext.azure.com",
|
||||||
|
"https://main.documentdb.ext.azure.cn",
|
||||||
|
"https://main.documentdb.ext.azure.us",
|
||||||
|
"https://main.cosmos.ext.azure",
|
||||||
|
"https://localhost:12901",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CassandraProxyOutboundIPs: { [key: string]: string[] } = {
|
||||||
|
[CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"],
|
||||||
|
[CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"],
|
||||||
|
[CassandraProxyEndpoints.Fairfax]: ["52.244.50.101", "52.227.165.24"],
|
||||||
|
[CassandraProxyEndpoints.Mooncake]: ["40.73.99.146", "143.64.62.47"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const allowedEmulatorEndpoints: ReadonlyArray<string> = ["https://localhost:8081", "http://localhost:8081"];
|
||||||
|
|
||||||
export const allowedArcadiaEndpoints: ReadonlyArray<string> = ["https://workspaceartifacts.projectarcadia.net"];
|
export const allowedArcadiaEndpoints: ReadonlyArray<string> = ["https://workspaceartifacts.projectarcadia.net"];
|
||||||
|
|
||||||
|
|||||||
112
src/Utils/PlatformFeatureUtils.ts
Normal file
112
src/Utils/PlatformFeatureUtils.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { Platform, configContext } from "../ConfigContext";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flags enumeration - centralized feature definitions
|
||||||
|
*/
|
||||||
|
export enum PlatformFeature {
|
||||||
|
// UI/Core Features
|
||||||
|
Queries = "Queries",
|
||||||
|
Notebooks = "Notebooks",
|
||||||
|
SynapseLink = "SynapseLink",
|
||||||
|
VSCodeIntegration = "VSCodeIntegration",
|
||||||
|
GlobalSecondaryIndex = "GlobalSecondaryIndex",
|
||||||
|
DataPlaneRbac = "DataPlaneRbac",
|
||||||
|
EntraIDLogin = "EntraIDLogin",
|
||||||
|
EntreIDRbac = "EntreIDRbac",
|
||||||
|
RetrySettings = "RetrySettings",
|
||||||
|
GraphAutoVizOption = "GraphAutoVizOption",
|
||||||
|
CrossPartitionOption = "CrossPartitionOption",
|
||||||
|
EnhancedQueryControl = "EnhancedQueryControl",
|
||||||
|
ParallelismOption = "ParallelismOption",
|
||||||
|
EnableEntraIdRbac = "EnableEntraIdRbac",
|
||||||
|
PriorityBasedExecution = "PriorityBasedExecution",
|
||||||
|
RegionSelection = "RegionSelection",
|
||||||
|
Copilot = "Copilot",
|
||||||
|
CloudShell = "CloudShell",
|
||||||
|
ContainerPagination = "ContainerPagination",
|
||||||
|
FullTextSearch = "FullTextSearch",
|
||||||
|
VectorSearch = "VectorSearch",
|
||||||
|
ThroughputBucketing = "ThroughputBucketing",
|
||||||
|
ComputedProperties = "ComputedProperties",
|
||||||
|
AnalyticalStore = "AnalyticalStore",
|
||||||
|
|
||||||
|
// CRUD Operations - Database
|
||||||
|
CreateDatabase = "CreateDatabase",
|
||||||
|
ReadDatabase = "ReadDatabase",
|
||||||
|
DeleteDatabase = "DeleteDatabase",
|
||||||
|
|
||||||
|
// CRUD Operations - Collection
|
||||||
|
CreateCollection = "CreateCollection",
|
||||||
|
ReadCollection = "ReadCollection",
|
||||||
|
UpdateCollection = "UpdateCollection",
|
||||||
|
DeleteCollection = "DeleteCollection",
|
||||||
|
|
||||||
|
// CRUD Operations - Document
|
||||||
|
CreateDocument = "CreateDocument",
|
||||||
|
ReadDocument = "ReadDocument",
|
||||||
|
UpdateDocument = "UpdateDocument",
|
||||||
|
DeleteDocument = "DeleteDocument",
|
||||||
|
|
||||||
|
// Advanced Database Features
|
||||||
|
StoredProcedures = "StoredProcedures",
|
||||||
|
UDF = "UDF",
|
||||||
|
Trigger = "Trigger",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature matrix per platform.
|
||||||
|
* - Only list platforms that have restrictions. If a platform is not present, all features are considered supported.
|
||||||
|
* - Start with VNextEmulator today; add more platforms/flags here later without touching calling code.
|
||||||
|
*/
|
||||||
|
const FEATURE_MATRIX: ReadonlyMap<Platform, ReadonlySet<PlatformFeature>> = new Map([
|
||||||
|
[
|
||||||
|
Platform.VNextEmulator,
|
||||||
|
new Set<PlatformFeature>([
|
||||||
|
PlatformFeature.Queries,
|
||||||
|
|
||||||
|
PlatformFeature.CreateDatabase,
|
||||||
|
PlatformFeature.ReadDatabase,
|
||||||
|
PlatformFeature.DeleteDatabase,
|
||||||
|
|
||||||
|
PlatformFeature.CreateCollection,
|
||||||
|
PlatformFeature.ReadCollection,
|
||||||
|
PlatformFeature.UpdateCollection,
|
||||||
|
PlatformFeature.DeleteCollection,
|
||||||
|
|
||||||
|
PlatformFeature.CreateDocument,
|
||||||
|
PlatformFeature.ReadDocument,
|
||||||
|
PlatformFeature.UpdateDocument,
|
||||||
|
PlatformFeature.DeleteDocument,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Central feature flag function - checks if a feature is enabled for current platform
|
||||||
|
* @param feature The feature to check
|
||||||
|
* @param platform Optional platform override, defaults to current platform
|
||||||
|
* @returns True if the feature is enabled for the platform, false otherwise
|
||||||
|
*/
|
||||||
|
export const isFeatureSupported = (feature: PlatformFeature, platform?: Platform): boolean => {
|
||||||
|
const currentPlatform = platform ?? configContext.platform;
|
||||||
|
if (currentPlatform !== Platform.VNextEmulator) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// VNextEmulator: check from the feature matrix
|
||||||
|
const vnextFeatures = FEATURE_MATRIX.get(Platform.VNextEmulator);
|
||||||
|
return vnextFeatures?.has(feature) ?? false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const areAdvancedScriptsSupported = (platform?: Platform): boolean => {
|
||||||
|
const currentPlatform = platform ?? configContext.platform;
|
||||||
|
if (currentPlatform !== Platform.VNextEmulator) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, require all script features to be enabled
|
||||||
|
return (
|
||||||
|
isFeatureSupported(PlatformFeature.StoredProcedures, currentPlatform) &&
|
||||||
|
isFeatureSupported(PlatformFeature.UDF, currentPlatform) &&
|
||||||
|
isFeatureSupported(PlatformFeature.Trigger, currentPlatform)
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
|
|
||||||
interface CarouselState {
|
interface CarouselState {
|
||||||
shouldOpen: boolean;
|
shouldOpen: boolean;
|
||||||
@@ -10,15 +9,11 @@ interface CarouselState {
|
|||||||
setShowCopilotCarousel: (showCopilotCarousel: boolean) => void;
|
setShowCopilotCarousel: (showCopilotCarousel: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCarousel = create<CarouselState>()(
|
export const useCarousel: UseStore<CarouselState> = create((set) => ({
|
||||||
subscribeWithSelector(
|
shouldOpen: false,
|
||||||
(set) => ({
|
showCoachMark: false,
|
||||||
shouldOpen: false,
|
showCopilotCarousel: false,
|
||||||
showCoachMark: false,
|
setShouldOpen: (shouldOpen: boolean) => set({ shouldOpen }),
|
||||||
showCopilotCarousel: false,
|
setShowCoachMark: (showCoachMark: boolean) => set({ showCoachMark }),
|
||||||
setShouldOpen: (shouldOpen: boolean) => set({ shouldOpen }),
|
setShowCopilotCarousel: (showCopilotCarousel: boolean) => set({ showCopilotCarousel }),
|
||||||
setShowCoachMark: (showCoachMark: boolean) => set({ showCoachMark }),
|
}));
|
||||||
setShowCopilotCarousel: (showCopilotCarousel: boolean) => set({ showCopilotCarousel }),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
interface ClientWriteEnabledState {
|
interface ClientWriteEnabledState {
|
||||||
clientWriteEnabled: boolean;
|
clientWriteEnabled: boolean;
|
||||||
setClientWriteEnabled: (writeEnabled: boolean) => void;
|
setClientWriteEnabled: (writeEnabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useClientWriteEnabled = create<ClientWriteEnabledState>((set) => ({
|
export const useClientWriteEnabled: UseStore<ClientWriteEnabledState> = create((set) => ({
|
||||||
clientWriteEnabled: true,
|
clientWriteEnabled: true,
|
||||||
setClientWriteEnabled: (clientWriteEnabled: boolean) => set({ clientWriteEnabled }),
|
setClientWriteEnabled: (clientWriteEnabled: boolean) => set({ clientWriteEnabled }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getDataTransferJobs } from "Common/dataAccess/dataTransfers";
|
import { getDataTransferJobs } from "Common/dataAccess/dataTransfers";
|
||||||
import { DataTransferJobGetResults } from "Utils/arm/generatedClients/dataTransferService/types";
|
import { DataTransferJobGetResults } from "Utils/arm/generatedClients/dataTransferService/types";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
export interface DataTransferJobsState {
|
export interface DataTransferJobsState {
|
||||||
dataTransferJobs: DataTransferJobGetResults[];
|
dataTransferJobs: DataTransferJobGetResults[];
|
||||||
@@ -9,7 +9,9 @@ export interface DataTransferJobsState {
|
|||||||
setPollingDataTransferJobs: (pollingDataTransferJobs: Set<string>) => void;
|
setPollingDataTransferJobs: (pollingDataTransferJobs: Set<string>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDataTransferJobs = create<DataTransferJobsState>((set) => ({
|
type DataTransferJobStore = UseStore<DataTransferJobsState>;
|
||||||
|
|
||||||
|
export const useDataTransferJobs: DataTransferJobStore = create((set) => ({
|
||||||
dataTransferJobs: [],
|
dataTransferJobs: [],
|
||||||
pollingDataTransferJobs: new Set<string>(),
|
pollingDataTransferJobs: new Set<string>(),
|
||||||
setDataTransferJobs: (dataTransferJobs: DataTransferJobGetResults[]) => set({ dataTransferJobs }),
|
setDataTransferJobs: (dataTransferJobs: DataTransferJobGetResults[]) => set({ dataTransferJobs }),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as Constants from "Common/Constants";
|
import * as Constants from "Common/Constants";
|
||||||
import { getEnvironmentScopeEndpoint } from "Common/EnvironmentUtility";
|
|
||||||
import { createUri } from "Common/UrlUtility";
|
import { createUri } from "Common/UrlUtility";
|
||||||
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
|
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
|
||||||
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
@@ -63,7 +62,6 @@ import {
|
|||||||
acquireTokenWithMsal,
|
acquireTokenWithMsal,
|
||||||
getAuthorizationHeader,
|
getAuthorizationHeader,
|
||||||
getMsalInstance,
|
getMsalInstance,
|
||||||
isDataplaneRbacEnabledForProxyApi,
|
|
||||||
} from "../Utils/AuthorizationUtils";
|
} from "../Utils/AuthorizationUtils";
|
||||||
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
|
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
|
||||||
import { get, getReadOnlyKeys, listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { get, getReadOnlyKeys, listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
@@ -88,7 +86,7 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
if (platform === Platform.Hosted) {
|
if (platform === Platform.Hosted) {
|
||||||
explorer = await configureHosted();
|
explorer = await configureHosted();
|
||||||
} else if (platform === Platform.Emulator) {
|
} else if (platform === Platform.Emulator || platform === Platform.VNextEmulator) {
|
||||||
explorer = configureEmulator();
|
explorer = configureEmulator();
|
||||||
} else if (platform === Platform.Portal) {
|
} else if (platform === Platform.Portal) {
|
||||||
explorer = await configurePortal();
|
explorer = await configurePortal();
|
||||||
@@ -333,12 +331,7 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
|||||||
const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
|
const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
|
||||||
let aadToken;
|
let aadToken;
|
||||||
if (account.properties?.documentEndpoint) {
|
if (account.properties?.documentEndpoint) {
|
||||||
let hrefEndpoint = "";
|
const hrefEndpoint = new URL(account.properties.documentEndpoint).href.replace(/\/$/, "/.default");
|
||||||
if (isDataplaneRbacEnabledForProxyApi(userContext)) {
|
|
||||||
hrefEndpoint = getEnvironmentScopeEndpoint();
|
|
||||||
} else {
|
|
||||||
hrefEndpoint = new URL(account.properties.documentEndpoint).href.replace(/\/$/, "/.default");
|
|
||||||
}
|
|
||||||
const msalInstance = await getMsalInstance();
|
const msalInstance = await getMsalInstance();
|
||||||
const cachedAccount = msalInstance.getAllAccounts()?.[0];
|
const cachedAccount = msalInstance.getAllAccounts()?.[0];
|
||||||
msalInstance.setActiveAccount(cachedAccount);
|
msalInstance.setActiveAccount(cachedAccount);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
export interface NotebookSnapshotHooks {
|
export interface NotebookSnapshotHooks {
|
||||||
snapshot?: string;
|
snapshot?: string;
|
||||||
@@ -7,7 +7,7 @@ export interface NotebookSnapshotHooks {
|
|||||||
setError: (error: string) => void;
|
setError: (error: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebookSnapshotStore = create<NotebookSnapshotHooks>((set) => ({
|
export const useNotebookSnapshotStore: UseStore<NotebookSnapshotHooks> = create((set) => ({
|
||||||
snapshot: undefined,
|
snapshot: undefined,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })),
|
setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
||||||
|
|
||||||
export interface NotificationConsoleState {
|
export interface NotificationConsoleState {
|
||||||
@@ -15,7 +15,7 @@ export interface NotificationConsoleState {
|
|||||||
setConsoleAnimationFinished: (consoleAnimationFinished: boolean) => void;
|
setConsoleAnimationFinished: (consoleAnimationFinished: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotificationConsole = create<NotificationConsoleState>((set) => ({
|
export const useNotificationConsole: UseStore<NotificationConsoleState> = create((set) => ({
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
consoleData: undefined,
|
consoleData: undefined,
|
||||||
inProgressConsoleDataIdToBeDeleted: "",
|
inProgressConsoleDataIdToBeDeleted: "",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
|
|
||||||
interface TeachingBubbleState {
|
interface TeachingBubbleState {
|
||||||
showPostgreTeachingBubble: boolean;
|
showPostgreTeachingBubble: boolean;
|
||||||
@@ -8,13 +7,9 @@ interface TeachingBubbleState {
|
|||||||
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => void;
|
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePostgres = create<TeachingBubbleState>()(
|
export const usePostgres: UseStore<TeachingBubbleState> = create((set) => ({
|
||||||
subscribeWithSelector(
|
showPostgreTeachingBubble: false,
|
||||||
(set) => ({
|
showResetPasswordBubble: false,
|
||||||
showPostgreTeachingBubble: false,
|
setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => set({ showPostgreTeachingBubble }),
|
||||||
showResetPasswordBubble: false,
|
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => set({ showResetPasswordBubble }),
|
||||||
setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => set({ showPostgreTeachingBubble }),
|
}));
|
||||||
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => set({ showResetPasswordBubble }),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { QueryResults } from "Contracts/ViewModels";
|
|||||||
import { CopilotMessage, CopilotSchemaAllocationInfo } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
import { CopilotMessage, CopilotSchemaAllocationInfo } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||||
import { guid } from "Explorer/Tables/Utilities";
|
import { guid } from "Explorer/Tables/Utilities";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { ContainerInfo } from "../Contracts/DataModels";
|
import { ContainerInfo } from "../Contracts/DataModels";
|
||||||
|
|
||||||
@@ -97,12 +96,120 @@ export interface QueryCopilotState {
|
|||||||
resetQueryCopilotStates: () => void;
|
resetQueryCopilotStates: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useQueryCopilot = create<Partial<QueryCopilotState>>()(
|
type QueryCopilotStore = UseStore<Partial<QueryCopilotState>>;
|
||||||
subscribeWithSelector(
|
|
||||||
(set) => ({
|
export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
||||||
copilotEnabled: false,
|
copilotEnabled: false,
|
||||||
copilotUserDBEnabled: false,
|
copilotUserDBEnabled: false,
|
||||||
copilotSampleDBEnabled: false,
|
copilotSampleDBEnabled: false,
|
||||||
|
generatedQuery: "",
|
||||||
|
likeQuery: false,
|
||||||
|
userPrompt: "",
|
||||||
|
showFeedbackModal: false,
|
||||||
|
hideFeedbackModalForLikedQueries: false,
|
||||||
|
correlationId: "",
|
||||||
|
query: "SELECT * FROM c",
|
||||||
|
selectedQuery: "",
|
||||||
|
isGeneratingQuery: null,
|
||||||
|
isGeneratingExplanation: false,
|
||||||
|
isExecuting: false,
|
||||||
|
dislikeQuery: undefined,
|
||||||
|
showCallout: false,
|
||||||
|
showSamplePrompts: false,
|
||||||
|
queryIterator: undefined,
|
||||||
|
queryResults: undefined,
|
||||||
|
errors: [],
|
||||||
|
isSamplePromptsOpen: false,
|
||||||
|
showDeletePopup: false,
|
||||||
|
showFeedbackBar: false,
|
||||||
|
showCopyPopup: false,
|
||||||
|
showErrorMessageBar: false,
|
||||||
|
showInvalidQueryMessageBar: false,
|
||||||
|
generatedQueryComments: "",
|
||||||
|
wasCopilotUsed: false,
|
||||||
|
showWelcomeSidebar: true,
|
||||||
|
showCopilotSidebar: false,
|
||||||
|
chatMessages: [],
|
||||||
|
shouldIncludeInMessages: true,
|
||||||
|
showExplanationBubble: false,
|
||||||
|
notebookServerInfo: {
|
||||||
|
notebookServerEndpoint: undefined,
|
||||||
|
authToken: undefined,
|
||||||
|
forwardingId: undefined,
|
||||||
|
},
|
||||||
|
containerStatus: {
|
||||||
|
status: undefined,
|
||||||
|
durationLeftInMinutes: undefined,
|
||||||
|
phoenixServerInfo: undefined,
|
||||||
|
},
|
||||||
|
schemaAllocationInfo: {
|
||||||
|
databaseId: undefined,
|
||||||
|
containerId: undefined,
|
||||||
|
},
|
||||||
|
isAllocatingContainer: false,
|
||||||
|
copilotEnabledforExecution: false,
|
||||||
|
|
||||||
|
setCopilotEnabled: (copilotEnabled: boolean) => set({ copilotEnabled }),
|
||||||
|
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => set({ copilotUserDBEnabled }),
|
||||||
|
setCopilotSampleDBEnabled: (copilotSampleDBEnabled: boolean) => set({ copilotSampleDBEnabled }),
|
||||||
|
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
||||||
|
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
||||||
|
closeFeedbackModal: () => set({ showFeedbackModal: false }),
|
||||||
|
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
||||||
|
set({ hideFeedbackModalForLikedQueries }),
|
||||||
|
refreshCorrelationId: () => set({ correlationId: guid() }),
|
||||||
|
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
|
||||||
|
setQuery: (query: string) => set({ query }),
|
||||||
|
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
|
||||||
|
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
|
||||||
|
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
|
||||||
|
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
|
||||||
|
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
|
||||||
|
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
|
||||||
|
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
|
||||||
|
setShowCallout: (showCallout: boolean) => set({ showCallout }),
|
||||||
|
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
|
||||||
|
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
|
||||||
|
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
|
||||||
|
setErrors: (errors: QueryError[]) => set({ errors }),
|
||||||
|
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
|
||||||
|
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
|
||||||
|
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
|
||||||
|
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
|
||||||
|
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
|
||||||
|
setShowInvalidQueryMessageBar: (showInvalidQueryMessageBar: boolean) => set({ showInvalidQueryMessageBar }),
|
||||||
|
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
|
||||||
|
setWasCopilotUsed: (wasCopilotUsed: boolean) => set({ wasCopilotUsed }),
|
||||||
|
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => set({ showWelcomeSidebar }),
|
||||||
|
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
|
||||||
|
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
|
||||||
|
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
|
||||||
|
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
|
||||||
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
|
set({ notebookServerInfo }),
|
||||||
|
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
||||||
|
setIsAllocatingContainer: (isAllocatingContainer: boolean) => set({ isAllocatingContainer }),
|
||||||
|
setSchemaAllocationInfo: (schemaAllocationInfo: CopilotSchemaAllocationInfo) => set({ schemaAllocationInfo }),
|
||||||
|
setCopilotEnabledforExecution: (copilotEnabledforExecution: boolean) => set({ copilotEnabledforExecution }),
|
||||||
|
|
||||||
|
resetContainerConnection: (): void => {
|
||||||
|
useTabs.getState().closeAllNotebookTabs(true);
|
||||||
|
useQueryCopilot.getState().setNotebookServerInfo(undefined);
|
||||||
|
useQueryCopilot.getState().setIsAllocatingContainer(false);
|
||||||
|
useQueryCopilot.getState().setContainerStatus({
|
||||||
|
status: undefined,
|
||||||
|
durationLeftInMinutes: undefined,
|
||||||
|
phoenixServerInfo: undefined,
|
||||||
|
});
|
||||||
|
useQueryCopilot.getState().setSchemaAllocationInfo({
|
||||||
|
databaseId: undefined,
|
||||||
|
containerId: undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
resetQueryCopilotStates: () => {
|
||||||
|
set((state) => ({
|
||||||
|
...state,
|
||||||
generatedQuery: "",
|
generatedQuery: "",
|
||||||
likeQuery: false,
|
likeQuery: false,
|
||||||
userPrompt: "",
|
userPrompt: "",
|
||||||
@@ -111,15 +218,15 @@ export const useQueryCopilot = create<Partial<QueryCopilotState>>()(
|
|||||||
correlationId: "",
|
correlationId: "",
|
||||||
query: "SELECT * FROM c",
|
query: "SELECT * FROM c",
|
||||||
selectedQuery: "",
|
selectedQuery: "",
|
||||||
isGeneratingQuery: null as boolean,
|
isGeneratingQuery: false,
|
||||||
isGeneratingExplanation: false,
|
isGeneratingExplanation: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
dislikeQuery: undefined as (boolean | undefined),
|
dislikeQuery: undefined,
|
||||||
showCallout: false,
|
showCallout: false,
|
||||||
showSamplePrompts: false,
|
showSamplePrompts: false,
|
||||||
queryIterator: undefined as MinimalQueryIterator | undefined,
|
queryIterator: undefined,
|
||||||
queryResults: undefined as QueryResults | undefined,
|
queryResults: undefined,
|
||||||
errors: [] as QueryError[],
|
errors: [],
|
||||||
isSamplePromptsOpen: false,
|
isSamplePromptsOpen: false,
|
||||||
showDeletePopup: false,
|
showDeletePopup: false,
|
||||||
showFeedbackBar: false,
|
showFeedbackBar: false,
|
||||||
@@ -128,135 +235,25 @@ export const useQueryCopilot = create<Partial<QueryCopilotState>>()(
|
|||||||
showInvalidQueryMessageBar: false,
|
showInvalidQueryMessageBar: false,
|
||||||
generatedQueryComments: "",
|
generatedQueryComments: "",
|
||||||
wasCopilotUsed: false,
|
wasCopilotUsed: false,
|
||||||
showWelcomeSidebar: true,
|
|
||||||
showCopilotSidebar: false,
|
showCopilotSidebar: false,
|
||||||
chatMessages: [] as CopilotMessage[],
|
chatMessages: [],
|
||||||
shouldIncludeInMessages: true,
|
shouldIncludeInMessages: true,
|
||||||
showExplanationBubble: false,
|
showExplanationBubble: false,
|
||||||
notebookServerInfo: {
|
notebookServerInfo: {
|
||||||
notebookServerEndpoint: undefined,
|
notebookServerEndpoint: undefined,
|
||||||
authToken: undefined,
|
authToken: undefined,
|
||||||
forwardingId: undefined,
|
forwardingId: undefined,
|
||||||
} as DataModels.NotebookWorkspaceConnectionInfo,
|
},
|
||||||
containerStatus: {
|
containerStatus: {
|
||||||
status: undefined,
|
status: undefined,
|
||||||
durationLeftInMinutes: undefined,
|
durationLeftInMinutes: undefined,
|
||||||
phoenixServerInfo: undefined,
|
phoenixServerInfo: undefined,
|
||||||
} as ContainerInfo,
|
},
|
||||||
schemaAllocationInfo: {
|
schemaAllocationInfo: {
|
||||||
databaseId: undefined,
|
databaseId: undefined,
|
||||||
containerId: undefined,
|
containerId: undefined,
|
||||||
} as CopilotSchemaAllocationInfo,
|
},
|
||||||
isAllocatingContainer: false,
|
isAllocatingContainer: false,
|
||||||
copilotEnabledforExecution: false,
|
}));
|
||||||
|
},
|
||||||
setCopilotEnabled: (copilotEnabled: boolean) => set({ copilotEnabled }),
|
}));
|
||||||
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => set({ copilotUserDBEnabled }),
|
|
||||||
setCopilotSampleDBEnabled: (copilotSampleDBEnabled: boolean) => set({ copilotSampleDBEnabled }),
|
|
||||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
|
||||||
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
|
||||||
closeFeedbackModal: () => set({ showFeedbackModal: false }),
|
|
||||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
|
||||||
set({ hideFeedbackModalForLikedQueries }),
|
|
||||||
refreshCorrelationId: () => set({ correlationId: guid() }),
|
|
||||||
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
|
|
||||||
setQuery: (query: string) => set({ query }),
|
|
||||||
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
|
|
||||||
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
|
|
||||||
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
|
|
||||||
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
|
|
||||||
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
|
|
||||||
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
|
|
||||||
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
|
|
||||||
setShowCallout: (showCallout: boolean) => set({ showCallout }),
|
|
||||||
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
|
|
||||||
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
|
|
||||||
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
|
|
||||||
setErrors: (errors: QueryError[]) => set({ errors }),
|
|
||||||
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
|
|
||||||
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
|
|
||||||
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
|
|
||||||
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
|
|
||||||
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
|
|
||||||
setShowInvalidQueryMessageBar: (showInvalidQueryMessageBar: boolean) => set({ showInvalidQueryMessageBar }),
|
|
||||||
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
|
|
||||||
setWasCopilotUsed: (wasCopilotUsed: boolean) => set({ wasCopilotUsed }),
|
|
||||||
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => set({ showWelcomeSidebar }),
|
|
||||||
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
|
|
||||||
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
|
|
||||||
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
|
|
||||||
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
|
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
|
||||||
set({ notebookServerInfo }),
|
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
|
||||||
setIsAllocatingContainer: (isAllocatingContainer: boolean) => set({ isAllocatingContainer }),
|
|
||||||
setSchemaAllocationInfo: (schemaAllocationInfo: CopilotSchemaAllocationInfo) => set({ schemaAllocationInfo }),
|
|
||||||
setCopilotEnabledforExecution: (copilotEnabledforExecution: boolean) => set({ copilotEnabledforExecution }),
|
|
||||||
|
|
||||||
resetContainerConnection: (): void => {
|
|
||||||
useTabs.getState().closeAllNotebookTabs(true);
|
|
||||||
useQueryCopilot.getState().setNotebookServerInfo(undefined);
|
|
||||||
useQueryCopilot.getState().setIsAllocatingContainer(false);
|
|
||||||
useQueryCopilot.getState().setContainerStatus({
|
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
phoenixServerInfo: undefined,
|
|
||||||
});
|
|
||||||
useQueryCopilot.getState().setSchemaAllocationInfo({
|
|
||||||
databaseId: undefined,
|
|
||||||
containerId: undefined,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
resetQueryCopilotStates: () => {
|
|
||||||
set((state) => ({
|
|
||||||
...state,
|
|
||||||
generatedQuery: "",
|
|
||||||
likeQuery: false,
|
|
||||||
userPrompt: "",
|
|
||||||
showFeedbackModal: false,
|
|
||||||
hideFeedbackModalForLikedQueries: false,
|
|
||||||
correlationId: "",
|
|
||||||
query: "SELECT * FROM c",
|
|
||||||
selectedQuery: "",
|
|
||||||
isGeneratingQuery: false,
|
|
||||||
isGeneratingExplanation: false,
|
|
||||||
isExecuting: false,
|
|
||||||
dislikeQuery: undefined,
|
|
||||||
showCallout: false,
|
|
||||||
showSamplePrompts: false,
|
|
||||||
queryIterator: undefined,
|
|
||||||
queryResults: undefined,
|
|
||||||
errors: [],
|
|
||||||
isSamplePromptsOpen: false,
|
|
||||||
showDeletePopup: false,
|
|
||||||
showFeedbackBar: false,
|
|
||||||
showCopyPopup: false,
|
|
||||||
showErrorMessageBar: false,
|
|
||||||
showInvalidQueryMessageBar: false,
|
|
||||||
generatedQueryComments: "",
|
|
||||||
wasCopilotUsed: false,
|
|
||||||
showCopilotSidebar: false,
|
|
||||||
chatMessages: [],
|
|
||||||
shouldIncludeInMessages: true,
|
|
||||||
showExplanationBubble: false,
|
|
||||||
notebookServerInfo: {
|
|
||||||
notebookServerEndpoint: undefined,
|
|
||||||
authToken: undefined,
|
|
||||||
forwardingId: undefined,
|
|
||||||
},
|
|
||||||
containerStatus: {
|
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
phoenixServerInfo: undefined,
|
|
||||||
},
|
|
||||||
schemaAllocationInfo: {
|
|
||||||
databaseId: undefined,
|
|
||||||
containerId: undefined,
|
|
||||||
},
|
|
||||||
isAllocatingContainer: false,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
export interface SidePanelState {
|
export interface SidePanelState {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -9,7 +9,7 @@ export interface SidePanelState {
|
|||||||
closeSidePanel: () => void;
|
closeSidePanel: () => void;
|
||||||
getRef?: React.RefObject<HTMLElement>; // Optional ref for focusing the last element.
|
getRef?: React.RefObject<HTMLElement>; // Optional ref for focusing the last element.
|
||||||
}
|
}
|
||||||
export const useSidePanel = create<SidePanelState>((set) => ({
|
export const useSidePanel: UseStore<SidePanelState> = create((set) => ({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
panelWidth: "440px",
|
panelWidth: "440px",
|
||||||
openSidePanel: (headerText, panelContent, panelWidth = "440px") =>
|
openSidePanel: (headerText, panelContent, panelWidth = "440px") =>
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import {
|
|||||||
OPEN_TABS_SUBCOMPONENT_NAME,
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
saveSubComponentState,
|
saveSubComponentState,
|
||||||
} from "Shared/AppStatePersistenceUtility";
|
} from "Shared/AppStatePersistenceUtility";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { CollectionTabKind } from "../Contracts/ViewModels";
|
import { CollectionTabKind } from "../Contracts/ViewModels";
|
||||||
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
||||||
@@ -52,198 +51,194 @@ export enum ReactTabKind {
|
|||||||
QueryCopilot,
|
QueryCopilot,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTabs = create<TabsState>()(
|
export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||||
subscribeWithSelector(
|
openedTabs: [] as TabsBase[],
|
||||||
(set, get) => ({
|
openedReactTabs: [ReactTabKind.Home],
|
||||||
openedTabs: [] as TabsBase[],
|
activeTab: undefined as TabsBase,
|
||||||
openedReactTabs: [ReactTabKind.Home],
|
activeReactTab: ReactTabKind.Home,
|
||||||
activeTab: undefined as TabsBase,
|
queryCopilotTabInitialInput: "",
|
||||||
activeReactTab: ReactTabKind.Home,
|
isTabExecuting: false,
|
||||||
queryCopilotTabInitialInput: "",
|
isQueryErrorThrown: false,
|
||||||
isTabExecuting: false,
|
activateTab: (tab: TabsBase): void => {
|
||||||
isQueryErrorThrown: false,
|
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
|
||||||
activateTab: (tab: TabsBase): void => {
|
set({ activeTab: tab, activeReactTab: undefined });
|
||||||
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
|
tab.onActivate();
|
||||||
set({ activeTab: tab, activeReactTab: undefined });
|
}
|
||||||
tab.onActivate();
|
},
|
||||||
|
activateNewTab: (tab: TabsBase): void => {
|
||||||
|
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined }));
|
||||||
|
tab.triggerPersistState = get().persistTabsState;
|
||||||
|
tab.onActivate();
|
||||||
|
get().persistTabsState();
|
||||||
|
},
|
||||||
|
activateReactTab: (tabKind: ReactTabKind): void => {
|
||||||
|
// Clear the selected node when switching to a react tab.
|
||||||
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
|
set({ activeTab: undefined, activeReactTab: tabKind });
|
||||||
|
},
|
||||||
|
updateTab: (tab: TabsBase) => {
|
||||||
|
if (get().activeTab?.tabId === tab.tabId) {
|
||||||
|
set({ activeTab: tab });
|
||||||
|
}
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
openedTabs: state.openedTabs.map((openedTab) => {
|
||||||
|
if (openedTab.tabId === tab.tabId) {
|
||||||
|
return tab;
|
||||||
}
|
}
|
||||||
},
|
return openedTab;
|
||||||
activateNewTab: (tab: TabsBase): void => {
|
}),
|
||||||
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined }));
|
}));
|
||||||
tab.triggerPersistState = get().persistTabsState;
|
},
|
||||||
tab.onActivate();
|
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] =>
|
||||||
get().persistTabsState();
|
get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))),
|
||||||
},
|
refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => {
|
||||||
activateReactTab: (tabKind: ReactTabKind): void => {
|
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
||||||
// Clear the selected node when switching to a react tab.
|
const activeTab = get().activeTab;
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
activeTab && comparator(activeTab) && activeTab.onActivate();
|
||||||
set({ activeTab: undefined, activeReactTab: tabKind });
|
},
|
||||||
},
|
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void =>
|
||||||
updateTab: (tab: TabsBase) => {
|
get()
|
||||||
if (get().activeTab?.tabId === tab.tabId) {
|
.openedTabs.filter(comparator)
|
||||||
set({ activeTab: tab });
|
.forEach((tab) => tab.onCloseTabButtonClick()),
|
||||||
|
closeTab: (tab: TabsBase): void => {
|
||||||
|
let tabIndex: number;
|
||||||
|
const { activeTab, openedTabs, openedReactTabs } = get();
|
||||||
|
const updatedTabs = openedTabs.filter((openedTab, index) => {
|
||||||
|
if (tab.tabId === openedTab.tabId) {
|
||||||
|
tabIndex = index;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (updatedTabs.length === 0 && !isFabricMirrored()) {
|
||||||
|
set({ activeTab: undefined, activeReactTab: undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab.tabId === activeTab?.tabId && tabIndex !== -1) {
|
||||||
|
const tabToTheRight = updatedTabs[tabIndex];
|
||||||
|
const lastOpenTab = updatedTabs[updatedTabs.length - 1];
|
||||||
|
const newActiveTab = tabToTheRight ?? lastOpenTab;
|
||||||
|
set({ activeTab: newActiveTab });
|
||||||
|
if (newActiveTab) {
|
||||||
|
newActiveTab.onActivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ openedTabs: updatedTabs });
|
||||||
|
|
||||||
|
if (updatedTabs.length === 0 && openedReactTabs.length > 0) {
|
||||||
|
set({ activeTab: undefined, activeReactTab: openedReactTabs[openedReactTabs.length - 1] });
|
||||||
|
}
|
||||||
|
|
||||||
|
get().persistTabsState();
|
||||||
|
},
|
||||||
|
closeAllNotebookTabs: (hardClose): void => {
|
||||||
|
const isNotebook = (tabKind: CollectionTabKind): boolean => {
|
||||||
|
if (
|
||||||
|
tabKind === CollectionTabKind.Notebook ||
|
||||||
|
tabKind === CollectionTabKind.NotebookV2 ||
|
||||||
|
tabKind === CollectionTabKind.SchemaAnalyzer ||
|
||||||
|
tabKind === CollectionTabKind.Terminal
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabList = get().openedTabs;
|
||||||
|
if (tabList && tabList.length > 0) {
|
||||||
|
tabList.forEach((tab: NotebookTabV2) => {
|
||||||
|
const tabKind: CollectionTabKind = tab.tabKind;
|
||||||
|
if (tabKind && isNotebook(tabKind)) {
|
||||||
|
tab.onCloseTabButtonClick(hardClose);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
set((state) => ({
|
if (get().openedTabs.length === 0 && !isFabricMirrored()) {
|
||||||
openedTabs: state.openedTabs.map((openedTab) => {
|
set({ activeTab: undefined, activeReactTab: undefined });
|
||||||
if (openedTab.tabId === tab.tabId) {
|
}
|
||||||
return tab;
|
}
|
||||||
}
|
},
|
||||||
return openedTab;
|
openAndActivateReactTab: (tabKind: ReactTabKind) => {
|
||||||
}),
|
if (get().openedReactTabs.indexOf(tabKind) === -1) {
|
||||||
}));
|
set((state) => ({
|
||||||
},
|
openedReactTabs: [...state.openedReactTabs, tabKind],
|
||||||
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] =>
|
}));
|
||||||
get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))),
|
}
|
||||||
refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => {
|
|
||||||
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
|
||||||
const activeTab = get().activeTab;
|
|
||||||
activeTab && comparator(activeTab) && activeTab.onActivate();
|
|
||||||
},
|
|
||||||
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void =>
|
|
||||||
get()
|
|
||||||
.openedTabs.filter(comparator)
|
|
||||||
.forEach((tab) => tab.onCloseTabButtonClick()),
|
|
||||||
closeTab: (tab: TabsBase): void => {
|
|
||||||
let tabIndex: number;
|
|
||||||
const { activeTab, openedTabs, openedReactTabs } = get();
|
|
||||||
const updatedTabs = openedTabs.filter((openedTab, index) => {
|
|
||||||
if (tab.tabId === openedTab.tabId) {
|
|
||||||
tabIndex = index;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
if (updatedTabs.length === 0 && !isFabricMirrored()) {
|
|
||||||
set({ activeTab: undefined, activeReactTab: undefined });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab.tabId === activeTab?.tabId && tabIndex !== -1) {
|
set({ activeTab: undefined, activeReactTab: tabKind });
|
||||||
const tabToTheRight = updatedTabs[tabIndex];
|
},
|
||||||
const lastOpenTab = updatedTabs[updatedTabs.length - 1];
|
closeReactTab: (tabKind: ReactTabKind) => {
|
||||||
const newActiveTab = tabToTheRight ?? lastOpenTab;
|
const { activeReactTab, openedTabs, openedReactTabs } = get();
|
||||||
set({ activeTab: newActiveTab });
|
const updatedOpenedReactTabs = openedReactTabs.filter((tab: ReactTabKind) => tabKind !== tab);
|
||||||
if (newActiveTab) {
|
if (activeReactTab === tabKind) {
|
||||||
newActiveTab.onActivate();
|
openedTabs?.length > 0
|
||||||
}
|
? set({ activeTab: openedTabs[0], activeReactTab: undefined })
|
||||||
}
|
: set({ activeTab: undefined, activeReactTab: updatedOpenedReactTabs[0] });
|
||||||
|
}
|
||||||
|
|
||||||
set({ openedTabs: updatedTabs });
|
set({ openedReactTabs: updatedOpenedReactTabs });
|
||||||
|
},
|
||||||
|
setQueryCopilotTabInitialInput: (input: string) => set({ queryCopilotTabInitialInput: input }),
|
||||||
|
setIsTabExecuting: (state: boolean) => {
|
||||||
|
set({ isTabExecuting: state });
|
||||||
|
},
|
||||||
|
setIsQueryErrorThrown: (state: boolean) => {
|
||||||
|
set({ isQueryErrorThrown: state });
|
||||||
|
},
|
||||||
|
getCurrentTabIndex: () => {
|
||||||
|
const state = get();
|
||||||
|
if (state.activeReactTab !== undefined) {
|
||||||
|
return state.openedReactTabs.indexOf(state.activeReactTab);
|
||||||
|
} else if (state.activeTab !== undefined) {
|
||||||
|
const nonReactTabIndex = state.openedTabs.indexOf(state.activeTab);
|
||||||
|
if (nonReactTabIndex !== -1) {
|
||||||
|
return state.openedReactTabs.length + nonReactTabIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (updatedTabs.length === 0 && openedReactTabs.length > 0) {
|
return -1;
|
||||||
set({ activeTab: undefined, activeReactTab: openedReactTabs[openedReactTabs.length - 1] });
|
},
|
||||||
}
|
selectTabByIndex: (index: number) => {
|
||||||
|
const state = get();
|
||||||
|
const totalTabCount = state.openedReactTabs.length + state.openedTabs.length;
|
||||||
|
const clampedIndex = clamp(index, totalTabCount - 1, 0);
|
||||||
|
|
||||||
get().persistTabsState();
|
if (clampedIndex < state.openedReactTabs.length) {
|
||||||
},
|
set({ activeTab: undefined, activeReactTab: state.openedReactTabs[clampedIndex] });
|
||||||
closeAllNotebookTabs: (hardClose): void => {
|
} else {
|
||||||
const isNotebook = (tabKind: CollectionTabKind): boolean => {
|
set({ activeTab: state.openedTabs[clampedIndex - state.openedReactTabs.length], activeReactTab: undefined });
|
||||||
if (
|
}
|
||||||
tabKind === CollectionTabKind.Notebook ||
|
},
|
||||||
tabKind === CollectionTabKind.NotebookV2 ||
|
selectLeftTab: () => {
|
||||||
tabKind === CollectionTabKind.SchemaAnalyzer ||
|
const state = get();
|
||||||
tabKind === CollectionTabKind.Terminal
|
state.selectTabByIndex(state.getCurrentTabIndex() - 1);
|
||||||
) {
|
},
|
||||||
return true;
|
selectRightTab: () => {
|
||||||
}
|
const state = get();
|
||||||
return false;
|
state.selectTabByIndex(state.getCurrentTabIndex() + 1);
|
||||||
};
|
},
|
||||||
|
closeActiveTab: () => {
|
||||||
|
const state = get();
|
||||||
|
if (state.activeReactTab !== undefined) {
|
||||||
|
state.closeReactTab(state.activeReactTab);
|
||||||
|
} else if (state.activeTab !== undefined) {
|
||||||
|
state.closeTab(state.activeTab);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeAllTabs: () => {
|
||||||
|
set({ openedTabs: [], openedReactTabs: [], activeTab: undefined, activeReactTab: undefined });
|
||||||
|
},
|
||||||
|
persistTabsState: () => {
|
||||||
|
const state = get();
|
||||||
|
const openTabsStates = state.openedTabs.map((tab) => tab.getPersistedState());
|
||||||
|
|
||||||
const tabList = get().openedTabs;
|
saveSubComponentState<OpenTab[]>(
|
||||||
if (tabList && tabList.length > 0) {
|
AppStateComponentNames.DataExplorerAction,
|
||||||
tabList.forEach((tab: NotebookTabV2) => {
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
const tabKind: CollectionTabKind = tab.tabKind;
|
undefined,
|
||||||
if (tabKind && isNotebook(tabKind)) {
|
openTabsStates,
|
||||||
tab.onCloseTabButtonClick(hardClose);
|
);
|
||||||
}
|
},
|
||||||
});
|
}));
|
||||||
|
|
||||||
if (get().openedTabs.length === 0 && !isFabricMirrored()) {
|
|
||||||
set({ activeTab: undefined, activeReactTab: undefined });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openAndActivateReactTab: (tabKind: ReactTabKind) => {
|
|
||||||
if (get().openedReactTabs.indexOf(tabKind) === -1) {
|
|
||||||
set((state) => ({
|
|
||||||
openedReactTabs: [...state.openedReactTabs, tabKind],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
set({ activeTab: undefined, activeReactTab: tabKind });
|
|
||||||
},
|
|
||||||
closeReactTab: (tabKind: ReactTabKind) => {
|
|
||||||
const { activeReactTab, openedTabs, openedReactTabs } = get();
|
|
||||||
const updatedOpenedReactTabs = openedReactTabs.filter((tab: ReactTabKind) => tabKind !== tab);
|
|
||||||
if (activeReactTab === tabKind) {
|
|
||||||
openedTabs?.length > 0
|
|
||||||
? set({ activeTab: openedTabs[0], activeReactTab: undefined })
|
|
||||||
: set({ activeTab: undefined, activeReactTab: updatedOpenedReactTabs[0] });
|
|
||||||
}
|
|
||||||
|
|
||||||
set({ openedReactTabs: updatedOpenedReactTabs });
|
|
||||||
},
|
|
||||||
setQueryCopilotTabInitialInput: (input: string) => set({ queryCopilotTabInitialInput: input }),
|
|
||||||
setIsTabExecuting: (state: boolean) => {
|
|
||||||
set({ isTabExecuting: state });
|
|
||||||
},
|
|
||||||
setIsQueryErrorThrown: (state: boolean) => {
|
|
||||||
set({ isQueryErrorThrown: state });
|
|
||||||
},
|
|
||||||
getCurrentTabIndex: () => {
|
|
||||||
const state = get();
|
|
||||||
if (state.activeReactTab !== undefined) {
|
|
||||||
return state.openedReactTabs.indexOf(state.activeReactTab);
|
|
||||||
} else if (state.activeTab !== undefined) {
|
|
||||||
const nonReactTabIndex = state.openedTabs.indexOf(state.activeTab);
|
|
||||||
if (nonReactTabIndex !== -1) {
|
|
||||||
return state.openedReactTabs.length + nonReactTabIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
},
|
|
||||||
selectTabByIndex: (index: number) => {
|
|
||||||
const state = get();
|
|
||||||
const totalTabCount = state.openedReactTabs.length + state.openedTabs.length;
|
|
||||||
const clampedIndex = clamp(index, totalTabCount - 1, 0);
|
|
||||||
|
|
||||||
if (clampedIndex < state.openedReactTabs.length) {
|
|
||||||
set({ activeTab: undefined, activeReactTab: state.openedReactTabs[clampedIndex] });
|
|
||||||
} else {
|
|
||||||
set({ activeTab: state.openedTabs[clampedIndex - state.openedReactTabs.length], activeReactTab: undefined });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectLeftTab: () => {
|
|
||||||
const state = get();
|
|
||||||
state.selectTabByIndex(state.getCurrentTabIndex() - 1);
|
|
||||||
},
|
|
||||||
selectRightTab: () => {
|
|
||||||
const state = get();
|
|
||||||
state.selectTabByIndex(state.getCurrentTabIndex() + 1);
|
|
||||||
},
|
|
||||||
closeActiveTab: () => {
|
|
||||||
const state = get();
|
|
||||||
if (state.activeReactTab !== undefined) {
|
|
||||||
state.closeReactTab(state.activeReactTab);
|
|
||||||
} else if (state.activeTab !== undefined) {
|
|
||||||
state.closeTab(state.activeTab);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closeAllTabs: () => {
|
|
||||||
set({ openedTabs: [], openedReactTabs: [], activeTab: undefined, activeReactTab: undefined });
|
|
||||||
},
|
|
||||||
persistTabsState: () => {
|
|
||||||
const state = get();
|
|
||||||
const openTabsStates = state.openedTabs.map((tab) => tab.getPersistedState());
|
|
||||||
|
|
||||||
saveSubComponentState<OpenTab[]>(
|
|
||||||
AppStateComponentNames.DataExplorerAction,
|
|
||||||
OPEN_TABS_SUBCOMPONENT_NAME,
|
|
||||||
undefined,
|
|
||||||
openTabsStates,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Collection } from "Contracts/ViewModels";
|
import { Collection } from "Contracts/ViewModels";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
interface TeachingBubbleState {
|
interface TeachingBubbleState {
|
||||||
step: number;
|
step: number;
|
||||||
@@ -12,7 +12,7 @@ interface TeachingBubbleState {
|
|||||||
setSampleCollection: (sampleCollection: Collection) => void;
|
setSampleCollection: (sampleCollection: Collection) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTeachingBubble = create<TeachingBubbleState>((set) => ({
|
export const useTeachingBubble: UseStore<TeachingBubbleState> = create((set) => ({
|
||||||
step: 1,
|
step: 1,
|
||||||
isSampleDBExpanded: false,
|
isSampleDBExpanded: false,
|
||||||
isDocumentsTabOpened: false,
|
isDocumentsTabOpened: false,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import postRobot from "post-robot";
|
import postRobot from "post-robot";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
interface TerminalState {
|
interface TerminalState {
|
||||||
terminalWindow: Window;
|
terminalWindow: Window;
|
||||||
@@ -7,7 +7,7 @@ interface TerminalState {
|
|||||||
sendMessage: (message: string) => void;
|
sendMessage: (message: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTerminal = create<TerminalState>((set, get) => ({
|
export const useTerminal: UseStore<TerminalState> = create((set, get) => ({
|
||||||
terminalWindow: undefined,
|
terminalWindow: undefined,
|
||||||
setTerminal: (terminalWindow: Window) => {
|
setTerminal: (terminalWindow: Window) => {
|
||||||
set({ terminalWindow });
|
set({ terminalWindow });
|
||||||
|
|||||||
291
src/quickstart-sql-only-http.html
Normal file
291
src/quickstart-sql-only-http.html
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<title>Azure Cosmos DB Emulator</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="divQuickStart">
|
||||||
|
<div class="Introlines">
|
||||||
|
<p class="Introline1">Congratulations! Your Azure Cosmos DB emulator is running.</p>
|
||||||
|
<p class="Introline2">Now, let's connect a sample app to it.</p>
|
||||||
|
<div id="divQuickStartConnections">
|
||||||
|
<p class="Introline2">URI</p>
|
||||||
|
<input type="text" class="codeblock" readonly="readonly" value="http://localhost:8081" />
|
||||||
|
<p class="Introline2">Primary Key</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="codeblock"
|
||||||
|
readonly="readonly"
|
||||||
|
value="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
|
||||||
|
/>
|
||||||
|
<p class="Introline2">Primary Connection String</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="codeblock"
|
||||||
|
readonly="readonly"
|
||||||
|
value="AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="Introline3"><b>Choose a platform</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ul class="nav nav-tabs qslevel">
|
||||||
|
<li class="active">
|
||||||
|
<a data-toggle="tab" href="#net"
|
||||||
|
><img class="qsmenuicons" src="../images/dotnet.png" alt=".NET platform" />.NET</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#corenet"
|
||||||
|
><img class="qsmenuicons" src="../images/dotnet.png" alt=".NET Core platform" />.NET Core</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#Java"
|
||||||
|
><img class="qsmenuicons" src="../images/java.png" alt="Java platform" />Java</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#NodeJs"
|
||||||
|
><img class="qsmenuicons" src="../images/nodejs.png" alt="Node.js platform" />Node.js</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#Python"
|
||||||
|
><img class="qsmenuicons" src="../images/python.png" alt="Python platform" />Python</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content tab-content-override">
|
||||||
|
<div id="net" class="tab-pane fade in active">
|
||||||
|
<div class="netApp">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Open and run a sample .NET app
|
||||||
|
<p>
|
||||||
|
We created a sample .NET app connected to your Azure Cosmos DB Emulator instance. Download, extract,
|
||||||
|
build and run the app.
|
||||||
|
</p>
|
||||||
|
<a href="quickstart/DocumentDB-Quickstart-DotNet.zip"
|
||||||
|
><button class="btncreatecoll">Download</button></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="netApp">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/dotnet"
|
||||||
|
>Code Samples</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity Planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="corenet" class="tab-pane fade">
|
||||||
|
<div class="netApp">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Open and run a sample .NET Core app
|
||||||
|
<p>
|
||||||
|
We created a sample .NET Core app connected to your Azure Cosmos DB Emulator instance. Download,
|
||||||
|
extract, build and run the app.
|
||||||
|
</p>
|
||||||
|
<a href="quickstart/DocumentDB-Quickstart-DotNetCore.zip"
|
||||||
|
><button class="btncreatecoll">Download</button></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="netApp">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/dotnet"
|
||||||
|
>Code Samples</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity Planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Java" class="tab-pane fade">
|
||||||
|
<div class="step1">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Open and run a sample Java app
|
||||||
|
<p>
|
||||||
|
We created a sample Java app connected to your Azure Cosmos DB Emulator instance. Download, extract,
|
||||||
|
build and run the app.
|
||||||
|
</p>
|
||||||
|
<a href="quickstart/DocumentDB-Quickstart-Java.zip"><button class="btncreatecoll">Download</button></a>
|
||||||
|
<p>
|
||||||
|
Follow instructions in the readme.md to setup prerequisites needed to run Java web apps, if you
|
||||||
|
haven’t already.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step1">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB.
|
||||||
|
<ul>
|
||||||
|
<!--<li><a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/java">Code Samples</a></li>-->
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity Planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="NodeJs" class="tab-pane fade">
|
||||||
|
<div class="step1">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Open and run a sample Node.js app
|
||||||
|
<p>
|
||||||
|
We created a sample Node.js app connected to your Azure Cosmos DB Emulator instance. Download,
|
||||||
|
extract, build and run the app.
|
||||||
|
</p>
|
||||||
|
<a href="quickstart/DocumentDB-Quickstart-NodeJs.zip"
|
||||||
|
><button class="btncreatecoll">Download</button></a
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Run <strong>npm install</strong> and <strong>npm start</strong>, and navigate to
|
||||||
|
<a href="http://localhost:3000" _targe="blank">http://localhost:3000</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step1">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/nodejs"
|
||||||
|
>Code Samples</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity Planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Python" class="tab-pane fade">
|
||||||
|
<div class="pythonApp">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Create a new Python app.
|
||||||
|
<p>
|
||||||
|
Follow this
|
||||||
|
<a href="https://aka.ms/cosmos-db-emulator/tutorial/python" target="_blank">tutorial</a>
|
||||||
|
to create a new Python app connected to Azure Cosmos DB.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pythonApp">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/python"
|
||||||
|
>Code Samples</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
class="atags"
|
||||||
|
href="https://social.msdn.microsoft.com/forums/azure/home?forum=AzureDocumentDB"
|
||||||
|
>Forum</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
291
src/quickstart-sql-only.html
Normal file
291
src/quickstart-sql-only.html
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<title>Azure Cosmos DB Emulator</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="divQuickStart">
|
||||||
|
<div class="Introlines">
|
||||||
|
<p class="Introline1">Congratulations! Your Azure Cosmos DB emulator is running.</p>
|
||||||
|
<p class="Introline2">Now, let's connect a sample app to it.</p>
|
||||||
|
<div id="divQuickStartConnections">
|
||||||
|
<p class="Introline2">URI</p>
|
||||||
|
<input type="text" class="codeblock" readonly="readonly" value="https://localhost:8081" />
|
||||||
|
<p class="Introline2">Primary Key</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="codeblock"
|
||||||
|
readonly="readonly"
|
||||||
|
value="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
|
||||||
|
/>
|
||||||
|
<p class="Introline2">Primary Connection String</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="codeblock"
|
||||||
|
readonly="readonly"
|
||||||
|
value="AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="Introline3"><b>Choose a platform</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ul class="nav nav-tabs qslevel">
|
||||||
|
<li class="active">
|
||||||
|
<a data-toggle="tab" href="#net"
|
||||||
|
><img class="qsmenuicons" src="../images/dotnet.png" alt=".NET platform" />.NET</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#corenet"
|
||||||
|
><img class="qsmenuicons" src="../images/dotnet.png" alt=".NET Core platform" />.NET Core</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#Java"
|
||||||
|
><img class="qsmenuicons" src="../images/java.png" alt="Java platform" />Java</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#NodeJs"
|
||||||
|
><img class="qsmenuicons" src="../images/nodejs.png" alt="Node.js platform" />Node.js</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#Python"
|
||||||
|
><img class="qsmenuicons" src="../images/python.png" alt="Python platform" />Python</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content tab-content-override">
|
||||||
|
<div id="net" class="tab-pane fade in active">
|
||||||
|
<div class="netApp">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Open and run a sample .NET app
|
||||||
|
<p>
|
||||||
|
We created a sample .NET app connected to your Azure Cosmos DB Emulator instance. Download, extract,
|
||||||
|
build and run the app.
|
||||||
|
</p>
|
||||||
|
<a href="quickstart/DocumentDB-Quickstart-DotNet.zip"
|
||||||
|
><button class="btncreatecoll">Download</button></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="netApp">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/dotnet"
|
||||||
|
>Code Samples</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity Planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="corenet" class="tab-pane fade">
|
||||||
|
<div class="netApp">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Open and run a sample .NET Core app
|
||||||
|
<p>
|
||||||
|
We created a sample .NET Core app connected to your Azure Cosmos DB Emulator instance. Download,
|
||||||
|
extract, build and run the app.
|
||||||
|
</p>
|
||||||
|
<a href="quickstart/DocumentDB-Quickstart-DotNetCore.zip"
|
||||||
|
><button class="btncreatecoll">Download</button></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="netApp">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/dotnet"
|
||||||
|
>Code Samples</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity Planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Java" class="tab-pane fade">
|
||||||
|
<div class="step1">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Open and run a sample Java app
|
||||||
|
<p>
|
||||||
|
We created a sample Java app connected to your Azure Cosmos DB Emulator instance. Download, extract,
|
||||||
|
build and run the app.
|
||||||
|
</p>
|
||||||
|
<a href="quickstart/DocumentDB-Quickstart-Java.zip"><button class="btncreatecoll">Download</button></a>
|
||||||
|
<p>
|
||||||
|
Follow instructions in the readme.md to setup prerequisites needed to run Java web apps, if you
|
||||||
|
haven’t already.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step1">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB.
|
||||||
|
<ul>
|
||||||
|
<!--<li><a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/java">Code Samples</a></li>-->
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity Planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="NodeJs" class="tab-pane fade">
|
||||||
|
<div class="step1">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Open and run a sample Node.js app
|
||||||
|
<p>
|
||||||
|
We created a sample Node.js app connected to your Azure Cosmos DB Emulator instance. Download,
|
||||||
|
extract, build and run the app.
|
||||||
|
</p>
|
||||||
|
<a href="quickstart/DocumentDB-Quickstart-NodeJs.zip"
|
||||||
|
><button class="btncreatecoll">Download</button></a
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Run <strong>npm install</strong> and <strong>npm start</strong>, and navigate to
|
||||||
|
<a href="http://localhost:3000" _targe="blank">http://localhost:3000</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step1">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/nodejs"
|
||||||
|
>Code Samples</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity Planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Python" class="tab-pane fade">
|
||||||
|
<div class="pythonApp">
|
||||||
|
<div class="numbersize numbersizePadding">1</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Create a new Python app.
|
||||||
|
<p>
|
||||||
|
Follow this
|
||||||
|
<a href="https://aka.ms/cosmos-db-emulator/tutorial/python" target="_blank">tutorial</a>
|
||||||
|
to create a new Python app connected to Azure Cosmos DB.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pythonApp">
|
||||||
|
<div class="numbersize">2</div>
|
||||||
|
<div class="numberheading">
|
||||||
|
Learn more about Azure Cosmos DB.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/python"
|
||||||
|
>Code Samples</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||||
|
>Capacity planner</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
class="atags"
|
||||||
|
href="https://social.msdn.microsoft.com/forums/azure/home?forum=AzureDocumentDB"
|
||||||
|
>Forum</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -15,18 +15,12 @@
|
|||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"lib": [
|
"lib": ["es5", "es6", "dom"],
|
||||||
"es5",
|
|
||||||
"es6",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": [
|
"types": ["jest"],
|
||||||
"jest"
|
|
||||||
],
|
|
||||||
"baseUrl": "src"
|
"baseUrl": "src"
|
||||||
},
|
},
|
||||||
"typedocOptions": {
|
"typedocOptions": {
|
||||||
@@ -43,14 +37,8 @@
|
|||||||
"includes": "./src/SelfServe/Documentation",
|
"includes": "./src/SelfServe/Documentation",
|
||||||
"disableSources": true
|
"disableSources": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src", "./src/**/*", "./utils/**/*"],
|
||||||
"src",
|
"exclude": ["./src/**/__mocks__/**/*", "./utils/local-proxy/**/*"],
|
||||||
"./src/**/*",
|
|
||||||
"./utils/**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"./src/**/__mocks__/**/*"
|
|
||||||
],
|
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "CommonJS"
|
"module": "CommonJS"
|
||||||
|
|||||||
6
utils/local-proxy/.gitignore
vendored
Normal file
6
utils/local-proxy/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
dist*
|
||||||
|
node_modules
|
||||||
|
*.cert
|
||||||
|
*.key
|
||||||
|
*.pfx
|
||||||
|
*.log
|
||||||
177
utils/local-proxy/index.js
Normal file
177
utils/local-proxy/index.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const { createProxyMiddleware } = require("http-proxy-middleware");
|
||||||
|
const { inspect } = require("util");
|
||||||
|
const fs = require("fs");
|
||||||
|
const https = require("https");
|
||||||
|
|
||||||
|
const conf = {};
|
||||||
|
conf.PORT = process.env.EXPLORER_PORT || 1234;
|
||||||
|
conf.LOG_LEVEL = process.env.LOG_LEVEL || "info";
|
||||||
|
conf.EMULATOR_ENDPOINT = process.env.EMULATOR_ENDPOINT || "http://localhost:8081";
|
||||||
|
conf.ENDPOINT_DISCOVERY_ENABLED = (process.env.ENDPOINT_DISCOVERY_ENABLED || "false").toLowerCase() === "true";
|
||||||
|
conf.GATEWAY_TLS_ENABLED = (process.env.GATEWAY_TLS_ENABLED || "false").toLowerCase() === "true";
|
||||||
|
conf.CERT_PATH = process.env.CERT_PATH;
|
||||||
|
conf.CERT_SECRET = process.env.CERT_SECRET;
|
||||||
|
|
||||||
|
const LOG_NUM = levelToNumber(conf.LOG_LEVEL);
|
||||||
|
function _log(level, msg, color) {
|
||||||
|
if (levelToNumber(level) >= LOG_NUM) {
|
||||||
|
console.log(`${colorToCode(color)}[${level || "debug"}]${msg}\x1b[0m`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _debug(msg, color) {
|
||||||
|
_log("debug", msg, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _info(msg, color) {
|
||||||
|
_log("info", msg, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _warn(msg, color) {
|
||||||
|
_log("warn", msg, color || "yellow");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _err(msg, color) {
|
||||||
|
_log("error", msg, color || "red");
|
||||||
|
}
|
||||||
|
|
||||||
|
function levelToNumber(level) {
|
||||||
|
switch (level) {
|
||||||
|
case "debug":
|
||||||
|
return 0;
|
||||||
|
case "info":
|
||||||
|
return 1;
|
||||||
|
case "warn":
|
||||||
|
return 2;
|
||||||
|
case "error":
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorToCode(color) {
|
||||||
|
switch (color) {
|
||||||
|
case "red":
|
||||||
|
return "\x1b[31m";
|
||||||
|
case "green":
|
||||||
|
return "\x1b[32m";
|
||||||
|
case "blue":
|
||||||
|
return "\x1b[34m";
|
||||||
|
case "yellow":
|
||||||
|
return "\x1b[33m";
|
||||||
|
default:
|
||||||
|
return "\x1b[0m";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusToColor(status) {
|
||||||
|
if (status < 300) {
|
||||||
|
return "green";
|
||||||
|
} else if (status < 400) {
|
||||||
|
return "blue";
|
||||||
|
} else {
|
||||||
|
return "red";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testEndpoint = () => {
|
||||||
|
fetch(conf.EMULATOR_ENDPOINT)
|
||||||
|
.then(async (res) => {
|
||||||
|
const body = await res.json();
|
||||||
|
_info("[EMU] Emulator is accessible");
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
_warn("[EMU] Emulator is not accessible");
|
||||||
|
_warn(`[EMU] ${inspect(e)}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
testEndpoint();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use((e, req, res, next) => {
|
||||||
|
_err(`[APP] ${inspect(e)}`);
|
||||||
|
res.status(500).json({ error: _err.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
req.startTime = new Date();
|
||||||
|
res.append("Access-Control-Allow-Origin", "*");
|
||||||
|
res.append("Access-Control-Allow-Credentials", "true");
|
||||||
|
res.append("Access-Control-Max-Age", "3600");
|
||||||
|
res.append("Access-Control-Allow-Headers", "*");
|
||||||
|
res.append("Access-Control-Allow-Methods", "*");
|
||||||
|
res.once("finish", () => {
|
||||||
|
const ms = new Date() - req.startTime;
|
||||||
|
(res.statusCode < 400 ? _debug : _err)(
|
||||||
|
`[APP] ${req.method} ${req.url} ${res.statusCode} - ${ms}ms`,
|
||||||
|
statusToColor(res.statusCode),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/_ready", (_, res) => {
|
||||||
|
res.status(200).send("Compilation complete.");
|
||||||
|
});
|
||||||
|
|
||||||
|
const appConf = {
|
||||||
|
PROXY_PATH: "/proxy",
|
||||||
|
EMULATOR_ENDPOINT: conf.EMULATOR_ENDPOINT,
|
||||||
|
platform: "VNextEmulator",
|
||||||
|
};
|
||||||
|
app.get("/config.json", (_, res) => {
|
||||||
|
res.status(200).json(appConf).end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const proxyProxy = createProxyMiddleware({
|
||||||
|
target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
logLevel: conf.LOG_LEVEL,
|
||||||
|
pathRewrite: { "^/proxy": "" },
|
||||||
|
router: (req) => {
|
||||||
|
if (conf.ENDPOINT_DISCOVERY_ENABLED) {
|
||||||
|
let newTarget = req.headers["x-ms-proxy-target"];
|
||||||
|
return newTarget;
|
||||||
|
} else {
|
||||||
|
return conf.EMULATOR_ENDPOINT;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use("/proxy", proxyProxy);
|
||||||
|
|
||||||
|
const unsupported = (req, res) => {
|
||||||
|
res.status(404).send("Unexpected operation. Please create issue.");
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: andersonc - I don't believe these are needed for emulator, should confirm and remove.
|
||||||
|
app.use("/api", unsupported);
|
||||||
|
app.use("/_explorer", unsupported);
|
||||||
|
app.use("/explorerProxy", unsupported);
|
||||||
|
app.use(`/${conf.AZURE_TENANT_ID}`, unsupported);
|
||||||
|
|
||||||
|
app.use(express.static("dist"));
|
||||||
|
|
||||||
|
_info(`[EMU] Expecting emulator on [${conf.EMULATOR_ENDPOINT}]`);
|
||||||
|
_info(`[APP] Listening on [${conf.PORT}]`);
|
||||||
|
if (conf.GATEWAY_TLS_ENABLED) {
|
||||||
|
if (!conf.CERT_PATH || !conf.CERT_SECRET) {
|
||||||
|
_err("[APP] Certificate path or secret not provided");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
pfx: fs.readFileSync(conf.CERT_PATH),
|
||||||
|
passphrase: conf.CERT_SECRET,
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = https.createServer(options, app);
|
||||||
|
server.listen(conf.PORT);
|
||||||
|
} else {
|
||||||
|
app.listen(conf.PORT);
|
||||||
|
}
|
||||||
1
utils/local-proxy/main.js
Normal file
1
utils/local-proxy/main.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
require('./index.js');
|
||||||
984
utils/local-proxy/package-lock.json
generated
Normal file
984
utils/local-proxy/package-lock.json
generated
Normal file
@@ -0,0 +1,984 @@
|
|||||||
|
{
|
||||||
|
"name": "local-proxy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "local-proxy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.21.1",
|
||||||
|
"http-proxy-middleware": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/http-proxy": {
|
||||||
|
"version": "1.17.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz",
|
||||||
|
"integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "22.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz",
|
||||||
|
"integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/accepts": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "~2.1.34",
|
||||||
|
"negotiator": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/array-flatten": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/body-parser": {
|
||||||
|
"version": "1.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||||
|
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"content-type": "~1.0.5",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"qs": "6.13.0",
|
||||||
|
"raw-body": "2.5.2",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/braces": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fill-range": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bytes": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bind": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"set-function-length": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-disposition": {
|
||||||
|
"version": "0.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "5.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-type": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||||
|
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-signature": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/define-data-property": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/depd": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/destroy": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ee-first": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/encodeurl": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"get-intrinsic": "^1.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escape-html": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/etag": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/express": {
|
||||||
|
"version": "4.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
||||||
|
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": "~1.3.8",
|
||||||
|
"array-flatten": "1.1.1",
|
||||||
|
"body-parser": "1.20.3",
|
||||||
|
"content-disposition": "0.5.4",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"cookie": "0.7.1",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"encodeurl": "~2.0.0",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"finalhandler": "1.3.1",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"merge-descriptors": "1.0.3",
|
||||||
|
"methods": "~1.1.2",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"path-to-regexp": "0.1.12",
|
||||||
|
"proxy-addr": "~2.0.7",
|
||||||
|
"qs": "6.13.0",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"send": "0.19.0",
|
||||||
|
"serve-static": "1.16.2",
|
||||||
|
"setprototypeof": "1.2.0",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"utils-merge": "1.0.1",
|
||||||
|
"vary": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fill-range": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"to-regex-range": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/finalhandler": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"encodeurl": "~2.0.0",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"unpipe": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/forwarded": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fresh": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"has-proto": "^1.0.1",
|
||||||
|
"has-symbols": "^1.0.3",
|
||||||
|
"hasown": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"get-intrinsic": "^1.1.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-property-descriptors": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-proto": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-errors": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"inherits": "2.0.4",
|
||||||
|
"setprototypeof": "1.2.0",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"toidentifier": "1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy": {
|
||||||
|
"version": "1.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||||
|
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^4.0.0",
|
||||||
|
"follow-redirects": "^1.0.0",
|
||||||
|
"requires-port": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy-middleware": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/http-proxy": "^1.17.15",
|
||||||
|
"debug": "^4.3.6",
|
||||||
|
"http-proxy": "^1.18.1",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"is-plain-object": "^5.0.0",
|
||||||
|
"micromatch": "^4.0.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy-middleware/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy-middleware/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/ipaddr.js": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-extglob": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-glob": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-extglob": "^2.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-number": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-plain-object": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/merge-descriptors": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/methods": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/micromatch": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"braces": "^3.0.3",
|
||||||
|
"picomatch": "^2.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-inspect": {
|
||||||
|
"version": "1.13.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
|
||||||
|
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/on-finished": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ee-first": "1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parseurl": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-to-regexp": {
|
||||||
|
"version": "0.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/picomatch": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/proxy-addr": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"forwarded": "0.2.0",
|
||||||
|
"ipaddr.js": "1.9.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/range-parser": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/raw-body": {
|
||||||
|
"version": "2.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||||
|
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/requires-port": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/send": {
|
||||||
|
"version": "0.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||||
|
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"mime": "1.6.0",
|
||||||
|
"ms": "2.1.3",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"statuses": "2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/send/node_modules/encodeurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/send/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/serve-static": {
|
||||||
|
"version": "1.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||||
|
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"encodeurl": "~2.0.0",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"send": "0.19.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/set-function-length": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"define-data-property": "^1.1.4",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"gopd": "^1.0.1",
|
||||||
|
"has-property-descriptors": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/setprototypeof": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/side-channel": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.7",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"object-inspect": "^1.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/statuses": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/to-regex-range": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-number": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/toidentifier": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||||
|
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/unpipe": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/utils-merge": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vary": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
utils/local-proxy/package.json
Normal file
17
utils/local-proxy/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "local-proxy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node main.js",
|
||||||
|
"pack": "cd ../.. && npm run build:proxy && cd utils/local-proxy"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.21.1",
|
||||||
|
"http-proxy-middleware": "^3.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
39
utils/local-proxy/readme.md
Normal file
39
utils/local-proxy/readme.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# local-proxy
|
||||||
|
|
||||||
|
Lightweight host for Cosmos Explorer
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
1. Pre-req - install packages for root project (`cd ../.. && npm ci && cd utils/local-proxy`)
|
||||||
|
2. Install - install packages for local-proxy (`npm ci`)
|
||||||
|
3. Pack - `npm run pack` - builds and packs Cosmos Explorer and copies files into project
|
||||||
|
4. Start - `npm start` - starts the proxy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ../..
|
||||||
|
npm ci
|
||||||
|
cd utils/local-proxy
|
||||||
|
npm ci
|
||||||
|
npm run pack
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
All config is current set via environment variables
|
||||||
|
|
||||||
|
| Name | Options (Default) | Description |
|
||||||
|
| ---------------------------- | ----------------------------------------- | ------------------------------------------------------------ |
|
||||||
|
| `PORT` | number (`1234`) | The port on which the proxy runs. |
|
||||||
|
| `LOG_LEVEL` | `debug`, `info`, `warn`, `error` (`info`) | The logging level for the proxy. |
|
||||||
|
| `EMULATOR_ENDPOINT` | string (`http://localhost:8081`) | The endpoint for the emulator which will be proxied. |
|
||||||
|
| `ENDPOINT_DISCOVERY_ENABLED` | boolean (`false`) | Determine whether the proxy will rewrite the endpoint or not |
|
||||||
|
|
||||||
|
## Dependenies
|
||||||
|
|
||||||
|
Node.js v20+
|
||||||
|
npm (optional)
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Copy the entire local-proxy directory to wherever you'd like. If you have npm, you can use `npm start`, else `node main.js`
|
||||||
41
utils/local-proxy/test/e2e/https/test_https.sh
Normal file
41
utils/local-proxy/test/e2e/https/test_https.sh
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
pushd $(dirname $0) > /dev/null
|
||||||
|
|
||||||
|
echo Creating self-signed certificate
|
||||||
|
|
||||||
|
# Create a self-signed certificate
|
||||||
|
|
||||||
|
export CERT_SECRET=$(openssl rand -base64 20)
|
||||||
|
openssl genrsa 2048 > host.key
|
||||||
|
chmod 400 host.key
|
||||||
|
#openssl req -new -x509 -nodes -sha256 -days 365 -key host.key -out host.cert --passin env:CERT_SECRET -subj "/C=US/ST=WA/L=BELLEVUE/O=Microsoft/OU=Azure Cosmos DB/CN=CHRIS ANDERSON/emailAddress=andersonc@microsoft.com"
|
||||||
|
openssl pkcs12 -export -out host.pfx -inkey host.key -in host.cert --passout env:CERT_SECRET --name "CHRIS ANDERSON"
|
||||||
|
|
||||||
|
export CERT_PATH=$(realpath host.pfx)
|
||||||
|
|
||||||
|
echo CERT_PATH=$CERT_PATH
|
||||||
|
|
||||||
|
popd > /dev/null
|
||||||
|
|
||||||
|
export GATEWAY_TLS_ENABLED=true
|
||||||
|
export EXPLORER_PORT=12345
|
||||||
|
|
||||||
|
# Use node to start so we can kill it later
|
||||||
|
node main.js > ./https-test.log &
|
||||||
|
node_pid=$!
|
||||||
|
echo node pid=$node_pid
|
||||||
|
|
||||||
|
sleep .5
|
||||||
|
|
||||||
|
output=$(curl --insecure -s "https://localhost:12345/_ready")
|
||||||
|
|
||||||
|
kill -KILL $node_pid
|
||||||
|
|
||||||
|
if [ "$output" != "Compilation complete." ]; then
|
||||||
|
echo "Failed to start HTTPS server"
|
||||||
|
cat ./https-test.log
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo https test completed
|
||||||
@@ -24,6 +24,8 @@ const AZURE_TENANT_ID = "72f988bf-86f1-41af-91ab-2d7cd011db47";
|
|||||||
const RESOURCE_GROUP = "de-e2e-tests";
|
const RESOURCE_GROUP = "de-e2e-tests";
|
||||||
const AZURE_CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET || process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET; // TODO Remove. Exists for backwards compat with old .env files. Prefer AZURE_CLIENT_SECRET
|
const AZURE_CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET || process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET; // TODO Remove. Exists for backwards compat with old .env files. Prefer AZURE_CLIENT_SECRET
|
||||||
|
|
||||||
|
const ishttps = process.env.GATEWAY_TLS_ENABLED !== "false"; // false -> false, true -> true, default -> true
|
||||||
|
|
||||||
if (!AZURE_CLIENT_SECRET) {
|
if (!AZURE_CLIENT_SECRET) {
|
||||||
console.warn("AZURE_CLIENT_SECRET is not set. testExplorer.html will not work.");
|
console.warn("AZURE_CLIENT_SECRET is not set. testExplorer.html will not work.");
|
||||||
}
|
}
|
||||||
@@ -131,11 +133,18 @@ module.exports = function (_env = {}, argv = {}) {
|
|||||||
template: "src/Terminal/index.html",
|
template: "src/Terminal/index.html",
|
||||||
chunks: ["terminal"],
|
chunks: ["terminal"],
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
//todo - dynamically include apis
|
||||||
filename: "quickstart.html",
|
ishttps
|
||||||
template: "src/quickstart.html",
|
? new HtmlWebpackPlugin({
|
||||||
chunks: ["quickstart"],
|
filename: "quickstart.html",
|
||||||
}),
|
template: "src/quickstart-sql-only.html",
|
||||||
|
chunks: ["quickstart"],
|
||||||
|
})
|
||||||
|
: new HtmlWebpackPlugin({
|
||||||
|
filename: "quickstart.html",
|
||||||
|
template: "src/quickstart-sql-only-http.html",
|
||||||
|
chunks: ["quickstart"],
|
||||||
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: "index.html",
|
filename: "index.html",
|
||||||
template: "src/index.html",
|
template: "src/index.html",
|
||||||
@@ -216,6 +225,14 @@ module.exports = function (_env = {}, argv = {}) {
|
|||||||
new EnvironmentPlugin(envVars),
|
new EnvironmentPlugin(envVars),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (process.env.EXPLORER_CONFIG_PATH) {
|
||||||
|
plugins.push(
|
||||||
|
new CopyWebpackPlugin({
|
||||||
|
patterns: [{ from: process.env.EXPLORER_CONFIG_PATH, to: "config.json" }],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (argv.analyze) {
|
if (argv.analyze) {
|
||||||
plugins.push(new BundleAnalyzerPlugin());
|
plugins.push(new BundleAnalyzerPlugin());
|
||||||
}
|
}
|
||||||
@@ -280,7 +297,7 @@ module.exports = function (_env = {}, argv = {}) {
|
|||||||
// disableHostCheck: true,
|
// disableHostCheck: true,
|
||||||
liveReload: !isCI,
|
liveReload: !isCI,
|
||||||
server: {
|
server: {
|
||||||
type: "https",
|
type: ishttps ? "https" : "http",
|
||||||
},
|
},
|
||||||
host: "0.0.0.0",
|
host: "0.0.0.0",
|
||||||
port: envVars.PORT,
|
port: envVars.PORT,
|
||||||
|
|||||||
Reference in New Issue
Block a user