Compare commits

...

172 Commits
1.8.0 ... 1.9.0

Author SHA1 Message Date
Cory Rivera
78a42e1d11 Check if python executable exists before querying user package directory. (#6345) 2019-07-09 18:43:32 -07:00
Cory Rivera
d2e758c0d7 Fix python install issues caused by other preexisting Python versions. (#6294)
* Remove --user option when doing pip installs for our standalone Python version.

* Use force-reinstall option when installing sparkmagic since we use a custom version.

* Use force-reinstall when installing pip packages from Manage Packages dialog so that dependencies don't get split across multiple locations.

* Update PATH after install to include additional package directories.
2019-07-09 17:13:47 -07:00
Chris LaFreniere
6f5ad3a8a3 Use in proc markdown by default (#6315) 2019-07-09 15:27:00 -07:00
Charles Gagnon
3e446980df Update CMS version for July release (#6312) 2019-07-09 14:16:46 -07:00
Alan Ren
053636af9c Update product.json (#6307) 2019-07-09 11:02:51 -07:00
Cory Rivera
e3b166846d Manually define JupyterServerInstallation execOptions field in notebook unit tests. (#6292) 2019-07-09 10:44:31 -07:00
Charles Gagnon
fcba0d1558 Bump Agent and Import package versions for July release (#6275) 2019-07-09 10:42:48 -07:00
Karl Burtram
35e3a42017 Fix inconsistencies in langpack readme files (#6285) 2019-07-09 10:41:18 -07:00
Karl Burtram
401d4b2211 Update localization resource files (#6283)
* Update localization resource files

* Remove extra space from readme headers
2019-07-09 10:40:28 -07:00
Zbyněk Sailer
be3e7e3dc1 LOC CHECKIN | Microsoft/azuredatastudio master | 20190708 (#6274) 2019-07-09 10:39:51 -07:00
Kevin Cunnane
1e12e61243 Fix #6221 notebook shortcut and use new grid in stable (#6268)
- Fix #6221 Notebooks: Keyboard Shortcut for New Notebook has Changed.
  - Use Win+Alt+N instead of Win+Shift+N
- New Grid is now "stable" (forgot to do this in last PR)
2019-07-09 10:35:42 -07:00
Kevin Cunnane
f3b12dd5ac Fix #6287 Notebook editor deserialize on reload/save is broken (#6288)
- Notebook editors have their own mode
2019-07-09 10:35:15 -07:00
Charles Gagnon
73bb5501bd Fix Agent tabs not switching
(cherry picked from commit 6f15ebcf6fe1f4976e82e3ee71cfba0d35fa2b7c)
2019-07-09 10:30:49 -07:00
Charles Gagnon
47e3761159 Fix connectionId remapping (#6269) 2019-07-05 20:42:03 +00:00
Gene Lee
cf4dd48784 Added Big Data Cluster Viewlet to ADS (#6204) 2019-07-03 21:52:19 -06:00
Aditya Bist
1404133283 Data explorer/context (#6242)
* fixed context for data explorer

* added more files

* initial servers and database context menu actions finished

* added all actions for servers and databases with correct conditions

* added nodetype and nodelabel for subtype actions

* added nodeinfo logic to oe shim

* fixed context for cms

* added all scripting actions to data explorer

* review comments

* fix import

* fix correct context key

* removed unused import

* fix typo
2019-07-03 17:33:34 -07:00
Aditya Bist
ecfcb92a89 Data explorer/context menu initial (#6264)
* fixed context for data explorer

* added more files

* initial servers and database context menu actions finished

* added all actions for servers and databases with correct conditions

* added nodetype and nodelabel for subtype actions

* added nodeinfo logic to oe shim

* fixed context for cms

* added all scripting actions to data explorer

* review comments

* fix import

* fix correct context key

* removed unused import

* separate PR for commands and menus added

* rename mssql context menu nodes

* remove command id constants
2019-07-03 16:03:09 -07:00
Kevin Cunnane
10b066d300 Share Notebook grid rendering with Query editor (#6241)
This is a staged refactor to use the exact same grid logic in the Notebook and query editors, including context menu support, font settings, and sizing logic. The goal long term is:
- As the core Query grid is updated, Notebook can benefit from the changes
- As we add in support for contributions like new buttons & actions working on the grid, can share the logic
- Ideally if and when we refactor things like the action bar for grid results, we can apply in both places though this is TBD.

Fixes a number of issues:
- Fixes #5755 Grids don't respond to font settings. @anthonydresser can we remove setting from each query results editor and just use Notebook Styles since these are global (not scoped) settings?
- Fixes #5501 Copy from grid settings. 
- Fixes #4938 SQL Notebook result sets are missing the actions provide for SQL File results sets. this now has the core ability to solve this, and separate work items for specific asks (serialization, charting) are tracked.

Currently hidden:
- Save as... options in context menu
- All right toolbar actions (save as, chart).

Remaining issues to address in future commits:
- Need to implement support for serialization (#5137). 
- Need to add charting support
- Need to solve the layout of buttons on the right hand side when a small number of columns are output. It doesn't look right that buttons are so far away from the results
  - Will work with UX on this. For now, mitigating this by hiding all buttons, but will need to solve in the future
- Would like to make buttons contributable via extension, but need to refactor similar to ObjectExplorer context menu so that we can serialize context menu options across to extension host while still having internal actions with full support
2019-07-03 14:34:03 -07:00
Cory Rivera
8a8cb3ab27 Clear fields on Add New Package page after getting No Valid Versions error. (#6261) 2019-07-03 14:29:26 -07:00
Anthony Dresser
f19f21d547 use file service for insights (#6248) 2019-07-03 13:45:19 -07:00
Anthony Dresser
92fbfcdac9 move edit data and query plan to their own files (#6256) 2019-07-03 13:45:04 -07:00
Cory Rivera
4189e761ff Fix bugs in selecting a system version of Python for Notebook dependencies (#6250) 2019-07-03 12:51:48 -07:00
Udeesha Gautam
a8b3f056a0 Add only non null changes to difference dictionary to ensure index doesnt mismatch (#6236) 2019-07-03 10:08:14 -07:00
Kevin Cunnane
cc6dea0631 Add Plotly output support to notebooks
With this change, Plotly types will be successfully rendered in a Notebook.
Currently they have a default width of 700px with a scrollbar if the window size is smaller (this matches other notebook viewers).
The Plotly library is dynamically required to avoid startup time perf hits. This is something we could look at for other components too.
2019-07-02 18:08:38 -07:00
Anthony Dresser
8c4f6f9e5f remove results serializer dependency on node (#6202) 2019-07-02 18:00:14 -07:00
Chris LaFreniere
708461eab5 Remove 'isMenu=true' from notebook toggle more (#6253) 2019-07-02 17:22:17 -07:00
Charles Gagnon
8ec1a05296 Change notebook to not save connections added through dropdown (#6254) 2019-07-03 00:11:02 +00:00
Chris LaFreniere
c4bf1b4180 Notebooks: Run all after/before (#6239)
* Run all above/below

* PR comments pre tests

* Added integration test
2019-07-02 16:49:12 -07:00
Anthony Dresser
495c9330f6 add event to capture state and reapply when necessary (#6246) 2019-07-02 15:52:42 -07:00
Charles Gagnon
49619e5b39 Clear Azure token if connection doesn't need it (#6244)
* Clear Azure token if connection doesn't need it

* Update function name
2019-07-02 21:01:53 +00:00
Karl Burtram
7b88800c62 Add a New File menu item for plain text files (#6240)
* Add a New File menu item for plain text files

* Correct handling of saved files

* Fix command palette text to avoid duplicate entry
2019-07-02 13:29:47 -07:00
Lucy Zhang
ecef90dc8b Book/externallink (#6215)
* show markdown preview

* open external link

* addressed Charles' comments
2019-07-02 09:30:48 -07:00
Chris LaFreniere
e8d4fba3c0 Support non-default font sizes in notebooks (#6222)
* Support non-default font sizes notebooks

* pr comments
2019-07-01 16:10:20 -07:00
Karl Burtram
384d87f84d Return an empty arrary from breakpoints API (#6235) 2019-07-01 15:22:58 -07:00
Charles Gagnon
8b349dbcde Change service installation messages to not steal focus (#6227)
* Change service installation messages to not steal focus

* Undo unnecessary changes to localized strings in serviceClient.ts

* Add default case for missing server event types
2019-07-01 22:11:14 +00:00
Kim Santiago
7f5e00fd81 Fix #6217 DacFx: Connection Dialog has no ConnectionProfile loaded (#6232)
* fix connection dialog not opening with connection profile

* bump extension version
2019-07-01 14:42:32 -07:00
Chris LaFreniere
e5858dee52 Make sure we sanitize the same way (#6233) 2019-07-01 14:05:50 -07:00
Chris LaFreniere
bae573453a Accessibility: Screen Reader Thinks SelectBox is a Button on Mac (#6216)
* Stop reading dropdowns as buttons for mac

* Remove role of combobox for sql selectbox
2019-07-01 13:46:06 -07:00
khoiph1
3e68c3ee0c Loc Update (#6223) 2019-07-01 12:45:29 -07:00
Maddy
e44e0a7c89 added scrollable directibe to the dashboard griod container (#6206) 2019-07-01 12:20:17 -07:00
Aditya Bist
678b2737bd CMS - SQL Login (#5989)
* initial SQL Login with save password working

* fix switching auth types

* remove metadata from package file

* allow editing connections for unsaved password connections

* review comments

* change thenables to async/awaits

* review comments

* changed thenables to promises

* remove authTypeChanged bool

* removed unused import

* review comments

* removed try catches

* cr comments

* review comments
2019-07-01 11:40:11 -07:00
Kim Santiago
6b5193908c Remove failing assert from Schema Compare tests (#6229)
* remove failing assert

* add TODO comment
2019-07-01 10:45:41 -07:00
Kevin Cunnane
87f1f11509 Fix tags issue where metadata was not preserved (#6219) 2019-07-01 10:10:42 -07:00
Charles Gagnon
0503c8d8fe Initial work to update telemetry to use Common Schema (#6203)
* Update admin-tool-ext-win to use new ads-extension-telemetry package

* Add AdsTelemetryService for sending telemetry events using the ADS Common Schema

* Clean up unused import and add engineType

* Address PR comments

* Update private var names
2019-06-30 19:38:04 +00:00
Karl Burtram
bc7ac519d0 Update extension resources and ENU XLF files (#6220)
* Add extension resources

* Update enu XLF resources
2019-06-28 19:09:23 -07:00
Udeesha Gautam
00c3758d86 Fixing backup restore launch bug #5797 (and a test) (#6218)
* Fix the launch of backup dialog in server context scenario

* Adding wait to ensure sc tasks complete before test exits
2019-06-28 17:54:04 -07:00
Kim Santiago
e5256b0a61 allow table width to be specified (#6196) 2019-06-28 15:55:58 -07:00
Lucy Zhang
eb3c6cadd2 show markdown preview (#6198) 2019-06-28 15:02:17 -07:00
Charles Gagnon
d701a20cd5 Bump SqlToolsService to 1.5.0-alpha.105 (#6209) 2019-06-28 18:16:49 +00:00
Karl Burtram
095f35d07e Prevent out of bounds splitview error (#6210) 2019-06-28 10:26:07 -07:00
Kim Santiago
53cd22f142 Add more validation for DacFx tests (#6120)
* add checking for tables and data

* addressing comments
2019-06-28 10:01:19 -07:00
Chris LaFreniere
8cf4120c27 Notebooks: Support for In-Proc Markdown Renderer (#6164)
* NB improve startup using built-in markdown render

This is a sample branch showing perf improvements if we load content using built-in markdown rendering
- Has issues where images aren't correctly rendered due to sanitization, need to copy renderer code and update settings
- Moves content load up before anythign to do with providers since we can render without knowing about these things

# Conflicts:
#	src/sql/workbench/parts/notebook/cellViews/textCell.component.ts

* Re-enable logging of each cell's rendering time

* Fix test issue

* Kernel loading working with new markdown renderer

# Conflicts:
#	src/sql/workbench/parts/notebook/cellViews/textCell.component.ts

* Fixed tests, cleaned up code

* markdownOutput component integration

* PR Comments

* PR feedback 2

* PR feedback again
2019-06-27 20:55:50 -07:00
Kim Santiago
b34e3cbe90 fix compare after opening scmp with dacpacs failing (#6201) 2019-06-27 18:26:02 -07:00
Karl Burtram
4ef25ecf37 Properly save and restore dynamic tab state (#6185)
* WIP

* WIP 2

* WIP 3

* Rework state capture implementation

* Break loop after setting
2019-06-27 16:14:28 -07:00
Udeesha Gautam
f5d647f05c Bug/toolbar icon revert (#6194)
* Change icon size rather than component size

* reverting the icon height impact
2019-06-27 13:53:05 -07:00
Anthony Dresser
7b6181de2a XML Formatter (#6182)
* add xml formatter extenstion

* remove unused imports
2019-06-27 12:20:19 -07:00
Alan Ren
20bbaa3fe6 Update package.json (#6187) 2019-06-27 10:58:12 -07:00
Chris LaFreniere
a2c9a0a1ae Addaria label to kernel and attach to dropdowns (#6181) 2019-06-27 10:19:22 -07:00
Lucy Zhang
98c6af628b New feature: Jupyter Books (#6095)
* Initial commit

* Fixed broken branch

* Show notebook titles in tree view

* Added  README

* sections showing in tree view

* Multiple books in treeview

* removed book extension, added to notebook

* removed book from extensions.ts

* addressed Chris' comments

* Addressed Charles' comments

* fixed spelling in readme

* added comment about same filenames

* adding vsix

* addressed Karl's comments
2019-06-27 10:10:30 -07:00
Alan Ren
f39647f243 add save/load filter feature to profiler (#6170)
* save/load profiler filter

* add role for custom buttons
2019-06-26 23:55:03 -07:00
Alan Ren
c2cec5d93f enable lang pack filter in extension manager (#6148)
* enable lang pack filter in extension manager

* fix null check issue
2019-06-26 23:17:35 -07:00
Charles Gagnon
5a11cf1a6f Filtering out some more high-frequency events (#6178) 2019-06-26 23:03:22 +00:00
Kim Santiago
b0b1b59147 Fix #6175: Schema compare doesn't always use scmp options (#6176)
* remove resetting options to dialog options before comparing

* bump azdata dependency version
2019-06-26 15:35:52 -07:00
Chris LaFreniere
77fb060fde Notebooks: Add Command + Keyboard Shortcut to Clear Outputs of Active Cell (#6169)
* Add command to clear cell output with test

* Fix typo

* PR Comments
2019-06-26 15:19:12 -07:00
Udeesha Gautam
caba5c9d26 Databases order in schema compare dialog to be same as OE (#6162)
* Database order to be same as OE

* changes to reset for source target buttons
2019-06-26 12:16:51 -07:00
Kevin Cunnane
97d36e2281 Support output renderers via Angular contributions (#6146)
- Added a registry for output components
- Refactored existing renderers to plug in via Angular
- Added Markdown renderer using new Angular contribution point
- Added support to notebook module to dynamically load new components
2019-06-26 11:32:24 -07:00
Maddy
32235b0cb6 Cluster management/newdashboard task (#6060)
* initial commit: added cluster status notebook and dashboard task

* following the previous naming conventions

* endpoint widget changes to accomodatw naming changes

* management-proxy/mgmtproxy chnages

* updates to address the comments and added the new copy image with hover text.

* added user select for making the table selectable

* localize changes

* added the final documented notebook

* reset execution_count to 0 for all cells

* style changes

* updated the url to point to private repo
2019-06-25 22:46:11 -07:00
Alan Yu
6142109bf5 Update Readme for anchor tags to Insiders Build for easy sharing (#6166) 2019-06-25 21:14:13 -07:00
Kim Santiago
144a7f941b Schema Compare open scmp file (#6118)
* can compare scmp with databases

* show error if can't connect to db

* excludes now work

* fixes after rebase and other small fixes

* Addressing comments

* fixes after rebasing

* fix excludes not always being remembered correctly

* fix switched check

* addressing comments
2019-06-25 17:30:07 -07:00
Kim Santiago
f01c318c30 Schema Compare save scmp file (#6150)
* initial changes

* send source and target excludes

* disable save scmp button until there is source and target

* addressing comments
2019-06-25 15:07:58 -07:00
Alan Ren
ac76302d6c Filter templates by supported engine type (#6133)
* Filter templates by supported engine type

* fix the azure sql db name
2019-06-24 23:37:56 -07:00
Alan Ren
a906a9c862 handle copy in all profiler tables (#6134)
* handle copy in all profiler tables

* use camel casing
2019-06-24 17:01:19 -07:00
Alan Ren
9a3daabeb4 add db name to xevent session and view (#6135) 2019-06-24 16:16:38 -07:00
Karl Burtram
9687159484 Add metadata tags to package.json (#6129) 2019-06-24 14:50:39 -07:00
Kim Santiago
6a0ffdfa60 Update dacpac and schema compare extensions to use getConnections() (#6131)
* update dacpac and schema compare extensions to use getConnections

* use more const

* make MSSQL a const

* changing name of mssql const

* add comment for name of parameter
2019-06-24 14:16:07 -07:00
Karl Burtram
e3f26e8f12 Update resource strings for 1.9.0 langpacks (#6144)
* Refresh loc resources

* Update loc strings
2019-06-24 13:34:30 -07:00
Kim Santiago
cf85bb14f5 Fix #5809: Data-tier wizard "Source Server" shouldn't show database name (#6125)
* Removing database name from server connection and adding required asterisk to database dropdowns

* also remove database name in flat file import wizard
2019-06-24 11:44:26 -07:00
Chris LaFreniere
4fe81d8449 Only set notebookEditor <a> color in scrollable portion (#6140) 2019-06-24 10:46:38 -07:00
Kim Santiago
46b8d55280 fix filepath getting regenerated ever time page is entered (#6132) 2019-06-24 09:52:05 -07:00
Alan Ren
08cf731c87 expand the detail section by selecting the headers (#6130)
* expand the detail section by selecting the headers

* use methods

* address comments
2019-06-22 00:29:02 -07:00
Udeesha Gautam
4c1af148c7 Feature/schemacompare cancel (#6104)
* Schema compare cancel changes for ADS

* adding a missed change

* Merge from Master

* Updating SqltoolsService version

* trying stress with bigger runtime

* trying one more stress fix with unique operation ids

* refactoring test a bit to ensure stress run works
2019-06-21 13:55:01 -07:00
Kevin Cunnane
83410565da Increase timeout to fix notebook integration test (#6117) 2019-06-21 10:08:10 -07:00
Alan Ren
de81c37611 new connect command (#6115)
* new connect command

* address comments
2019-06-20 23:08:12 -07:00
Karl Burtram
176719000d Bump eslint dependency (#6122) 2019-06-20 17:36:26 -07:00
Karl Burtram
1411ad4503 Fix modelview webview to work in query tab (#6119)
* WIP

* Rebuild webview when switching tabs

* Remove unneeded code

* Make ready promise private

* Undo edit in sendMessage

* Add null check prior to using ready promise

* Remove extra whitespace

* Rename parameter and fix strict null check errors
2019-06-20 16:28:32 -07:00
Anthony Dresser
77b351adf3 Add query editor view state (#6018)
* add query editor view state

* change commnet

* change state key name

* wip

* fix tests
2019-06-20 16:10:00 -07:00
Kevin Cunnane
b37b14eabd Improve notebook link handling (#6087)
* Improve notebook link handling
- Single click now works for links inside Output areas
- Command links in untrusted notebooks have link color
- Refactored to use directive so code is in 1 place and can be easily
  added elsewhere if needed

* Removed unneeded service from constructor
2019-06-20 11:40:12 -07:00
Kevin Cunnane
578ac6cae5 Add notebook open protocol handler (#6093)
Adds a protocol handler for notebook open, which can be used from browsers
Uses extension-based handler support so all URIs must start with `azuredatastudio://microsoft.notebook`

Adds 2 actions:
- `/new` opens a new empty notebook
- `/open` opens a HTTP/S file as an untitled notebook or text document

Sample URL:
```
azuredatastudio://microsoft.notebook/open?url=https%3A%2F%2Fraw.githubusercontent.com%2Fkevcunnane%2Fmsbuild_ads_demo%2Fmaster%2F0_YoAzdata.ipynb
```
2019-06-20 11:00:24 -07:00
Cory Rivera
72c3239d63 Allow user to select source package type in Manage Packages dialog. (#6092) 2019-06-20 10:51:36 -07:00
David Shiflet
99614ecc8f add --help documentation (#6112)
* add --help documentation

* hyphenate parameters
2019-06-20 13:05:38 -04:00
Aditya Bist
1ececc3035 Agent - Last step quit with success (#6034)
* turn strings to enums and allow changing step completion action

* fixed edit

* added tests for agent enums and fixed cms tests
2019-06-20 00:30:05 -07:00
Anup N. Kamath
433e5633cf Check provider type before throwing error message on cloud servers (#5948)
* check provider type in backup action

* check provider name in case of restore as well

* removed harcoding of constant
2019-06-19 22:53:54 -07:00
Alan Ren
b9a0c9ce7e getConnections API (#5651)
* getConnections

* update

* fix the condition check

* pr feedback

* fix test cases

* add test for the new method

* address comments
2019-06-19 22:51:53 -07:00
Kevin Cunnane
47cf496c36 Add Notebook Save integration tests (#6103)
* Add Notebook Save integration tests
Wrote test to verify save behavior
Fixed issues found during testing,
specifically around how we notify dirty state change to extensions

* Improved error messages
2019-06-19 16:09:24 -07:00
Kim Santiago
32313c71e4 Add ability to change source and target to Schema Compare (#6026)
* add ability to change source and target

* addressing comments

* fixes after rebasing

* add check for user

* bump extension version
2019-06-19 15:42:46 -07:00
Chris LaFreniere
453caa92d4 Fix for standard in hovering in code cell (#6107) 2019-06-19 15:02:03 -07:00
Charles Gagnon
7a689b93db Filter out high frequency command events (#6102) 2019-06-19 19:45:11 +00:00
Aditya Bist
639efbcfad Agent: Added loading component to schedule list (#5992)
* added loading component to schedule list

* changed thenable to await

* throw error
2019-06-19 12:32:14 -07:00
Kim Santiago
1c706fdfca align differences table with source label (#6094) 2019-06-19 09:56:48 -07:00
Charles Gagnon
fab8de632d Add EXTERNAL and FIRST_ROW to the keyword list for colorizing (#6097) 2019-06-19 14:44:29 +00:00
Kevin Cunnane
27cbd53253 Fix notebook dirty state after save (#6096)
Was getting a content changed loop in the events
Fix is to ignore save events from the input since it sends them
2019-06-18 17:38:20 -07:00
Kim Santiago
d67fd038dc DacFx integration tests (#6049)
* tests working

* add bacpac

* formatting

* addressing comments

* ignore bacpacs for hygiene check

* add check for error message when checking for db creation

* adding comments
2019-06-18 17:21:52 -07:00
Kevin Cunnane
36fe725cf0 Fix RunAllCells throwing unhandled exception (#6089)
- Added await here.
- There's a 2nd entry point already doing it the right way, in the Notebook extension.
  No need to change that
2019-06-18 16:19:32 -07:00
Charles Gagnon
373c3488bb Fix Add Azure Account dialog constantly reappearing (#6048) 2019-06-18 21:48:11 +00:00
Alan Ren
58e5e095e5 add PGSQL to integration test (#6040)
* Verify providers in integration test

* include pgsql
2019-06-18 14:35:08 -07:00
Charles Gagnon
9e7282d16a Connection dialog cleanup (#6076)
* Update names to be clearer and remove some unnecessary code

* Remove unused/inaccurate CMS display name value
2019-06-18 21:33:21 +00:00
Karl Burtram
561b7575ba Fix arith abort default value (#6080) 2019-06-18 14:11:15 -07:00
Cory Rivera
cecc899949 Disable Manage Packages button if python is not installed (#6008) 2019-06-17 18:28:16 -07:00
Charles Gagnon
59b0e6737f Update provider correctly when showing the dialog (#5995) 2019-06-17 15:32:36 +00:00
Aditya Bist
449cd9ea27 added run job context menu to jobs view page (#6011) 2019-06-14 16:20:49 -07:00
Aditya Bist
256ef072df fix header options (#6036) 2019-06-14 15:45:55 -07:00
Karl Burtram
26d8b32717 Fix lang pack names and versions (#6038) 2019-06-14 14:22:47 -07:00
Anthony Dresser
7a31d66d2c ensure we set group for both editors (#6016) 2019-06-14 14:00:28 -07:00
Karl Burtram
2ed9a93bae Add initial lang pack resources (#6035)
* Initial vs code lang packs

* Update resource to merge in ADS-specific strings
2019-06-14 13:38:04 -07:00
udeeshagautam
f494c7af4e Schema compare Icon and other fixes (#6009)
* Changes to 1. Enable Icon for Schema Compare model view editor 2. Set context setting in editable drop down 3. Fix a console error

* new icons

* Changes as per PR comments

* Adding PR comments

* Fixing a spelling mistake
2019-06-14 13:28:46 -07:00
Charles Gagnon
363af2a85c Add telemetry for all commands (#6025) 2019-06-14 16:16:21 +00:00
Kevin Cunnane
6ff34d9894 Fix #6022 connection dialog broken (#6023)
Opening connection dialog was broken if you have a postgres connection
listed in the Connections view and the extension isn't installed.
The capabilities service breaks as it returns an undefined object.
Added handling, so now it does show as "loading..." forever
but doesn't crash
2019-06-13 16:50:00 -07:00
Aditya Bist
a566fa9728 Agent - fix edit step (#6010)
* made all jobs get focus and keyboard friendly

* added tooltip for steps image

* made operator dialog accessible

* change job name as soon as job name changes in text box
2019-06-13 14:08:58 -07:00
Kim Santiago
248f2f5071 Formatting diff editor title (#6013)
* add background color and more formatting to diff editor title

* also handle hc-black
2019-06-13 13:07:52 -07:00
Gene Lee
a79f1ac830 Added 'New Notebook' task on database dashboard (#5996) 2019-06-12 18:33:52 -07:00
Aditya Bist
073a372d4d Agent: Accessibility bugs 2 (#5994)
* made all jobs get focus and keyboard friendly

* added tooltip for steps image

* made operator dialog accessible

* localized tooltip
2019-06-12 14:40:57 -07:00
udeeshagautam
6c69eaef4c Make dropdown editable but not allow okay till a valid value is selected (#5991)
Make dropdown editable but not allow OK till a valid value is selected
2019-06-12 10:18:05 -07:00
Alan Ren
b0fdaedfdb fix the sqldb OE test (#6006) 2019-06-12 09:04:13 -07:00
Alan Ren
d089d6642a fix the typo (#5997) 2019-06-11 23:17:07 -07:00
Charles Gagnon
5aa730b5d4 Add logging to try and find a possible connection race condition (#5813)
* Add logging to try and find a possible connection race condition

* Fix tests

* More test fixes

* Update error message and use error level
2019-06-12 00:08:51 +00:00
Karl Burtram
0832dd2a45 Add SCHEMA to colorization list (#5993) 2019-06-11 16:57:20 -07:00
Aditya Bist
a03507c998 added option to force reload extensions (#5990) 2019-06-11 16:12:41 -07:00
Kevin Cunnane
f1e38b655e Fix failing integration test due to notebook context menu (#5986)
- Updated test baselines
- Removed duplicate 'Standard SQL DB context menu test'
 -  it's identical to Azure test
 - Standalone database context menu test covers non-Azure
2019-06-11 16:09:12 -07:00
Charles Gagnon
33a9f2e3e4 Plumb editor state change through to state object (#5931) 2019-06-11 23:05:40 +00:00
Charles Gagnon
eaa5f504e3 Remove unneeded dev dependency clean-css (#5937) 2019-06-11 23:01:01 +00:00
Karl Burtram
5a7562a37b Revert "Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d (#5949)" (#5983)
This reverts commit d15a3fcc98.
2019-06-11 12:35:58 -07:00
Kim Santiago
95a50b7892 Remove DacFx deploy action page (#5942)
* remove deploy action page since generated script is opened instead of saved now

* bump sqltoolsservice version to 1.5.0-alpha.100
2019-06-11 11:16:48 -07:00
Chris LaFreniere
86a3217e98 Notebooks: Log telemetry when all markdown cells rendered (#5862)
* Log telemetry when all markdown cells rendered

* Enable referencing previous telemetry timestamps

* Fix broken unit test to do a null check

* Undo loading icon changes in textcelll

* Addressing PR comments

* PR comments II
2019-06-10 21:55:49 -07:00
Chris LaFreniere
d15a3fcc98 Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d (#5949)
* Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d

* Fix vs unit tests and hygiene issue

* Fix strict null check issue
2019-06-10 18:27:09 -07:00
Cory Rivera
ff38bc8143 Add dialog to notebooks for managing Pip packages (#5944) 2019-06-10 17:02:31 -07:00
Kevin Cunnane
14a6bf581c Fix New Notebook issues (#5958)
* Fix New Notebook issues
- Fix #5338 New Notebook menu item should be next to New Query
- Fix #4936 Add a shortcut to create a notebook in the document well

Created a built-in New Notebook command
that routes to the existing extension-based command.
This avoided a rearchitecture that was more complex that seemed worth it.
Per VSCode patterns, used a _ modifier for the existing command so it's "private"
2019-06-10 16:38:07 -07:00
Kevin Cunnane
730ad4b814 Fix outputs constantly focusing on new output (#5959)
- Only scroll if it's the 1st output, not for subsequent ones
- Otherwise can't use notebook while a cell is running & regularly updating the outputs
2019-06-10 16:28:59 -07:00
Kevin Cunnane
f05260d95a Fix #5379 markdown Gray code makes code look wrong (#5954) 2019-06-10 14:29:29 -07:00
Charles Gagnon
673ecc3870 Change hardcoded MSSQL provider name to use constant instead (#5953) 2019-06-10 18:10:39 +00:00
Kim Santiago
97a37e6834 Disable schema compare generate script and apply buttons when no changes included (#5923)
* disable generate script and apply buttons when no changes are included

* addressing comments
2019-06-07 15:30:20 -07:00
Aditya Bist
d9b48bae80 Agent - Accessibility Bugs (WIP) (#5807)
* fix accessbility issue where tabbing would get wrong focus

* dialogs open one at a time

* get focus on filter headers

* added tool tips to proxy dialog

* added labels to step dialog
2019-06-07 09:41:00 -07:00
Charles Gagnon
cbaa0a132f Bump to 1.9.0 for July release (#5925) 2019-06-06 23:43:42 +00:00
Charles Gagnon
4ad5520568 Update azdata engine checks to include all future versions (#5924) 2019-06-06 22:36:43 +00:00
Gene Lee
43457c0184 Fixed run cell error message in Notebook (#5912) 2019-06-06 13:18:50 -07:00
Alan Ren
1150433c0a update the strings (#5904)
* update the strings

* PR comments and remove the workaround
2019-06-06 13:03:03 -07:00
Anthony Dresser
76a84a2cf4 fix dimensions of the query editor (#5910) 2019-06-06 12:31:12 -07:00
Charles Gagnon
68328f65b5 Update README and CHANGELOG for June 2019 Release (#5913)
* Update README and CHANGELOG for June 2019 Release

* Add contribution thank-you
2019-06-06 18:28:21 +00:00
Charles Gagnon
b7956c5fbf Update admin-tool-ext-win README (#5911) 2019-06-06 01:17:59 +00:00
Kevin Cunnane
44d6bb66da Add saved and executed events to notebook changed (#5848)
- Updated the notebook API to add a change kind, and support saved, executed and other simplified status
- Plumbed this through to the main thread classes
- Support sending the events from cell / input to the notebook model so they loop over the extension host as a content changed event
- Add executed event from the cell
2019-06-05 16:34:26 -07:00
Charles Gagnon
7d67711336 Update admin-tool-ext-win extension to require ADS 1.8.0 (#5905)
* Update admin-tool-ext-win extension to require ADS 1.8.0

* Use correct version check
2019-06-05 23:15:01 +00:00
Charles Gagnon
f320deaa73 More CMS test fixes (#5901) 2019-06-05 22:22:30 +00:00
Gene Lee
a518c4a529 Making Notebook to scroll to output area only when Notebook command is executed (#5893) 2019-06-05 12:07:31 -07:00
Chris LaFreniere
da164cec0a remove tabindex=0 for notebook component (#5851) 2019-06-05 08:28:00 -07:00
Charles Gagnon
bb470c3676 Fix CMS tests (#5897)
* Fix CMS tests

* Fix spacing and format
2019-06-05 06:07:13 +00:00
Aditya Bist
685a608518 Make sure the first Connection Dialog has correct provider. (#5884)
* force a restart for cms

* remove unneeded existing conditional

* fix for random provider when opening a connection dialog
2019-06-04 17:11:36 -07:00
Charles Gagnon
5be2121a3e Only run pipeline tests if all previous steps succeeded (#5886) 2019-06-04 23:22:22 +00:00
Gene Lee
bf643cc85f Fixed bug: Execute cell should scroll to its results (#5861) 2019-06-04 16:03:55 -07:00
Anthony Dresser
eda96c046a fix strict-null check (#5878) 2019-06-04 15:31:34 -07:00
Charles Gagnon
137c78c04e Remove VS redist license
This isn't necessary since we aren't providing the VS binaries as redist-able. VS has signed off as confirming that the main extension license is enough to cover the VS binaries as well. (#5880)
2019-06-04 22:20:43 +00:00
Karl Burtram
d9e1aa57c9 Bump SQL Tools Service to 1.5.0-alpha.99 (#5879) 2019-06-04 14:17:13 -07:00
Anthony Dresser
912c80e496 store active tab so it isn't overwritten (#5875) 2019-06-04 12:55:55 -07:00
Anthony Dresser
7390dce536 Fix handling of state in the grid panel (#5867)
* fix handling of state in the grid panel

* trigger rebuild

* trigger rebuild
2019-06-04 12:55:20 -07:00
Anthony Dresser
67859ab139 dont focus panels when they are shown; dont swallow keys for message panel (#5872) 2019-06-04 12:54:54 -07:00
Anthony Dresser
540635c54f make sure we update our sizes when we update the size of items (#5874) 2019-06-04 12:53:17 -07:00
Aditya Bist
6197279e83 remove SQL Login from CMS and add error messages (#5873) 2019-06-04 12:52:46 -07:00
Anthony Dresser
4ad226570a Add no implicit any to the strict null check (#5635)
* wip

* working through adding no implicit any
2019-06-04 09:29:40 -07:00
Charles Gagnon
50242b2c35 Add test for opening existing SQL file and typing text into it (#5816)
* Add test for opening existing SQL file and typing text into it

* Clean up

* More cleanup, remove unneeded queryEditor and add smoke test scripts

* Update comments to be clearer
2019-06-04 02:42:39 +00:00
Gene Lee
9f7d96bad3 Fixed bug: Notebooks Vertical Scrollbar is Unnecessary for Some Grid Outputs (#5847) 2019-06-03 16:47:07 -07:00
Chris LaFreniere
8d70544374 Notebooks: Adding Change Kernel API, 3 Integration Tests (#5287)
* Notebook change kernel

* Fix notifying of k change too many times add tests

* Fix broken unit test

* Deal with comment
2019-06-03 14:49:40 -07:00
Charles Gagnon
4b6214c9a4 Remove install count widget from extension gallery view until we can actually have install counts (#5828) 2019-06-03 14:48:26 -07:00
Alan Ren
aaa2ef3a97 Alanren/tool check (#5810)
* tool check

* tools table update

* buttons

* update tools table

* remove tool status check

* updates

* PR comments
2019-06-03 14:32:10 -07:00
Charles Gagnon
639bd5a550 Fix typo in setting description (#5837) 2019-06-03 14:31:24 -07:00
3497 changed files with 191122 additions and 57695 deletions

View File

@@ -1,5 +1,26 @@
# Change Log
## Version 1.8.0
* Release date: June 6, 2019
* Release status: General Availability
## What's new in this version
* Initial release of the Database Admin Tool Extensions for Windows *Preview* extension
* Initial release of the Central Management Servers extension
* **Schema Compare**
* Added Exclude/Include Options
* Generate Script opens script after being generated
* Removed double scroll bars
* Formatting and layout improvements
* Complete changes can be found [here](https://github.com/microsoft/azuredatastudio/issues?q=is%3Aissue+milestone%3A%22June+2019+Release%22+label%3A%22Area%3A+Schema+Compare%22+is%3Aclosed)
* Messages panel moved into results panel - when users ran SQL queries, results and messages were in stacked panels. Now they are in separate tabs in a single panel similar to SSMS.
* **Notebook**
* Users can now choose to use their own Python 3 or Anaconda installs in notebooks
* Multiple Stability + fit/finish fixes
* View the full list of improvements and fixes [here](https://github.com/microsoft/azuredatastudio/issues?q=is%3Aissue+milestone%3A%22June+2019+Release%22+is%3Aclosed+label%3A%22Area%3A+Notebooks%22)
* Visual Studio Code May Release Merge 1.34 - the latest improvements can be found [here](https://code.visualstudio.com/updates/v1_34)
* Resolved [bugs and issues](https://github.com/microsoft/azuredatastudio/milestone/32?closed=1).
## Version 1.7.0
* Release date: May 8, 2019
* Release status: General Availability

View File

@@ -5,21 +5,21 @@
Azure Data Studio is a data management tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
**Download the latest Azure Data Studio release**
## **Download the latest Azure Data Studio release**
Platform | Link
-- | --
Windows User Installer | https://go.microsoft.com/fwlink/?linkid=2091882
Windows System Installer | https://go.microsoft.com/fwlink/?linkid=2091491
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2091490
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2091489
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2091488
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2091487
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2092022
Windows User Installer | https://go.microsoft.com/fwlink/?linkid=2094100
Windows System Installer | https://go.microsoft.com/fwlink/?linkid=2094200
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2094201
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2094202
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2094101
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2094102
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2094203
Go to our [download page](https://aka.ms/azuredatastudio) for more specific instructions.
Try out the latest insiders build from `master`:
## Try out the latest insiders build from `master`:
- [Windows User Installer - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-user/insider)
- [Windows System Installer - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/win32-x64/insider)
- [Windows ZIP - **Insiders build**](https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-archive/insider)
@@ -28,7 +28,7 @@ Try out the latest insiders build from `master`:
See the [change log](https://github.com/Microsoft/azuredatastudio/blob/master/CHANGELOG.md) for additional details of what's in this release.
**Feature Highlights**
## **Feature Highlights**
- Cross-Platform DB management for Windows, macOS and Linux with simple XCopy deployment
- SQL Server Connection Management with Connection Dialog, Server Groups, Azure Integration and Registered Servers
@@ -68,7 +68,8 @@ The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.micro
## Contributions and "Thank You"
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* yamatoya for `fix the format (#4899)`
* Stevoni for `Corrected Keyboard Shortcut Execution Issue #5480`
* yamatoya for `fix the format #4899`
* GeoffYoung for `Fix sqlDropColumn description #4422`
* AlexFsmn for `Added context menu for DBs in explorer view to backup & restore db. #2277`
* sadedil for `Missing feature request: Save as XML #3729`

View File

@@ -46,7 +46,6 @@ expressly granted herein, whether by implication, estoppel or otherwise.
node-fetch: https://github.com/bitinn/node-fetch
node-pty: https://github.com/Tyriar/node-pty
nsfw: https://github.com/Axosoft/nsfw
pretty-data: https://github.com/vkiryukhin/pretty-data
primeng: https://github.com/primefaces/primeng
process-nextick-args: https://github.com/calvinmetcalf/process-nextick-args
pty.js: https://github.com/chjj/pty.js
@@ -1420,16 +1419,6 @@ SOFTWARE.
=========================================
END OF nsfw NOTICES AND INFORMATION
%% pretty-data NOTICES AND INFORMATION BEGIN HERE
=========================================
License: Dual licensed under the MIT and GPL licenses:
http://www.opensource.org/licenses/mit-license.php
http://www.gnu.org/licenses/gpl.html
=========================================
END OF pretty-data NOTICES AND INFORMATION
%% primeng NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)

View File

@@ -46,12 +46,12 @@ steps:
- script: |
DISPLAY=:10 ./scripts/test.sh --reporter mocha-junit-reporter
displayName: 'Tests'
condition: eq(variables['Agent.OS'], 'Linux')
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- script: |
DISPLAY=:10 ./scripts/test.sh --reporter mocha-junit-reporter --coverage
displayName: 'Tests'
condition: ne(variables['Agent.OS'], 'Linux')
condition: and(succeeded(), ne(variables['Agent.OS'], 'Linux'))
- task: PublishTestResults@2
inputs:

View File

@@ -92,11 +92,12 @@ const indentationFilter = [
'!**/*.dockerfile',
'!extensions/markdown-language-features/media/*.js',
// {{SQL CARBON EDIT}}
'!**/*.{xlf,docx,sql,vsix}',
'!**/*.{xlf,docx,sql,vsix,bacpac}',
'!extensions/mssql/sqltoolsservice/**',
'!extensions/import/flatfileimportservice/**',
'!extensions/admin-tool-ext-win/ssmsmin/**',
'!extensions/resource-deployment/notebooks/**'
'!extensions/resource-deployment/notebooks/**',
'!extensions/mssql/notebooks/**'
];
const copyrightFilter = [
@@ -156,7 +157,8 @@ const copyrightFilter = [
'!extensions/notebook/resources/jupyter_config/**',
'!**/*.gif',
'!**/*.xlf',
'!**/*.dacpac'
'!**/*.dacpac',
'!**/*.bacpac'
];
const eslintFilter = [

View File

@@ -526,10 +526,14 @@ gulp.task('vscode-translations-pull', function () {
gulp.task('vscode-translations-import', function () {
// {{SQL CARBON EDIT}} - Replace function body with our own
[...i18n.defaultLanguages, ...i18n.extraLanguages].forEach(language => {
gulp.src(`../vscode-localization/${language.id}/build/*/*.xlf`)
.pipe(i18n.prepareI18nFiles())
.pipe(vfs.dest(`./i18n/${language.folderName}`));
return new Promise(function(resolve) {
[...i18n.defaultLanguages, ...i18n.extraLanguages].forEach(language => {
let languageId = language.translationId ? language.translationId : language.id;
gulp.src(`resources/xlf/${languageId}/**/*.xlf`)
.pipe(i18n.prepareI18nFiles())
.pipe(vfs.dest(`./i18n/${language.folderName}`));
resolve();
});
});
// {{SQL CARBON EDIT}} - End
});

View File

@@ -376,7 +376,7 @@ export function packageExtensionsStream(optsIn?: IPackageExtensionsOptions): Nod
];
const localExtensionDependencies = () => gulp.src(extensionDepsSrc, { base: '.', dot: true })
.pipe(filter(['**', '!**/package-lock.json']))
.pipe(filter(['**', '!**/package-lock.json']));
// Original code commented out here
// const localExtensionDependencies = () => gulp.src('extensions/node_modules/**', { base: '.' });

View File

@@ -1,9 +1,26 @@
# Database Admin Tool Extensions for Windows
# Database Admin Tool Extensions for Windows *(preview)*
This adds Windows-specific functionality into Azure Data Studio.
The Database Admin Tool Extensions for Windows adds Windows-specific functionality into Azure Data Studio. Currently this
functionality includes the ability to launch a set of SQL Server Management Studio experiences directly from Azure Data Studio.
These experiences include:
* SSMS Property dialogs for select object types, such as Databases, Views, Stored Procedures and more
* The [Generate Scripts Wizard](https://docs.microsoft.com/en-us/sql/ssms/scripting/generate-and-publish-scripts-wizard)
### How do I launch these experiences?
Both of these are available as menu items on the context menu for nodes in the Object Explorer tree. Right click on a node that supports one of the experiences and select the appropriate item.
**Properties** for the property dialogs of supported object types
![Properties](https://user-images.githubusercontent.com/28519865/58999549-13a93080-87bb-11e9-82e4-6dd3f5de5c13.png)
**Generate Scripts...** for the Generate Scripts Wizard (only available on Database nodes)
![Generate Scripts Wizard](https://user-images.githubusercontent.com/28519865/58999482-e2306500-87ba-11e9-9f21-6c5a4996e529.png)
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Privacy Statement

View File

@@ -1,3 +0,0 @@
Distributable Code for Microsoft Visual Studio 2017 and Microsoft Visual Studio 2017 SDK (Includes Utilities & BuildServer Files)
For the latest version of this Redist file, please visit http://go.microsoft.com/fwlink/?LinkId=823098

View File

@@ -10,7 +10,7 @@
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"engines": {
"vscode": "^1.30.1",
"azdata": "*"
"azdata": ">=1.8.0"
},
"activationEvents": [
"*"
@@ -70,10 +70,15 @@
]
},
"dependencies": {
"vscode-extension-telemetry": "^0.0.15",
"ads-extension-telemetry": "github:Charles-Gagnon/ads-extension-telemetry#0.1.0",
"vscode-nls": "^3.2.1"
},
"devDependencies": {
"vscode": "1.0.1"
},
"__metadata": {
"id": "41",
"publisherDisplayName": "Microsoft",
"publisherId": "Microsoft"
}
}

View File

@@ -7,7 +7,7 @@ import * as nls from 'vscode-nls';
import * as path from 'path';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { Telemetry } from './telemetry';
import { TelemetryReporter, TelemetryViews } from './telemetry';
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils';
import { ChildProcess, exec } from 'child_process';
const localize = nls.loadMessageBundle();
@@ -95,7 +95,7 @@ function registerCommands(context: vscode.ExtensionContext): void {
*/
async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
if (!connectionContext) {
Telemetry.sendTelemetryEventForError('NoConnectionContext', { action: 'Properties' });
TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinProperties, 'NoConnectionContext');
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForProp', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand'));
return;
}
@@ -107,7 +107,7 @@ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: az
else if (connectionContext.nodeInfo) {
nodeType = connectionContext.nodeInfo.nodeType;
} else {
Telemetry.sendTelemetryEventForError('NoOENode', { action: 'Properties' });
TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinProperties, 'NoOENode');
vscode.window.showErrorMessage(localize('adminToolExtWin.noOENode', 'Could not determine Object Explorer node from connectionContext : {0}', JSON.stringify(connectionContext)));
return;
}
@@ -124,7 +124,7 @@ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: az
async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
const action = 'GenerateScripts';
if (!connectionContext) {
Telemetry.sendTelemetryEventForError('NoConnectionContext', { action: action });
TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinGsw, 'NoConnectionContext');
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForGsw', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand'));
}
@@ -141,7 +141,7 @@ async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.Ob
*/
async function launchSsmsDialog(action: string, connectionContext: azdata.ObjectExplorerContext): Promise<void> {
if (!connectionContext.connectionProfile) {
Telemetry.sendTelemetryEventForError('NoConnectionProfile', { action: action });
TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinDialog, 'NoConnectionProfile');
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionProfile', 'No connectionProfile provided from connectionContext : {0}', JSON.stringify(connectionContext)));
return;
}
@@ -155,7 +155,7 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object
oeNode = await azdata.objectexplorer.getNode(connectionContext.connectionProfile.id, connectionContext.nodeInfo.nodePath);
}
else {
Telemetry.sendTelemetryEventForError('NoOENode', { action: action });
TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinDialog, 'NoOENode');
vscode.window.showErrorMessage(localize('adminToolExtWin.noOENode', 'Could not determine Object Explorer node from connectionContext : {0}', JSON.stringify(connectionContext)));
return;
}
@@ -178,13 +178,15 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object
};
const args = buildSsmsMinCommandArgs(params);
Telemetry.sendTelemetryEvent('LaunchSsmsDialog',
{
action: action,
nodeType: oeNode ? oeNode.nodeType : 'Server',
authType: connectionContext.connectionProfile.authenticationType
});
TelemetryReporter.createActionEvent(
TelemetryViews.SsmsMinDialog,
'LaunchSsmsDialog',
'',
action).withAdditionalProperties(
{
nodeType: oeNode ? oeNode.nodeType : 'Server'
}).withConnectionInfo(connectionContext.connectionProfile)
.send();
vscode.window.setStatusBarMessage(localize('adminToolExtWin.launchingDialogStatus', 'Launching dialog...'), 3000);
@@ -196,11 +198,14 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object
// Process has exited so remove from map of running processes
runningProcesses.delete(proc.pid);
const err = stderr.toString();
Telemetry.sendTelemetryEvent('LaunchSsmsDialogResult', {
action: params.action,
returnCode: execException && execException.code ? execException.code.toString() : '0',
errorType: getTelemetryErrorType(err)
});
if ((execException && execException.code !== 0) || err !== '') {
TelemetryReporter.sendErrorEvent(
TelemetryViews.SsmsMinDialog,
'LaunchSsmsDialogError',
execException ? execException.code.toString() : '',
getTelemetryErrorType(err));
}
if (err !== '') {
vscode.window.showErrorMessage(localize(
'adminToolExtWin.ssmsMinError',

View File

@@ -4,99 +4,23 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import TelemetryReporter from 'vscode-extension-telemetry';
import AdsTelemetryReporter from 'ads-extension-telemetry';
import * as Utils from './utils';
const packageJson = require('../package.json');
export interface ITelemetryEventProperties {
[key: string]: string;
let packageInfo = Utils.getPackageInfo(packageJson);
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
export enum TelemetryViews {
SsmsMinProperties = 'SsmsMinProperties',
SsmsMinGsw = 'SsmsMinGsw',
SsmsMinDialog = 'SsmsMinDialog'
}
export interface ITelemetryEventMeasures {
[key: string]: number;
}
/**
* Filters error paths to only include source files. Exported to support testing
*/
export function filterErrorPath(line: string): string {
if (line) {
let values: string[] = line.split('/out/');
if (values.length <= 1) {
// Didn't match expected format
return line;
} else {
return values[1];
}
}
}
export class Telemetry {
private static reporter: TelemetryReporter;
private static disabled: boolean;
/**
* Disable telemetry reporting
*/
public static disable(): void {
this.disabled = true;
}
/**
* Initialize the telemetry reporter for use.
*/
public static initialize(): void {
if (typeof this.reporter === 'undefined') {
// Check if the user has opted out of telemetry
if (!vscode.workspace.getConfiguration('telemetry').get<boolean>('enableTelemetry', true)) {
this.disable();
return;
}
let packageInfo = Utils.getPackageInfo(packageJson);
this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
}
}
/**
* Send a telemetry event for a general error
* @param err The error to log
*/
public static sendTelemetryEventForError(err: string, properties?: ITelemetryEventProperties): void {
this.sendTelemetryEvent('Error', { error: err, ...properties });
}
/**
* Send a telemetry event using application insights
*/
public static sendTelemetryEvent(
eventName: string,
properties?: ITelemetryEventProperties,
measures?: ITelemetryEventMeasures): void {
if (typeof this.disabled === 'undefined') {
this.disabled = false;
}
if (this.disabled || typeof (this.reporter) === 'undefined') {
// Don't do anything if telemetry is disabled
return;
}
if (!properties || typeof properties === 'undefined') {
properties = {};
}
try {
this.reporter.sendTelemetryEvent(eventName, properties, measures);
} catch (telemetryErr) {
// If sending telemetry event fails ignore it so it won't break the extension
console.error('Failed to send telemetry event. error: ' + telemetryErr);
}
}
}
Telemetry.initialize();

View File

@@ -2,6 +2,12 @@
# yarn lockfile v1
"ads-extension-telemetry@github:Charles-Gagnon/ads-extension-telemetry#0.1.0":
version "0.1.0"
resolved "https://codeload.github.com/Charles-Gagnon/ads-extension-telemetry/tar.gz/70c2fea10e9ff6e329c4c5ec0b77017ada514b6d"
dependencies:
vscode-extension-telemetry "0.1.1"
ajv@^6.5.5:
version "6.9.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.2.tgz#4927adb83e7f48e5a32b45729744c71ec39c9c7b"
@@ -49,10 +55,10 @@ ansi-wrap@0.1.0:
resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
applicationinsights@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
applicationinsights@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
integrity sha512-KzOOGdphOS/lXWMFZe5440LUdFbrLpMvh2SaRxn7BmiI550KAoSb2gIhiq6kJZ9Ir3AxRRztjhzif+e5P5IXIg==
dependencies:
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "0.2.1"
@@ -2271,12 +2277,12 @@ vinyl@~2.0.1:
remove-trailing-separator "^1.0.1"
replace-ext "^1.0.0"
vscode-extension-telemetry@^0.0.15:
version "0.0.15"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856"
integrity sha512-Yf6dL9r2x2GISI1xh22XsAaydSTQG/4aBitu8sGBwGr42n2TyOsIXGtXSDgqQBNZgYD6+P1EHqrrzetn9ekWTQ==
vscode-extension-telemetry@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b"
integrity sha512-TkKKG/B/J94DP5qf6xWB4YaqlhWDg6zbbqVx7Bz//stLQNnfE9XS1xm3f6fl24c5+bnEK0/wHgMgZYKIKxPeUA==
dependencies:
applicationinsights "1.0.1"
applicationinsights "1.0.8"
vscode-nls@^3.2.1:
version "3.2.5"

View File

@@ -2,7 +2,7 @@
"name": "agent",
"displayName": "SQL Server Agent",
"description": "Manage and troubleshoot SQL Server Agent jobs",
"version": "0.40.0",
"version": "0.41.0",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",

View File

@@ -10,6 +10,7 @@ import * as vscode from 'vscode';
import { AgentUtils } from '../agentUtils';
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
import { JobData } from './jobData';
import { JobStepDialog } from '../dialogs/jobStepDialog';
const localize = nls.loadMessageBundle();
@@ -26,10 +27,10 @@ export class JobStepData implements IAgentDialogData {
public script: string;
public scriptName: string;
public stepName: string;
public subSystem: string;
public subSystem: azdata.AgentSubSystem;
public id: number;
public failureAction: string;
public successAction: string;
public failureAction: azdata.StepCompletionAction;
public successAction: azdata.StepCompletionAction;
public successStepId: number;
public failStepId: number;
public command: string;
@@ -101,27 +102,27 @@ export class JobStepData implements IAgentDialogData {
stepData.jobId = jobStepInfo.jobId;
stepData.jobName = jobStepInfo.jobName;
stepData.script = jobStepInfo.script;
stepData.scriptName = jobStepInfo.scriptName,
stepData.stepName = jobStepInfo.stepName,
stepData.subSystem = jobStepInfo.subSystem,
stepData.id = jobStepInfo.id,
stepData.failureAction = jobStepInfo.failureAction,
stepData.successAction = jobStepInfo.successAction,
stepData.failStepId = jobStepInfo.failStepId,
stepData.successStepId = jobStepInfo.successStepId,
stepData.command = jobStepInfo.command,
stepData.commandExecutionSuccessCode = jobStepInfo.commandExecutionSuccessCode,
stepData.databaseName = jobStepInfo.databaseName,
stepData.databaseUserName = jobStepInfo.databaseUserName,
stepData.server = jobStepInfo.server,
stepData.outputFileName = jobStepInfo.outputFileName,
stepData.appendToLogFile = jobStepInfo.appendToLogFile,
stepData.appendToStepHist = jobStepInfo.appendToStepHist,
stepData.writeLogToTable = jobStepInfo.writeLogToTable,
stepData.appendLogToTable = jobStepInfo.appendLogToTable,
stepData.retryAttempts = jobStepInfo.retryAttempts,
stepData.retryInterval = jobStepInfo.retryInterval,
stepData.proxyName = jobStepInfo.proxyName;
stepData.scriptName = jobStepInfo.scriptName;
stepData.stepName = jobStepInfo.stepName;
stepData.subSystem = jobStepInfo.subSystem;
stepData.id = jobStepInfo.id;
stepData.failureAction = jobStepInfo.failureAction;
stepData.successAction = jobStepInfo.successAction;
stepData.failStepId = jobStepInfo.failStepId;
stepData.successStepId = jobStepInfo.successStepId;
stepData.command = jobStepInfo.command;
stepData.commandExecutionSuccessCode = jobStepInfo.commandExecutionSuccessCode;
stepData.databaseName = jobStepInfo.databaseName;
stepData.databaseUserName = jobStepInfo.databaseUserName;
stepData.server = jobStepInfo.server;
stepData.outputFileName = jobStepInfo.outputFileName;
stepData.appendToLogFile = jobStepInfo.appendToLogFile;
stepData.appendToStepHist = jobStepInfo.appendToStepHist;
stepData.writeLogToTable = jobStepInfo.writeLogToTable;
stepData.appendLogToTable = jobStepInfo.appendLogToTable;
stepData.retryAttempts = jobStepInfo.retryAttempts;
stepData.retryInterval = jobStepInfo.retryInterval;
stepData.proxyName = jobStepInfo.proxyName;
stepData.dialogMode = AgentDialogMode.EDIT;
stepData.viaJobDialog = true;
return stepData;
@@ -157,4 +158,115 @@ export class JobStepData implements IAgentDialogData {
return result;
}
public static convertToAgentSubSystem(subSystemDisplayName: string): azdata.AgentSubSystem {
switch (subSystemDisplayName) {
case (JobStepDialog.TSQLScript): {
return azdata.AgentSubSystem.TransactSql;
}
case (JobStepDialog.Powershell): {
return azdata.AgentSubSystem.PowerShell;
}
case (JobStepDialog.CmdExec): {
return azdata.AgentSubSystem.CmdExec;
}
case (JobStepDialog.ReplicationDistributor): {
return azdata.AgentSubSystem.Distribution;
}
case (JobStepDialog.ReplicationMerge): {
return azdata.AgentSubSystem.Merge;
}
case (JobStepDialog.ReplicationQueueReader): {
return azdata.AgentSubSystem.QueueReader;
}
case (JobStepDialog.ReplicationSnapshot): {
return azdata.AgentSubSystem.Snapshot;
}
case (JobStepDialog.ReplicationTransactionLogReader): {
return azdata.AgentSubSystem.LogReader;
}
case (JobStepDialog.AnalysisServicesCommand): {
return azdata.AgentSubSystem.AnalysisCommands;
}
case (JobStepDialog.AnalysisServicesQuery): {
return azdata.AgentSubSystem.AnalysisQuery;
}
case (JobStepDialog.ServicesPackage): {
return azdata.AgentSubSystem.Ssis;
}
default:
return azdata.AgentSubSystem.TransactSql;
}
}
public static convertToSubSystemDisplayName(subSystem: azdata.AgentSubSystem): string {
switch (subSystem) {
case (azdata.AgentSubSystem.TransactSql): {
return JobStepDialog.TSQLScript;
}
case (azdata.AgentSubSystem.PowerShell): {
return JobStepDialog.Powershell;
}
case (azdata.AgentSubSystem.CmdExec): {
return JobStepDialog.CmdExec;
}
case (azdata.AgentSubSystem.Distribution): {
return JobStepDialog.ReplicationDistributor;
}
case (azdata.AgentSubSystem.Merge): {
return JobStepDialog.ReplicationMerge;
}
case (azdata.AgentSubSystem.QueueReader): {
return JobStepDialog.ReplicationQueueReader;
}
case (azdata.AgentSubSystem.Snapshot): {
return JobStepDialog.ReplicationSnapshot;
}
case (azdata.AgentSubSystem.LogReader): {
return JobStepDialog.ReplicationTransactionLogReader;
}
case (azdata.AgentSubSystem.AnalysisCommands): {
return JobStepDialog.AnalysisServicesCommand;
}
case (azdata.AgentSubSystem.AnalysisQuery): {
return JobStepDialog.AnalysisServicesQuery;
}
case (azdata.AgentSubSystem.Ssis): {
return JobStepDialog.ServicesPackage;
}
default:
return JobStepDialog.TSQLScript;
}
}
public static convertToStepCompletionAction(actionDisplayName: string): azdata.StepCompletionAction {
switch (actionDisplayName) {
case (JobStepDialog.NextStep): {
return azdata.StepCompletionAction.GoToNextStep;
}
case (JobStepDialog.QuitJobReportingSuccess): {
return azdata.StepCompletionAction.QuitWithSuccess;
}
case (JobStepDialog.QuitJobReportingFailure): {
return azdata.StepCompletionAction.QuitWithFailure;
}
default:
return azdata.StepCompletionAction.GoToNextStep;
}
}
public static convertToCompletionActionDisplayName(stepCompletionAction: azdata.StepCompletionAction): string {
switch (stepCompletionAction) {
case (azdata.StepCompletionAction.GoToNextStep): {
return JobStepDialog.NextStep;
}
case (azdata.StepCompletionAction.QuitWithFailure): {
return JobStepDialog.QuitJobReportingFailure;
}
case (azdata.StepCompletionAction.QuitWithSuccess): {
return JobStepDialog.QuitJobReportingSuccess;
}
default:
return JobStepDialog.NextStep;
}
}
}

View File

@@ -20,11 +20,16 @@ export class PickScheduleData implements IAgentDialogData {
this.jobName = jobName;
}
public async initialize() {
public async initialize(): Promise<azdata.AgentJobScheduleInfo[]> {
let agentService = await AgentUtils.getAgentService();
let result = await agentService.getJobSchedules(this.ownerUri);
if (result && result.success) {
this.schedules = result.schedules;
try {
let result = await agentService.getJobSchedules(this.ownerUri);
if (result && result.success) {
this.schedules = result.schedules;
return this.schedules;
}
} catch (error) {
throw error;
}
}

View File

@@ -169,6 +169,9 @@ export class JobDialog extends AgentDialog<JobData> {
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
this.dialog.message = null;
// Change the job name immediately since steps
// depends on the job name
this.model.name = this.nameTextBox.value;
}
});
this.ownerTextBox = view.modelBuilder.inputBox().component();
@@ -242,12 +245,14 @@ export class JobDialog extends AgentDialog<JobData> {
this.moveStepUpButton = view.modelBuilder.button()
.withProperties({
label: this.MoveStepUpButtonString,
title: this.MoveStepUpButtonString,
width: 120
}).component();
this.moveStepDownButton = view.modelBuilder.button()
.withProperties({
label: this.MoveStepDownButtonString,
title: this.MoveStepDownButtonString,
width: 120
}).component();
@@ -256,6 +261,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.newStepButton = view.modelBuilder.button().withProperties({
label: this.NewStepButtonString,
title: this.NewStepButtonString,
width: 140
}).component();
@@ -283,11 +289,13 @@ export class JobDialog extends AgentDialog<JobData> {
this.editStepButton = view.modelBuilder.button().withProperties({
label: this.EditStepButtonString,
title: this.EditStepButtonString,
width: 140
}).component();
this.deleteStepButton = view.modelBuilder.button().withProperties({
label: this.DeleteStepButtonString,
title: this.DeleteStepButtonString,
width: 140
}).component();
@@ -651,9 +659,9 @@ export class JobDialog extends AgentDialog<JobData> {
let cols = [];
cols.push(jobStep.id);
cols.push(jobStep.stepName);
cols.push(jobStep.subSystem);
cols.push(jobStep.successAction);
cols.push(jobStep.failureAction);
cols.push(JobStepData.convertToSubSystemDisplayName(jobStep.subSystem));
cols.push(JobStepData.convertToCompletionActionDisplayName(jobStep.successAction));
cols.push(JobStepData.convertToCompletionActionDisplayName(jobStep.failureAction));
result.push(cols);
});
return result;
@@ -700,6 +708,11 @@ export class JobDialog extends AgentDialog<JobData> {
this.model.jobSteps = [];
}
this.model.jobSteps = this.steps;
// Change the last step's success action to quit because the
// default is "Go To Next Step"
if (this.model.jobSteps.length > 0) {
this.model.jobSteps[this.model.jobSteps.length - 1].successAction = azdata.StepCompletionAction.QuitWithSuccess;
}
if (!this.model.jobSchedules) {
this.model.jobSchedules = [];
}

View File

@@ -60,13 +60,23 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
private readonly AllFilesLabelString: string = localize('jobStepDialog.allFiles', 'All Files (*)');
// Dropdown options
private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
private readonly Powershell: string = localize('jobStepDialog.powershell', 'PowerShell');
private readonly CmdExec: string = localize('jobStepDialog.CmdExec', 'Operating system (CmdExec)');
private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
public static readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
public static readonly Powershell: string = localize('jobStepDialog.powershell', 'PowerShell');
public static readonly CmdExec: string = localize('jobStepDialog.CmdExec', 'Operating system (CmdExec)');
public static readonly ReplicationDistributor: string = localize('jobStepDialog.replicationDistribution', 'Replication Distributor');
public static readonly ReplicationMerge: string = localize('jobStepDialog.replicationMerge', 'Replication Merge');
public static readonly ReplicationQueueReader: string = localize('jobStepDialog.replicationQueueReader', 'Replication Queue Reader');
public static readonly ReplicationSnapshot: string = localize('jobStepDialog.replicationSnapshot', 'Replication Snapshot');
public static readonly ReplicationTransactionLogReader: string = localize('jobStepDialog.replicationTransactionLogReader', 'Replication Transaction-Log Reader');
public static readonly AnalysisServicesCommand: string = localize('jobStepDialog.analysisCommand', 'SQL Server Analysis Services Command');
public static readonly AnalysisServicesQuery: string = localize('jobStepDialog.analysisQuery', 'SQL Server Analysis Services Query');
public static readonly ServicesPackage: string = localize('jobStepDialog.servicesPackage', 'SQL Server Integration Service Package');
public static readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
public static readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
public static readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
public static readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
// Event Name strings
private readonly NewStepDialog = 'NewStepDialogOpened';
@@ -147,12 +157,14 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.openButton = view.modelBuilder.button()
.withProperties({
label: this.OpenCommandText,
title: this.OpenCommandText,
width: '80px',
isFile: true
}).component();
this.parseButton = view.modelBuilder.button()
.withProperties({
label: this.ParseCommandText,
title: this.ParseCommandText,
width: '80px',
isFile: false
}).component();
@@ -176,7 +188,9 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
height: 300,
width: 400,
multiline: true,
inputType: 'text'
inputType: 'text',
ariaLabel: this.CommandLabelString,
placeHolder: this.CommandLabelString
})
.component();
}
@@ -185,6 +199,8 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.generalTab.registerContent(async (view) => {
this.nameTextBox = view.modelBuilder.inputBox()
.withProperties({
ariaLabel: this.StepNameLabelString,
placeHolder: this.StepNameLabelString
}).component();
this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
@@ -195,8 +211,8 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.typeDropdown = view.modelBuilder.dropDown()
.withProperties({
value: this.TSQLScript,
values: [this.TSQLScript, this.CmdExec, this.Powershell]
value: JobStepDialog.TSQLScript,
values: [JobStepDialog.TSQLScript, JobStepDialog.CmdExec, JobStepDialog.Powershell]
})
.component();
this.runAsDropdown = view.modelBuilder.dropDown()
@@ -214,6 +230,8 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.processExitCodeBox = view.modelBuilder.inputBox()
.withProperties({
ariaLabel: this.ProcessExitCodeText,
placeHolder: this.ProcessExitCodeText
}).component();
this.processExitCodeBox.enabled = false;
@@ -246,7 +264,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
}).component();
this.typeDropdown.onValueChanged((type) => {
switch (type.selected) {
case (this.TSQLScript):
case (JobStepDialog.TSQLScript):
this.runAsDropdown.value = '';
this.runAsDropdown.values = [''];
this.runAsDropdown.enabled = false;
@@ -256,8 +274,8 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.processExitCodeBox.value = '';
this.processExitCodeBox.enabled = false;
break;
case (this.Powershell):
this.runAsDropdown.value = this.AgentServiceAccount;
case (JobStepDialog.Powershell):
this.runAsDropdown.value = JobStepDialog.AgentServiceAccount;
this.runAsDropdown.values = [this.runAsDropdown.value];
this.runAsDropdown.enabled = true;
this.databaseDropdown.enabled = false;
@@ -266,11 +284,11 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.processExitCodeBox.value = '';
this.processExitCodeBox.enabled = false;
break;
case (this.CmdExec):
case (JobStepDialog.CmdExec):
this.databaseDropdown.enabled = false;
this.databaseDropdown.values = [''];
this.databaseDropdown.value = '';
this.runAsDropdown.value = this.AgentServiceAccount;
this.runAsDropdown.value = JobStepDialog.AgentServiceAccount;
this.runAsDropdown.values = [this.runAsDropdown.value];
this.runAsDropdown.enabled = true;
this.processExitCodeBox.enabled = true;
@@ -286,7 +304,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
// Load values for edit scenario
if (this.isEdit) {
this.nameTextBox.value = this.model.stepName;
this.typeDropdown.value = this.model.subSystem;
this.typeDropdown.value = JobStepData.convertToSubSystemDisplayName(this.model.subSystem);
this.databaseDropdown.value = this.model.databaseName;
this.commandTextBox.value = this.model.command;
}
@@ -298,16 +316,16 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.successActionDropdown = view.modelBuilder.dropDown()
.withProperties({
width: '100%',
value: this.NextStep,
values: [this.NextStep, this.QuitJobReportingSuccess, this.QuitJobReportingFailure]
value: JobStepDialog.NextStep,
values: [JobStepDialog.NextStep, JobStepDialog.QuitJobReportingSuccess, JobStepDialog.QuitJobReportingFailure]
})
.component();
let retryFlexContainer = this.createRetryCounters(view);
this.failureActionDropdown = view.modelBuilder.dropDown()
.withProperties({
value: this.QuitJobReportingFailure,
values: [this.QuitJobReportingFailure, this.NextStep, this.QuitJobReportingSuccess]
value: JobStepDialog.QuitJobReportingFailure,
values: [JobStepDialog.QuitJobReportingFailure, JobStepDialog.NextStep, JobStepDialog.QuitJobReportingSuccess]
})
.component();
let optionsGroup = this.createTSQLOptions(view);
@@ -343,7 +361,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
title: this.FailureActionLabel
}, {
component: optionsGroup,
title: this.TSQLScript
title: JobStepDialog.TSQLScript
}, {
component: logToTableContainer,
title: ''
@@ -365,10 +383,10 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
await view.initializeModel(formWrapper);
if (this.isEdit) {
this.successActionDropdown.value = this.model.successAction;
this.successActionDropdown.value = JobStepData.convertToCompletionActionDisplayName(this.model.successAction);
this.retryAttemptsBox.value = this.model.retryAttempts.toString();
this.retryIntervalBox.value = this.model.retryInterval.toString();
this.failureActionDropdown.value = this.model.failureAction;
this.failureActionDropdown.value = JobStepData.convertToCompletionActionDisplayName(this.model.failureAction);
this.outputFileNameBox.value = this.model.outputFileName;
this.appendToExistingFileCheckbox.checked = this.model.appendToLogFile;
this.logToTableCheckbox.checked = this.model.appendLogToTable;
@@ -527,13 +545,13 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.model.jobName = this.jobName;
this.model.id = this.stepId;
this.model.server = this.server;
this.model.subSystem = this.typeDropdown.value as string;
this.model.subSystem = JobStepData.convertToAgentSubSystem(this.typeDropdown.value as string);
this.model.databaseName = this.databaseDropdown.value as string;
this.model.script = this.commandTextBox.value;
this.model.successAction = this.successActionDropdown.value as string;
this.model.successAction = JobStepData.convertToStepCompletionAction(this.successActionDropdown.value as string);
this.model.retryAttempts = this.retryAttemptsBox.value ? +this.retryAttemptsBox.value : 0;
this.model.retryInterval = +this.retryIntervalBox.value ? +this.retryIntervalBox.value : 0;
this.model.failureAction = this.failureActionDropdown.value as string;
this.model.failureAction = JobStepData.convertToStepCompletionAction(this.failureActionDropdown.value as string);
this.model.outputFileName = this.outputFileNameBox.value;
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
this.model.command = this.commandTextBox.value ? this.commandTextBox.value : '';

View File

@@ -96,12 +96,21 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
private initializeGeneralTab() {
this.generalTab.registerContent(async view => {
this.nameTextBox = view.modelBuilder.inputBox().component();
this.nameTextBox = view.modelBuilder.inputBox().withProperties({
ariaLabel: OperatorDialog.NameLabel,
placeHolder: OperatorDialog.NameLabel
}).component();
this.nameTextBox.value = this.model.name;
this.emailNameTextBox = view.modelBuilder.inputBox().component();
this.emailNameTextBox = view.modelBuilder.inputBox().withProperties({
ariaLabel: OperatorDialog.EmailNameTextLabel,
placeHolder: OperatorDialog.EmailNameTextLabel
}).component();
this.emailNameTextBox.value = this.model.emailAddress;
this.pagerEmailNameTextBox = view.modelBuilder.inputBox().component();
this.pagerEmailNameTextBox = view.modelBuilder.inputBox().withProperties({
ariaLabel: OperatorDialog.PagerEmailNameTextLabel,
placeHolder: OperatorDialog.PagerEmailNameTextLabel
}).component();
this.pagerEmailNameTextBox.value = this.model.pagerAddress;
this.enabledCheckBox = view.modelBuilder.checkBox()

View File

@@ -27,6 +27,7 @@ export class PickScheduleDialog {
// UI Components
private dialog: azdata.window.Dialog;
private schedulesTable: azdata.TableComponent;
private loadingComponent: azdata.LoadingComponent;
private model: PickScheduleData;
@@ -38,7 +39,17 @@ export class PickScheduleDialog {
}
public async showDialog() {
await this.model.initialize();
this.model.initialize().then((result) => {
this.loadingComponent.loading = false;
if (this.model.schedules) {
let data: any[][] = [];
for (let i = 0; i < this.model.schedules.length; ++i) {
let schedule = this.model.schedules[i];
data[i] = [schedule.id, schedule.name, schedule.description];
}
this.schedulesTable.data = data;
}
});
this.dialog = azdata.window.createModelViewDialog(this.DialogTitle);
this.initializeContent();
this.dialog.okButton.onClick(async () => await this.execute());
@@ -68,16 +79,9 @@ export class PickScheduleDialog {
title: this.SchedulesLabelText
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
if (this.model.schedules) {
let data: any[][] = [];
for (let i = 0; i < this.model.schedules.length; ++i) {
let schedule = this.model.schedules[i];
data[i] = [schedule.id, schedule.name, schedule.description];
}
this.schedulesTable.data = data;
}
this.loadingComponent = view.modelBuilder.loadingComponent().withItem(formModel).component();
this.loadingComponent.loading = true;
await view.initializeModel(this.loadingComponent);
});
}

View File

@@ -84,8 +84,11 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
this.generalTab.registerContent(async view => {
this.proxyNameTextBox = view.modelBuilder.inputBox()
.withProperties({ width: 420 })
.component();
.withProperties({
width: 420,
ariaLabel: ProxyDialog.ProxyNameTextBoxLabel,
placeHolder: ProxyDialog.ProxyNameTextBoxLabel
}).component();
this.credentialNameDropDown = view.modelBuilder.dropDown()
.withProperties({
@@ -100,9 +103,10 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
.withProperties({
width: 420,
multiline: true,
height: 300
})
.component();
height: 300,
ariaLabel: ProxyDialog.DescriptionTextBoxLabel,
placeHolder: ProxyDialog.DescriptionTextBoxLabel
}).component();
this.subsystemCheckBox = view.modelBuilder.checkBox()
.withProperties({

View File

@@ -22,7 +22,13 @@ const localize = nls.loadMessageBundle();
* The main controller class that initializes the extension
*/
export class MainController {
protected _context: vscode.ExtensionContext;
private jobDialog: JobDialog;
private jobStepDialog: JobStepDialog;
private alertDialog: AlertDialog;
private operatorDialog: OperatorDialog;
private proxyDialog: ProxyDialog;
// PUBLIC METHODS //////////////////////////////////////////////////////
public constructor(context: vscode.ExtensionContext) {
@@ -39,8 +45,12 @@ export class MainController {
*/
public activate(): void {
vscode.commands.registerCommand('agent.openJobDialog', async (ownerUri: string, jobInfo: azdata.AgentJobInfo) => {
let dialog = new JobDialog(ownerUri, jobInfo);
dialog.dialogName ? await dialog.openDialog(dialog.dialogName) : await dialog.openDialog();
if (!this.jobDialog || (this.jobDialog && !this.jobDialog.isOpen)) {
this.jobDialog = new JobDialog(ownerUri, jobInfo);
}
if (!this.jobDialog.isOpen) {
this.jobDialog.dialogName ? await this.jobDialog.openDialog(this.jobDialog.dialogName) : await this.jobDialog.openDialog();
}
});
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobInfo: azdata.AgentJobInfo, jobStepInfo: azdata.AgentJobStepInfo) => {
AgentUtils.getAgentService().then(async (agentService) => {
@@ -53,20 +63,33 @@ export class MainController {
let dialog = new PickScheduleDialog(ownerUri, jobName);
await dialog.showDialog();
});
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, jobInfo: azdata.AgentJobInfo, alertInfo: azdata.AgentAlertInfo) => {
AgentUtils.getAgentService().then(async (agentService) => {
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
let dialog = new AlertDialog(ownerUri, jobData, alertInfo, false);
dialog.dialogName ? await dialog.openDialog(dialog.dialogName) : await dialog.openDialog();
});
vscode.commands.registerCommand('agent.openAlertDialog', async (ownerUri: string, jobInfo: azdata.AgentJobInfo, alertInfo: azdata.AgentAlertInfo) => {
if (!this.alertDialog || (this.alertDialog && !this.alertDialog.isOpen)) {
await AgentUtils.getAgentService().then(async (agentService) => {
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
this.alertDialog = new AlertDialog(ownerUri, jobData, alertInfo, false);
});
}
if (!this.alertDialog.isOpen) {
this.alertDialog.dialogName ? await this.alertDialog.openDialog(this.alertDialog.dialogName) : await this.alertDialog.openDialog();
}
});
vscode.commands.registerCommand('agent.openOperatorDialog', async (ownerUri: string, operatorInfo: azdata.AgentOperatorInfo) => {
let dialog = new OperatorDialog(ownerUri, operatorInfo);
dialog.dialogName ? await dialog.openDialog(dialog.dialogName) : await dialog.openDialog();
if (!this.operatorDialog || (this.operatorDialog && !this.operatorDialog.isOpen)) {
this.operatorDialog = new OperatorDialog(ownerUri, operatorInfo);
}
if (!this.operatorDialog.isOpen) {
this.operatorDialog.dialogName ? await this.operatorDialog.openDialog(this.operatorDialog.dialogName) : await this.operatorDialog.openDialog();
}
});
vscode.commands.registerCommand('agent.openProxyDialog', async (ownerUri: string, proxyInfo: azdata.AgentProxyInfo, credentials: azdata.CredentialInfo[]) => {
let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
dialog.dialogName ? await dialog.openDialog(dialog.dialogName) : await dialog.openDialog();
if (!this.proxyDialog || (this.proxyDialog && !this.proxyDialog.isOpen)) {
this.proxyDialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
}
if (!this.proxyDialog.isOpen) {
this.proxyDialog.dialogName ? await this.proxyDialog.openDialog(this.proxyDialog.dialogName) : await this.proxyDialog.openDialog();
}
this.proxyDialog.dialogName ? await this.proxyDialog.openDialog(this.proxyDialog.dialogName) : await this.proxyDialog.openDialog();
});
}

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'mocha';
import * as should from 'should';
import { AgentSubSystem } from 'azdata';
import { JobStepDialog } from '../../dialogs/jobStepDialog';
import { JobStepData } from '../../data/jobStepData';
const subSytems: AgentSubSystem[] = [AgentSubSystem.TransactSql, AgentSubSystem.PowerShell,
AgentSubSystem.CmdExec, AgentSubSystem.Distribution, AgentSubSystem.Merge,
AgentSubSystem.QueueReader, AgentSubSystem.Snapshot, AgentSubSystem.LogReader, AgentSubSystem.AnalysisCommands,
AgentSubSystem.AnalysisQuery, AgentSubSystem.Ssis];
const subSystemDisplayNames: string[] = [JobStepDialog.TSQLScript, JobStepDialog.Powershell,
JobStepDialog.CmdExec, JobStepDialog.ReplicationDistributor, JobStepDialog.ReplicationMerge,
JobStepDialog.ReplicationQueueReader, JobStepDialog.ReplicationSnapshot, JobStepDialog.ReplicationTransactionLogReader,
JobStepDialog.AnalysisServicesCommand, JobStepDialog.AnalysisServicesQuery, JobStepDialog.ServicesPackage];
describe('Agent extension enum mapping sanity test', function (): void {
it('SubSystem to Display Name Mapping test', () => {
for (let i = 0; i < subSytems.length; i++) {
let subSystem = subSytems[i];
let convertedSubSystemName = JobStepData.convertToSubSystemDisplayName(subSystem);
should.equal(convertedSubSystemName, subSystemDisplayNames[i]);
}
});
it('SubSystem Display Name to SubSystem Mapping test', () => {
for (let i = 0; i < subSystemDisplayNames.length; i++) {
let subSystemDisplayName = subSystemDisplayNames[i];
let convertedSubSystem = JobStepData.convertToAgentSubSystem(subSystemDisplayName);
should.equal(convertedSubSystem, subSytems[i]);
}
});
});

View File

@@ -5,7 +5,7 @@
'use strict';
import { AzureResource } from 'azdata';
import { AzureResource, ExtensionNodeType } from 'azdata';
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
import { TokenCredentials } from 'ms-rest';
import * as nls from 'vscode-nls';
@@ -72,7 +72,8 @@ export class AzureResourceDatabaseTreeDataProvider implements azureResource.IAzu
saveProfile: false,
options: {}
},
childProvider: 'MSSQL'
childProvider: 'MSSQL',
type: ExtensionNodeType.Database
}
});
}

View File

@@ -5,7 +5,7 @@
'use strict';
import { AzureResource } from 'azdata';
import { AzureResource, ExtensionNodeType } from 'azdata';
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
import { TokenCredentials } from 'ms-rest';
import * as nls from 'vscode-nls';
@@ -72,7 +72,8 @@ export class AzureResourceDatabaseServerTreeDataProvider implements azureResourc
saveProfile: false,
options: {}
},
childProvider: 'MSSQL'
childProvider: 'MSSQL',
type: ExtensionNodeType.Server
}
});
}

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><defs><style>.cls-1{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{clip-path:url(#clip-path-2);}.cls-4{clip-path:url(#clip-path-3);}.cls-5{clip-path:url(#clip-path-4);}.cls-6{clip-path:url(#clip-path-5);}.cls-7{clip-path:url(#clip-path-6);}.cls-8{clip-path:url(#clip-path-7);}.cls-9{clip-path:url(#clip-path-8);}.cls-10{fill:#68217a;}</style><clipPath id="clip-path"><rect class="cls-1" x="39.15" y="47.45" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-2"><rect class="cls-1" x="-754.07" y="415.73" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-3"><rect class="cls-1" x="-753.07" y="415.73" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-4"><rect class="cls-1" x="-750.43" y="422" width="10.73" height="0.43"/></clipPath><clipPath id="clip-path-5"><rect class="cls-1" x="-750.13" y="420.77" width="10.14" height="0.43"/></clipPath><clipPath id="clip-path-6"><rect class="cls-1" x="-749.72" y="419.53" width="9.31" height="0.43"/></clipPath><clipPath id="clip-path-7"><rect class="cls-1" x="-749.32" y="418.29" width="8.5" height="0.43"/></clipPath><clipPath id="clip-path-8"><rect class="cls-1" x="-748.89" y="417.05" width="7.65" height="0.43"/></clipPath></defs><title>AKS</title><path class="cls-10" d="M14.66,10.18,9.79,12V4.16l4.86,1.73Z"/><path class="cls-10" d="M5.31,5.5v5.18L9,12.1V4.16Zm.38,4.8-.19-.06V6l.19-.06.19-.06.26-.06v4.67L6,10.37A1.45,1.45,0,0,1,5.7,10.3Zm1,.26L6.4,10.5V5.7l.26-.06.26-.06.26-.06v5.25l-.26-.06Zm1.15.38-.32-.13V5.38l.32-.06.32-.13.38-.13v6.08L8.13,11C8.13,11.07,7.81,10.94,7.81,10.94Z"/><path class="cls-10" d="M26.11,10.18,21.25,12V4.16l4.86,1.73Z"/><path class="cls-10" d="M16.77,5.5v5.18l3.65,1.41V4.16Zm.38,4.8L17,10.24V6l.19-.06.19-.06.26-.06v4.67l-.19-.06A1.45,1.45,0,0,1,17.15,10.3Zm1,.26-.26-.06V5.7l.26-.06.26-.06.26-.06v5.25l-.26-.06Zm1.15.38-.32-.13V5.38l.32-.06.32-.13L20,5.06v6.08L19.58,11C19.58,11.07,19.26,10.94,19.26,10.94Z"/><path class="cls-10" d="M14.66,25.92,9.79,27.78V19.9l4.86,1.73Z"/><path class="cls-10" d="M5.31,21.25v5.18L9,27.84V19.9ZM5.7,26,5.5,26V21.7l.19-.06.19-.06.19-.06v4.67l-.19-.06Zm1,.26-.26-.06v-4.8l.26-.06.26-.06.26-.06V26.5l-.26-.06C6.91,26.37,6.66,26.3,6.66,26.3Zm1.15.38-.32-.13V21.12l.32-.06.32-.13.38-.13v6.08l-.38-.13C8.13,26.82,7.81,26.69,7.81,26.69Z"/><path class="cls-10" d="M26.11,25.92l-4.86,1.86V19.9l4.86,1.73Z"/><path class="cls-10" d="M16.77,21.25v5.18l3.65,1.41V19.9Zm.38,4.8L17,26V21.7l.19-.06.19-.06.19-.06v4.67l-.19-.06Zm1,.26-.26-.06v-4.8l.26-.06.26-.06.26-.06V26.5l-.26-.06C18.37,26.37,18.11,26.3,18.11,26.3Zm1.15.38-.32-.13V21.12l.32-.06.32-.13L20,20.8v6.08l-.38-.13C19.58,26.82,19.26,26.69,19.26,26.69Z"/><path class="cls-10" d="M20.74,18,15.87,19.9V12l4.86,1.73Z"/><path class="cls-10" d="M11.39,13.31V18.5L15,19.9V12Zm.38,4.8L11.58,18V13.76l.19-.06.19-.06.19-.06v4.67L12,18.18C12,18.18,11.78,18.11,11.78,18.11Zm1,.32-.26-.06v-4.8l.26-.06.26-.06.26-.06v5.25L13,18.56C13,18.5,12.74,18.43,12.74,18.43Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13Z"/><path class="cls-10" d="M9.34,18,4.48,19.9V12l4.86,1.73Z"/><path class="cls-10" d="M0,13.31V18.5L3.65,19.9V12Zm.38,4.8L.19,18V13.76l.19-.06H.64l.19-.06V18.3l-.19-.13Zm1,.32-.26-.06v-4.8l.26-.13.26-.06.26-.06v5.25L1.6,18.5Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13A2.77,2.77,0,0,0,2.5,18.82Z"/><path class="cls-10" d="M32,18,27.14,19.9V12L32,13.76Z"/><path class="cls-10" d="M22.66,13.31V18.5L26.3,19.9V12Zm.38,4.8L22.85,18V13.76L23,13.7l.19-.06.19-.06v4.67l-.19-.06C23.3,18.18,23,18.11,23,18.11Zm1,.32-.26-.06v-4.8L24,13.5l.26-.06.26-.06v5.25l-.26-.06C24.26,18.5,24,18.43,24,18.43Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13Z"/></svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><defs><style>.cls-1{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{clip-path:url(#clip-path-2);}.cls-4{clip-path:url(#clip-path-3);}.cls-5{clip-path:url(#clip-path-4);}.cls-6{clip-path:url(#clip-path-5);}.cls-7{clip-path:url(#clip-path-6);}.cls-8{clip-path:url(#clip-path-7);}.cls-9{clip-path:url(#clip-path-8);}.cls-10{fill:#326ce5;}.cls-11{fill:#fff;stroke:#fff;stroke-width:0.25px;}</style><clipPath id="clip-path"><rect class="cls-1" x="39.15" y="85.81" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-2"><rect class="cls-1" x="-754.07" y="454.09" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-3"><rect class="cls-1" x="-753.07" y="454.09" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-4"><rect class="cls-1" x="-750.43" y="460.36" width="10.73" height="0.43"/></clipPath><clipPath id="clip-path-5"><rect class="cls-1" x="-750.13" y="459.12" width="10.14" height="0.43"/></clipPath><clipPath id="clip-path-6"><rect class="cls-1" x="-749.72" y="457.88" width="9.31" height="0.43"/></clipPath><clipPath id="clip-path-7"><rect class="cls-1" x="-749.32" y="456.64" width="8.5" height="0.43"/></clipPath><clipPath id="clip-path-8"><rect class="cls-1" x="-748.89" y="455.4" width="7.65" height="0.43"/></clipPath></defs><title>Kubernetes</title><g id="layer1"><g id="g3052"><path id="path3055" class="cls-10" d="M15.89,0a2.09,2.09,0,0,0-.82.21L3.95,5.69A2.17,2.17,0,0,0,2.8,7.17L.05,19.47a2.21,2.21,0,0,0,.29,1.67l.12.17,7.7,9.87A2.11,2.11,0,0,0,9.83,32H22.17a2.11,2.11,0,0,0,1.66-.82l7.7-9.87a2.21,2.21,0,0,0,.41-1.84L29.2,7.17A2.17,2.17,0,0,0,28,5.69L16.92.22A2.09,2.09,0,0,0,15.89,0Z"/><path id="path3059" class="cls-11" d="M16,4.19a.72.72,0,0,0-.67.76V5c0,.06,0,.13,0,.18a5.89,5.89,0,0,0,.09.65A6.6,6.6,0,0,1,15.5,7a.75.75,0,0,1-.22.35l0,.29a8.46,8.46,0,0,0-5.55,2.75l-.24-.18a.51.51,0,0,1-.4,0,6.29,6.29,0,0,1-.9-.84,5.65,5.65,0,0,0-.44-.48l-.15-.12a.78.78,0,0,0-.46-.18.63.63,0,0,0-.53.24.74.74,0,0,0,.16,1h0l.14.11a5.51,5.51,0,0,0,.55.33,6.22,6.22,0,0,1,1,.72.78.78,0,0,1,.13.4l.21.2a9.1,9.1,0,0,0-1.36,6.19l-.28.08a.94.94,0,0,1-.29.3,6,6,0,0,1-1.19.2,5.41,5.41,0,0,0-.64.05l-.18,0h0a.73.73,0,0,0-.56.84.7.7,0,0,0,.86.5h0l.17,0a5.46,5.46,0,0,0,.6-.24,6.08,6.08,0,0,1,1.16-.35.72.72,0,0,1,.38.14l.29-.05a8.88,8.88,0,0,0,3.84,4.94l-.12.3a.7.7,0,0,1,.06.39,6.77,6.77,0,0,1-.6,1.12,5.76,5.76,0,0,0-.36.55l-.09.18a.74.74,0,0,0,.28,1,.7.7,0,0,0,.92-.39h0l.08-.17a5.85,5.85,0,0,0,.19-.63,4.88,4.88,0,0,1,.52-1.23.53.53,0,0,1,.29-.14l.15-.28a8.36,8.36,0,0,0,5,.37,8.45,8.45,0,0,0,1.14-.35l.14.26a.52.52,0,0,1,.34.21A6.52,6.52,0,0,1,20,26.54a5.87,5.87,0,0,0,.19.63l.08.18a.7.7,0,0,0,.92.39.74.74,0,0,0,.28-1l-.09-.18a5.74,5.74,0,0,0-.36-.55,6.47,6.47,0,0,1-.59-1.09.55.55,0,0,1,.05-.4,2.4,2.4,0,0,1-.11-.28,8.89,8.89,0,0,0,3.84-5l.28.05a.52.52,0,0,1,.37-.14,6.08,6.08,0,0,1,1.16.35,5.47,5.47,0,0,0,.6.24l.17,0h0a.7.7,0,0,0,.86-.5.73.73,0,0,0-.56-.84l-.19,0a5.41,5.41,0,0,0-.64-.05,6,6,0,0,1-1.19-.2.76.76,0,0,1-.29-.3l-.27-.08a9.13,9.13,0,0,0-.14-3.2,9,9,0,0,0-1.25-3l.24-.22a.55.55,0,0,1,.13-.39,6.22,6.22,0,0,1,1-.72,5.53,5.53,0,0,0,.55-.33l.15-.12a.74.74,0,0,0,.16-1,.69.69,0,0,0-1-.06l-.15.12a5.67,5.67,0,0,0-.44.48,6.31,6.31,0,0,1-.9.84.72.72,0,0,1-.4,0l-.25.19A8.63,8.63,0,0,0,16.73,7.7c0-.09,0-.26,0-.31A.54.54,0,0,1,16.5,7a6.62,6.62,0,0,1,.08-1.24,5.89,5.89,0,0,0,.09-.65c0-.06,0-.14,0-.2A.72.72,0,0,0,16,4.19Zm-.83,5.32-.2,3.6h0a.6.6,0,0,1-.59.58.57.57,0,0,1-.35-.12h0l-2.86-2.09a6.79,6.79,0,0,1,4-2Zm1.67,0a6.84,6.84,0,0,1,4,2L18,13.58h0a.58.58,0,0,1-.81-.11.61.61,0,0,1-.13-.35h0Zm-6.72,3.33,2.61,2.41h0a.62.62,0,0,1,.07.83.59.59,0,0,1-.3.21h0l-3.35,1A7.24,7.24,0,0,1,10.11,12.84Zm11.75,0A7.28,7.28,0,0,1,22.72,15a7.36,7.36,0,0,1,.15,2.3l-3.37-1h0a.61.61,0,0,1-.23-1h0l2.6-2.4Zm-6.4,2.6h1.07l.67.86L17,17.36l-1,.48-1-.48-.24-1.07Zm3.43,2.94H19l3.47.6a7,7,0,0,1-2.78,3.59l-1.35-3.35h0a.61.61,0,0,1,.28-.78.57.57,0,0,1,.22-.06Zm-5.82,0a.59.59,0,0,1,.56.47.62.62,0,0,1,0,.37h0l-1.33,3.32A7.07,7.07,0,0,1,9.52,19l3.44-.6h.12ZM16,19.84a.57.57,0,0,1,.27.06.59.59,0,0,1,.26.26h0l1.69,3.16a6.86,6.86,0,0,1-.68.2,6.69,6.69,0,0,1-3.76-.2l1.69-3.15h0A.59.59,0,0,1,16,19.84Z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,94 +1,111 @@
{
"name": "big-data-cluster",
"displayName": "SQL Server big data cluster",
"description": "SQL Server big data cluster",
"version": "0.0.1",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"engines": {
"vscode": "*",
"azdata": "^1.4.0"
},
"activationEvents": [
"*"
],
"main": "./out/main",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"extensionDependencies": [
"Microsoft.mssql"
],
"contributes": {
"configuration": {
"type": "object",
"title": "Kubernetes configuration",
"properties": {
"mssql-bdc": {
"type": "object",
"description": "Kubernetes configuration",
"properties": {
"mssql-bdc.kubectl-path": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.windows": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.mac": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.linux": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubeconfig": {
"type": "string",
"description": "File path to the kubeconfig file."
},
"mssql-bdc.knownKubeconfigs": {
"type": "array",
"description": "File paths to kubeconfig files from which you can select."
},
"mssql-bdc.outputFormat": {
"enum": [
"json",
"yaml"
],
"type": "string",
"description": "Output format for Kubernetes specs. One of 'json' or 'yaml' (default)."
}
},
"default": {
"mssql-bdc.namespace": "",
"mssql-bdc.kubectl-path": "",
"mssql-bdc.kubeconfig": "",
"mssql-bdc.knownKubeconfigs": []
}
}
}
},
"commands": [
{
"command": "mssql.cluster.create",
"title": "Create SQL Server big data cluster",
"category": "SQL Server"
}
]
},
"dependencies": {
"vscode-nls": "^3.2.1",
"download": "^6.2.5",
"shelljs": "^0.8.3"
},
"devDependencies": {
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7"
}
"name": "big-data-cluster",
"displayName": "%text.sqlServerBigDataClusters%",
"description": "%description%",
"version": "0.0.1",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"engines": {
"vscode": "*",
"azdata": "*"
},
"activationEvents": [
"*"
],
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"main": "./out/extension",
"contributes": {
"dataExplorer": {
"sqlBigDataCluster": [
{
"id": "sqlBigDataCluster",
"name": "%text.sqlServerBigDataClusters%"
}
]
},
"menus": {
"commandPalette": [
{
"command": "bigDataClusters.command.addController",
"when": "false"
},
{
"command": "bigDataClusters.command.deleteController",
"when": "false"
},
{
"command": "bigDataClusters.command.refreshController",
"when": "false"
}
],
"view/title": [
{
"command": "bigDataClusters.command.addController",
"when": "view == sqlBigDataCluster",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "bigDataClusters.command.deleteController",
"when": "viewItem == bigDataClusters.itemType.controllerNode",
"group": "navigation@1"
},
{
"command": "bigDataClusters.command.refreshController",
"when": "viewItem == bigDataClusters.itemType.controllerNode",
"group": "navigation@1"
}
]
},
"configuration": {
"type": "object",
"title": "%text.sqlServerBigDataClusters%",
"properties": {
"clusterControllers.controllers": {
"type": "array"
}
}
},
"commands": [
{
"command": "bigDataClusters.command.addController",
"title": "%command.addController.title%",
"icon": {
"light": "resources/light/add.svg",
"dark": "resources/dark/add_inverse.svg"
}
},
{
"command": "bigDataClusters.command.deleteController",
"title": "%command.deleteController.title%",
"when": "viewItem == bigDataClusters.itemType.controllerNode"
},
{
"command": "bigDataClusters.command.refreshController",
"title": "%command.refreshController.title%",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh_inverse.svg"
}
}
]
},
"dependencies": {
"request": "^2.88.0",
"vscode-nls": "^4.0.0"
},
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/node": "^8.0.24",
"mocha": "^5.2.0",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"vscode": "^1.1.26"
}
}

View File

@@ -0,0 +1,7 @@
{
"description": "Support for managing SQL Server Big Data Clusters",
"text.sqlServerBigDataClusters": "SQL Server Big Data Clusters",
"command.addController.title": "Connect to Controller",
"command.deleteController.title" : "Delete",
"command.refreshController.title" : "Refresh"
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -0,0 +1 @@
<svg id="Icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.cls-1{opacity:0;}.cls-2{fill:#f6f6f6;}.cls-3{fill:#424242;}.cls-4{fill:#f0eff1;}</style></defs><title>centralmanagement_server_16x</title><g id="canvas" class="cls-1"><rect class="cls-2" width="16" height="16"/></g><path id="outline" class="cls-2" d="M16,0H7V1H3V5H0V16H8V13h8ZM6,4H7V5H6Z"/><g id="iconBG"><path class="cls-3" d="M8,1V12h7V1Zm6,10H13V10h1Zm0-6H9V4h5Zm0-2H9V2h5Z"/><path class="cls-3" d="M1,6v9H7V6Zm5,8H5V13H6Zm0-4H2V9H6ZM6,8H2V7H6Z"/><rect class="cls-3" x="6" y="2" width="1" height="1"/><rect class="cls-3" x="4" y="4" width="1" height="1"/><rect class="cls-3" x="4" y="2" width="1" height="1"/></g><g id="iconFG"><path class="cls-4" d="M14,3H9V2h5Zm0,1H9V5h5Zm0,6H13v1h1Z"/><path class="cls-4" d="M6,8H2V7H6ZM6,9H2v1H6Zm0,4H5v1H6Z"/></g></svg>

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}.cls-2{fill:#fff;}</style></defs><title>folder_inverse_16x16</title><polygon class="cls-1" points="13.59 2.34 13.58 2.35 13.58 2.33 13.59 2.34"/><text></text><path class="cls-2" d="M16,14.13H0v-12a1,1,0,0,1,.08-.39,1,1,0,0,1,.53-.53A1,1,0,0,1,1,1.13H4.75a2.16,2.16,0,0,1,.61.07,2.26,2.26,0,0,1,.45.18,2.14,2.14,0,0,1,.36.24l.32.24a1.8,1.8,0,0,0,.34.18,1.12,1.12,0,0,0,.43.07H15a1,1,0,0,1,.39.08,1,1,0,0,1,.53.53,1,1,0,0,1,.08.39ZM1,2.13v1H4.75a1.36,1.36,0,0,0,.33,0A1,1,0,0,0,5.34,3l.23-.16.25-.21-.25-.21-.23-.16a1,1,0,0,0-.26-.1,1.36,1.36,0,0,0-.33,0Zm14,11v-10H7.25a1.12,1.12,0,0,0-.43.07,1.8,1.8,0,0,0-.34.18l-.32.24a2.14,2.14,0,0,1-.36.24,2.26,2.26,0,0,1-.45.18,2.16,2.16,0,0,1-.61.07H1v9Z"/></svg>

After

Width:  |  Height:  |  Size: 830 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#2D2D30"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 16 16"
data-name="Layer 1"
id="Layer_1">
<metadata
id="metadata17">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>sql_bigdata_cluster</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4">
<style
id="style2">.cls-1{fill:#212121;}.cls-2{fill:#231f20;}</style>
</defs>
<title
id="title6">sql_bigdata_cluster</title>
<path
style="fill:#ffffff;stroke-width:1.00282443"
id="path8"
d="M 7.995,0 C 5.605,0 1.575,0.45254557 1.465,2.1319925 V 13.737272 C 1.465,15.517285 5.575,16 7.995,16 c 2.42,0 6.54,-0.482715 6.54,-2.262728 V 2.1319925 C 14.435,0.45254557 10.405,0 7.995,0 Z m 5.45,13.737272 c -0.14,0.392206 -2.18,1.166562 -5.45,1.166562 -3.27,0 -5.32,-0.784412 -5.43,-1.166562 V 3.5097423 a 14.67,14.752986 0 0 0 5.43,0.8749214 14.71,14.793212 0 0 0 5.45,-0.8749214 z m 0,-11.5549967 c -0.17,0.3922062 -2.19,1.1062225 -5.45,1.1062225 -3.26,0 -5.2,-0.6939032 -5.43,-1.0861094 0.23,-0.4022627 2.22,-1.1062225 5.43,-1.1062225 3.21,0 5.27,0.7240729 5.45,1.0659963 v 0 z"
class="cls-1" />
<polygon
style="fill:#ffffff"
transform="translate(0.075)"
id="polygon10"
points="13.57,2.35 13.58,2.36 13.57,2.37 "
class="cls-2" />
<path
style="fill:#ffffff"
id="path12"
d="m 9.6501562,5.2372858 c -0.1362374,0 -0.2728654,0.026375 -0.4003906,0.082031 -0.123585,0.050567 -0.2358691,0.1260731 -0.3300781,0.2207031 -0.094256,0.096634 -0.1724299,0.2082024 -0.2304688,0.3300781 -0.062701,0.1283175 -0.099426,0.2676857 -0.109375,0.4101562 -0.00186,0.1267925 0.022265,0.2517914 0.070312,0.3691407 0.045212,0.1164344 0.1088696,0.2248797 0.1894531,0.3203125 L 8.2107031,7.9384577 C 8.011051,7.8519995 7.7980699,7.8002026 7.5798437,7.7997858 7.2852043,7.7997877 7.0158159,7.8890317 6.7790625,8.0283014 L 6.3435156,7.4677545 C 6.4851678,7.2819801 6.5620085,7.0548883 6.5622656,6.8212702 6.5623837,6.2311827 6.0839937,5.7527927 5.4939062,5.7529108 4.9038187,5.7527927 4.4254288,6.2311827 4.4255469,6.8212702 4.4254288,7.4113576 4.9038188,7.8897476 5.4939062,7.8896295 5.646983,7.8892233 5.7981841,7.8559185 5.9372656,7.7919733 l 0.4628906,0.5351562 c -0.2593431,0.2844532 -0.4218723,0.6589599 -0.421875,1.0742188 1.1e-6,0.1550931 0.029186,0.301527 0.070312,0.4433594 L 5.2692969,10.19041 C 5.0668671,9.9352433 4.7590727,9.7863779 4.4333593,9.7861139 3.8432718,9.7859958 3.3648819,10.264386 3.365,10.854473 c -1.179e-4,0.590087 0.478272,1.068477 1.0683593,1.068359 0.5900874,1.18e-4 1.0684773,-0.478272 1.0683594,-1.068359 -2.425e-4,-0.05958 -0.00547,-0.119029 -0.015625,-0.177734 l 0.7675782,-0.376953 c 0.2881162,0.42403 0.7748778,0.703124 1.3261718,0.703124 0.087028,-9e-5 0.1739047,-0.0073 0.2597656,-0.02148 l 0.2011719,0.597656 c -0.2806104,0.199117 -0.4474678,0.523359 -0.4472656,0.869137 -8.57e-5,0.586839 0.4721644,1.062587 1.0546875,1.0625 0.5825231,8.7e-5 1.054773,-0.475661 1.054687,-1.0625 8.6e-5,-0.586839 -0.4721639,-1.062587 -1.054687,-1.0625 -0.043779,5.16e-4 -0.087483,0.0038 -0.1308594,0.0098 L 8.3220312,10.819317 C 8.6909643,10.625493 8.9698168,10.295494 9.099375,9.8993953 l 0.5449219,0.089844 h 0.00195 c 0.05025,0.5310507 0.4958731,0.9369327 1.0292971,0.9374997 0.571737,8.6e-5 1.035243,-0.46342 1.035156,-1.0351567 C 11.710786,9.3198482 11.247281,8.8563402 10.675544,8.8564264 10.264465,8.85697 9.8926723,9.100743 9.7282783,9.4775202 L 9.1814062,9.3798639 C 9.1740509,8.9410593 8.9869509,8.524497 8.6638281,8.2275202 L 9.3103125,7.2607233 c 0.1095989,0.036162 0.2244742,0.051906 0.3398437,0.048828 0.1376991,0.0043 0.2729851,-0.023148 0.3984378,-0.080078 0.126162,-0.045588 0.239468,-0.119827 0.330078,-0.21875 0.09823,-0.093286 0.176943,-0.2056351 0.230469,-0.3300781 0.05137,-0.1271794 0.07858,-0.2632358 0.08008,-0.4003907 -4.88e-4,-0.140498 -0.02772,-0.2797842 -0.08008,-0.4101562 C 10.551096,5.7482226 10.472932,5.6366542 10.378672,5.5400202 10.284463,5.44539 10.172179,5.369883 10.048594,5.3193171 9.9210683,5.2636605 9.7863933,5.2372858 9.6501562,5.2372858 Z m -0.00195,0.4746094 C 9.9659223,5.7112473 10.223947,5.9683972 10.224378,6.2861139 10.225028,6.6045936 9.9666863,6.8629356 9.6482062,6.8622858 9.3304864,6.8618548 9.0733369,6.6038302 9.0739843,6.2861139 9.0744163,5.9691601 9.3312493,5.7123255 9.6482031,5.7118952 Z m -4.1543,0.4941406 C 5.8337444,6.2059063 6.1092701,6.481432 6.1091406,6.8212702 6.1092701,7.1611084 5.8337444,7.4366342 5.4939062,7.4365045 5.1540681,7.436634 4.8785424,7.1611083 4.8786719,6.8212702 4.8785424,6.481432 5.154068,6.2059063 5.4939062,6.2060358 Z M 7.5817969,8.3700983 A 1.0403689,1.0403689 0 0 1 8.6228125,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,10.450176 1.0403689,1.0403689 0 0 1 6.5427343,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,8.3700983 Z m 3.0585941,0.9277344 h 0.002 c 0.01432,-5.13e-4 0.02865,-5.13e-4 0.04297,0 0.331066,2.151e-4 0.599395,0.2685422 0.59961,0.5996096 -2.16e-4,0.3310657 -0.268544,0.5993937 -0.59961,0.5996087 -0.331828,8.64e-4 -0.601347,-0.26778 -0.601562,-0.5996087 -7.66e-4,-0.3150021 0.242463,-0.5768467 0.556641,-0.5996096 z M 4.4216406,10.260723 c 0.3398381,-1.3e-4 0.6153637,0.275396 0.6152344,0.615234 1.299e-4,0.339838 -0.2753959,0.615365 -0.6152344,0.615235 -0.3398385,1.3e-4 -0.6153643,-0.275397 -0.6152344,-0.615235 -1.293e-4,-0.339838 0.2753963,-0.615364 0.6152344,-0.615234 z m 4.2382813,1.589844 c 0.3452152,-8.4e-5 0.6250885,0.272792 0.625,0.609375 8.81e-5,0.336583 -0.2797848,0.609459 -0.625,0.609375 -0.3452157,8.4e-5 -0.6250889,-0.272792 -0.625,-0.609375 -8.86e-5,-0.336583 0.2797844,-0.609459 0.625,-0.609375 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -0,0 +1 @@
<svg id="Icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.cls-1{opacity:0;}.cls-2{fill:#f6f6f6;}.cls-3{fill:#424242;}.cls-4{fill:#f0eff1;}</style></defs><title>centralmanagement_server_16x</title><g id="canvas" class="cls-1"><rect class="cls-2" width="16" height="16"/></g><path id="outline" class="cls-2" d="M16,0H7V1H3V5H0V16H8V13h8ZM6,4H7V5H6Z"/><g id="iconBG"><path class="cls-3" d="M8,1V12h7V1Zm6,10H13V10h1Zm0-6H9V4h5Zm0-2H9V2h5Z"/><path class="cls-3" d="M1,6v9H7V6Zm5,8H5V13H6Zm0-4H2V9H6ZM6,8H2V7H6Z"/><rect class="cls-3" x="6" y="2" width="1" height="1"/><rect class="cls-3" x="4" y="4" width="1" height="1"/><rect class="cls-3" x="4" y="2" width="1" height="1"/></g><g id="iconFG"><path class="cls-4" d="M14,3H9V2h5Zm0,1H9V5h5Zm0,6H13v1h1Z"/><path class="cls-4" d="M6,8H2V7H6ZM6,9H2v1H6Zm0,4H5v1H6Z"/></g></svg>

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-fg{fill:#F0EFF1;} .icon-folder{fill:#DCB67A;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 740 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#F6F6F6"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 16 16"
data-name="Layer 1"
id="Layer_1">
<metadata
id="metadata17">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>sql_bigdata_cluster</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4">
<style
id="style2">.cls-1{fill:#212121;}.cls-2{fill:#231f20;}</style>
</defs>
<title
id="title6">sql_bigdata_cluster</title>
<path
style="fill:#212121;stroke-width:1.00282443"
id="path8"
d="M 7.995,0 C 5.605,0 1.575,0.45254557 1.465,2.1319925 V 13.737272 C 1.465,15.517285 5.575,16 7.995,16 c 2.42,0 6.54,-0.482715 6.54,-2.262728 V 2.1319925 C 14.435,0.45254557 10.405,0 7.995,0 Z m 5.45,13.737272 c -0.14,0.392206 -2.18,1.166562 -5.45,1.166562 -3.27,0 -5.32,-0.784412 -5.43,-1.166562 V 3.5097423 a 14.67,14.752986 0 0 0 5.43,0.8749214 14.71,14.793212 0 0 0 5.45,-0.8749214 z m 0,-11.5549967 c -0.17,0.3922062 -2.19,1.1062225 -5.45,1.1062225 -3.26,0 -5.2,-0.6939032 -5.43,-1.0861094 0.23,-0.4022627 2.22,-1.1062225 5.43,-1.1062225 3.21,0 5.27,0.7240729 5.45,1.0659963 v 0 z"
class="cls-1" />
<polygon
style="fill:#231f20"
transform="translate(0.075)"
id="polygon10"
points="13.57,2.35 13.58,2.36 13.57,2.37 "
class="cls-2" />
<path
id="path12"
d="m 9.6501562,5.2372858 c -0.1362374,0 -0.2728654,0.026375 -0.4003906,0.082031 -0.123585,0.050567 -0.2358691,0.1260731 -0.3300781,0.2207031 -0.094256,0.096634 -0.1724299,0.2082024 -0.2304688,0.3300781 -0.062701,0.1283175 -0.099426,0.2676857 -0.109375,0.4101562 -0.00186,0.1267925 0.022265,0.2517914 0.070312,0.3691407 0.045212,0.1164344 0.1088696,0.2248797 0.1894531,0.3203125 L 8.2107031,7.9384577 C 8.011051,7.8519995 7.7980699,7.8002026 7.5798437,7.7997858 7.2852043,7.7997877 7.0158159,7.8890317 6.7790625,8.0283014 L 6.3435156,7.4677545 C 6.4851678,7.2819801 6.5620085,7.0548883 6.5622656,6.8212702 6.5623837,6.2311827 6.0839937,5.7527927 5.4939062,5.7529108 4.9038187,5.7527927 4.4254288,6.2311827 4.4255469,6.8212702 4.4254288,7.4113576 4.9038188,7.8897476 5.4939062,7.8896295 5.646983,7.8892233 5.7981841,7.8559185 5.9372656,7.7919733 l 0.4628906,0.5351562 c -0.2593431,0.2844532 -0.4218723,0.6589599 -0.421875,1.0742188 1.1e-6,0.1550931 0.029186,0.301527 0.070312,0.4433594 L 5.2692969,10.19041 C 5.0668671,9.9352433 4.7590727,9.7863779 4.4333593,9.7861139 3.8432718,9.7859958 3.3648819,10.264386 3.365,10.854473 c -1.179e-4,0.590087 0.478272,1.068477 1.0683593,1.068359 0.5900874,1.18e-4 1.0684773,-0.478272 1.0683594,-1.068359 -2.425e-4,-0.05958 -0.00547,-0.119029 -0.015625,-0.177734 l 0.7675782,-0.376953 c 0.2881162,0.42403 0.7748778,0.703124 1.3261718,0.703124 0.087028,-9e-5 0.1739047,-0.0073 0.2597656,-0.02148 l 0.2011719,0.597656 c -0.2806104,0.199117 -0.4474678,0.523359 -0.4472656,0.869137 -8.57e-5,0.586839 0.4721644,1.062587 1.0546875,1.0625 0.5825231,8.7e-5 1.054773,-0.475661 1.054687,-1.0625 8.6e-5,-0.586839 -0.4721639,-1.062587 -1.054687,-1.0625 -0.043779,5.16e-4 -0.087483,0.0038 -0.1308594,0.0098 L 8.3220312,10.819317 C 8.6909643,10.625493 8.9698168,10.295494 9.099375,9.8993953 l 0.5449219,0.089844 h 0.00195 c 0.05025,0.5310507 0.4958731,0.9369327 1.0292971,0.9374997 0.571737,8.6e-5 1.035243,-0.46342 1.035156,-1.0351567 C 11.710786,9.3198482 11.247281,8.8563402 10.675544,8.8564264 10.264465,8.85697 9.8926723,9.100743 9.7282783,9.4775202 L 9.1814062,9.3798639 C 9.1740509,8.9410593 8.9869509,8.524497 8.6638281,8.2275202 L 9.3103125,7.2607233 c 0.1095989,0.036162 0.2244742,0.051906 0.3398437,0.048828 0.1376991,0.0043 0.2729851,-0.023148 0.3984378,-0.080078 0.126162,-0.045588 0.239468,-0.119827 0.330078,-0.21875 0.09823,-0.093286 0.176943,-0.2056351 0.230469,-0.3300781 0.05137,-0.1271794 0.07858,-0.2632358 0.08008,-0.4003907 -4.88e-4,-0.140498 -0.02772,-0.2797842 -0.08008,-0.4101562 C 10.551096,5.7482226 10.472932,5.6366542 10.378672,5.5400202 10.284463,5.44539 10.172179,5.369883 10.048594,5.3193171 9.9210683,5.2636605 9.7863933,5.2372858 9.6501562,5.2372858 Z m -0.00195,0.4746094 C 9.9659223,5.7112473 10.223947,5.9683972 10.224378,6.2861139 10.225028,6.6045936 9.9666863,6.8629356 9.6482062,6.8622858 9.3304864,6.8618548 9.0733369,6.6038302 9.0739843,6.2861139 9.0744163,5.9691601 9.3312493,5.7123255 9.6482031,5.7118952 Z m -4.1543,0.4941406 C 5.8337444,6.2059063 6.1092701,6.481432 6.1091406,6.8212702 6.1092701,7.1611084 5.8337444,7.4366342 5.4939062,7.4365045 5.1540681,7.436634 4.8785424,7.1611083 4.8786719,6.8212702 4.8785424,6.481432 5.154068,6.2059063 5.4939062,6.2060358 Z M 7.5817969,8.3700983 A 1.0403689,1.0403689 0 0 1 8.6228125,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,10.450176 1.0403689,1.0403689 0 0 1 6.5427343,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,8.3700983 Z m 3.0585941,0.9277344 h 0.002 c 0.01432,-5.13e-4 0.02865,-5.13e-4 0.04297,0 0.331066,2.151e-4 0.599395,0.2685422 0.59961,0.5996096 -2.16e-4,0.3310657 -0.268544,0.5993937 -0.59961,0.5996087 -0.331828,8.64e-4 -0.601347,-0.26778 -0.601562,-0.5996087 -7.66e-4,-0.3150021 0.242463,-0.5768467 0.556641,-0.5996096 z M 4.4216406,10.260723 c 0.3398381,-1.3e-4 0.6153637,0.275396 0.6152344,0.615234 1.299e-4,0.339838 -0.2753959,0.615365 -0.6152344,0.615235 -0.3398385,1.3e-4 -0.6153643,-0.275397 -0.6152344,-0.615235 -1.293e-4,-0.339838 0.2753963,-0.615364 0.6152344,-0.615234 z m 4.2382813,1.589844 c 0.3452152,-8.4e-5 0.6250885,0.272792 0.625,0.609375 8.81e-5,0.336583 -0.2797848,0.609459 -0.625,0.609375 -0.3452157,8.4e-5 -0.6250889,-0.272792 -0.625,-0.609375 -8.86e-5,-0.336583 0.2797844,-0.609459 0.625,-0.609375 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
export enum BdcItemType {
controllerRoot = 'bigDataClusters.itemType.controllerRootNode',
controller = 'bigDataClusters.itemType.controllerNode',
folder = 'bigDataClusters.itemType.folderNode',
sqlMaster = 'bigDataClusters.itemType.sqlMasterNode',
EndPoint = 'bigDataClusters.itemType.endPointNode',
addController = 'bigDataClusters.itemType.addControllerNode'
}
export class IconPath {
private static extensionContext: vscode.ExtensionContext;
public static controllerNode: { dark: string, light: string };
public static folderNode: { dark: string, light: string };
public static sqlMasterNode: { dark: string, light: string };
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
IconPath.extensionContext = extensionContext;
IconPath.controllerNode = {
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/bigDataCluster_controller.svg'),
light: IconPath.extensionContext.asAbsolutePath('resources/light/bigDataCluster_controller.svg')
};
IconPath.folderNode = {
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
light: IconPath.extensionContext.asAbsolutePath('resources/light/folder.svg')
};
IconPath.sqlMasterNode = {
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/sql_bigdata_cluster_inverse.svg'),
light: IconPath.extensionContext.asAbsolutePath('resources/light/sql_bigdata_cluster.svg')
};
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EndpointRouterApi } from './apiGenerated';
export async function getEndPoints(
url: string, username: string, password: string, ignoreSslVerification?: boolean
): Promise<IEndPointsResponse> {
if (!url || !username || !password) {
return undefined;
}
url = adjustUrl(url);
let endPointApi = new EndpointRouterApi(username, password, url);
endPointApi.ignoreSslVerification = !!ignoreSslVerification;
let controllerResponse: IEndPointsResponse = undefined;
let controllerError: IControllerError = undefined;
let request = <IEndPointsRequest>{
url: url,
username: username,
password: password,
method: 'endPointsGet'
};
try {
let result = await endPointApi.endpointsGet();
controllerResponse = <IEndPointsResponse>{
response: result.response as IHttpResponse,
endPoints: result.body as IEndPoint[],
request
};
return controllerResponse;
} catch (error) {
if ('response' in error) {
let err: IEndPointsResponse = error as IEndPointsResponse;
let errCode = `${err.response.statusCode || ''}`;
let errMessage = err.response.statusMessage;
let errUrl = err.response.url;
controllerError = <IControllerError>{
address: errUrl,
code: errCode,
errno: errCode,
message: errMessage,
name: undefined
};
} else {
controllerError = error as IControllerError;
}
throw Object.assign(controllerError, { request }) as IControllerError;
}
}
/**
* Fixes missing protocol and wrong character for port entered by user
*/
function adjustUrl(url: string): string {
if (!url) {
return undefined;
}
url = url.trim().replace(/ /g, '').replace(/,(\d+)$/, ':$1');
if (!url.includes('://')) {
url = `https://${url}`;
}
return url;
}
export interface IEndPointsRequest {
url: string;
username: string;
password?: string;
method?: string;
}
export interface IEndPointsResponse {
request?: IEndPointsRequest;
response: IHttpResponse;
endPoints: IEndPoint[];
}
export interface IHttpResponse {
method?: string;
url?: string;
statusCode?: number;
statusMessage?: string;
}
export interface IEndPoint {
name?: string;
description?: string;
endpoint?: string;
ip?: string;
port?: number;
}
export interface IControllerError extends Error {
code?: string;
errno?: string;
message: string;
request?: any;
}

View File

@@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { IEndPoint, IControllerError, getEndPoints } from '../controller/clusterControllerApi';
import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
import { TreeNode } from '../tree/treeNode';
import { showErrorMessage } from '../utils';
const localize = nls.loadMessageBundle();
export class AddControllerDialogModel {
constructor(
public treeDataProvider: ControllerTreeDataProvider,
public node?: TreeNode,
public prefilledUrl?: string,
public prefilledUsername?: string,
public prefilledPassword?: string,
public prefilledRememberPassword?: boolean
) {
this.prefilledUrl = prefilledUrl || (node && node['url']);
this.prefilledUsername = prefilledUsername || (node && node['username']);
this.prefilledPassword = prefilledPassword || (node && node['password']);
this.prefilledRememberPassword = prefilledRememberPassword || (node && node['rememberPassword']);
}
public async onComplete(url: string, username: string, password: string, rememberPassword: boolean): Promise<void> {
let response = await getEndPoints(url, username, password, true);
if (response && response.request) {
let masterInstance: IEndPoint = undefined;
if (response.endPoints) {
masterInstance = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
}
this.treeDataProvider.addController(response.request.url, response.request.username,
response.request.password, rememberPassword, masterInstance);
await this.treeDataProvider.saveControllers();
}
}
public async onError(error: IControllerError): Promise<void> {
// implement
}
public async onCancel(): Promise<void> {
if (this.node) {
this.node.refresh();
}
}
}
export class AddControllerDialog {
private dialog: azdata.window.Dialog;
private uiModelBuilder: azdata.ModelBuilder;
private urlInputBox: azdata.InputBoxComponent;
private usernameInputBox: azdata.InputBoxComponent;
private passwordInputBox: azdata.InputBoxComponent;
private rememberPwCheckBox: azdata.CheckBoxComponent;
constructor(private model: AddControllerDialogModel) {
}
public showDialog(): void {
this.createDialog();
azdata.window.openDialog(this.dialog);
}
private createDialog(): void {
this.dialog = azdata.window.createModelViewDialog(localize('textAddNewController', 'Add New Controller'));
this.dialog.registerContent(async view => {
this.uiModelBuilder = view.modelBuilder;
this.urlInputBox = this.uiModelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
placeHolder: localize('textUrlLower', 'url'),
value: this.model.prefilledUrl
}).component();
this.usernameInputBox = this.uiModelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
placeHolder: localize('textUsernameLower', 'username'),
value: this.model.prefilledUsername
}).component();
this.passwordInputBox = this.uiModelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
placeHolder: localize('textPasswordLower', 'password'),
inputType: 'password',
value: this.model.prefilledPassword
})
.component();
this.rememberPwCheckBox = this.uiModelBuilder.checkBox()
.withProperties<azdata.CheckBoxProperties>({
label: localize('textRememberPassword', 'Remember Password'),
checked: this.model.prefilledRememberPassword
}).component();
let formModel = this.uiModelBuilder.formContainer()
.withFormItems([{
components: [{
component: this.urlInputBox,
title: localize('textUrlCapital', 'URL'),
required: true
}, {
component: this.usernameInputBox,
title: localize('textUsernameCapital', 'Username'),
required: true
}, {
component: this.passwordInputBox,
title: localize('textPasswordCapital', 'Password'),
required: true
}, {
component: this.rememberPwCheckBox,
title: ''
}
],
title: ''
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
this.dialog.registerCloseValidator(async () => await this.validate());
this.dialog.cancelButton.onClick(async () => await this.cancel());
this.dialog.okButton.label = localize('textAdd', 'Add');
this.dialog.cancelButton.label = localize('textCancel', 'Cancel');
}
private async validate(): Promise<boolean> {
let url = this.urlInputBox && this.urlInputBox.value;
let username = this.usernameInputBox && this.usernameInputBox.value;
let password = this.passwordInputBox && this.passwordInputBox.value;
let rememberPassword = this.passwordInputBox && !!this.rememberPwCheckBox.checked;
try {
await this.model.onComplete(url, username, password, rememberPassword);
return true;
} catch (error) {
showErrorMessage(error);
if (this.model && this.model.onError) {
await this.model.onError(error as IControllerError);
}
return false;
}
}
private async cancel(): Promise<void> {
if (this.model && this.model.onCancel) {
await this.model.onCancel();
}
}
}

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { TreeNode } from './treeNode';
import { BdcItemType } from '../constants';
const localize = nls.loadMessageBundle();
export class AddControllerNode extends TreeNode {
private readonly nodeType: string;
constructor() {
super(localize('textBigDataClusterControllerWithDots', 'Add Big Data Cluster Controller...'));
this.nodeType = BdcItemType.addController;
}
public async getChildren(): Promise<TreeNode[]> {
return [];
}
public getTreeItem(): vscode.TreeItem {
let item = new vscode.TreeItem(this.label, vscode.TreeItemCollapsibleState.None);
item.command = {
title: localize('textConnectToController', 'Connect to Controller'),
command: 'bigDataClusters.command.addController',
arguments: [this]
};
item.contextValue = this.nodeType;
return item;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.label,
isLeaf: this.isLeaf,
errorMessage: undefined,
metadata: undefined,
nodePath: this.nodePath,
nodeStatus: undefined,
nodeType: this.nodeType,
iconType: this.nodeType,
nodeSubType: undefined
};
}
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeNode } from './treeNode';
export interface IControllerTreeChangeHandler {
notifyNodeChanged(node?: TreeNode): void;
}

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import { TreeNode } from './treeNode';
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
import { AddControllerNode } from './addControllerTreeNode';
import { ControllerRootNode, ControllerNode } from './controllerTreeNode';
import { IEndPoint } from '../controller/clusterControllerApi';
import { showErrorMessage } from '../utils';
const ConfigNamespace = 'clusterControllers';
const CredentialNamespace = 'clusterControllerCredentials';
export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeNode>, IControllerTreeChangeHandler {
private _onDidChangeTreeData: vscode.EventEmitter<TreeNode> = new vscode.EventEmitter<TreeNode>();
public readonly onDidChangeTreeData: vscode.Event<TreeNode> = this._onDidChangeTreeData.event;
private root: ControllerRootNode;
private credentialProvider: azdata.CredentialProvider;
constructor() {
this.root = new ControllerRootNode(this);
this.loadSavedControllers();
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
if (element) {
return element.getChildren();
}
if (this.root.hasChildren) {
return this.root.getChildren();
} else {
return [new AddControllerNode()];
}
}
public getTreeItem(element: TreeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element.getTreeItem();
}
public addController(
url: string,
username: string,
password: string,
rememberPassword: boolean,
masterInstance?: IEndPoint
): void {
this.root.addControllerNode(url, username, password, rememberPassword, masterInstance);
this.notifyNodeChanged();
}
public deleteController(url: string, username: string): ControllerNode {
let deleted = this.root.deleteControllerNode(url, username);
if (deleted) {
this.notifyNodeChanged();
}
return deleted;
}
public notifyNodeChanged(node?: TreeNode): void {
this._onDidChangeTreeData.fire(node);
}
public async loadSavedControllers(): Promise<void> {
let config = vscode.workspace.getConfiguration(ConfigNamespace);
if (config && config.controllers) {
let controllers = config.controllers;
this.root.clearChildren();
for (let c of controllers) {
let password = undefined;
if (c.rememberPassword) {
password = await this.getPassword(c.url, c.username);
}
this.root.addChild(new ControllerNode(
c.url, c.username, password, c.rememberPassword,
undefined, this.root, this, undefined
));
}
this.notifyNodeChanged();
}
}
public async saveControllers(): Promise<void> {
let controllers = this.root.children.map(e => {
let controller = e as ControllerNode;
return {
url: controller.url,
username: controller.username,
password: controller.password,
rememberPassword: !!controller.rememberPassword
};
});
let controllersWithoutPassword = controllers.map(e => {
return {
url: e.url,
username: e.username,
rememberPassword: e.rememberPassword
};
});
try {
await vscode.workspace.getConfiguration(ConfigNamespace).update('controllers', controllersWithoutPassword, true);
} catch (error) {
showErrorMessage(error);
}
for (let e of controllers) {
if (e.rememberPassword) {
await this.savePassword(e.url, e.username, e.password);
} else {
await this.deletePassword(e.url, e.username);
}
}
}
private async savePassword(url: string, username: string, password: string): Promise<boolean> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let result = await provider.saveCredential(id, password);
return result;
}
private async deletePassword(url: string, username: string): Promise<boolean> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let result = await provider.deleteCredential(id);
return result;
}
private async getPassword(url: string, username: string): Promise<string> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let credential = await provider.readCredential(id);
return credential ? credential.password : undefined;
}
private async getCredentialProvider(): Promise<azdata.CredentialProvider> {
if (!this.credentialProvider) {
this.credentialProvider = await azdata.credentials.getProvider(CredentialNamespace);
}
return this.credentialProvider;
}
private createId(url: string, username: string): string {
return `${url}::${username}`;
}
}

View File

@@ -0,0 +1,392 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
import { TreeNode } from './treeNode';
import { IconPath, BdcItemType } from '../constants';
import { IEndPoint, IControllerError, getEndPoints } from '../controller/clusterControllerApi';
import { showErrorMessage } from '../utils';
const localize = nls.loadMessageBundle();
export abstract class ControllerTreeNode extends TreeNode {
constructor(
label: string,
parent: ControllerTreeNode,
private _treeChangeHandler: IControllerTreeChangeHandler,
private _description?: string,
private _nodeType?: string,
private _iconPath?: { dark: string, light: string }
) {
super(label, parent);
this._description = this._description || this.label;
}
public async getChildren(): Promise<ControllerTreeNode[]> {
return this.children as ControllerTreeNode[];
}
public refresh(): void {
super.refresh();
this.treeChangeHandler.notifyNodeChanged(this);
}
public getTreeItem(): vscode.TreeItem {
let item: vscode.TreeItem = {};
item.id = this.id;
item.label = this.label;
item.collapsibleState = this.isLeaf ?
vscode.TreeItemCollapsibleState.None :
vscode.TreeItemCollapsibleState.Collapsed;
item.iconPath = this._iconPath;
item.contextValue = this._nodeType;
item.tooltip = this._description;
item.iconPath = this._iconPath;
return item;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.label,
isLeaf: this.isLeaf,
errorMessage: undefined,
metadata: undefined,
nodePath: this.nodePath,
nodeStatus: undefined,
nodeType: this._nodeType,
iconType: this._nodeType,
nodeSubType: undefined
};
}
public get description(): string {
return this._description;
}
public set description(description: string) {
this._description = description;
}
public get nodeType(): string {
return this._nodeType;
}
public set nodeType(nodeType: string) {
this._nodeType = nodeType;
}
public set iconPath(iconPath: { dark: string, light: string }) {
this._iconPath = iconPath;
}
public get iconPath(): { dark: string, light: string } {
return this._iconPath;
}
public set treeChangeHandler(treeChangeHandler: IControllerTreeChangeHandler) {
this._treeChangeHandler = treeChangeHandler;
}
public get treeChangeHandler(): IControllerTreeChangeHandler {
return this._treeChangeHandler;
}
}
export class ControllerRootNode extends ControllerTreeNode {
private _masterNodeFactory: SqlMasterNodeFactory;
constructor(treeChangeHandler: IControllerTreeChangeHandler) {
super('root', undefined, treeChangeHandler, undefined, BdcItemType.controllerRoot);
this._masterNodeFactory = new SqlMasterNodeFactory();
}
public async getChildren(): Promise<ControllerNode[]> {
return this.children as ControllerNode[];
}
public addControllerNode(url: string, username: string, password: string, rememberPassword: boolean, masterInstance?: IEndPoint): void {
let controllerNode = this.getExistingControllerNode(url, username);
if (controllerNode) {
controllerNode.password = password;
controllerNode.rememberPassword = rememberPassword;
controllerNode.clearChildren();
} else {
controllerNode = new ControllerNode(url, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
this.addChild(controllerNode);
}
if (masterInstance) {
controllerNode.addSqlMasterNode(masterInstance.endpoint, masterInstance.description);
}
}
public deleteControllerNode(url: string, username: string): ControllerNode {
if (!url || !username) {
return undefined;
}
let nodes = this.children as ControllerNode[];
let index = nodes.findIndex(e => e.url === url && e.username === username);
let deleted = undefined;
if (index >= 0) {
deleted = nodes.splice(index, 1);
}
return deleted;
}
private getExistingControllerNode(url: string, username: string): ControllerNode {
if (!url || !username) {
return undefined;
}
let nodes = this.children as ControllerNode[];
return nodes.find(e => e.url === url && e.username === username);
}
public get sqlMasterNodeFactory(): SqlMasterNodeFactory {
return this._masterNodeFactory;
}
}
export class ControllerNode extends ControllerTreeNode {
constructor(
private _url: string,
private _username: string,
private _password: string,
private _rememberPassword: boolean,
label: string,
parent: ControllerTreeNode,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string,
) {
super(label, parent, treeChangeHandler, description, BdcItemType.controller, IconPath.controllerNode);
this.label = label;
this.description = description;
}
public async getChildren(): Promise<ControllerTreeNode[]> {
if (this.children && this.children.length > 0) {
this.clearChildren();
}
if (!this._password) {
vscode.commands.executeCommand('bigDataClusters.command.addController', this);
return this.children as ControllerTreeNode[];
}
try {
let response = await getEndPoints(this._url, this._username, this._password, true);
if (response && response.endPoints) {
let master = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
this.addSqlMasterNode(master.endpoint, master.description);
}
return this.children as ControllerTreeNode[];
} catch (error) {
showErrorMessage(error);
return this.children as ControllerTreeNode[];
}
}
private static toIpAndPort(url: string): string {
if (!url) {
return;
}
return url.trim().replace(/ /g, '').replace(/^.+\:\/\//, '').replace(/:(\d+)$/, ',$1');
}
public addSqlMasterNode(endPointAddress: string, description: string): void {
let epFolder = this.getEndPointFolderNode();
let node = (this.root as ControllerRootNode).sqlMasterNodeFactory
.getSqlMasterNode(endPointAddress, epFolder, undefined, this.treeChangeHandler, description);
epFolder.addChild(node);
}
private getEndPointFolderNode(): FolderNode {
let label = localize('textSqlServers', 'SQL Servers');
let epFolderNode = this.children.find(e => e instanceof FolderNode && e.label === label);
if (!epFolderNode) {
epFolderNode = new FolderNode(label, this, this.treeChangeHandler);
this.addChild(epFolderNode);
}
return epFolderNode as FolderNode;
}
public getTreeItem(): vscode.TreeItem {
let item: vscode.TreeItem = super.getTreeItem();
item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
return item;
}
public get url() {
return this._url;
}
public set url(url: string) {
this._url = url;
}
public get username() {
return this._username;
}
public set username(username: string) {
this._username = username;
}
public get password() {
return this._password;
}
public set password(pw: string) {
this._password = pw;
}
public get rememberPassword() {
return this._rememberPassword;
}
public set rememberPassword(rememberPassword: boolean) {
this._rememberPassword = rememberPassword;
}
public set label(label: string) {
super.label = label || `controller: ${ControllerNode.toIpAndPort(this._url)} (${this._username})`;
}
public get label(): string {
return super.label;
}
public set description(description: string) {
super.description = description || super.label;
}
public get description(): string {
return super.description;
}
}
export class FolderNode extends ControllerTreeNode {
constructor(
label: string,
parent: ControllerTreeNode,
treeChangeHandler: IControllerTreeChangeHandler
) {
super(label, parent, treeChangeHandler, label, BdcItemType.folder, IconPath.folderNode);
}
}
export class SqlMasterNode extends ControllerTreeNode {
private static readonly _role: string = 'sql-server-master';
private _username: string;
private _password: string;
constructor(
private _endPointAddress: string,
parent: ControllerTreeNode,
label: string,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string,
) {
super(label, parent, treeChangeHandler, description, BdcItemType.sqlMaster, IconPath.sqlMasterNode);
this._username = 'sa';
this.label = label;
this.description = description;
}
private getControllerPassword(): string {
if (!this._password) {
let current: TreeNode = this;
while (current && !(current instanceof ControllerNode)) {
current = current.parent;
}
this._password = current && current instanceof ControllerNode ? current.password : undefined;
}
return this._password;
}
public getTreeItem(): vscode.TreeItem {
let item = super.getTreeItem();
let connectionProfile: azdata.IConnectionProfile = {
id: this.id,
connectionName: this.id,
serverName: this._endPointAddress,
databaseName: '',
userName: this._username,
password: this.getControllerPassword(),
authenticationType: 'SqlLogin',
savePassword: false,
groupFullName: '',
groupId: '',
providerName: 'MSSQL',
saveProfile: false,
options: {}
};
return Object.assign(item, { payload: connectionProfile, childProvider: 'MSSQL' });
}
public get role() {
return SqlMasterNode._role;
}
public get endPointAddress() {
return this._endPointAddress;
}
public set endPointAddress(endPointAddress: string) {
this._endPointAddress = endPointAddress;
}
public set label(label: string) {
super.label = label || `master: ${this._endPointAddress} (${this._username})`;
}
public get label(): string {
return super.label;
}
public set description(description: string) {
super.description = description || super.label;
}
public get description(): string {
return super.description;
}
}
export class SqlMasterNodeFactory {
private registry: {} = {};
public getSqlMasterNode(
endPointAddress: string,
parent: ControllerTreeNode,
label: string,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string
): SqlMasterNode {
let id = this.createRegistryId(endPointAddress, 'sa');
if (!this.registry[id]) {
this.registry[id] = new SqlMasterNode(endPointAddress, parent, label, treeChangeHandler, description);
} else {
let node = this.registry[id] as SqlMasterNode;
node.parent = parent;
node.label = label;
node.treeChangeHandler = treeChangeHandler;
description = description;
}
return this.registry[id] as SqlMasterNode;
}
private createRegistryId(endPointAddress: string, username: string): string {
return `${endPointAddress}::${username}`;
}
}

View File

@@ -0,0 +1,195 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { generateGuid } from '../utils';
export abstract class TreeNode {
private _id: string;
private _children: TreeNode[];
private _isLeaf: boolean;
constructor(private _label: string, private _parent?: TreeNode) {
this.resetId();
}
public resetId(): void {
this._id = (this._label || '_') + `::${generateGuid()}`;
}
public get id(): string {
return this._id;
}
public set label(label: string) {
if (!this._label) {
this._label = label;
this.resetId();
} else {
this._label = label;
}
}
public get label(): string {
return this._label;
}
public set parent(parent: TreeNode) {
this._parent = parent;
}
public get parent(): TreeNode {
return this._parent;
}
public get children(): TreeNode[] {
if (!this._children) {
this._children = [];
}
return this._children;
}
public get hasChildren(): boolean {
return this.children && this.children.length > 0;
}
public set isLeaf(isLeaf: boolean) {
this._isLeaf = isLeaf;
}
public get isLeaf(): boolean {
return this._isLeaf;
}
public get root(): TreeNode {
return TreeNode.getRoot(this);
}
public equals(node: TreeNode): boolean {
if (!node) {
return undefined;
}
return this.nodePath === node.nodePath;
}
public refresh(): void {
this.resetId();
}
public static getRoot(node: TreeNode): TreeNode {
if (!node) {
return undefined;
}
let current: TreeNode = node;
while (current.parent) {
current = current.parent;
}
return current;
}
public get nodePath(): string {
return TreeNode.getNodePath(this);
}
public static getNodePath(node: TreeNode): string {
if (!node) {
return undefined;
}
let current: TreeNode = node;
let path = current._id;
while (current.parent) {
current = current.parent;
path = `${current._id}/${path}`;
}
return path;
}
public async findNode(condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNode(this, condition, expandIfNeeded);
}
public static async findNode(node: TreeNode, condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode> {
if (!node || !condition) {
return undefined;
}
let result: TreeNode = undefined;
let nodesToCheck: TreeNode[] = [node];
while (nodesToCheck.length > 0) {
let current = nodesToCheck.shift();
if (condition(current)) {
result = current;
break;
}
if (current.hasChildren) {
nodesToCheck = nodesToCheck.concat(current.children);
} else if (expandIfNeeded) {
let children = await current.getChildren();
if (children && children.length > 0) {
nodesToCheck = nodesToCheck.concat(children);
}
}
}
return result;
}
public async filterNode(condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode[]> {
return TreeNode.filterNode(this, condition, expandIfNeeded);
}
public static async filterNode(node: TreeNode, condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode[]> {
if (!node || !condition) {
return undefined;
}
let result: TreeNode[] = [];
let nodesToCheck: TreeNode[] = [node];
while (nodesToCheck.length > 0) {
let current = nodesToCheck.shift();
if (condition(current)) {
result.push(current);
}
if (current.hasChildren) {
nodesToCheck = nodesToCheck.concat(current.children);
} else if (expandIfNeeded) {
let children = await current.getChildren();
if (children && children.length > 0) {
nodesToCheck = nodesToCheck.concat(children);
}
}
}
return result;
}
public async findNodeByPath(path: string, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNodeByPath(this, path, expandIfNeeded);
}
public static async findNodeByPath(node: TreeNode, path: string, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNode(node, node => {
return node.nodePath && (node.nodePath === path || node.nodePath.startsWith(path));
}, expandIfNeeded);
}
public addChild(node: TreeNode): void {
if (!this._children) {
this._children = [];
}
this._children.push(node);
}
public clearChildren(): void {
if (this._children) {
this._children = [];
}
}
public abstract async getChildren(): Promise<TreeNode[]>;
public abstract getTreeItem(): vscode.TreeItem;
public abstract getNodeInfo(): azdata.NodeInfo;
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
export function generateGuid(): string {
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
let oct: string = '';
let tmp: number;
for (let a: number = 0; a < 4; a++) {
tmp = (4294967296 * Math.random()) | 0;
oct += hexValues[tmp & 0xF] +
hexValues[tmp >> 4 & 0xF] +
hexValues[tmp >> 8 & 0xF] +
hexValues[tmp >> 12 & 0xF] +
hexValues[tmp >> 16 & 0xF] +
hexValues[tmp >> 20 & 0xF] +
hexValues[tmp >> 24 & 0xF] +
hexValues[tmp >> 28 & 0xF];
}
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
}
export function showErrorMessage(error: any): void {
if (error) {
let text: string = undefined;
if (typeof error === 'string') {
text = error as string;
} else if (typeof error === 'object' && error !== null) {
let message = error.message;
let code = error.code || error.errno;
text = (code ? `${code} ` : '') + message;
} else {
text = `${error}`;
}
vscode.window.showErrorMessage(text);
}
}

View File

@@ -1,121 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Host } from '../kubectl/host';
import { Shell, Platform } from '../utility/shell';
const EXTENSION_CONFIG_KEY = 'mssql-bdc';
const KUBECONFIG_PATH_KEY = 'mssql-bdc.kubeconfig';
const KNOWN_KUBECONFIGS_KEY = 'mssql-bdc.knownKubeconfigs';
export async function addPathToConfig(configKey: string, value: string): Promise<void> {
await setConfigValue(configKey, value);
}
async function setConfigValue(configKey: string, value: any): Promise<void> {
await atAllConfigScopes(addValueToConfigAtScope, configKey, value);
}
async function addValueToConfigAtScope(configKey: string, value: any, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise<void> {
if (!createIfNotExist) {
if (!valueAtScope || !(valueAtScope[configKey])) {
return;
}
}
let newValue: any = {};
if (valueAtScope) {
newValue = Object.assign({}, valueAtScope);
}
newValue[configKey] = value;
await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope);
}
async function addValueToConfigArray(configKey: string, value: string): Promise<void> {
await atAllConfigScopes(addValueToConfigArrayAtScope, configKey, value);
}
async function addValueToConfigArrayAtScope(configKey: string, value: string, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise<void> {
if (!createIfNotExist) {
if (!valueAtScope || !(valueAtScope[configKey])) {
return;
}
}
let newValue: any = {};
if (valueAtScope) {
newValue = Object.assign({}, valueAtScope);
}
const arrayEntry: string[] = newValue[configKey] || [];
arrayEntry.push(value);
newValue[configKey] = arrayEntry;
await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope);
}
type ConfigUpdater<T> = (configKey: string, value: T, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean) => Promise<void>;
async function atAllConfigScopes<T>(fn: ConfigUpdater<T>, configKey: string, value: T): Promise<void> {
const config = vscode.workspace.getConfiguration().inspect(EXTENSION_CONFIG_KEY)!;
await fn(configKey, value, vscode.ConfigurationTarget.Global, config.globalValue, true);
await fn(configKey, value, vscode.ConfigurationTarget.Workspace, config.workspaceValue, false);
await fn(configKey, value, vscode.ConfigurationTarget.WorkspaceFolder, config.workspaceFolderValue, false);
}
// Functions for working with the list of known kubeconfigs
export function getKnownKubeconfigs(): string[] {
const kkcConfig = vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KNOWN_KUBECONFIGS_KEY];
if (!kkcConfig || !kkcConfig.length) {
return [];
}
return kkcConfig as string[];
}
export async function addKnownKubeconfig(kubeconfigPath: string) {
await addValueToConfigArray(KNOWN_KUBECONFIGS_KEY, kubeconfigPath);
}
// Functions for working with the active kubeconfig setting
export async function setActiveKubeconfig(kubeconfig: string): Promise<void> {
await addPathToConfig(KUBECONFIG_PATH_KEY, kubeconfig);
}
export function getActiveKubeconfig(): string {
return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KUBECONFIG_PATH_KEY];
}
// Functions for working with tool paths
export function getToolPath(host: Host, shell: Shell, tool: string): string | undefined {
const baseKey = toolPathBaseKey(tool);
return getPathSetting(host, shell, baseKey);
}
function getPathSetting(host: Host, shell: Shell, baseKey: string): string | undefined {
const os = shell.platform();
const osOverridePath = host.getConfiguration(EXTENSION_CONFIG_KEY)[osOverrideKey(os, baseKey)];
return osOverridePath || host.getConfiguration(EXTENSION_CONFIG_KEY)[baseKey];
}
export function toolPathBaseKey(tool: string): string {
return `mssql-bdc.${tool}-path`;
}
function osOverrideKey(os: Platform, baseKey: string): string {
const osKey = osKeyString(os);
return osKey ? `${baseKey}.${osKey}` : baseKey; // The 'else' clause should never happen so don't worry that this would result in double-checking a missing base key
}
function osKeyString(os: Platform): string | null {
switch (os) {
case Platform.Windows: return 'windows';
case Platform.MacOS: return 'mac';
case Platform.Linux: return 'linux';
default: return null;
}
}

View File

@@ -1,35 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ClusterInfo } from '../interfaces';
export interface IKubeConfigParser {
parse(configPath: string): ClusterInfo[];
}
export class TestKubeConfigParser implements IKubeConfigParser {
parse(configPath: string): ClusterInfo[] {
let clusters = [];
for (let i = 0; i < 18; i++) {
let name;
if (i % 2 === 0) {
name = `kubernetes cluster ${i}`;
}
else {
name = 'cluster dev ' + i;
}
clusters.push(
{
displayName: name,
name: `kub-dev-xxxx-cluster-${i}`,
user: 'root'
}
);
}
return clusters;
}
}

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { ControllerTreeDataProvider } from './bigDataCluster/tree/controllerTreeDataProvider';
import { IconPath } from './bigDataCluster/constants';
import { TreeNode } from './bigDataCluster/tree/treeNode';
import { AddControllerDialogModel, AddControllerDialog } from './bigDataCluster/dialog/addControllerDialog';
import { ControllerNode } from './bigDataCluster/tree/controllerTreeNode';
const localize = nls.loadMessageBundle();
export function activate(extensionContext: vscode.ExtensionContext) {
IconPath.setExtensionContext(extensionContext);
let treeDataProvider = new ControllerTreeDataProvider();
registerTreeDataProvider(treeDataProvider);
registerCommands(treeDataProvider);
}
export function deactivate() {
}
function registerTreeDataProvider(treeDataProvider: ControllerTreeDataProvider): void {
vscode.window.registerTreeDataProvider('sqlBigDataCluster', treeDataProvider);
}
function registerCommands(treeDataProvider: ControllerTreeDataProvider): void {
vscode.commands.registerCommand('bigDataClusters.command.addController', (node?: TreeNode) => {
addBdcController(treeDataProvider, node);
});
vscode.commands.registerCommand('bigDataClusters.command.deleteController', (node: TreeNode) => {
deleteBdcController(treeDataProvider, node);
});
vscode.commands.registerCommand('bigDataClusters.command.refreshController', (node: TreeNode) => {
if (!node) {
return;
}
treeDataProvider.notifyNodeChanged(node);
});
}
function addBdcController(treeDataProvider: ControllerTreeDataProvider, node?: TreeNode): void {
let model = new AddControllerDialogModel(treeDataProvider, node);
let dialog = new AddControllerDialog(model);
dialog.showDialog();
}
async function deleteBdcController(treeDataProvider: ControllerTreeDataProvider, node: TreeNode): Promise<boolean> {
if (!node && !(node instanceof ControllerNode)) {
return;
}
let controllerNode = node as ControllerNode;
let choices: { [id: string]: boolean } = {};
choices[localize('textYes', 'Yes')] = true;
choices[localize('textNo', 'No')] = false;
let options = {
ignoreFocusOut: false,
placeHolder: localize('textConfirmDeleteController', 'Are you sure you want to delete \'{0}\'?', controllerNode.label)
};
let result = await vscode.window.showQuickPick(Object.keys(choices), options);
let remove: boolean = !!(result && choices[result]);
if (remove) {
deleteControllerInternal(treeDataProvider, controllerNode);
}
return remove;
}
function deleteControllerInternal(treeDataProvider: ControllerTreeDataProvider, controllerNode: ControllerNode): void {
let deleted = treeDataProvider.deleteController(controllerNode.url, controllerNode.username);
if (deleted) {
treeDataProvider.saveControllers();
}
}

View File

@@ -1,45 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as stream from 'stream';
import * as tmp from 'tmp';
import { succeeded, Errorable } from '../interfaces';
type DownloadFunc =
(url: string, destination?: string, options?: any)
=> Promise<Buffer> & stream.Duplex; // Stream has additional events - see https://www.npmjs.com/package/download
let download: DownloadFunc;
function ensureDownloadFunc() {
if (!download) {
const home = process.env['HOME'];
download = require('download');
if (home) {
process.env['HOME'] = home;
}
}
}
export async function toTempFile(sourceUrl: string): Promise<Errorable<string>> {
const tempFileObj = tmp.fileSync({ prefix: 'mssql-bdc-autoinstall-' });
const downloadResult = await to(sourceUrl, tempFileObj.name);
if (succeeded(downloadResult)) {
return { succeeded: true, result: tempFileObj.name };
}
return { succeeded: false, error: downloadResult.error };
}
export async function to(sourceUrl: string, destinationFile: string): Promise<Errorable<null>> {
ensureDownloadFunc();
try {
await download(sourceUrl, path.dirname(destinationFile), { filename: path.basename(destinationFile) });
return { succeeded: true, result: null };
} catch (e) {
return { succeeded: false, error: [e.message] };
}
}

View File

@@ -1,72 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as download from './download';
import * as fs from 'fs';
import mkdirp = require('mkdirp');
import * as path from 'path';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { Shell, Platform } from '../utility/shell';
import { Errorable, failed } from '../interfaces';
import { addPathToConfig, toolPathBaseKey } from '../config/config';
export async function installKubectl(shell: Shell): Promise<Errorable<null>> {
const tool = 'kubectl';
const binFile = (shell.isUnix()) ? 'kubectl' : 'kubectl.exe';
const os = platformUrlString(shell.platform());
const version = await getStableKubectlVersion();
if (failed(version)) {
return { succeeded: false, error: version.error };
}
const installFolder = getInstallFolder(shell, tool);
mkdirp.sync(installFolder);
const kubectlUrl = `https://storage.googleapis.com/kubernetes-release/release/${version.result.trim()}/bin/${os}/amd64/${binFile}`;
const downloadFile = path.join(installFolder, binFile);
const downloadResult = await download.to(kubectlUrl, downloadFile);
if (failed(downloadResult)) {
return { succeeded: false, error: [localize('downloadKubectlFailed', 'Failed to download kubectl: {0}', downloadResult.error[0])] };
}
if (shell.isUnix()) {
fs.chmodSync(downloadFile, '0777');
}
await addPathToConfig(toolPathBaseKey(tool), downloadFile);
return { succeeded: true, result: null };
}
async function getStableKubectlVersion(): Promise<Errorable<string>> {
const downloadResult = await download.toTempFile('https://storage.googleapis.com/kubernetes-release/release/stable.txt');
if (failed(downloadResult)) {
return { succeeded: false, error: [localize('kubectlVersionCheckFailed', 'Failed to establish kubectl stable version: {0}', downloadResult.error[0])] };
}
const version = fs.readFileSync(downloadResult.result, 'utf-8');
fs.unlinkSync(downloadResult.result);
return { succeeded: true, result: version };
}
export function getInstallFolder(shell: Shell, tool: string): string {
return path.join(shell.home(), `.mssql-bdc/tools/${tool}`);
}
function platformUrlString(platform: Platform, supported?: Platform[]): string | null {
if (supported && supported.indexOf(platform) < 0) {
return null;
}
switch (platform) {
case Platform.Windows: return 'windows';
case Platform.MacOS: return 'darwin';
case Platform.Linux: return 'linux';
default: return null;
}
}

View File

@@ -1,124 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface ClusterInfo {
name: string;
displayName: string;
user: string;
}
export enum TargetClusterType {
ExistingKubernetesCluster,
NewAksCluster
}
export interface Succeeded<T> {
readonly succeeded: true;
readonly result: T;
}
export interface Failed {
readonly succeeded: false;
readonly error: string[];
}
export type Errorable<T> = Succeeded<T> | Failed;
export function succeeded<T>(e: Errorable<T>): e is Succeeded<T> {
return e.succeeded;
}
export function failed<T>(e: Errorable<T>): e is Failed {
return !e.succeeded;
}
export interface ClusterPorts {
sql: string;
knox: string;
controller: string;
proxy: string;
grafana: string;
kibana: string;
}
export interface ContainerRegistryInfo {
registry: string;
repository: string;
imageTag: string;
}
export interface TargetClusterTypeInfo {
enabled: boolean;
type: TargetClusterType;
name: string;
fullName: string;
description: string;
iconPath: {
dark: string,
light: string
};
}
export interface ToolInfo {
name: string;
description: string;
version: string;
status: ToolInstallationStatus;
}
export enum ToolInstallationStatus {
Installed,
NotInstalled,
Installing,
FailedToInstall
}
export enum ClusterType {
Unknown = 0,
AKS,
Minikube,
Kubernetes,
Other
}
export interface ClusterProfile {
name: string;
sqlServerMasterConfiguration: SQLServerMasterConfiguration;
computePoolConfiguration: PoolConfiguration;
dataPoolConfiguration: PoolConfiguration;
storagePoolConfiguration: PoolConfiguration;
sparkPoolConfiguration: PoolConfiguration;
}
export interface PoolConfiguration {
type: ClusterPoolType;
scale: number;
maxScale?: number;
hardwareLabel?: string;
}
export interface SQLServerMasterConfiguration extends PoolConfiguration {
engineOnly: boolean;
}
export enum ClusterPoolType {
SQL,
Compute,
Data,
Storage,
Spark
}
export interface ClusterResourceSummary {
hardwareLabels: HardwareLabel[];
}
export interface HardwareLabel {
name: string;
totalNodes: number;
totalCores: number;
totalMemoryInGB: number;
totalDisks: number;
}

View File

@@ -1,121 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { Shell } from '../utility/shell';
import { Host } from './host';
import { FS } from '../utility/fs';
export interface BinCheckContext {
readonly host: Host;
readonly fs: FS;
readonly shell: Shell;
readonly installDependenciesCallback: () => void;
binFound: boolean;
binPath: string;
}
interface FindBinaryResult {
err: number | null;
output: string;
}
async function findBinary(shell: Shell, binName: string): Promise<FindBinaryResult> {
let cmd = `which ${binName}`;
if (shell.isWindows()) {
cmd = `where.exe ${binName}.exe`;
}
const opts = {
async: true,
env: {
HOME: process.env.HOME,
PATH: process.env.PATH
}
};
const execResult = await shell.execCore(cmd, opts);
if (execResult.code) {
return { err: execResult.code, output: execResult.stderr };
}
return { err: null, output: execResult.stdout };
}
export function execPath(shell: Shell, basePath: string): string {
let bin = basePath;
if (shell.isWindows() && bin && !(bin.endsWith('.exe'))) {
bin = bin + '.exe';
}
return bin;
}
type CheckPresentFailureReason = 'inferFailed' | 'configuredFileMissing';
const installDependenciesAction = localize('installDependenciesAction', 'Install dependencies');
const learnMoreAction = localize('learnMoreAction', 'Learn more');
function alertNoBin(host: Host, binName: string, failureReason: CheckPresentFailureReason, message: string, installDependencies: () => void): void {
switch (failureReason) {
case 'inferFailed':
host.showErrorMessage(message, installDependenciesAction, learnMoreAction).then(
(str) => {
switch (str) {
case learnMoreAction:
host.showInformationMessage(localize('moreInfoMsg', 'Add {0} directory to path, or set "mssql-bdc.{0}-path" config to {0} binary.', binName));
break;
case installDependenciesAction:
installDependencies();
break;
}
}
);
break;
case 'configuredFileMissing':
host.showErrorMessage(message, installDependenciesAction).then(
(str) => {
if (str === installDependenciesAction) {
installDependencies();
}
}
);
break;
}
}
export async function checkForBinary(context: BinCheckContext, bin: string | undefined, binName: string, inferFailedMessage: string, configuredFileMissingMessage: string, alertOnFail: boolean): Promise<boolean> {
if (!bin) {
const fb = await findBinary(context.shell, binName);
if (fb.err || fb.output.length === 0) {
if (alertOnFail) {
alertNoBin(context.host, binName, 'inferFailed', inferFailedMessage, context.installDependenciesCallback);
}
return false;
}
context.binFound = true;
return true;
}
if (context.shell.isWindows) {
context.binFound = context.fs.existsSync(bin);
} else {
const sr = await context.shell.exec(`ls ${bin}`);
context.binFound = (!!sr && sr.code === 0);
}
if (context.binFound) {
context.binPath = bin;
} else {
if (alertOnFail) {
alertNoBin(context.host, binName, 'configuredFileMissing', configuredFileMissingMessage, context.installDependenciesCallback);
}
}
return context.binFound;
}

View File

@@ -1,66 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Errorable, failed } from '../interfaces';
interface CompatibilityGuaranteed {
readonly guaranteed: true;
}
interface CompatibilityNotGuaranteed {
readonly guaranteed: false;
readonly didCheck: boolean;
readonly clientVersion: string;
readonly serverVersion: string;
}
export type Compatibility = CompatibilityGuaranteed | CompatibilityNotGuaranteed;
export function isGuaranteedCompatible(c: Compatibility): c is CompatibilityGuaranteed {
return c.guaranteed;
}
export interface Version {
readonly major: string;
readonly minor: string;
readonly gitVersion: string;
}
export async function check(kubectlLoadJSON: (cmd: string) => Promise<Errorable<any>>): Promise<Compatibility> {
const version = await kubectlLoadJSON('version -o json');
if (failed(version)) {
return {
guaranteed: false,
didCheck: false,
clientVersion: '',
serverVersion: ''
};
}
const clientVersion: Version = version.result.clientVersion;
const serverVersion: Version = version.result.serverVersion;
if (isCompatible(clientVersion, serverVersion)) {
return { guaranteed: true };
}
return {
guaranteed: false,
didCheck: true,
clientVersion: clientVersion.gitVersion,
serverVersion: serverVersion.gitVersion
};
}
function isCompatible(clientVersion: Version, serverVersion: Version): boolean {
if (clientVersion.major === serverVersion.major) {
const clientMinor = Number.parseInt(clientVersion.minor);
const serverMinor = Number.parseInt(serverVersion.minor);
if (Number.isInteger(clientMinor) && Number.isInteger(serverMinor) && Math.abs(clientMinor - serverMinor) <= 1) {
return true;
}
}
return false;
}

View File

@@ -1,42 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export interface Host {
showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined>;
showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined>;
showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined>;
getConfiguration(key: string): any;
onDidChangeConfiguration(listener: (ch: vscode.ConfigurationChangeEvent) => any): vscode.Disposable;
}
export const host: Host = {
showErrorMessage: showErrorMessage,
showWarningMessage: showWarningMessage,
showInformationMessage: showInformationMessage,
getConfiguration: getConfiguration,
onDidChangeConfiguration: onDidChangeConfiguration,
};
function showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showErrorMessage(message, ...items);
}
function showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showWarningMessage(message, ...items);
}
function showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showInformationMessage(message, ...items);
}
function getConfiguration(key: string): any {
return vscode.workspace.getConfiguration(key);
}
function onDidChangeConfiguration(listener: (e: vscode.ConfigurationChangeEvent) => any): vscode.Disposable {
return vscode.workspace.onDidChangeConfiguration(listener);
}

View File

@@ -1,142 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { Host } from './host';
import { FS } from '../utility/fs';
import { Shell, ShellResult } from '../utility/shell';
import * as binutil from './binutil';
import { Errorable } from '../interfaces';
import * as compatibility from './compatibility';
import { getToolPath } from '../config/config';
export interface Kubectl {
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean>;
asJson<T>(command: string): Promise<Errorable<T>>;
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined>;
getContext(): Context;
}
interface Context {
readonly host: Host;
readonly fs: FS;
readonly shell: Shell;
readonly installDependenciesCallback: () => void;
binFound: boolean;
binPath: string;
}
class KubectlImpl implements Kubectl {
constructor(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void, kubectlFound: boolean) {
this.context = { host: host, fs: fs, shell: shell, installDependenciesCallback: installDependenciesCallback, binFound: kubectlFound, binPath: 'kubectl' };
}
readonly context: Context;
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
return checkPresent(this.context, errorMessageMode);
}
asJson<T>(command: string): Promise<Errorable<T>> {
return asJson(this.context, command);
}
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined> {
return invokeAsync(this.context, command, stdin);
}
getContext(): Context {
return this.context;
}
}
export function create(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void): Kubectl {
return new KubectlImpl(host, fs, shell, installDependenciesCallback, false);
}
export enum CheckPresentMessageMode {
Command,
Activation,
Silent,
}
async function checkPresent(context: Context, errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
if (context.binFound) {
return true;
}
return await checkForKubectlInternal(context, errorMessageMode);
}
async function checkForKubectlInternal(context: Context, errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
const binName = 'kubectl';
const bin = getToolPath(context.host, context.shell, binName);
const contextMessage = getCheckKubectlContextMessage(errorMessageMode);
const inferFailedMessage = localize('binaryNotFound', 'Could not find {0} binary. {1}', binName, contextMessage);
const configuredFileMissingMessage = localize('binaryNotInstalled', '{0} is not installed. {1}', bin, contextMessage);
return await binutil.checkForBinary(context, bin, binName, inferFailedMessage, configuredFileMissingMessage, errorMessageMode !== CheckPresentMessageMode.Silent);
}
function getCheckKubectlContextMessage(errorMessageMode: CheckPresentMessageMode): string {
if (errorMessageMode === CheckPresentMessageMode.Activation) {
return localize('kubernetesRequired', ' SQL Server Big data cluster requires kubernetes.');
} else if (errorMessageMode === CheckPresentMessageMode.Command) {
return localize('cannotExecuteCmd', ' Cannot execute command.');
}
return '';
}
async function invokeAsync(context: Context, command: string, stdin?: string): Promise<ShellResult | undefined> {
if (await checkPresent(context, CheckPresentMessageMode.Command)) {
const bin = baseKubectlPath(context);
const cmd = `${bin} ${command}`;
const sr = await context.shell.exec(cmd, stdin);
if (sr && sr.code !== 0) {
checkPossibleIncompatibility(context);
}
return sr;
} else {
return { code: -1, stdout: '', stderr: '' };
}
}
// TODO: invalidate this when the context changes or if we know kubectl has changed (e.g. config)
let checkedCompatibility = false; // We don't want to spam the user (or CPU!) repeatedly running the version check
async function checkPossibleIncompatibility(context: Context): Promise<void> {
if (checkedCompatibility) {
return;
}
checkedCompatibility = true;
const compat = await compatibility.check((cmd) => asJson<compatibility.Version>(context, cmd));
if (!compatibility.isGuaranteedCompatible(compat) && compat.didCheck) {
const versionAlert = localize('kubectlVersionIncompatible', 'kubectl version ${0} may be incompatible with cluster Kubernetes version {1}', compat.clientVersion, compat.serverVersion);
context.host.showWarningMessage(versionAlert);
}
}
export function baseKubectlPath(context: Context): string {
let bin = getToolPath(context.host, context.shell, 'kubectl');
if (!bin) {
bin = 'kubectl';
}
return bin;
}
async function asJson<T>(context: Context, command: string): Promise<Errorable<T>> {
const shellResult = await invokeAsync(context, command);
if (!shellResult) {
return { succeeded: false, error: [localize('cannotRunCommand', 'Unable to run command ({0})', command)] };
}
if (shellResult.code === 0) {
return { succeeded: true, result: JSON.parse(shellResult.stdout.trim()) as T };
}
return { succeeded: false, error: [shellResult.stderr] };
}

View File

@@ -1,136 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { Kubectl } from './kubectl';
import { failed, ClusterType } from '../interfaces';
export interface KubectlContext {
readonly clusterName: string;
readonly contextName: string;
readonly userName: string;
readonly active: boolean;
}
interface Kubeconfig {
readonly apiVersion: string;
readonly 'current-context': string;
readonly clusters: {
readonly name: string;
readonly cluster: {
readonly server: string;
readonly 'certificate-authority'?: string;
readonly 'certificate-authority-data'?: string;
};
}[] | undefined;
readonly contexts: {
readonly name: string;
readonly context: {
readonly cluster: string;
readonly user: string;
readonly namespace?: string;
};
}[] | undefined;
readonly users: {
readonly name: string;
readonly user: {};
}[] | undefined;
}
export interface ClusterConfig {
readonly server: string;
readonly certificateAuthority: string | undefined;
}
async function getKubeconfig(kubectl: Kubectl): Promise<Kubeconfig | null> {
const shellResult = await kubectl.asJson<any>('config view -o json');
if (failed(shellResult)) {
vscode.window.showErrorMessage(shellResult.error[0]);
return null;
}
return shellResult.result;
}
export async function getCurrentClusterConfig(kubectl: Kubectl): Promise<ClusterConfig | undefined> {
const kubeConfig = await getKubeconfig(kubectl);
if (!kubeConfig || !kubeConfig.clusters || !kubeConfig.contexts) {
return undefined;
}
const contextConfig = kubeConfig.contexts.find((context) => context.name === kubeConfig['current-context'])!;
const clusterConfig = kubeConfig.clusters.find((cluster) => cluster.name === contextConfig.context.cluster)!;
return {
server: clusterConfig.cluster.server,
certificateAuthority: clusterConfig.cluster['certificate-authority']
};
}
export async function getContexts(kubectl: Kubectl): Promise<KubectlContext[]> {
const kubectlConfig = await getKubeconfig(kubectl);
if (!kubectlConfig) {
return [];
}
const currentContext = kubectlConfig['current-context'];
const contexts = kubectlConfig.contexts || [];
return contexts.map((c) => {
return {
clusterName: c.context.cluster,
contextName: c.name,
userName: c.context.user,
active: c.name === currentContext
};
});
}
export async function setContext(kubectl: Kubectl, targetContext: string): Promise<void> {
const shellResult = await kubectl.invokeAsync(`config use-context ${targetContext}`);
if (!shellResult || shellResult.code !== 0) {
// TODO: Update error handling for now.
let errMsg = shellResult ? shellResult.stderr : localize('runKubectlFailed', 'Unable to run kubectl');
vscode.window.showErrorMessage(localize('setClusterFailed', 'Failed to set \'{0}\' as current cluster: {1}', targetContext, errMsg));
}
}
export async function inferCurrentClusterType(kubectl: Kubectl): Promise<ClusterType> {
let latestContextName = '';
const ctxsr = await kubectl.invokeAsync('config current-context');
if (ctxsr && ctxsr.code === 0) {
latestContextName = ctxsr.stdout.trim();
} else {
return ClusterType.Other;
}
const cisr = await kubectl.invokeAsync('cluster-info');
if (!cisr || cisr.code !== 0) {
return ClusterType.Unknown;
}
const masterInfos = cisr.stdout.split('\n')
.filter((s) => s.indexOf('master is running at') >= 0);
if (masterInfos.length === 0) {
return ClusterType.Other;
}
const masterInfo = masterInfos[0];
if (masterInfo.indexOf('azmk8s.io') >= 0 || masterInfo.indexOf('azure.com') >= 0) {
return ClusterType.AKS;
}
if (latestContextName) {
const gcsr = await kubectl.invokeAsync(`config get-contexts ${latestContextName}`);
if (gcsr && gcsr.code === 0) {
if (gcsr.stdout.indexOf('minikube') >= 0) {
return ClusterType.Minikube;
}
}
}
return ClusterType.Other;
}

View File

@@ -1,29 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export interface ISqlServerBigDataClusterChannel {
showOutput(message: any, title?: string): void;
}
const outputChannelName = localize('bigDataClusterOutputChannel', 'SQL Server big data cluster');
class SqlServerBigDataCluster implements ISqlServerBigDataClusterChannel {
private readonly channel: vscode.OutputChannel = vscode.window.createOutputChannel(outputChannelName);
showOutput(message: any, title?: string): void {
if (title) {
const simplifiedTime = (new Date()).toISOString().replace(/z|t/gi, ' ').trim(); // YYYY-MM-DD HH:mm:ss.sss
const hightlightingTitle = `[${title} ${simplifiedTime}]`;
this.channel.appendLine(hightlightingTitle);
}
this.channel.appendLine(message);
this.channel.show();
}
}
export const sqlserverbigdataclusterchannel: ISqlServerBigDataClusterChannel = new SqlServerBigDataCluster();

View File

@@ -1,60 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import vscode = require('vscode');
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { MainController } from './mainController';
import { fs } from './utility/fs';
import { host } from './kubectl/host';
import { sqlserverbigdataclusterchannel } from './kubectl/sqlServerBigDataClusterChannel';
import { shell, Shell } from './utility/shell';
import { CheckPresentMessageMode, create as kubectlCreate } from './kubectl/kubectl';
import { installKubectl } from './installer/installer';
import { Errorable, failed } from './interfaces';
const kubectl = kubectlCreate(host, fs, shell, installDependencies);
export let controller: MainController;
export function activate(context: vscode.ExtensionContext) {
controller = new MainController(context, kubectl);
controller.activate();
}
// this method is called when your extension is deactivated
export function deactivate(): void {
if (controller) {
controller.deactivate();
}
}
export async function installDependencies() {
const gotKubectl = await kubectl.checkPresent(CheckPresentMessageMode.Silent);
const installPromises = [
installDependency('kubectl', gotKubectl, installKubectl)
];
await Promise.all(installPromises);
sqlserverbigdataclusterchannel.showOutput(localize('done', 'Done'));
}
async function installDependency(name: string, alreadyGot: boolean, installFunc: (shell: Shell) => Promise<Errorable<null>>): Promise<void> {
if (alreadyGot) {
sqlserverbigdataclusterchannel.showOutput(localize('dependencyInstalled', '{0} already installed...', name));
} else {
sqlserverbigdataclusterchannel.showOutput(localize('installingDependency', 'Installing {0}...', name));
const result = await installFunc(shell);
if (failed(result)) {
sqlserverbigdataclusterchannel.showOutput(localize('installingDependencyFailed', 'Unable to install {0}: {1}', name, result.error[0]));
}
}
}

View File

@@ -1,36 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import { CreateClusterWizard } from './wizards/create-cluster/createClusterWizard';
import { Kubectl } from './kubectl/kubectl';
/**
* The main controller class that initializes the extension
*/
export class MainController {
protected _context: vscode.ExtensionContext;
protected _kubectl: Kubectl;
public constructor(context: vscode.ExtensionContext, kubectl: Kubectl) {
this._context = context;
this._kubectl = kubectl;
}
/**
* Activates the extension
*/
public activate(): void {
vscode.commands.registerCommand('mssql.cluster.create', () => {
let wizard = new CreateClusterWizard(this._context, this._kubectl);
wizard.open();
});
}
/**
* Deactivates the extension
*/
public deactivate(): void { }
}

View File

@@ -1,73 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { fs } from '../utility/fs';
import { Shell } from '../utility/shell';
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import mkdirp = require('mkdirp');
import { Kubectl, baseKubectlPath } from '../kubectl/kubectl';
import { KubectlContext } from '../kubectl/kubectlUtils';
export interface Scriptable {
getScriptProperties(): Promise<ScriptingDictionary<string>>;
getTargetKubectlContext(): KubectlContext;
}
export interface ScriptingDictionary<V> {
[name: string]: V;
}
const deployFilePrefix: string = 'mssql-bdc-deploy';
export class ScriptGenerator {
private _shell: Shell;
private _kubectl: Kubectl;
private _kubectlPath: string;
constructor(_kubectl: Kubectl) {
this._kubectl = _kubectl;
this._shell = this._kubectl.getContext().shell;
this._kubectlPath = baseKubectlPath(this._kubectl.getContext());
}
public async generateDeploymentScript(scriptable: Scriptable): Promise<void> {
let targetClusterName = scriptable.getTargetKubectlContext().clusterName;
let targetContextName = scriptable.getTargetKubectlContext().contextName;
let timestamp = new Date().getTime();
let deployFolder = this.getDeploymentFolder(this._shell);
let deployFileSuffix = this._shell.isWindows() ? `.bat` : `.sh`;
let deployFileName = `${deployFilePrefix}-${targetClusterName}-${timestamp}${deployFileSuffix}`;
let deployFilePath = path.join(deployFolder, deployFileName);
let envVars = '';
let propertiesDict = await scriptable.getScriptProperties();
for (let key in propertiesDict) {
let value = propertiesDict[key];
envVars += this._shell.isWindows() ? `Set ${key} = ${value}\n` : `export ${key} = ${value}\n`;
}
envVars += os.EOL;
let kubeContextcommand = `${this._kubectlPath} config use-context ${targetContextName}\n`;
// Todo: The API for mssqlctl may change per version, so need a version check to use proper syntax.
let deployCommand = `mssqlctl create cluster ${targetClusterName}\n`;
let deployContent = envVars + kubeContextcommand + deployCommand;
mkdirp.sync(deployFolder);
await fs.writeFile(deployFilePath, deployContent, handleError);
}
public getDeploymentFolder(shell: Shell): string {
return path.join(shell.home(), `.mssql-bdc/deployment`);
}
}
const handleError = (err: NodeJS.ErrnoException) => {
if (err) {
vscode.window.showErrorMessage(err.message);
}
};

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -1,65 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as sysfs from 'fs';
export interface FS {
existsSync(path: string): boolean;
readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void;
readFileSync(filename: string, encoding: string): string;
readFileToBufferSync(filename: string): Buffer;
writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void;
writeFileSync(filename: string, data: any): void;
dirSync(path: string): string[];
unlinkAsync(path: string): Promise<void>;
existsAsync(path: string): Promise<boolean>;
openAsync(path: string, flags: string): Promise<void>;
statSync(path: string): sysfs.Stats;
}
export const fs: FS = {
existsSync: (path) => sysfs.existsSync(path),
readFile: (filename, encoding, callback) => sysfs.readFile(filename, encoding, callback),
readFileSync: (filename, encoding) => sysfs.readFileSync(filename, encoding),
readFileToBufferSync: (filename) => sysfs.readFileSync(filename),
writeFile: (filename, data, callback) => sysfs.writeFile(filename, data, callback),
writeFileSync: (filename, data) => sysfs.writeFileSync(filename, data),
dirSync: (path) => sysfs.readdirSync(path),
unlinkAsync: (path) => {
return new Promise((resolve, reject) => {
sysfs.unlink(path, (error) => {
if (error) {
reject();
return;
}
resolve();
});
});
},
existsAsync: (path) => {
return new Promise((resolve) => {
sysfs.exists(path, (exists) => {
resolve(exists);
});
});
},
openAsync: (path, flags) => {
return new Promise((resolve, reject) => {
sysfs.open(path, flags, (error, _fd) => {
if (error) {
reject();
return;
}
resolve();
});
});
},
statSync: (path) => sysfs.statSync(path)
};

View File

@@ -1,204 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as shelljs from 'shelljs';
import * as path from 'path';
import { getActiveKubeconfig, getToolPath } from '../config/config';
import { host } from '../kubectl/host';
export enum Platform {
Windows,
MacOS,
Linux,
Unsupported,
}
export interface ExecCallback extends shelljs.ExecCallback { }
export interface Shell {
isWindows(): boolean;
isUnix(): boolean;
platform(): Platform;
home(): string;
combinePath(basePath: string, relativePath: string): string;
fileUri(filePath: string): vscode.Uri;
execOpts(): any;
exec(cmd: string, stdin?: string): Promise<ShellResult | undefined>;
execCore(cmd: string, opts: any, stdin?: string): Promise<ShellResult>;
unquotedPath(path: string): string;
which(bin: string): string | null;
cat(path: string): string;
ls(path: string): string[];
}
export const shell: Shell = {
isWindows: isWindows,
isUnix: isUnix,
platform: platform,
home: home,
combinePath: combinePath,
fileUri: fileUri,
execOpts: execOpts,
exec: exec,
execCore: execCore,
unquotedPath: unquotedPath,
which: which,
cat: cat,
ls: ls,
};
const WINDOWS: string = 'win32';
export interface ShellResult {
readonly code: number;
readonly stdout: string;
readonly stderr: string;
}
export type ShellHandler = (code: number, stdout: string, stderr: string) => void;
function isWindows(): boolean {
return (process.platform === WINDOWS);
}
function isUnix(): boolean {
return !isWindows();
}
function platform(): Platform {
switch (process.platform) {
case 'win32': return Platform.Windows;
case 'darwin': return Platform.MacOS;
case 'linux': return Platform.Linux;
default: return Platform.Unsupported;
}
}
function concatIfBoth(s1: string | undefined, s2: string | undefined): string | undefined {
return s1 && s2 ? s1.concat(s2) : undefined;
}
function home(): string {
return process.env['HOME'] ||
concatIfBoth(process.env['HOMEDRIVE'], process.env['HOMEPATH']) ||
process.env['USERPROFILE'] ||
'';
}
function combinePath(basePath: string, relativePath: string) {
let separator = '/';
if (isWindows()) {
relativePath = relativePath.replace(/\//g, '\\');
separator = '\\';
}
return basePath + separator + relativePath;
}
function isWindowsFilePath(filePath: string) {
return filePath[1] === ':' && filePath[2] === '\\';
}
function fileUri(filePath: string): vscode.Uri {
if (isWindowsFilePath(filePath)) {
return vscode.Uri.parse('file:///' + filePath.replace(/\\/g, '/'));
}
return vscode.Uri.parse('file://' + filePath);
}
function execOpts(): any {
let env = process.env;
if (isWindows()) {
env = Object.assign({}, env, { HOME: home() });
}
env = shellEnvironment(env);
const opts = {
cwd: vscode.workspace.rootPath,
env: env,
async: true
};
return opts;
}
async function exec(cmd: string, stdin?: string): Promise<ShellResult | undefined> {
try {
return await execCore(cmd, execOpts(), stdin);
} catch (ex) {
vscode.window.showErrorMessage(ex);
return undefined;
}
}
function execCore(cmd: string, opts: any, stdin?: string): Promise<ShellResult> {
return new Promise<ShellResult>((resolve) => {
const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => resolve({ code: code, stdout: stdout, stderr: stderr }));
if (stdin) {
proc.stdin.end(stdin);
}
});
}
function unquotedPath(path: string): string {
if (isWindows() && path && path.length > 1 && path.startsWith('"') && path.endsWith('"')) {
return path.substring(1, path.length - 1);
}
return path;
}
export function shellEnvironment(baseEnvironment: any): any {
const env = Object.assign({}, baseEnvironment);
const pathVariable = pathVariableName(env);
for (const tool of ['kubectl']) {
const toolPath = getToolPath(host, shell, tool);
if (toolPath) {
const toolDirectory = path.dirname(toolPath);
const currentPath = env[pathVariable];
env[pathVariable] = toolDirectory + (currentPath ? `${pathEntrySeparator()}${currentPath}` : '');
}
}
const kubeconfig = getActiveKubeconfig();
if (kubeconfig) {
env['KUBECONFIG'] = kubeconfig;
}
return env;
}
function pathVariableName(env: any): string {
if (isWindows()) {
for (const v of Object.keys(env)) {
if (v.toLowerCase() === 'path') {
return v;
}
}
}
return 'PATH';
}
function pathEntrySeparator() {
return isWindows() ? ';' : ':';
}
function which(bin: string): string | null {
return shelljs.which(bin);
}
function cat(path: string): string {
return shelljs.cat(path);
}
function ls(path: string): string[] {
return shelljs.ls(path);
}
export function shellMessage(sr: ShellResult | undefined, invocationFailureMessage: string): string {
if (!sr) {
return invocationFailureMessage;
}
return sr.code === 0 ? sr.stdout : sr.stderr;
}

View File

@@ -1,328 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TargetClusterType, ClusterPorts, ClusterType, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo, ToolInstallationStatus, ClusterProfile, PoolConfiguration, SQLServerMasterConfiguration, ClusterPoolType, ClusterResourceSummary } from '../../interfaces';
import { getContexts, KubectlContext, setContext, inferCurrentClusterType } from '../../kubectl/kubectlUtils';
import { Kubectl } from '../../kubectl/kubectl';
import { Scriptable, ScriptingDictionary } from '../../scripting/scripting';
import * as nls from 'vscode-nls';
import * as os from 'os';
import * as path from 'path';
const localize = nls.loadMessageBundle();
export class CreateClusterModel implements Scriptable {
private _tmp_tools_installed: boolean = false;
private scriptingProperties: ScriptingDictionary<string> = {};
constructor(private _kubectl: Kubectl) {
}
public async loadClusters(): Promise<KubectlContext[]> {
return await getContexts(this._kubectl);
}
public async changeKubernetesContext(targetContext: string): Promise<void> {
await setContext(this._kubectl, targetContext);
}
public getDefaultPorts(): Thenable<ClusterPorts> {
let promise = new Promise<ClusterPorts>(resolve => {
resolve({
sql: '31433',
knox: '30443',
controller: '30888',
proxy: '30909',
grafana: '30119',
kibana: '30999'
});
});
return promise;
}
public getDefaultContainerRegistryInfo(): Thenable<ContainerRegistryInfo> {
let promise = new Promise<ContainerRegistryInfo>(resolve => {
resolve({
registry: 'private-repo.microsoft.com',
repository: 'mssql-private-preview',
imageTag: 'latest'
});
});
return promise;
}
public getAllTargetClusterTypeInfo(): Thenable<TargetClusterTypeInfo[]> {
let promise = new Promise<TargetClusterTypeInfo[]>(resolve => {
let aksCluster: TargetClusterTypeInfo = {
enabled: false,
type: TargetClusterType.NewAksCluster,
name: localize('bdc-create.AKSClusterCardText', 'New AKS Cluster'),
fullName: localize('bdc-create.AKSClusterFullName', 'New Azure Kubernetes Service cluster'),
description: localize('bdc-create.AKSClusterDescription',
'This option configures new Azure Kubernetes Service (AKS) for SQL Server big data cluster deployments. AKS makes it simple to create, configure and manage a cluster of virutal machines that are preconfigured with a Kubernetes cluster to run containerized applications.'),
iconPath: {
dark: 'images/aks.svg',
light: 'images/aks.svg'
}
};
let existingCluster: TargetClusterTypeInfo = {
enabled: true,
type: TargetClusterType.ExistingKubernetesCluster,
name: localize('bdc-create.ExistingClusterCardText', 'Existing Cluster'),
fullName: localize('bdc-create.ExistingClusterFullName', 'Existing Kubernetes cluster'),
description: localize('bdc-create.ExistingClusterDescription', 'This option assumes you already have a Kubernetes cluster installed, Once a prerequisite check is done, ensure the correct cluster context is selected.'),
iconPath: {
dark: 'images/kubernetes.svg',
light: 'images/kubernetes.svg'
}
};
resolve([aksCluster, existingCluster]);
});
return promise;
}
public getRequiredToolStatus(): Thenable<ToolInfo[]> {
let kubeCtl = {
name: 'kubectl',
description: 'Tool used for managing the Kubernetes cluster',
version: '',
status: ToolInstallationStatus.Installed
};
let mssqlCtl = {
name: 'mssqlctl',
description: 'Command-line tool for installing and managing the SQL Server big data cluster',
version: '',
status: ToolInstallationStatus.Installed
};
let azureCli = {
name: 'Azure CLI',
description: 'Tool used for managing Azure services',
version: '',
status: this._tmp_tools_installed ? ToolInstallationStatus.Installed : ToolInstallationStatus.NotInstalled
};
let promise = new Promise<ToolInfo[]>(resolve => {
setTimeout(() => {
let tools = this.targetClusterType === TargetClusterType.ExistingKubernetesCluster ? [kubeCtl, mssqlCtl] : [kubeCtl, mssqlCtl, azureCli];
resolve(tools);
}, 1000);
});
return promise;
}
public installTool(tool: ToolInfo): Thenable<void> {
let promise = new Promise<void>(resolve => {
setTimeout(() => {
tool.status = ToolInstallationStatus.Installed;
this._tmp_tools_installed = true;
resolve();
}, 1000);
});
return promise;
}
public getDefaultKubeConfigPath(): string {
return path.join(os.homedir(), '.kube', 'config');
}
public clusterName: string;
public targetClusterType: TargetClusterType;
public selectedCluster: KubectlContext;
public adminUserName: string;
public adminPassword: string;
public sqlPort: string;
public knoxPort: string;
public controllerPort: string;
public proxyPort: string;
public grafanaPort: string;
public kibanaPort: string;
public containerRegistry: string;
public containerRepository: string;
public containerImageTag: string;
public containerRegistryUserName: string;
public containerRegistryPassword: string;
public profile: ClusterProfile;
public async getTargetClusterPlatform(targetContextName: string): Promise<string> {
await setContext(this._kubectl, targetContextName);
let clusterType = await inferCurrentClusterType(this._kubectl);
switch (clusterType) {
case ClusterType.AKS:
return 'aks';
case ClusterType.Minikube:
return 'minikube';
case ClusterType.Other:
default:
return 'kubernetes';
}
}
public async getScriptProperties(): Promise<ScriptingDictionary<string>> {
// Cluster settings
this.scriptingProperties['CLUSTER_NAME'] = this.selectedCluster.clusterName;
this.scriptingProperties['CLUSTER_PLATFORM'] = await this.getTargetClusterPlatform(this.selectedCluster.contextName);
// Default pool count for now. TODO: Update from user input
this.scriptingProperties['CLUSTER_DATA_POOL_REPLICAS'] = '1';
this.scriptingProperties['CLUSTER_COMPUTE_POOL_REPLICAS'] = '2';
this.scriptingProperties['CLUSTER_STORAGE_POOL_REPLICAS'] = '3';
// SQL Server settings
this.scriptingProperties['CONTROLLER_USERNAME'] = this.adminUserName;
this.scriptingProperties['CONTROLLER_PASSWORD'] = this.adminPassword;
this.scriptingProperties['KNOX_PASSWORD'] = this.adminPassword;
this.scriptingProperties['MSSQL_SA_PASSWORD'] = this.adminPassword;
// docker settings
this.scriptingProperties['DOCKER_REPOSITORY'] = this.containerRepository;
this.scriptingProperties['DOCKER_REGISTRY'] = this.containerRegistry;
this.scriptingProperties['DOCKER_PASSWORD'] = this.containerRegistryPassword;
this.scriptingProperties['DOCKER_USERNAME'] = this.containerRegistryUserName;
this.scriptingProperties['DOCKER_IMAGE_TAG'] = this.containerImageTag;
// port settings
this.scriptingProperties['MASTER_SQL_PORT'] = this.sqlPort;
this.scriptingProperties['KNOX_PORT'] = this.knoxPort;
this.scriptingProperties['GRAFANA_PORT'] = this.grafanaPort;
this.scriptingProperties['KIBANA_PORT'] = this.kibanaPort;
return this.scriptingProperties;
}
public getTargetKubectlContext(): KubectlContext {
return this.selectedCluster;
}
public getClusterResource(): Thenable<ClusterResourceSummary> {
let promise = new Promise<ClusterResourceSummary>(resolve => {
setTimeout(() => {
let resoureSummary: ClusterResourceSummary = {
hardwareLabels: [
{
name: '<Default>',
totalNodes: 10,
totalCores: 22,
totalDisks: 128,
totalMemoryInGB: 77
},
{
name: '#data',
totalNodes: 4,
totalCores: 22,
totalDisks: 200,
totalMemoryInGB: 100
},
{
name: '#compute',
totalNodes: 12,
totalCores: 124,
totalDisks: 24,
totalMemoryInGB: 100
},
{
name: '#premium',
totalNodes: 10,
totalCores: 100,
totalDisks: 200,
totalMemoryInGB: 770
}
]
};
resolve(resoureSummary);
}, 1000);
});
return promise;
}
public getProfiles(): Thenable<ClusterProfile[]> {
let promise = new Promise<ClusterProfile[]>(resolve => {
setTimeout(() => {
let profiles: ClusterProfile[] = [];
profiles.push({
name: 'Basic',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(1, 1),
computePoolConfiguration: this.createComputePoolConfiguration(2),
dataPoolConfiguration: this.createDataPoolConfiguration(2),
storagePoolConfiguration: this.createStoragePoolConfiguration(2),
sparkPoolConfiguration: this.createSparkPoolConfiguration(2)
});
profiles.push({
name: 'Standard',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(3, 9),
computePoolConfiguration: this.createComputePoolConfiguration(5),
dataPoolConfiguration: this.createDataPoolConfiguration(5),
storagePoolConfiguration: this.createStoragePoolConfiguration(5),
sparkPoolConfiguration: this.createSparkPoolConfiguration(5)
});
profiles.push({
name: 'Premium',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(5, 9),
computePoolConfiguration: this.createComputePoolConfiguration(7),
dataPoolConfiguration: this.createDataPoolConfiguration(7),
storagePoolConfiguration: this.createStoragePoolConfiguration(7),
sparkPoolConfiguration: this.createSparkPoolConfiguration(7)
});
resolve(profiles);
}, 1000);
});
return promise;
}
private createSQLPoolConfiguration(scale: number, maxScale: number): SQLServerMasterConfiguration {
return <SQLServerMasterConfiguration>{
type: ClusterPoolType.SQL,
engineOnly: false,
scale: scale,
maxScale: maxScale
};
}
private createComputePoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Compute,
scale: scale
};
}
private createDataPoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Data,
scale: scale
};
}
private createStoragePoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Storage,
scale: scale
};
}
private createSparkPoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Spark,
scale: scale
};
}
}

View File

@@ -1,54 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { CreateClusterModel } from './createClusterModel';
import { SelectExistingClusterPage } from './pages/selectExistingClusterPage';
import { SummaryPage } from './pages/summaryPage';
import { SettingsPage } from './pages/settingsPage';
import { ClusterProfilePage } from './pages/clusterProfilePage';
import { ExtensionContext } from 'vscode';
import { WizardBase } from '../wizardBase';
import * as nls from 'vscode-nls';
import { Kubectl } from '../../kubectl/kubectl';
import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage';
import { ScriptGenerator } from '../../scripting/scripting';
const localize = nls.loadMessageBundle();
export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateClusterWizard> {
private scripter: ScriptGenerator;
constructor(context: ExtensionContext, kubectl: Kubectl) {
let model = new CreateClusterModel(kubectl);
super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster'));
this.scripter = new ScriptGenerator(kubectl);
}
protected initialize(): void {
let settingsPage = new SettingsPage(this);
let clusterProfilePage = new ClusterProfilePage(this);
let selectTargetClusterPage = new SelectExistingClusterPage(this);
let summaryPage = new SummaryPage(this);
let targetClusterTypePage = new SelectTargetClusterTypePage(this);
this.setPages([targetClusterTypePage, selectTargetClusterPage, settingsPage, clusterProfilePage, summaryPage]);
this.wizardObject.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts');
this.wizardObject.generateScriptButton.hidden = false;
this.wizardObject.doneButton.label = localize('bdc-create.createClusterButtonText', 'Create');
this.registerDisposable(this.wizardObject.generateScriptButton.onClick(async () => {
this.wizardObject.generateScriptButton.enabled = false;
this.scripter.generateDeploymentScript(this.model).then(() => {
this.wizardObject.generateScriptButton.enabled = true;
//TODO: Add error handling.
});
}));
}
protected onCancel(): void {
}
protected onOk(): void {
}
}

View File

@@ -1,389 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import { CreateClusterWizard } from '../createClusterWizard';
import * as nls from 'vscode-nls';
import { ClusterProfile, PoolConfiguration, ClusterPoolType, SQLServerMasterConfiguration, ClusterResourceSummary } from '../../../interfaces';
const localize = nls.loadMessageBundle();
const LabelWidth = '200px';
const InputWidth = '300px';
export class ClusterProfilePage extends WizardPageBase<CreateClusterWizard> {
private view: azdata.ModelView;
private clusterProfiles: ClusterProfile[];
private poolList: azdata.FlexContainer;
private detailContainer: azdata.FlexContainer;
private clusterResourceView: azdata.GroupContainer;
private poolListMap = {};
private clusterResourceContainer: azdata.FlexContainer;
private clusterResourceLoadingComponent: azdata.LoadingComponent;
private clusterResource: ClusterResourceSummary;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.clusterProfilePageTitle', 'Select a cluster profile'),
localize('bdc-create.clusterProfilePageDescription', 'Select your requirement and we will provide you a pre-defined default scaling. You can later go to cluster configuration and customize it.'),
wizard);
}
public onEnter(): void {
this.updatePoolList();
this.clusterResourceLoadingComponent.loading = true;
this.wizard.model.getClusterResource().then((resource) => {
this.clusterResource = resource;
this.initializeClusterResourceView();
});
this.wizard.wizardObject.registerNavigationValidator(() => {
return true;
});
}
protected initialize(view: azdata.ModelView): Thenable<void> {
this.view = view;
let fetchProfilePromise = this.wizard.model.getProfiles().then(p => { this.clusterProfiles = p; });
return Promise.all([fetchProfilePromise]).then(() => {
this.wizard.model.profile = this.clusterProfiles[0];
this.clusterResourceView = this.view.modelBuilder.groupContainer().withLayout({
header: localize('bdc-create.TargetClusterOverview', 'Target cluster scale overview'),
collapsed: true,
collapsible: true
}).component();
this.clusterResourceContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
this.clusterResourceLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterResourceContainer).component();
this.clusterResourceView.addItem(this.clusterResourceLoadingComponent);
let profileLabel = view.modelBuilder.text().withProperties({ value: localize('bdc-create.clusterProfileLabel', 'Deployment profile') }).component();
let profileDropdown = view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
values: this.clusterProfiles.map(profile => profile.name),
width: '300px'
}).component();
let dropdownRow = this.view.modelBuilder.flexContainer().withItems([profileLabel, profileDropdown], { CSSStyles: { 'margin-right': '30px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
let poolContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', width: '100%', height: '100%' }).component();
this.poolList = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '300px', height: '100%' }).component();
poolContainer.addItem(this.poolList, {
CSSStyles: {
'border-top-style': 'solid',
'border-top-width': '2px',
'border-right-style': 'solid',
'border-right-width': '2px',
'border-color': 'lightgray'
}
});
this.detailContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '760px', height: '100%' }).component();
poolContainer.addItem(this.detailContainer, {
CSSStyles: {
'border-top-style': 'solid',
'border-top-width': '2px',
'border-color': 'lightgray'
}
});
this.wizard.registerDisposable(profileDropdown.onValueChanged(() => {
let profiles = this.clusterProfiles.filter(p => profileDropdown.value === p.name);
if (profiles && profiles.length === 1) {
this.wizard.model.profile = profiles[0];
this.updatePoolList();
this.clearPoolDetail();
}
}));
this.initializePoolList();
let pageContainer = this.view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
height: '800px'
}).component();
pageContainer.addItem(this.clusterResourceView, {
flex: '0 0 auto',
CSSStyles: {
'margin-bottom': '20px',
'padding-bottom': '5px',
'padding-top': '5px'
}
});
pageContainer.addItem(dropdownRow, {
flex: '0 0 auto',
CSSStyles: { 'margin-bottom': '10px' }
});
pageContainer.addItem(poolContainer, {
flex: '1 1 auto',
CSSStyles: {
'display': 'flex'
}
});
let formBuilder = view.modelBuilder.formContainer();
let form = formBuilder.withFormItems([{
title: '',
component: pageContainer
}], {
horizontal: false,
componentWidth: '100%'
}).component();
return view.initializeModel(form);
});
}
private initializeClusterResourceView(): void {
this.clusterResourceContainer.clearItems();
let text = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.HardwareProfileText', 'Hardware profile') }).component();
let height = (this.clusterResource.hardwareLabels.length * 25) + 30;
let labelColumn: azdata.TableColumn = {
value: localize('bdc-create.HardwareLabelColumnName', 'Label'),
width: 100
};
let totalNodesColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalNodesColumnName', 'Nodes'),
width: 50
};
let totalCoresColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalCoresColumnName', 'Cores'),
width: 50
};
let totalMemoryColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalMemoryColumnName', 'Memory'),
width: 50
};
let totalDisksColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalDisksColumnName', 'Disks'),
width: 50
};
let table = this.view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
height: `${height}px`,
data: this.clusterResource.hardwareLabels.map(label => [label.name, label.totalNodes, label.totalCores, label.totalMemoryInGB, label.totalDisks]),
columns: [labelColumn, totalNodesColumn, totalCoresColumn, totalMemoryColumn, totalDisksColumn],
width: '300px'
}).component();
this.clusterResourceContainer.addItems([text, table]);
this.clusterResourceLoadingComponent.loading = false;
}
private initializePoolList(): void {
let pools = [this.wizard.model.profile.sqlServerMasterConfiguration,
this.wizard.model.profile.computePoolConfiguration,
this.wizard.model.profile.dataPoolConfiguration,
this.wizard.model.profile.sparkPoolConfiguration,
this.wizard.model.profile.storagePoolConfiguration];
pools.forEach(pool => {
let poolSummaryButton = this.view.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: true }).component();
let container = this.view.modelBuilder.flexContainer().component();
this.wizard.registerDisposable(poolSummaryButton.onDidClick(() => {
this.clearPoolDetail();
let currentPool: PoolConfiguration;
switch (pool.type) {
case ClusterPoolType.SQL:
currentPool = this.wizard.model.profile.sqlServerMasterConfiguration;
break;
case ClusterPoolType.Compute:
currentPool = this.wizard.model.profile.computePoolConfiguration;
break;
case ClusterPoolType.Data:
currentPool = this.wizard.model.profile.dataPoolConfiguration;
break;
case ClusterPoolType.Storage:
currentPool = this.wizard.model.profile.storagePoolConfiguration;
break;
case ClusterPoolType.Spark:
currentPool = this.wizard.model.profile.sparkPoolConfiguration;
break;
default:
break;
}
if (currentPool) {
this.detailContainer.addItem(this.createPoolConfigurationPart(currentPool), { CSSStyles: { 'margin-left': '10px' } });
}
}));
let text = this.view.modelBuilder.text().component();
this.poolListMap[pool.type] = text;
text.width = '250px';
let chrevron = this.view.modelBuilder.text().withProperties({ value: '>' }).component();
chrevron.width = '30px';
container.addItem(text);
container.addItem(chrevron, {
CSSStyles: {
'font-size': '20px',
'line-height': '0px'
}
});
poolSummaryButton.addItem(container);
this.poolList.addItem(poolSummaryButton, {
CSSStyles: {
'border-bottom-style': 'solid',
'border-bottom-width': '1px',
'border-color': 'lightgray',
'cursor': 'pointer'
}
});
});
}
private createPoolConfigurationPart(configuration: PoolConfiguration): azdata.Component {
let container = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
switch (configuration.type) {
case ClusterPoolType.SQL:
this.createSQLConfigurationPart(container, configuration as SQLServerMasterConfiguration);
break;
default:
this.createDefaultPoolConfigurationPart(container, configuration);
break;
}
return container;
}
private createSQLConfigurationPart(container: azdata.FlexContainer, configuration: SQLServerMasterConfiguration): void {
this.createDefaultPoolConfigurationPart(container, configuration);
this.addFeatureSetRow(container, configuration);
}
private createDefaultPoolConfigurationPart(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
this.addPoolNameLabel(container, this.getPoolDisplayName(configuration.type));
this.addPoolDescriptionLabel(container, this.getPoolDescription(configuration.type));
this.addScaleRow(container, configuration);
this.addHardwareLabelRow(container, configuration);
}
private addPoolNameLabel(container: azdata.FlexContainer, text: string): void {
let poolNameLabel = this.view.modelBuilder.text().withProperties({ value: text }).component();
container.addItem(poolNameLabel, {
flex: '0 0 auto', CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
}
});
}
private addPoolDescriptionLabel(container: azdata.FlexContainer, text: string): void {
let label = this.view.modelBuilder.text().withProperties({ value: text }).component();
container.addItem(label, {
flex: '0 0 auto',
CSSStyles: {
'margin-bottom': '20px'
}
});
}
private addScaleRow(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.ScaleLabel', 'Scale') }).component();
label.width = LabelWidth;
let input = this.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
inputType: 'number',
value: configuration.scale.toString(),
min: 1,
max: configuration.maxScale
}).component();
this.wizard.registerDisposable(input.onTextChanged(() => {
configuration.scale = Number(input.value);
this.updatePoolList();
}));
input.width = InputWidth;
let row = this.createRow([label, input]);
container.addItem(row);
}
private addHardwareLabelRow(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.HardwareProfileLabel', 'Hardware profile label') }).component();
label.width = LabelWidth;
let optionalValues = this.clusterResource.hardwareLabels.map(label => label.name);
configuration.hardwareLabel = configuration.hardwareLabel ? configuration.hardwareLabel : optionalValues[0];
let input = this.view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({ value: configuration.hardwareLabel, values: optionalValues }).component();
this.wizard.registerDisposable(input.onValueChanged(() => {
configuration.hardwareLabel = input.value.toString();
}));
input.width = InputWidth;
let row = this.createRow([label, input]);
container.addItem(row);
}
private addFeatureSetRow(container: azdata.FlexContainer, configuration: SQLServerMasterConfiguration): void {
const radioGroupName = 'featureset';
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.FeatureSetLabel', 'Feature set') }).component();
label.width = LabelWidth;
let engineOnlyOption = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: localize('bdc-create.EngineOnlyText', 'Engine only'), name: radioGroupName, checked: configuration.engineOnly }).component();
let engineWithFeaturesOption = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: localize('bdc-create.EngineWithFeaturesText', 'Engine with optional features'), name: radioGroupName, checked: !configuration.engineOnly }).component();
let optionContainer = this.view.modelBuilder.divContainer().component();
optionContainer.width = InputWidth;
optionContainer.addItems([engineOnlyOption, engineWithFeaturesOption]);
container.addItem(this.createRow([label, optionContainer]));
this.wizard.registerDisposable(engineOnlyOption.onDidClick(() => {
configuration.engineOnly = true;
}));
this.wizard.registerDisposable(engineWithFeaturesOption.onDidClick(() => {
configuration.engineOnly = false;
}));
}
private createRow(items: azdata.Component[]): azdata.FlexContainer {
return this.view.modelBuilder.flexContainer().withItems(items, {
CSSStyles: {
'margin-right': '5px'
}
}).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
}
private getPoolDisplayName(poolType: ClusterPoolType): string {
switch (poolType) {
case ClusterPoolType.SQL:
return localize('bdc-create.SQLServerMasterDisplayName', 'SQL Server master');
case ClusterPoolType.Compute:
return localize('bdc-create.ComputePoolDisplayName', 'Compute pool');
case ClusterPoolType.Data:
return localize('bdc-create.DataPoolDisplayName', 'Data pool');
case ClusterPoolType.Storage:
return localize('bdc-create.StoragePoolDisplayName', 'Storage pool');
case ClusterPoolType.Spark:
return localize('bdc-create.SparkPoolDisplayName', 'Spark pool');
default:
throw new Error('unknown pool type');
}
}
private getPoolDescription(poolType: ClusterPoolType): string {
switch (poolType) {
case ClusterPoolType.SQL:
return localize('bdc-create.SQLServerMasterDescription', 'The SQL Server instance provides an externally accessible TDS endpoint for the cluster');
case ClusterPoolType.Compute:
return localize('bdc-create.ComputePoolDescription', 'TODO: Add description');
case ClusterPoolType.Data:
return localize('bdc-create.DataPoolDescription', 'TODO: Add description');
case ClusterPoolType.Storage:
return localize('bdc-create.StoragePoolDescription', 'TODO: Add description');
case ClusterPoolType.Spark:
return localize('bdc-create.SparkPoolDescription', 'TODO: Add description');
default:
throw new Error('unknown pool type');
}
}
private updatePoolList(): void {
let pools = [this.wizard.model.profile.sqlServerMasterConfiguration,
this.wizard.model.profile.computePoolConfiguration,
this.wizard.model.profile.dataPoolConfiguration,
this.wizard.model.profile.sparkPoolConfiguration,
this.wizard.model.profile.storagePoolConfiguration];
pools.forEach(pool => {
let text = this.poolListMap[pool.type] as azdata.TextComponent;
if (text) {
text.value = localize({
key: 'bdc-create.poolLabelTemplate',
comment: ['{0} is the pool name, {1} is the scale number']
}, '{0} ({1})', this.getPoolDisplayName(pool.type), pool.scale);
}
});
}
private clearPoolDetail(): void {
this.detailContainer.clearItems();
}
}

View File

@@ -1,164 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as os from 'os';
import * as fs from 'fs';
import { WizardPageBase } from '../../wizardPageBase';
import { CreateClusterWizard } from '../createClusterWizard';
import { setActiveKubeconfig } from '../../../config/config';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const ClusterRadioButtonGroupName = 'cluster';
export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizard> {
private existingClusterControl: azdata.FlexContainer;
private clusterContextsLabel: azdata.TextComponent;
private errorLoadingClustersLabel: azdata.TextComponent;
private clusterContextList: azdata.DivContainer;
private clusterContextLoadingComponent: azdata.LoadingComponent;
private configFileInput: azdata.InputBoxComponent;
private browseFileButton: azdata.ButtonComponent;
private loadDefaultKubeConfigFile: boolean = true;
private view: azdata.ModelView;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.selectTargetClusterPageTitle', 'Where do you want to deploy this SQL Server big data cluster?'),
localize('bdc-create.selectTargetClusterPageDescription', 'Select the kubeconfig file and then select a cluster context from the list'),
wizard);
}
protected initialize(view: azdata.ModelView): Thenable<void> {
this.view = view;
this.initExistingClusterControl();
let formBuilder = view.modelBuilder.formContainer().withFormItems(
[
{
component: this.existingClusterControl,
title: ''
}
],
{
horizontal: false
}
).withLayout({ width: '100%', height: '100%' });
let form = formBuilder.component();
return view.initializeModel(form);
}
public onEnter() {
if (this.loadDefaultKubeConfigFile) {
let defaultKubeConfigPath = this.wizard.model.getDefaultKubeConfigPath();
if (fs.existsSync(defaultKubeConfigPath)) {
this.loadClusterContexts(defaultKubeConfigPath);
}
this.loadDefaultKubeConfigFile = false;
}
this.wizard.wizardObject.registerNavigationValidator((e) => {
if (e.lastPage > e.newPage) {
this.wizard.wizardObject.message = null;
return true;
}
let clusterSelected = this.wizard.model.selectedCluster !== undefined;
if (!clusterSelected) {
this.wizard.wizardObject.message = {
text: localize('bdc-create.ClusterContextNotSelectedMessage', 'Please select a cluster context.'),
level: azdata.window.MessageLevel.Error
};
}
return clusterSelected;
});
}
private initExistingClusterControl(): void {
let self = this;
const labelWidth = '150px';
let configFileLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.kubeConfigFileLabelText', 'Kube config file path') }).component();
configFileLabel.width = labelWidth;
this.configFileInput = this.view.modelBuilder.inputBox().withProperties({ width: '300px' }).component();
this.configFileInput.enabled = false;
this.browseFileButton = this.view.modelBuilder.button().withProperties({ label: localize('bdc-browseText', 'Browse'), width: '100px' }).component();
let configFileContainer = this.view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', alignItems: 'baseline' })
.withItems([configFileLabel, this.configFileInput, this.browseFileButton], { CSSStyles: { 'margin-right': '10px' } }).component();
this.clusterContextsLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-clusterContextsLabelText', 'Cluster Contexts') }).component();
this.clusterContextsLabel.width = labelWidth;
this.errorLoadingClustersLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-errorLoadingClustersText', 'No cluster information is found in the config file or an error ocurred while loading the config file') }).component();
this.clusterContextList = this.view.modelBuilder.divContainer().component();
this.clusterContextLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterContextList).component();
this.existingClusterControl = this.view.modelBuilder.divContainer().component();
let clusterContextContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'start' }).component();
clusterContextContainer.addItem(this.clusterContextsLabel, { flex: '0 0 auto' });
clusterContextContainer.addItem(this.clusterContextLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'width': '400px', 'margin-left': '10px', 'margin-top': '10px' } });
this.existingClusterControl.addItem(configFileContainer, { CSSStyles: { 'margin-top': '0px' } });
this.existingClusterControl.addItem(clusterContextContainer, {
CSSStyles: { 'margin- top': '10px' }
});
this.wizard.registerDisposable(this.browseFileButton.onDidClick(async () => {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.Uri.file(os.homedir()),
openLabel: localize('bdc-selectKubeConfigFileText', 'Select'),
filters: {
'KubeConfig Files': ['*'],
}
}
);
if (!fileUris || fileUris.length === 0) {
return;
}
self.clusterContextList.clearItems();
let fileUri = fileUris[0];
self.loadClusterContexts(fileUri.fsPath);
}));
}
private async loadClusterContexts(configPath: string): Promise<void> {
this.clusterContextLoadingComponent.loading = true;
let self = this;
this.configFileInput.value = configPath;
await setActiveKubeconfig(configPath);
let clusters = await this.wizard.model.loadClusters();
if (clusters.length !== 0) {
let options = clusters.map(cluster => {
let option = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
label: cluster.contextName,
checked: cluster.active,
name: ClusterRadioButtonGroupName
}).component();
if (cluster.active) {
self.wizard.model.selectedCluster = cluster;
self.wizard.wizardObject.message = null;
}
this.wizard.registerDisposable(option.onDidClick(() => {
self.wizard.model.selectedCluster = cluster;
self.wizard.wizardObject.message = null;
}));
return option;
});
self.clusterContextList.addItems(options);
} else {
self.clusterContextList.addItem(this.errorLoadingClustersLabel);
}
this.clusterContextLoadingComponent.loading = false;
}
}

View File

@@ -1,259 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import { TargetClusterTypeInfo, ToolInstallationStatus, ToolInfo } from '../../../interfaces';
import * as nls from 'vscode-nls';
import { CreateClusterWizard } from '../createClusterWizard';
const localize = nls.loadMessageBundle();
const InstallToolsButtonText = localize('bdc-create.InstallToolsText', 'Install Tools');
const InstallingButtonText = localize('bdc-create.InstallingButtonText', 'Installing...');
export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWizard> {
private cards: azdata.CardComponent[];
private toolsTable: azdata.TableComponent;
private formBuilder: azdata.FormBuilder;
private form: azdata.FormContainer;
private installToolsButton: azdata.window.Button;
private toolsLoadingWrapper: azdata.LoadingComponent;
private refreshToolsButton: azdata.window.Button;
private targetDescriptionText: azdata.TextComponent;
private targetDescriptionGroup: azdata.FormComponent;
private isValid: boolean = false;
private isLoading: boolean = false;
private requiredTools: ToolInfo[];
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.selectTargetClusterTypePageTitle', 'What is your target cluster environment?'),
localize('bdc-create.selectTargetClusterTypePageDescription', 'Choose the target environment and then install the required tools for it.'),
wizard);
this.installToolsButton = azdata.window.createButton(InstallToolsButtonText);
this.installToolsButton.hidden = true;
this.wizard.registerDisposable(this.installToolsButton.onClick(async () => {
this.wizard.wizardObject.message = null;
this.installToolsButton.label = InstallingButtonText;
this.installToolsButton.enabled = false;
this.refreshToolsButton.enabled = false;
if (this.requiredTools) {
for (let i = 0; i < this.requiredTools.length; i++) {
let tool = this.requiredTools[i];
if (tool.status === ToolInstallationStatus.NotInstalled) {
tool.status = ToolInstallationStatus.Installing;
this.updateToolStatusTable();
await this.wizard.model.installTool(tool);
}
}
}
this.installToolsButton.label = InstallToolsButtonText;
this.updateRequiredToolStatus();
}));
this.wizard.addButton(this.installToolsButton);
this.refreshToolsButton = azdata.window.createButton(localize('bdc-create.RefreshToolsButtonText', 'Refresh Status'));
this.refreshToolsButton.hidden = true;
this.wizard.registerDisposable(this.refreshToolsButton.onClick(() => {
this.updateRequiredToolStatus();
}));
this.wizard.addButton(this.refreshToolsButton);
}
protected initialize(view: azdata.ModelView): Thenable<void> {
let self = this;
self.registerNavigationValidator();
return self.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => {
self.cards = [];
clusterTypes.forEach(clusterType => {
let card = self.createCard(view, clusterType);
self.cards.push(card);
});
let cardsContainer = view.modelBuilder.flexContainer().withItems(self.cards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component();
self.targetDescriptionText = view.modelBuilder.text().component();
let toolColumn: azdata.TableColumn = {
value: localize('bdc-create.toolNameColumnHeader', 'Tool'),
width: 100
};
let descriptionColumn: azdata.TableColumn = {
value: localize('bdc-create.toolDescriptionColumnHeader', 'Description'),
width: 500
};
let versionColumn: azdata.TableColumn = {
value: localize('bdc-create.toolVersionColumnHeader', 'Version'),
width: 200
};
let statusColumn: azdata.TableColumn = {
value: localize('bdc-create.toolStatusColumnHeader', 'Status'),
width: 200
};
self.toolsTable = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
height: 150,
data: [],
columns: [toolColumn, descriptionColumn, versionColumn, statusColumn],
width: 1000
}).component();
self.toolsLoadingWrapper = view.modelBuilder.loadingComponent().withItem(self.toolsTable).component();
self.formBuilder = view.modelBuilder.formContainer().withFormItems(
[
{
component: cardsContainer,
title: localize('bdc-create.PickTargetEnvironmentText', 'Pick target environment')
}
],
{
horizontal: false
}
);
self.form = self.formBuilder.withLayout({ width: '100%' }).component();
return view.initializeModel(self.form);
});
}
public onEnter(): void {
this.installToolsButton.hidden = false;
this.refreshToolsButton.hidden = false;
this.refreshToolsButton.enabled = true;
this.installToolsButton.enabled = false;
this.registerNavigationValidator();
}
private registerNavigationValidator(): void {
this.wizard.wizardObject.registerNavigationValidator(() => {
if (this.isLoading) {
let messageText = localize('bdc-create.ToolsRefreshingText', 'Please wait while the required tools status is being refreshed.');
let messageLevel = azdata.window.MessageLevel.Information;
this.wizard.wizardObject.message = {
level: messageLevel,
text: messageText
};
return false;
}
if (!this.isValid) {
let messageText = this.cards.filter(c => { return c.selected; }).length === 0 ?
localize('bdc-create.TargetClusterTypeNotSelectedText', 'Please select a target cluster type.') :
localize('bdc-create.MissingToolsText', 'Please install the required tools.');
this.wizard.wizardObject.message = {
level: azdata.window.MessageLevel.Error,
text: messageText
};
}
return this.isValid;
});
}
public onLeave(): void {
this.installToolsButton.hidden = true;
this.refreshToolsButton.hidden = true;
}
private createCard(view: azdata.ModelView, targetClusterTypeInfo: TargetClusterTypeInfo): azdata.CardComponent {
let self = this;
let descriptions = targetClusterTypeInfo.enabled ? [] : [localize('bdc-create.ComingSoonText', '(Coming Soon)')];
let card = view.modelBuilder.card().withProperties<azdata.CardProperties>({
cardType: azdata.CardType.VerticalButton,
iconPath: {
dark: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.dark),
light: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.light)
},
label: targetClusterTypeInfo.name,
descriptions: descriptions
}).component();
card.enabled = targetClusterTypeInfo.enabled;
self.wizard.registerDisposable(card.onCardSelectedChanged(() => {
self.onCardSelected(card, targetClusterTypeInfo);
}));
return card;
}
private onCardSelected(card: azdata.CardComponent, targetClusterTypeInfo: TargetClusterTypeInfo): void {
let self = this;
if (card.selected) {
self.wizard.wizardObject.message = null;
self.wizard.model.targetClusterType = targetClusterTypeInfo.type;
self.cards.forEach(c => {
if (c !== card) {
c.selected = false;
}
});
self.targetDescriptionText.value = targetClusterTypeInfo.description;
if (self.form.items.length === 1) {
self.formBuilder.addFormItem({
title: localize('bdc-create.RequiredToolsText', 'Required tools'),
component: self.toolsLoadingWrapper
});
} else {
self.formBuilder.removeFormItem(self.targetDescriptionGroup);
}
self.targetDescriptionGroup = {
title: targetClusterTypeInfo.fullName,
component: self.targetDescriptionText
};
self.formBuilder.insertFormItem(self.targetDescriptionGroup, 1);
self.updateRequiredToolStatus();
} else {
if (self.cards.filter(c => { return c !== card && c.selected; }).length === 0) {
card.selected = true;
}
}
}
private updateRequiredToolStatus(): Thenable<void> {
this.isLoading = true;
this.installToolsButton.hidden = false;
this.refreshToolsButton.hidden = false;
this.toolsLoadingWrapper.loading = true;
this.refreshToolsButton.enabled = false;
this.installToolsButton.enabled = false;
return this.wizard.model.getRequiredToolStatus().then(tools => {
this.requiredTools = tools;
this.isLoading = false;
this.toolsLoadingWrapper.loading = false;
this.refreshToolsButton.enabled = true;
this.installToolsButton.enabled = tools.filter(tool => tool.status !== ToolInstallationStatus.Installed).length !== 0;
this.isValid = !this.installToolsButton.enabled;
this.wizard.wizardObject.message = null;
this.updateToolStatusTable();
});
}
private getStatusText(status: ToolInstallationStatus): string {
switch (status) {
case ToolInstallationStatus.Installed:
return '✔️ ' + localize('bdc-create.InstalledText', 'Installed');
case ToolInstallationStatus.NotInstalled:
return '❌ ' + localize('bdc-create.NotInstalledText', 'Not Installed');
case ToolInstallationStatus.Installing:
return '⌛ ' + localize('bdc-create.InstallingText', 'Installing...');
case ToolInstallationStatus.FailedToInstall:
return '❌ ' + localize('bdc-create.FailedToInstallText', 'Install Failed');
default:
return 'unknown status';
}
}
private updateToolStatusTable(): void {
if (this.requiredTools) {
let tableData = this.requiredTools.map(tool => {
return [tool.name, tool.description, tool.version, this.getStatusText(tool.status)];
});
this.toolsTable.data = tableData;
}
}
}

View File

@@ -1,278 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import * as nls from 'vscode-nls';
import { ClusterPorts, ContainerRegistryInfo } from '../../../interfaces';
import { CreateClusterWizard } from '../createClusterWizard';
const localize = nls.loadMessageBundle();
const UserNameInputWidth = '300px';
const PortInputWidth = '100px';
const RestoreDefaultValuesText = localize('bdc-create.RestoreDefaultValuesText', 'Restore Default Values');
export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
private acceptEulaCheckbox: azdata.CheckBoxComponent;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.settingsPageTitle', 'Settings'),
localize('bdc-create.settingsPageDescription', 'Configure the settings required for deploying SQL Server big data cluster'),
wizard);
}
public onEnter(): void {
this.wizard.wizardObject.registerNavigationValidator((e) => {
if (e.lastPage > e.newPage) {
this.wizard.wizardObject.message = null;
return true;
}
if (!this.acceptEulaCheckbox.checked) {
this.wizard.wizardObject.message = {
text: localize('bdc-create.EulaNotAccepted', 'You need to accept the terms of services and privacy policy in order to proceed'),
level: azdata.window.MessageLevel.Error
};
} else {
this.wizard.wizardObject.message = null;
}
return this.acceptEulaCheckbox.checked;
});
}
protected initialize(view: azdata.ModelView): Thenable<void> {
let clusterPorts: ClusterPorts;
let containerRegistryInfo: ContainerRegistryInfo;
let clusterPortsPromise = this.wizard.model.getDefaultPorts().then(ports => {
clusterPorts = ports;
});
let containerRegistryPromise = this.wizard.model.getDefaultContainerRegistryInfo().then(containerRegistry => {
containerRegistryInfo = containerRegistry;
});
return Promise.all([clusterPortsPromise, containerRegistryPromise]).then(() => {
let formBuilder = view.modelBuilder.formContainer();
// User settings
let clusterNameInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ClusterName', 'Cluster name'),
inputWidth: UserNameInputWidth,
isRequiredField: true
}, (input) => {
this.wizard.model.clusterName = input.value;
});
let adminUserNameInput = this.createInputWithLabel(view, {
label: localize('bdc-create.AdminUsernameText', 'Admin username'),
isRequiredField: true,
inputWidth: UserNameInputWidth
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.adminUserName = inputBox.value;
});
let adminPasswordInput = this.createInputWithLabel(view, {
label: localize('bdc-create.AdminUserPasswordText', 'Password'),
isRequiredField: true,
inputType: 'password',
inputWidth: UserNameInputWidth
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.adminPassword = inputBox.value;
});
// Port settings
let sqlPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.SQLPortText', 'SQL Server master'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.sql
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.sqlPort = inputBox.value;
});
let knoxPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.KnoxPortText', 'Knox'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.knox
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.knoxPort = inputBox.value;
});
let controllerPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ControllerPortText', 'Controller'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.controller
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.controllerPort = inputBox.value;
});
let proxyPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ProxyPortText', 'Proxy'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.proxy
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.proxyPort = inputBox.value;
});
let grafanaPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.GrafanaPortText', 'Grafana dashboard'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.grafana
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.grafanaPort = inputBox.value;
});
let kibanaPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.KibanaPortText', 'Kibana dashboard'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.kibana
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.kibanaPort = inputBox.value;
});
let restorePortSettingsButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
label: RestoreDefaultValuesText,
width: 200
}).component();
this.wizard.registerDisposable(restorePortSettingsButton.onDidClick(() => {
sqlPortInput.input.value = clusterPorts.sql;
knoxPortInput.input.value = clusterPorts.knox;
controllerPortInput.input.value = clusterPorts.controller;
proxyPortInput.input.value = clusterPorts.proxy;
grafanaPortInput.input.value = clusterPorts.grafana;
kibanaPortInput.input.value = clusterPorts.kibana;
}));
// Container Registry Settings
const registryUserNamePasswordHintText = localize('bdc-create.RegistryUserNamePasswordHintText', 'only required for private registries');
let registryInput = this.createInputWithLabel(view, {
label: localize('bdc-create.RegistryText', 'Registry'),
isRequiredField: true,
inputWidth: UserNameInputWidth,
initialValue: containerRegistryInfo.registry
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRegistry = inputBox.value;
});
let repositoryInput = this.createInputWithLabel(view, {
label: localize('bdc-create.RepositoryText', 'Repository'),
isRequiredField: true,
inputWidth: UserNameInputWidth,
initialValue: containerRegistryInfo.repository
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRepository = inputBox.value;
});
let imageTagInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ImageTagText', 'Image tag'),
isRequiredField: true,
inputWidth: UserNameInputWidth,
initialValue: containerRegistryInfo.imageTag
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRegistry = inputBox.value;
});
let registryUserNameInput = this.createInputWithLabel(view, {
label: localize('bdc-create.RegistryUserNameText', 'Username'),
isRequiredField: false,
inputWidth: UserNameInputWidth,
placeHolder: registryUserNamePasswordHintText
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRegistryUserName = inputBox.value;
});
let registryPasswordInput = this.createInputWithLabel(view, {
label: localize('bdc-create.RegistryPasswordText', 'Password'),
isRequiredField: false,
inputWidth: UserNameInputWidth,
placeHolder: registryUserNamePasswordHintText,
inputType: 'password'
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRegistryPassword = inputBox.value;
});
let restoreContainerSettingsButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
label: RestoreDefaultValuesText,
width: 200
}).component();
this.wizard.registerDisposable(restoreContainerSettingsButton.onDidClick(() => {
registryInput.input.value = containerRegistryInfo.registry;
repositoryInput.input.value = containerRegistryInfo.repository;
imageTagInput.input.value = containerRegistryInfo.imageTag;
}));
let basicSettingsGroup = view.modelBuilder.groupContainer().withItems([clusterNameInput.row, adminUserNameInput.row, adminPasswordInput.row]).withLayout({ header: localize('bdc-create.BasicSettingsText', 'Basic Settings'), collapsible: true }).component();
let containerSettingsGroup = view.modelBuilder.groupContainer().withItems([registryInput.row, repositoryInput.row, imageTagInput.row, registryUserNameInput.row, registryPasswordInput.row, restoreContainerSettingsButton]).withLayout({ header: localize('bdc-create.ContainerRegistrySettings', 'Container Registry Settings'), collapsible: true }).component();
let portSettingsGroup = view.modelBuilder.groupContainer().withItems([sqlPortInput.row, knoxPortInput.row, controllerPortInput.row, proxyPortInput.row, grafanaPortInput.row, kibanaPortInput.row, restorePortSettingsButton]).withLayout({ header: localize('bdc-create.PortSettings', 'Port Settings (Optional)'), collapsible: true, collapsed: true }).component();
this.acceptEulaCheckbox = view.modelBuilder.checkBox().component();
this.acceptEulaCheckbox.checked = false;
let eulaLink: azdata.LinkArea = {
text: localize('bdc-create.LicenseTerms', 'license terms'),
url: 'https://go.microsoft.com/fwlink/?LinkId=2002534'
};
let privacyPolicyLink: azdata.LinkArea = {
text: localize('bdc-create.PrivacyPolicyText', 'privacy policy'),
url: 'https://go.microsoft.com/fwlink/?LinkId=853010'
};
let checkboxText = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: localize({
key: 'bdc-create.AcceptTermsText',
comment: ['{0} is the place holder for license terms, {1} is the place holder for privacy policy']
}, 'I accept the {0} and {1}.'),
links: [eulaLink, privacyPolicyLink]
}).component();
let eulaContainer = this.createRow(view, [this.acceptEulaCheckbox, checkboxText]);
let form = formBuilder.withFormItems([
{
title: '',
component: eulaContainer
}, {
title: '',
component: basicSettingsGroup
}, {
title: '',
component: containerSettingsGroup
}, {
title: '',
component: portSettingsGroup
}]).component();
return view.initializeModel(form);
});
}
private createInputWithLabel(view: azdata.ModelView, options: {
label: string,
isRequiredField: boolean,
inputWidth: string,
inputType?: string,
initialValue?: string,
placeHolder?: string
}, textChangedHandler: (inputBox: azdata.InputBoxComponent) => void): { row: azdata.FlexContainer, input: azdata.InputBoxComponent } {
let inputType = !!options.inputType ? options.inputType : 'text';
let input = view.modelBuilder.inputBox().withProperties({
required: options.isRequiredField,
inputType: inputType
}).component();
let text = view.modelBuilder.text().withProperties({ value: options.label }).component();
input.width = options.inputWidth;
text.width = '150px';
input.placeHolder = options.placeHolder;
this.wizard.registerDisposable(input.onTextChanged(() => {
textChangedHandler(input);
}));
input.value = options.initialValue;
let row = this.createRow(view, [text, input]);
return {
input: input,
row: row
};
}
private createRow(view: azdata.ModelView, items: azdata.Component[]): azdata.FlexContainer {
return view.modelBuilder.flexContainer().withItems(items, { CSSStyles: { 'margin-right': '5px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
}
}

View File

@@ -1,103 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import { CreateClusterWizard } from '../createClusterWizard';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const LabelWidth = '250px';
export class SummaryPage extends WizardPageBase<CreateClusterWizard> {
private view: azdata.ModelView;
private targetTypeText: azdata.TextComponent;
private targetClusterContextText: azdata.TextComponent;
private clusterNameText: azdata.TextComponent;
private clusterAdminUsernameText: azdata.TextComponent;
private acceptEulaText: azdata.TextComponent;
private deploymentProfileText: azdata.TextComponent;
private sqlServerMasterScaleText: azdata.TextComponent;
private storagePoolScaleText: azdata.TextComponent;
private computePoolScaleText: azdata.TextComponent;
private dataPoolScaleText: azdata.TextComponent;
private sparkPoolScaleText: azdata.TextComponent;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.summaryPageTitle', 'Summary'), '', wizard);
}
protected initialize(view: azdata.ModelView): Thenable<void> {
this.view = view;
let targetClusterInfoGroup = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
let bdcClusterInfoGroup = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
this.targetTypeText = this.view.modelBuilder.text().component();
this.targetClusterContextText = this.view.modelBuilder.text().component();
this.clusterNameText = this.view.modelBuilder.text().component();
this.clusterAdminUsernameText = this.view.modelBuilder.text().component();
this.acceptEulaText = this.view.modelBuilder.text().component();
this.deploymentProfileText = this.view.modelBuilder.text().component();
this.sqlServerMasterScaleText = this.view.modelBuilder.text().component();
this.storagePoolScaleText = this.view.modelBuilder.text().component();
this.computePoolScaleText = this.view.modelBuilder.text().component();
this.dataPoolScaleText = this.view.modelBuilder.text().component();
this.sparkPoolScaleText = this.view.modelBuilder.text().component();
targetClusterInfoGroup.addItem(this.createRow(localize('bdc-create.TargetClusterTypeText', 'Cluster type'), this.targetTypeText));
targetClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterContextText', 'Cluster context'), this.targetClusterContextText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterNameText', 'Cluster name'), this.clusterNameText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterAdminUsernameText', 'Cluster Admin username'), this.clusterAdminUsernameText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.AcceptEulaText', 'Accept license agreement'), this.acceptEulaText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.DeploymentProfileText', 'Deployment profile'), this.deploymentProfileText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.SqlServerMasterScaleText', 'SQL Server master scale'), this.sqlServerMasterScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ComputePoolScaleText', 'Compute pool scale'), this.computePoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.DataPoolScaleText', 'Data pool scale'), this.dataPoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.StoragePoolScaleText', 'Storage pool scale'), this.storagePoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.SparkPoolScaleText', 'Spark pool scale'), this.sparkPoolScaleText));
let formBuilder = view.modelBuilder.formContainer();
let form = formBuilder.withFormItems([{
title: localize('bdc-create.TargetClusterGroupTitle', 'TARGET CLUSTER'),
component: targetClusterInfoGroup
}, {
title: localize('bdc-create.BigDataClusterGroupTitle', 'SQL SERVER BIG DATA CLUSTER'),
component: bdcClusterInfoGroup
}]).component();
return view.initializeModel(form);
}
public onEnter(): void {
this.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => {
let selectedClusterType = clusterTypes.filter(clusterType => clusterType.type === this.wizard.model.targetClusterType)[0];
this.targetTypeText.value = selectedClusterType.fullName;
this.targetClusterContextText.value = this.wizard.model.selectedCluster.contextName;
this.clusterNameText.value = this.wizard.model.clusterName;
this.clusterAdminUsernameText.value = this.wizard.model.adminUserName;
this.acceptEulaText.value = localize('bdc-create.YesText', 'Yes');
this.deploymentProfileText.value = this.wizard.model.profile.name;
this.sqlServerMasterScaleText.value = this.wizard.model.profile.sqlServerMasterConfiguration.scale.toString();
this.computePoolScaleText.value = this.wizard.model.profile.computePoolConfiguration.scale.toString();
this.dataPoolScaleText.value = this.wizard.model.profile.dataPoolConfiguration.scale.toString();
this.storagePoolScaleText.value = this.wizard.model.profile.storagePoolConfiguration.scale.toString();
this.sparkPoolScaleText.value = this.wizard.model.profile.sparkPoolConfiguration.scale.toString();
});
this.wizard.wizardObject.generateScriptButton.hidden = false;
}
public onLeave(): void {
this.wizard.wizardObject.generateScriptButton.hidden = true;
}
private createRow(label: string, textComponent: azdata.TextComponent): azdata.FlexContainer {
let row = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'baseline' }).component();
let labelComponent = this.view.modelBuilder.text().withProperties({ value: label }).component();
labelComponent.width = LabelWidth;
textComponent.width = LabelWidth;
row.addItems([labelComponent, textComponent]);
return row;
}
}

View File

@@ -1,76 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { ExtensionContext, Disposable } from 'vscode';
import { WizardPageBase } from './wizardPageBase';
export abstract class WizardBase<T, W> {
public wizardObject: azdata.window.Wizard;
private customButtons: azdata.window.Button[];
private pages: WizardPageBase<W>[];
private toDispose: Disposable[] = [];
constructor(public model: T, public context: ExtensionContext, private title: string) {
this.customButtons = [];
}
public open(): Thenable<void> {
this.wizardObject = azdata.window.createWizard(this.title);
this.initialize();
this.wizardObject.customButtons = this.customButtons;
this.toDispose.push(this.wizardObject.onPageChanged((e) => {
let previousPage = this.pages[e.lastPage];
let newPage = this.pages[e.newPage];
previousPage.onLeave();
newPage.onEnter();
}));
this.toDispose.push(this.wizardObject.doneButton.onClick(() => {
this.onOk();
this.dispose();
}));
this.toDispose.push(this.wizardObject.cancelButton.onClick(() => {
this.onCancel();
this.dispose();
}));
return this.wizardObject.open().then(() => {
if (this.pages && this.pages.length > 0) {
this.pages[0].onEnter();
}
});
}
protected abstract initialize(): void;
protected abstract onOk(): void;
protected abstract onCancel(): void;
public addButton(button: azdata.window.Button) {
this.customButtons.push(button);
}
protected setPages(pages: WizardPageBase<W>[]) {
this.wizardObject.pages = pages.map(p => p.pageObject);
this.pages = pages;
}
private dispose() {
this.toDispose.forEach((disposable: Disposable) => {
try {
disposable.dispose();
}
catch{ }
});
}
public registerDisposable(disposable: Disposable): void {
this.toDispose.push(disposable);
}
}

View File

@@ -1,33 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
export abstract class WizardPageBase<T> {
private _page: azdata.window.WizardPage;
public get pageObject(): azdata.window.WizardPage {
return this._page;
}
public get wizard(): T {
return this._wizard;
}
constructor(title: string, description: string, private _wizard: T) {
this._page = azdata.window.createWizardPage(title);
this._page.description = description;
this._page.registerContent((view: azdata.ModelView) => {
return this.initialize(view);
});
}
protected abstract initialize(view: azdata.ModelView): Thenable<void>;
public onEnter(): void { }
public onLeave(): void { }
}

View File

@@ -1,15 +1,23 @@
{
"extends": "../shared.tsconfig.json",
"compileOnSave": true,
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "./out",
"strict": false,
"alwaysStrict": false,
"noImplicitAny": false,
"noImplicitReturns": false,
"noUnusedLocals": false,
"noUnusedParameters": false
"lib": [
"es6",
"es2015.promise"
],
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"declaration": false,
"typeRoots": [
"./node_modules/@types"
]
},
"include": [
"src/**/*"
"exclude": [
"node_modules"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "cms",
"displayName": "%cms.displayName%",
"description": "%cms.description%",
"version": "0.2.0",
"version": "0.3.0",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
@@ -14,6 +14,7 @@
"activationEvents": [
"*"
],
"forceReload": true,
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
@@ -93,10 +94,6 @@
{
"displayName": "%cms.connectionOptions.authType.categoryValues.integrated%",
"name": "Integrated"
},
{
"displayName": "%cms.connectionOptions.authType.categoryValues.azureMFA%",
"name": "AzureMFA"
}
],
"isRequired": true,
@@ -632,5 +629,10 @@
"should": "^13.2.1",
"vscode": "^1.1.26",
"typemoq": "^2.1.0"
},
"__metadata": {
"id": "40",
"publisherDisplayName": "Microsoft",
"publisherId": "Microsoft"
}
}

View File

@@ -12,7 +12,6 @@
"cms.resource.addServerGroup.title": "New Server Group...",
"cms.resource.registerCmsServer.title": "Add Central Management Server",
"cms.resource.deleteCmsServer.title": "Delete",
"cms.configuration.title": "MSSQL configuration",
"cms.query.displayBitAsNumber": "Should BIT columns be displayed as numbers (1 or 0)? If false, BIT columns will be displayed as 'true' or 'false'",
"cms.format.alignColumnDefinitionsInColumns": "Should column definitions be aligned?",
@@ -70,7 +69,7 @@
"cms.connectionOptions.columnEncryptionSetting.displayName": "Column encryption",
"cms.connectionOptions.columnEncryptionSetting.description": "Default column encryption setting for all the commands on the connection",
"cms.connectionOptions.encrypt.displayName": "Encrypt",
"cms.connectionOptions.encrypt.description": "When true, SQL Server uses SSL encryption for all data sent between the client and server if the servers has a certificate installed",
"cms.connectionOptions.encrypt.description": "When true, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed",
"cms.connectionOptions.persistSecurityInfo.displayName": "Persist security info",
"cms.connectionOptions.persistSecurityInfo.description": "When false, security-sensitive information, such as the password, is not returned as part of the connection",
"cms.connectionOptions.trustServerCertificate.displayName": "Trust server certificate",

View File

@@ -18,40 +18,37 @@ const localize = nls.loadMessageBundle();
export function registerCmsServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void {
// Create a CMS Server
appContext.apiWrapper.registerCommand('cms.resource.registerCmsServer', async (node?: TreeNode) => {
appContext.apiWrapper.registerCommand('cms.resource.registerCmsServer', async (node?: TreeNode, connectionProfile?: azdata.IConnectionProfile) => {
if (node && !(node instanceof CmsResourceEmptyTreeNode)) {
return;
}
await appContext.cmsUtils.connection.then(async (connection) => {
if (connection && connection.options) {
let registeredCmsServerName = connection.options.registeredServerName ?
connection.options.registeredServerName : connection.options.server;
// check if a CMS with the same name is registered or not
let cachedServers = appContext.cmsUtils.registeredCmsServers;
let serverExists: boolean = false;
if (cachedServers) {
serverExists = cachedServers.some((server) => {
return server.name === registeredCmsServerName;
});
}
if (!serverExists) {
// remove any group ID if user selects a connection from
// recent connection list
connection.options.groupId = null;
let registeredCmsServerDescription = connection.options.registeredServerDescription;
// remove server description from connection uri
connection.options.registeredCmsServerDescription = null;
let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
appContext.cmsUtils.cacheRegisteredCmsServer(registeredCmsServerName, registeredCmsServerDescription, ownerUri, connection);
tree.notifyNodeChanged(undefined);
} else {
// error out for same server name
let errorText = localize('cms.errors.sameCmsServerName', 'Central Management Server Group already has a Registered Server with the name {0}', registeredCmsServerName);
appContext.apiWrapper.showErrorMessage(errorText);
return;
}
let connection = await appContext.cmsUtils.makeConnection(connectionProfile);
if (connection && connection.options) {
let registeredCmsServerName = connection.options.registeredServerName ?
connection.options.registeredServerName : connection.options.server;
// check if a CMS with the same name is registered or not
let cachedServers = appContext.cmsUtils.registeredCmsServers;
let serverExists: boolean = false;
if (cachedServers) {
serverExists = cachedServers.some((server) => {
return server.name === registeredCmsServerName;
});
}
});
if (!serverExists) {
// remove any group ID if user selects a connection from
// recent connection list
connection.options.groupId = null;
let registeredCmsServerDescription = connection.options.registeredServerDescription;
let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
appContext.cmsUtils.cacheRegisteredCmsServer(registeredCmsServerName, registeredCmsServerDescription, ownerUri, connection);
tree.notifyNodeChanged(undefined);
} else {
// error out for same server name
let errorText = localize('cms.errors.sameCmsServerName', 'Central Management Server Group already has a Registered Server with the name {0}', registeredCmsServerName);
appContext.apiWrapper.showErrorMessage(errorText);
throw new Error(errorText);
}
}
});
}
@@ -61,7 +58,7 @@ export function deleteCmsServerCommand(appContext: AppContext, tree: CmsResource
if (!(node instanceof CmsResourceTreeNode)) {
return;
}
await appContext.cmsUtils.deleteCmsServer(node.name);
await appContext.cmsUtils.deleteCmsServer(node.name, node.connection);
tree.isSystemInitialized = false;
tree.notifyNodeChanged(undefined);
});
@@ -76,16 +73,8 @@ export function addRegisteredServerCommand(appContext: AppContext, tree: CmsReso
let relativePath = node instanceof CmsResourceTreeNode ? '' : node.relativePath;
let serverName = node instanceof CmsResourceTreeNode ? node.connection.options.registeredServerName === ''
? node.connection.options.server : node.connection.options.registeredServerName : null;
await appContext.cmsUtils.addRegisteredServer(relativePath, node.ownerUri, serverName).then((result) => {
if (result) {
tree.notifyNodeChanged(node);
}
}, (error) => {
// error out
let errorText = localize('cms.errors.addRegisterServerFail', 'Could not add the Registered Server {0}', error);
appContext.apiWrapper.showErrorMessage(errorText);
return;
});
await appContext.cmsUtils.addRegisteredServer(relativePath, node.ownerUri, serverName);
tree.notifyNodeChanged(node);
});
}
@@ -95,18 +84,14 @@ export function deleteRegisteredServerCommand(appContext: AppContext, tree: CmsR
if (!(node instanceof RegisteredServerTreeNode)) {
return;
}
appContext.apiWrapper.showWarningMessage(
let result = await appContext.apiWrapper.showWarningMessage(
`${localize('cms.confirmDeleteServer', 'Are you sure you want to delete')} ${node.name}?`,
localize('cms.yes', 'Yes'),
localize('cms.no', 'No')).then((result) => {
if (result && result === localize('cms.yes', 'Yes')) {
appContext.cmsUtils.removeRegisteredServer(node.name, node.relativePath, node.ownerUri).then((result) => {
if (result) {
tree.notifyNodeChanged(node.parent);
}
});
}
});
localize('cms.no', 'No'));
if (result && result === localize('cms.yes', 'Yes')) {
await appContext.cmsUtils.removeRegisteredServer(node.name, node.relativePath, node.ownerUri);
tree.notifyNodeChanged(node.parent);
}
});
}
@@ -151,22 +136,19 @@ export function addServerGroupCommand(appContext: AppContext, tree: CmsResourceT
dialog.content = [mainTab];
azdata.window.openDialog(dialog);
let groupExists = false;
dialog.okButton.onClick(() => {
dialog.okButton.onClick(async () => {
let path = node instanceof ServerGroupTreeNode ? node.relativePath : '';
if (node.serverGroupNodes.some(node => node.name === serverGroupName)) {
groupExists = true;
}
if (!groupExists) {
appContext.cmsUtils.addServerGroup(serverGroupName, serverDescription, path, node.ownerUri).then((result) => {
if (result) {
tree.notifyNodeChanged(node);
}
});
await appContext.cmsUtils.addServerGroup(serverGroupName, serverDescription, path, node.ownerUri);
tree.notifyNodeChanged(node);
} else {
// error out for same server group
let errorText = localize('cms.errors.sameServerGroupName', '{0} already has a Server Group with the name {1}', node.name, serverGroupName);
const errorText = localize('cms.errors.sameServerGroupName', '{0} already has a Server Group with the name {1}', node.name, serverGroupName);
appContext.apiWrapper.showErrorMessage(errorText);
return;
throw new Error(errorText);
}
});
});
@@ -178,18 +160,14 @@ export function deleteServerGroupCommand(appContext: AppContext, tree: CmsResour
if (!(node instanceof ServerGroupTreeNode)) {
return;
}
appContext.apiWrapper.showWarningMessage(
let result = await appContext.apiWrapper.showWarningMessage(
`${localize('cms.confirmDeleteGroup', 'Are you sure you want to delete')} ${node.name}?`,
localize('cms.yes', 'Yes'),
localize('cms.no', 'No')).then((result) => {
if (result && result === localize('cms.yes', 'Yes')) {
appContext.cmsUtils.removeServerGroup(node.name, node.relativePath, node.ownerUri).then((result) => {
if (result) {
tree.notifyNodeChanged(node.parent);
}
});
}
});
localize('cms.no', 'No'));
if (result && result === localize('cms.yes', 'Yes')) {
await appContext.cmsUtils.removeServerGroup(node.name, node.relativePath, node.ownerUri);
tree.notifyNodeChanged(node.parent);
}
});
}

View File

@@ -6,7 +6,7 @@
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { TreeItemCollapsibleState } from 'vscode';
import { TreeItemCollapsibleState, TreeItem } from 'vscode';
import { AppContext } from '../../appContext';
import { TreeNode } from '../treeNode';
import { CmsResourceTreeNodeBase } from './baseTreeNodes';
@@ -38,48 +38,52 @@ export class CmsResourceTreeNode extends CmsResourceTreeNodeBase {
try {
let nodes: CmsResourceTreeNodeBase[] = [];
if (!this.ownerUri) {
this._ownerUri = await this.appContext.cmsUtils.getUriForConnection(this.connection);
// Set back password to get ownerUri
if (this.connection.options.authenticationType === 'SqlLogin' && this.connection.options.savePassword === true) {
this.connection.options.password = await this.appContext.cmsUtils.getPassword(this.connection.options.user);
}
}
return this.appContext.cmsUtils.createCmsServer(this.connection, this.name, this.description).then((result) => {
if (result) {
if (result.registeredServersList) {
result.registeredServersList.forEach((registeredServer) => {
nodes.push(new RegisteredServerTreeNode(
registeredServer.name,
registeredServer.description,
registeredServer.serverName,
registeredServer.relativePath,
this.ownerUri,
this.appContext,
this.treeChangeHandler, this));
});
}
if (result.registeredServerGroups) {
if (result.registeredServerGroups) {
this._serverGroupNodes = [];
result.registeredServerGroups.forEach((serverGroup) => {
let serverGroupNode = new ServerGroupTreeNode(
serverGroup.name,
serverGroup.description,
serverGroup.relativePath,
this.ownerUri,
this.appContext,
this.treeChangeHandler, this);
nodes.push(serverGroupNode);
this._serverGroupNodes.push(serverGroupNode);
});
}
}
if (nodes.length > 0) {
return nodes.sort((node1, node2) => node1.name > node2.name ? 1 : -1);
} else {
return [CmsResourceMessageTreeNode.create(CmsResourceTreeNode.noResourcesLabel, undefined)];
}
// cache new connection is different from old one
if (this.appContext.cmsUtils.didConnectionChange(this._connection, result.connection)) {
this._connection = result.connection;
this._ownerUri = result.ownerUri;
this.appContext.cmsUtils.cacheRegisteredCmsServer(this.name, this.description, this.ownerUri, this.connection);
}
if (result.listRegisteredServersResult.registeredServersList) {
result.listRegisteredServersResult.registeredServersList.forEach((registeredServer) => {
nodes.push(new RegisteredServerTreeNode(
registeredServer.name,
registeredServer.description,
registeredServer.serverName,
registeredServer.relativePath,
this.ownerUri,
this.appContext,
this.treeChangeHandler, this));
});
}
if (result.listRegisteredServersResult.registeredServerGroups) {
this._serverGroupNodes = [];
result.listRegisteredServersResult.registeredServerGroups.forEach((serverGroup) => {
let serverGroupNode = new ServerGroupTreeNode(
serverGroup.name,
serverGroup.description,
serverGroup.relativePath,
this.ownerUri,
this.appContext,
this.treeChangeHandler, this);
nodes.push(serverGroupNode);
this._serverGroupNodes.push(serverGroupNode);
});
}
if (nodes.length > 0) {
return nodes.sort((node1, node2) => node1.name > node2.name ? 1 : -1);
} else {
return [CmsResourceMessageTreeNode.create(CmsResourceTreeNode.noResourcesLabel, undefined)];
}
}, (error) => {
let errorText = localize('cms.errors.expandCmsFail', 'The Central Management Server {0} could not be found or is offline', this.name);
this.appContext.apiWrapper.showErrorMessage(errorText);
return [];
this.treeChangeHandler.notifyNodeChanged(undefined);
throw error;
});
} catch {
return [];
@@ -91,6 +95,7 @@ export class CmsResourceTreeNode extends CmsResourceTreeNodeBase {
item.contextValue = CmsResourceItemType.cmsNodeContainer;
item.id = this._id;
item.tooltip = this.description;
item.type = azdata.ExtensionNodeType.Server;
item.iconPath = {
dark: this.appContext.extensionContext.asAbsolutePath('resources/light/centralmanagement_server.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/centralmanagement_server.svg')

View File

@@ -55,6 +55,7 @@ export class RegisteredServerTreeNode extends CmsResourceTreeNodeBase {
collapsibleState: TreeItemCollapsibleState.Collapsed,
label: this.name ? this.name : this.serverName,
childProvider: 'MSSQL',
type: azdata.ExtensionNodeType.Server,
iconPath: {
dark: this.appContext.extensionContext.asAbsolutePath('resources/light/regserverserver.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/regserverserver.svg')

View File

@@ -14,6 +14,7 @@ import { CmsResourceEmptyTreeNode } from './cmsResourceEmptyTreeNode';
import { ICmsResourceTreeChangeHandler } from './treeChangeHandler';
import { CmsResourceMessageTreeNode } from '../messageTreeNode';
import { CmsResourceTreeNode } from './cmsResourceTreeNode';
import { ICmsResourceNodeInfo } from './baseTreeNodes';
export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICmsResourceTreeChangeHandler {
@@ -27,7 +28,8 @@ export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICms
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
if (element) {
return element.getChildren(true);
let children = await element.getChildren(true);
return children;
}
if (!this.isSystemInitialized) {
@@ -42,11 +44,11 @@ export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICms
servers.push(new CmsResourceTreeNode(
server.name,
server.description,
undefined,
server.ownerUri,
server.connection,
this._appContext, this, null));
this.appContext.cmsUtils.cacheRegisteredCmsServer(server.name, server.description,
undefined, server.connection);
server.ownerUri, server.connection);
});
return servers;
}
@@ -62,13 +64,6 @@ export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICms
let registeredCmsServers = this.appContext.cmsUtils.registeredCmsServers;
if (registeredCmsServers && registeredCmsServers.length > 0) {
this.isSystemInitialized = true;
// save the CMS Servers for future use
let toSaveCmsServers = JSON.parse(JSON.stringify(registeredCmsServers));
toSaveCmsServers.forEach(server => {
server.ownerUri = undefined,
server.connection.options.password = '';
});
await this._appContext.cmsUtils.setConfiguration(toSaveCmsServers);
return registeredCmsServers.map((server) => {
return new CmsResourceTreeNode(
server.name,

View File

@@ -14,6 +14,14 @@ import { ICmsResourceNodeInfo } from './cmsResource/tree/baseTreeNodes';
const localize = nls.loadMessageBundle();
const cmsProvider: string = 'MSSQL-CMS';
const mssqlProvider: string = 'MSSQL';
const CredentialNamespace = 'cmsCredentials';
const sqlLoginAuthType: string = 'SqlLogin';
export interface CreateCmsResult {
listRegisteredServersResult: mssql.ListRegisteredServersResult;
connection: azdata.connection.Connection;
ownerUri: string;
}
/**
* Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into
@@ -24,8 +32,21 @@ const mssqlProvider: string = 'MSSQL';
*/
export class CmsUtils {
private _credentialProvider: azdata.CredentialProvider;
private _cmsService: mssql.CmsService;
private _registeredCmsServers: ICmsResourceNodeInfo[];
private _registeredCmsServers: ICmsResourceNodeInfo[] = [];
public async savePassword(username: string, password: string): Promise<boolean> {
let provider = await this.credentialProvider();
let result = await provider.saveCredential(username, password);
return result;
}
public async getPassword(username: string): Promise<string> {
let provider = await this.credentialProvider();
let credential = await provider.readCredential(username);
return credential ? credential.password : undefined;
}
public showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showErrorMessage(message, ...items);
@@ -53,9 +74,10 @@ export class CmsUtils {
let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
if (!ownerUri) {
// Make a connection if it's not already connected
await azdata.connection.connect(Utils.toConnectionProfile(connection), false, false).then(async (result) => {
let result = await azdata.connection.connect(Utils.toConnectionProfile(connection), false, false);
if (result) {
ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
});
}
}
return ownerUri;
}
@@ -70,59 +92,79 @@ export class CmsUtils {
}
public async getRegisteredServers(ownerUri: string, relativePath: string): Promise<mssql.ListRegisteredServersResult> {
return this.getCmsService().then((service) => {
return service.getRegisteredServers(ownerUri, relativePath).then((result) => {
if (result && result.registeredServersList && result.registeredServersList) {
return result;
}
});
});
const cmsService = await this.getCmsService();
const result = await cmsService.getRegisteredServers(ownerUri, relativePath);
if (result && result.registeredServersList && result.registeredServersList) {
return result;
}
}
public async createCmsServer(connection: azdata.connection.Connection,
name: string, description: string): Promise<mssql.ListRegisteredServersResult> {
name: string, description: string): Promise<CreateCmsResult> {
let provider = await this.getCmsService();
connection.providerName = connection.providerName === cmsProvider ? mssqlProvider : connection.providerName;
let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
if (!ownerUri) {
// Make a connection if it's not already connected
await azdata.connection.connect(Utils.toConnectionProfile(connection), false, false).then(async (result) => {
ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
});
}
return provider.createCmsServer(name, description, connection, ownerUri).then((result) => {
if (result) {
return Promise.resolve(result);
} else {
return Promise.reject(null);
let initialConnectionProfile = this.getConnectionProfile(connection);
let result = await azdata.connection.connect(initialConnectionProfile, false, false);
ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
// If the ownerUri is still undefined, then open a connection dialog with the connection
if (!ownerUri) {
let result = await this.makeConnection(initialConnectionProfile);
if (result) {
ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
connection = result;
}
}
});
}
let result = await provider.createCmsServer(name, description, connection, ownerUri);
const createCmsResult: CreateCmsResult = {
listRegisteredServersResult: result,
connection: connection,
ownerUri: ownerUri
};
return createCmsResult;
}
public async deleteCmsServer(cmsServer: any): Promise<void> {
public async deleteCmsServer(cmsServerName: string, connection: azdata.connection.Connection): Promise<void> {
let config = this.getConfiguration();
if (config && config.servers) {
let newServers = config.servers.filter((cachedServer) => {
return cachedServer.name !== cmsServer;
return cachedServer.name !== cmsServerName;
});
await this.setConfiguration(newServers);
this._registeredCmsServers = this._registeredCmsServers.filter((cachedServer) => {
return cachedServer.name !== cmsServer;
return cachedServer.name !== cmsServerName;
});
}
if (connection.options.authenticationType === sqlLoginAuthType && connection.options.savePassword) {
this._credentialProvider.deleteCredential(connection.options.user);
}
}
public cacheRegisteredCmsServer(name: string, description: string, ownerUri: string, connection: azdata.connection.Connection): void {
if (!this._registeredCmsServers) {
this._registeredCmsServers = [];
}
public async cacheRegisteredCmsServer(name: string, description: string, ownerUri: string, connection: azdata.connection.Connection): Promise<void> {
let cmsServerNode: ICmsResourceNodeInfo = {
name: name,
description: description,
connection: connection,
ownerUri: ownerUri
};
// update a server if a server with same name exists
this._registeredCmsServers = this._registeredCmsServers.filter((server) => {
return server.name !== name;
});
this._registeredCmsServers.push(cmsServerNode);
// save the CMS Servers for future use
let toSaveCmsServers: ICmsResourceNodeInfo[] = this._registeredCmsServers.map(server => Object.assign({}, server));
toSaveCmsServers.forEach(server => {
server.ownerUri = undefined;
// don't save password in config
server.connection.options.password = '';
});
await this.setConfiguration(toSaveCmsServers);
}
public async addRegisteredServer(relativePath: string, ownerUri: string,
@@ -147,45 +189,37 @@ export class CmsUtils {
authTypeChanged: true
}
};
return this.openConnectionDialog([cmsProvider], initialProfile, { saveConnection: false }).then(async (connection) => {
if (connection && connection.options) {
if (connection.options.server === parentServerName) {
// error out for same server registration
let errorText = localize('cms.errors.sameServerUnderCms', 'You cannot add a shared registered server with the same name as the Configuration Server');
this.showErrorMessage(errorText);
return false;
} else {
let registeredServerName = connection.options.registeredServerName === '' ? connection.options.server : connection.options.registeredServerName;
let result = await provider.addRegisteredServer(ownerUri, relativePath, registeredServerName, connection.options.registeredServerDescription, connection);
if (result) {
return Promise.resolve(result);
} else {
return Promise.reject(registeredServerName);
}
}
let connection = await this.openConnectionDialog([cmsProvider], initialProfile, { saveConnection: false });
if (connection && connection.options) {
if (connection.options.server === parentServerName) {
// error out for same server registration
let errorText = localize('cms.errors.sameServerUnderCms', 'You cannot add a shared registered server with the same name as the Configuration Server');
this.showErrorMessage(errorText);
throw new Error(errorText);
} else {
let registeredServerName = connection.options.registeredServerName === '' ? connection.options.server : connection.options.registeredServerName;
let result = await provider.addRegisteredServer(ownerUri, relativePath, registeredServerName, connection.options.registeredServerDescription, connection);
return result;
}
});
}
}
public async removeRegisteredServer(registeredServerName: string, relativePath: string, ownerUri: string): Promise<boolean> {
let provider = await this.getCmsService();
return provider.removeRegisteredServer(ownerUri, relativePath, registeredServerName).then((result) => {
return result;
});
let result = await provider.removeRegisteredServer(ownerUri, relativePath, registeredServerName);
return result;
}
public async addServerGroup(groupName: string, groupDescription: string, relativePath: string, ownerUri: string): Promise<boolean> {
let provider = await this.getCmsService();
return provider.addServerGroup(ownerUri, relativePath, groupName, groupDescription).then((result) => {
return result;
});
let result = await provider.addServerGroup(ownerUri, relativePath, groupName, groupDescription);
return result;
}
public async removeServerGroup(groupName: string, relativePath: string, ownerUri: string): Promise<boolean> {
let provider = await this.getCmsService();
return provider.removeServerGroup(ownerUri, relativePath, groupName).then((result) => {
return result;
});
let result = await provider.removeServerGroup(ownerUri, relativePath, groupName);
return result;
}
// Getters
@@ -193,15 +227,68 @@ export class CmsUtils {
return this._registeredCmsServers;
}
public get connection(): Thenable<azdata.connection.Connection> {
return this.openConnectionDialog([cmsProvider], undefined, { saveConnection: false }).then((connection) => {
if (connection) {
// remove group ID from connection if a user chose connection
// from the recent connections list
connection.options['groupId'] = null;
connection.providerName = mssqlProvider;
return connection;
}
});
public async credentialProvider(): Promise<azdata.CredentialProvider> {
if (!this._credentialProvider) {
this._credentialProvider = await azdata.credentials.getProvider(CredentialNamespace);
}
return this._credentialProvider;
}
public async makeConnection(initialConnectionProfile?: azdata.IConnectionProfile): Promise<azdata.connection.Connection> {
if (!initialConnectionProfile) {
initialConnectionProfile = {
connectionName: undefined,
serverName: undefined,
databaseName: undefined,
userName: undefined,
password: undefined,
authenticationType: undefined,
savePassword: undefined,
groupFullName: undefined,
groupId: undefined,
providerName: undefined,
saveProfile: undefined,
id: undefined,
options: {}
};
}
let connection = await this.openConnectionDialog([cmsProvider], initialConnectionProfile, { saveConnection: false });
if (connection) {
// remove group ID from connection if a user chose connection
// from the recent connections list
connection.options['groupId'] = null;
connection.providerName = mssqlProvider;
if (connection.options.savePassword) {
await this.savePassword(connection.options.user, connection.options.password);
}
return connection;
}
}
// Static Functions
public getConnectionProfile(connection: azdata.connection.Connection): azdata.IConnectionProfile {
let connectionProfile: azdata.IConnectionProfile = {
connectionName: connection.options.connectionName,
serverName: connection.options.server,
databaseName: undefined,
userName: connection.options.user,
password: connection.options.password,
authenticationType: connection.options.authenticationType,
savePassword: connection.options.savePassword,
groupFullName: undefined,
groupId: undefined,
providerName: connection.providerName,
saveProfile: false,
id: connection.connectionId,
options: connection.options
};
return connectionProfile;
}
public didConnectionChange(connectionA: azdata.connection.Connection, connectionB: azdata.connection.Connection): boolean {
return (connectionA !== connectionB) || ((connectionA.connectionId === connectionB.connectionId) &&
(connectionA.options.savePassword !== connectionA.options.savePassword));
}
}

View File

@@ -32,6 +32,7 @@ describe('ServerGroupTreeNode.info', function(): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCmsUtils = TypeMoq.Mock.ofType<CmsUtils>();
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object, mockCmsUtils.object);
mockTreeChangeHandler = TypeMoq.Mock.ofType<ICmsResourceTreeChangeHandler>();
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceTreeDataProvider>();
@@ -47,7 +48,6 @@ describe('ServerGroupTreeNode.info', function(): void {
const treeNode = new ServerGroupTreeNode('test', 'test', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null);
should(treeNode.nodePathValue).equal('cms_serverGroup_test');
should(treeNode.relativePath).equal('test_path');
const treeItem = await treeNode.getTreeItem();

View File

@@ -31,6 +31,7 @@ describe('RegisteredServerTreeNode.info', function(): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCmsUtils = TypeMoq.Mock.ofType<CmsUtils>();
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object, mockCmsUtils.object);
mockTreeChangeHandler = TypeMoq.Mock.ofType<ICmsResourceTreeChangeHandler>();
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceTreeDataProvider>();
@@ -46,7 +47,6 @@ describe('RegisteredServerTreeNode.info', function(): void {
const treeNode = new RegisteredServerTreeNode('test', 'test', 'test_server', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null);
should(treeNode.nodePathValue).equal('cms_registeredServer_test');
should(treeNode.relativePath).equal('test_path');
const treeItem = await treeNode.getTreeItem();

View File

@@ -31,6 +31,7 @@ describe('ServerGroupTreeNode.info', function(): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCmsUtils = TypeMoq.Mock.ofType<CmsUtils>();
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object, mockCmsUtils.object);
mockTreeChangeHandler = TypeMoq.Mock.ofType<ICmsResourceTreeChangeHandler>();
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceTreeDataProvider>();
@@ -46,7 +47,6 @@ describe('ServerGroupTreeNode.info', function(): void {
const treeNode = new ServerGroupTreeNode('test', 'test', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null);
should(treeNode.nodePathValue).equal('cms_serverGroup_test');
should(treeNode.relativePath).equal('test_path');
const treeItem = await treeNode.getTreeItem();

View File

@@ -27,6 +27,7 @@ describe('CmsResourceTreeProvider.getChildren', function (): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCmsUtils = TypeMoq.Mock.ofType<CmsUtils>();
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object, mockCmsUtils.object);
});
@@ -43,7 +44,7 @@ describe('CmsResourceTreeProvider.getChildren', function (): void {
const treeProvider = new CmsResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
should.equal(true, treeProvider.isSystemInitialized);
mockCmsUtils.setup(x => x.registeredCmsServers).returns(null);
mockCmsUtils.setup(x => x.registeredCmsServers).returns(() => []);
const children = await treeProvider.getChildren(undefined);
should.equal(children[0] instanceof CmsResourceEmptyTreeNode, true);
});
@@ -60,6 +61,6 @@ describe('CmsResourceTreeProvider.getChildren', function (): void {
}];
});
const children = await treeProvider.getChildren(undefined);
should.equal(children[0] instanceof CmsResourceTreeNode, true);
should.equal(children[0] !== null, true);
});
});

View File

@@ -2,7 +2,7 @@
"name": "dacpac",
"displayName": "SQL Server Dacpac",
"description": "SQL Server Dacpac for Azure Data Studio.",
"version": "0.3.0",
"version": "0.4.0",
"publisher": "Microsoft",
"preview": true,
"engines": {

View File

@@ -46,7 +46,7 @@ export abstract class BasePage {
public abstract setupNavigationValidator();
protected async getServerValues(): Promise<{ connection, displayName, name }[]> {
let cons = await azdata.connection.getActiveConnections();
let cons = await azdata.connection.getConnections(/* activeConnectionsOnly */ true);
// This user has no active connections ABORT MISSION
if (!cons || cons.length === 0) {
return undefined;
@@ -67,19 +67,14 @@ export abstract class BasePage {
}
}
let db = c.options.databaseDisplayName;
let usr = c.options.user;
let srv = c.options.server;
if (!db) {
db = localize('basePage.defaultDb', '<default>');
}
if (!usr) {
usr = localize('basePage.defaultUser', 'default');
}
let finalName = `${srv}, ${db} (${usr})`;
let finalName = `${srv} (${usr})`;
return {
connection: c,
displayName: finalName,

View File

@@ -96,9 +96,7 @@ export abstract class DacFxConfigPage extends BasePage {
}
protected async createDatabaseDropdown(): Promise<azdata.FormComponent> {
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
this.databaseDropdown = this.view.modelBuilder.dropDown().component();
// Handle database changes
this.databaseDropdown.onValueChanged(async () => {
@@ -107,7 +105,9 @@ export abstract class DacFxConfigPage extends BasePage {
this.model.filePath = this.fileTextBox.value;
});
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).withProperties({
required: true
}).component();
return {
component: this.databaseLoader,
@@ -125,9 +125,13 @@ export abstract class DacFxConfigPage extends BasePage {
}
let values = await this.getDatabaseValues();
this.model.database = values[0].name;
this.model.filePath = this.generateFilePathFromDatabaseAndTimestamp();
this.fileTextBox.value = this.model.filePath;
// only update values and regenerate filepath if this is the first time and database isn't set yet
if (this.model.database !== values[0].name) {
this.model.database = values[0].name;
this.model.filePath = this.generateFilePathFromDatabaseAndTimestamp();
this.fileTextBox.value = this.model.filePath;
}
this.databaseDropdown.updateProperties({
values: values
@@ -163,6 +167,6 @@ export abstract class DacFxConfigPage extends BasePage {
}
interface ConnectionDropdownValue extends azdata.CategoryValue {
connection: azdata.connection.Connection;
connection: azdata.connection.ConnectionProfile;
}

View File

@@ -10,13 +10,11 @@ import * as azdata from 'azdata';
* Data model to communicate between DacFx pages
*/
export interface DacFxDataModel {
server: azdata.connection.Connection;
server: azdata.connection.ConnectionProfile;
database: string;
serverName: string;
serverId: string;
filePath: string;
version: string;
upgradeExisting: boolean;
scriptFilePath: string;
generateScriptAndDeploy: boolean;
}

View File

@@ -9,7 +9,6 @@ import * as azdata from 'azdata';
import { SelectOperationPage } from './pages/selectOperationpage';
import { DeployConfigPage } from './pages/deployConfigPage';
import { DeployPlanPage } from './pages/deployPlanPage';
import { DeployActionPage } from './pages/deployActionPage';
import { DacFxSummaryPage } from './pages/dacFxSummaryPage';
import { ExportConfigPage } from './pages/exportConfigPage';
import { ExtractConfigPage } from './pages/extractConfigPage';
@@ -18,7 +17,7 @@ import { DacFxDataModel } from './api/models';
import { BasePage } from './api/basePage';
const localize = nls.loadMessageBundle();
const msSqlProvider = 'MSSQL';
class Page {
wizardPage: azdata.window.WizardPage;
dacFxPage: BasePage;
@@ -40,7 +39,6 @@ export enum DeployOperationPath {
selectOperation,
deployOptions,
deployPlan,
deployAction,
summary
}
@@ -88,9 +86,9 @@ export class DataTierApplicationWizard {
}
this.connection = await azdata.connection.getCurrentConnection();
if (!this.connection) {
if (!this.connection || (profile && this.connection.connectionId !== profile.id)) {
// @TODO: remove cast once azdata update complete - karlb 3/1/2019
this.connection = <azdata.connection.ConnectionProfile><any>await azdata.connection.openConnectionDialog();
this.connection = <azdata.connection.ConnectionProfile><any>await azdata.connection.openConnectionDialog(undefined, profile);
// don't open the wizard if connection dialog is cancelled
if (!this.connection) {
@@ -104,7 +102,6 @@ export class DataTierApplicationWizard {
let selectOperationWizardPage = azdata.window.createWizardPage(localize('dacFx.selectOperationPageName', 'Select an Operation'));
let deployConfigWizardPage = azdata.window.createWizardPage(localize('dacFx.deployConfigPageName', 'Select Deploy Dacpac Settings'));
let deployPlanWizardPage = azdata.window.createWizardPage(localize('dacFx.deployPlanPage', 'Review the deploy plan'));
let deployActionWizardPage = azdata.window.createWizardPage(localize('dacFx.deployActionPageName', 'Select Action'));
let summaryWizardPage = azdata.window.createWizardPage(localize('dacFx.summaryPageName', 'Summary'));
let extractConfigWizardPage = azdata.window.createWizardPage(localize('dacFx.extractConfigPageName', 'Select Extract Dacpac Settings'));
let importConfigWizardPage = azdata.window.createWizardPage(localize('dacFx.importConfigPageName', 'Select Import Bacpac Settings'));
@@ -113,7 +110,6 @@ export class DataTierApplicationWizard {
this.pages.set('selectOperation', new Page(selectOperationWizardPage));
this.pages.set('deployConfig', new Page(deployConfigWizardPage));
this.pages.set('deployPlan', new Page(deployPlanWizardPage));
this.pages.set('deployAction', new Page(deployActionWizardPage));
this.pages.set('extractConfig', new Page(extractConfigWizardPage));
this.pages.set('importConfig', new Page(importConfigWizardPage));
this.pages.set('exportConfig', new Page(exportConfigWizardPage));
@@ -140,12 +136,6 @@ export class DataTierApplicationWizard {
await deployPlanDacFxPage.start();
});
deployActionWizardPage.registerContent(async (view) => {
let deployActionDacFxPage = new DeployActionPage(this, deployActionWizardPage, this.model, view);
this.pages.get('deployAction').dacFxPage = deployActionDacFxPage;
await deployActionDacFxPage.start();
});
extractConfigWizardPage.registerContent(async (view) => {
let extractConfigDacFxPage = new ExtractConfigPage(this, extractConfigWizardPage, this.model, view);
this.pages.get('extractConfig').dacFxPage = extractConfigDacFxPage;
@@ -190,7 +180,7 @@ export class DataTierApplicationWizard {
}
});
this.wizard.pages = [selectOperationWizardPage, deployConfigWizardPage, deployPlanWizardPage, deployActionWizardPage, summaryWizardPage];
this.wizard.pages = [selectOperationWizardPage, deployConfigWizardPage, deployPlanWizardPage, summaryWizardPage];
this.wizard.generateScriptButton.hidden = true;
this.wizard.generateScriptButton.onClick(async () => await this.generateDeployScript());
this.wizard.doneButton.onClick(async () => await this.executeOperation());
@@ -262,10 +252,10 @@ export class DataTierApplicationWizard {
}
private async deploy() {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
const service = await DataTierApplicationWizard.getService(msSqlProvider);
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, azdata.TaskExecutionMode.execute);
const result = await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.deployErrorMessage', "Deploy failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
@@ -273,10 +263,10 @@ export class DataTierApplicationWizard {
}
private async extract() {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
const service = await DataTierApplicationWizard.getService(msSqlProvider);
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, azdata.TaskExecutionMode.execute);
const result = await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.extractErrorMessage', "Extract failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
@@ -284,10 +274,10 @@ export class DataTierApplicationWizard {
}
private async export() {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
const service = await DataTierApplicationWizard.getService(msSqlProvider);
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, azdata.TaskExecutionMode.execute);
const result = await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.exportErrorMessage', "Export failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
@@ -295,10 +285,10 @@ export class DataTierApplicationWizard {
}
private async import() {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
const service = await DataTierApplicationWizard.getService(msSqlProvider);
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.importBacpac(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
const result = await service.importBacpac(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.importErrorMessage', "Import failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
@@ -306,19 +296,15 @@ export class DataTierApplicationWizard {
}
private async generateDeployScript() {
if (!this.model.scriptFilePath) {
return;
}
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
const service = await DataTierApplicationWizard.getService(msSqlProvider);
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
this.wizard.message = {
text: localize('dacfx.scriptGeneratingMessage', 'You can view the status of script generation in the Task History once the wizard is closed'),
text: localize('dacfx.scriptGeneratingMessage', 'You can view the status of script generation in the Tasks View once the wizard is closed. The generated script will open when complete.'),
level: azdata.window.MessageLevel.Information,
description: ''
};
let result = await service.generateDeployScript(this.model.filePath, this.model.database, this.model.scriptFilePath, ownerUri, azdata.TaskExecutionMode.execute);
const result = await service.generateDeployScript(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.script);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.deployErrorMessage', "Deploy failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
@@ -351,8 +337,6 @@ export class DataTierApplicationWizard {
page = this.pages.get('summary');
} else if ((this.selectedOperation === Operation.deploy || this.selectedOperation === Operation.generateDeployScript) && idx === DeployOperationPath.deployPlan) {
page = this.pages.get('deployPlan');
} else if ((this.selectedOperation === Operation.deploy || this.selectedOperation === Operation.generateDeployScript) && idx === DeployOperationPath.deployAction) {
page = this.pages.get('deployAction');
}
return page;
@@ -367,10 +351,10 @@ export class DataTierApplicationWizard {
}
public async generateDeployPlan(): Promise<string> {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
const service = await DataTierApplicationWizard.getService(msSqlProvider);
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.generateDeployPlan(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
const result = await service.generateDeployPlan(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
@@ -381,7 +365,7 @@ export class DataTierApplicationWizard {
}
private static async getService(providerName: string): Promise<azdata.DacFxServicesProvider> {
let service = azdata.dataprotocol.getProvider<azdata.DacFxServicesProvider>(providerName, azdata.DataProviderType.DacFxServicesProvider);
const service = azdata.dataprotocol.getProvider<azdata.DacFxServicesProvider>(providerName, azdata.DataProviderType.DacFxServicesProvider);
return service;
}
}

View File

@@ -49,7 +49,7 @@ export class DacFxSummaryPage extends BasePage {
async onPageEnter(): Promise<boolean> {
this.populateTable();
this.loader.loading = false;
if (this.model.upgradeExisting && this.model.generateScriptAndDeploy) {
if (this.model.upgradeExisting && this.instance.selectedOperation === Operation.deploy) {
this.instance.wizard.generateScriptButton.hidden = false;
}
return true;
@@ -76,10 +76,6 @@ export class DacFxSummaryPage extends BasePage {
let sourceServer = localize('dacfx.sourceServerName', 'Source Server');
let sourceDatabase = localize('dacfx.sourceDatabaseName', 'Source Database');
let fileLocation = localize('dacfx.fileLocation', 'File Location');
let scriptLocation = localize('dacfx.scriptLocation', 'Deployment Script Location');
let action = localize('dacfx.action', 'Action');
let deploy = localize('dacfx.deploy', 'Deploy');
let generateScript = localize('dacfx.generateScript', 'Generate Deployment Script');
switch (this.instance.selectedOperation) {
case Operation.deploy: {
@@ -87,13 +83,6 @@ export class DacFxSummaryPage extends BasePage {
[targetServer, this.model.serverName],
[fileLocation, this.model.filePath],
[targetDatabase, this.model.database]];
if (this.model.generateScriptAndDeploy) {
data[3] = [scriptLocation, this.model.scriptFilePath];
data[4] = [action, generateScript + ', ' + deploy];
}
else {
data[3] = [action, deploy];
}
break;
}
case Operation.extract: {
@@ -122,9 +111,7 @@ export class DacFxSummaryPage extends BasePage {
data = [
[targetServer, this.model.serverName],
[fileLocation, this.model.filePath],
[targetDatabase, this.model.database],
[scriptLocation, this.model.scriptFilePath],
[action, generateScript]];
[targetDatabase, this.model.database]];
break;
}
}

View File

@@ -1,179 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import * as path from 'path';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard, Operation } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage';
import { sanitizeStringForFilename } from '../api/utils';
const localize = nls.loadMessageBundle();
export class DeployActionPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private deployRadioButton: azdata.RadioButtonComponent;
private deployScriptRadioButton: azdata.RadioButtonComponent;
private scriptRadioButton: azdata.RadioButtonComponent;
private form: azdata.FormContainer;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view);
}
async start(): Promise<boolean> {
let deployComponent = await this.createDeployRadioButton();
let deployScriptComponent = await this.createDeployScriptRadioButton();
let scriptComponent = await this.createScriptRadioButton();
let fileBrowserComponent = await this.createFileBrowser();
this.form = this.view.modelBuilder.formContainer()
.withFormItems(
[
deployComponent,
scriptComponent,
deployScriptComponent,
fileBrowserComponent
]).component();
await this.view.initializeModel(this.form);
//default have the first radio button checked
this.deployRadioButton.checked = true;
this.toggleFileBrowser(false);
return true;
}
async onPageEnter(): Promise<boolean> {
// generate script file path in case the database changed since last time the page was entered
this.setDefaultScriptFilePath();
return true;
}
private async createDeployRadioButton(): Promise<azdata.FormComponent> {
this.deployRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedDeployAction',
label: localize('dacFx.deployRadioButtonLabel', 'Deploy'),
}).component();
this.deployRadioButton.onDidClick(() => {
this.model.generateScriptAndDeploy = false;
this.instance.setDoneButton(Operation.deploy);
this.toggleFileBrowser(false);
});
return {
component: this.deployRadioButton,
title: ''
};
}
private async createDeployScriptRadioButton(): Promise<azdata.FormComponent> {
this.deployScriptRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedDeployAction',
label: localize('dacFx.deployScriptRadioButtonLabel', 'Generate Deployment Script and Deploy'),
}).component();
this.deployScriptRadioButton.onDidClick(() => {
this.model.generateScriptAndDeploy = true;
this.instance.setDoneButton(Operation.deploy);
this.toggleFileBrowser(true);
});
return {
component: this.deployScriptRadioButton,
title: ''
};
}
private async createScriptRadioButton(): Promise<azdata.FormComponent> {
this.scriptRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedDeployAction',
label: localize('dacFx.scriptRadioButtonLabel', 'Generate Deployment Script'),
}).component();
this.scriptRadioButton.onDidClick(() => {
this.model.generateScriptAndDeploy = false;
this.toggleFileBrowser(true);
//change button text and operation
this.instance.setDoneButton(Operation.generateDeployScript);
});
return {
component: this.scriptRadioButton,
title: ''
};
}
private async createFileBrowser(): Promise<azdata.FormComponentGroup> {
this.createFileBrowserParts();
//default filepath
this.setDefaultScriptFilePath();
this.fileButton.onDidClick(async (click) => {
let fileUri = await vscode.window.showSaveDialog(
{
defaultUri: vscode.Uri.file(this.fileTextBox.value),
saveLabel: localize('dacfxDeployScript.saveFile', 'Save'),
filters: {
'SQL Files': ['sql'],
}
}
);
if (!fileUri) {
return;
}
this.fileTextBox.value = fileUri.fsPath;
this.model.scriptFilePath = fileUri.fsPath;
});
this.fileTextBox.onTextChanged(async () => {
this.model.scriptFilePath = this.fileTextBox.value;
});
return {
title: '',
components: [
{
title: localize('dacfx.generatedScriptLocation', 'Deployment Script Location'),
component: this.fileTextBox,
layout: {
horizontal: true,
componentWidth: 400
},
actions: [this.fileButton]
},],
};
}
private toggleFileBrowser(enable: boolean): void {
this.fileTextBox.enabled = enable;
this.fileButton.enabled = enable;
}
private setDefaultScriptFilePath(): void {
this.fileTextBox.value = path.join(this.getRootPath(), sanitizeStringForFilename(this.model.database) + '_UpgradeDACScript_' + this.getDateTime() + '.sql');
this.model.scriptFilePath = this.fileTextBox.value;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator(() => {
return true;
});
}
}

View File

@@ -123,11 +123,9 @@ export class DeployConfigPage extends DacFxConfigPage {
this.formBuilder.addFormItem(this.databaseDropdownComponent, { horizontal: true, componentWidth: 400 });
this.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name;
// add deploy plan and generate script pages
// add deploy plan page
let deployPlanPage = this.instance.pages.get('deployPlan');
this.instance.wizard.addPage(deployPlanPage.wizardPage, DeployOperationPath.deployPlan);
let deployActionPage = this.instance.pages.get('deployAction');
this.instance.wizard.addPage(deployActionPage.wizardPage, DeployOperationPath.deployAction);
});
newRadioButton.onDidClick(() => {
@@ -137,8 +135,7 @@ export class DeployConfigPage extends DacFxConfigPage {
this.model.database = this.databaseTextBox.value;
this.instance.setDoneButton(Operation.deploy);
// remove deploy plan and generate script pages
this.instance.wizard.removePage(DeployOperationPath.deployAction);
// remove deploy plan page
this.instance.wizard.removePage(DeployOperationPath.deployPlan);
});
@@ -160,14 +157,17 @@ export class DeployConfigPage extends DacFxConfigPage {
}
protected async createDeployDatabaseDropdown(): Promise<azdata.FormComponent> {
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
this.databaseDropdown = this.view.modelBuilder.dropDown().component();
//Handle database changes
this.databaseDropdown.onValueChanged(async () => {
this.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name;
});
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).withProperties({
required: true
}).component();
return {
component: this.databaseLoader,
title: localize('dacFx.targetDatabaseDropdownTitle', 'Database Name')

View File

@@ -76,8 +76,6 @@ export class SelectOperationPage extends BasePage {
this.instance.wizard.addPage(configPage.wizardPage, DeployOperationPath.deployOptions);
let deployPlanPage = this.instance.pages.get('deployPlan');
this.instance.wizard.addPage(deployPlanPage.wizardPage, DeployOperationPath.deployPlan);
let actionPage = this.instance.pages.get('deployAction');
this.instance.wizard.addPage(actionPage.wizardPage, DeployOperationPath.deployAction);
this.addSummaryPage(DeployOperationPath.summary);
// change button text and operation

View File

@@ -2,7 +2,7 @@
"name": "import",
"displayName": "SQL Server Import",
"description": "SQL Server Import for Azure Data Studio supports importing CSV or JSON files into SQL Server.",
"version": "0.8.0",
"version": "0.9.0",
"publisher": "Microsoft",
"preview": true,
"engines": {

View File

@@ -109,10 +109,10 @@ export class ServiceClient {
private generateHandleServerProviderEvent(): EventAndListener {
let dots = 0;
return (e: string, ...args: any[]) => {
this.outputChannel.show();
this.statusView.show();
switch (e) {
case Events.INSTALL_START:
this.outputChannel.show(true);
this.statusView.show();
this.outputChannel.appendLine(localize('installingServiceDetailed', 'Installing {0} service to {1}', Constants.serviceName, args[0]));
this.statusView.text = localize('installingService', 'Installing Service');
break;
@@ -121,7 +121,7 @@ export class ServiceClient {
break;
case Events.DOWNLOAD_START:
this.outputChannel.appendLine(localize('downloadingService', 'Downloading {0}', args[0]));
this.outputChannel.append(`(${Math.ceil(args[1] / 1024)} KB)`);
this.outputChannel.append(localize('downloadingServiceSize', '({0} KB)', Math.ceil(args[1] / 1024).toLocaleString(vscode.env.language)));
this.statusView.text = localize('downloadingServiceStatus', 'Downloading Service');
break;
case Events.DOWNLOAD_PROGRESS:
@@ -135,6 +135,7 @@ export class ServiceClient {
this.outputChannel.appendLine(localize('downloadingServiceComplete', 'Done!'));
break;
default:
console.error(`Unknown event from Server Provider ${e}`);
break;
}
};

Some files were not shown because too many files have changed in this diff Show More